Current section
Files
Jump to
Current section
Files
lib/guesswork/knowledge_base/collection.ex
defmodule Guesswork.KnowledgeBase.Collection do
@moduledoc """
Allows for the easy creation of `Guesswork.KnowledgeBase` by using the `deffact/1`,
`deffalsehood/1`, and `defrule/2` macros in modules that invoke
`use Guesswork.KnowledgeBase.Collection`.
Currently, everything is stored in the module's functions using a
`Guesswork.KnowledgeBase.Collection.Store` that is created at compile time.
As such, the collection is not mutable.
"""
import Guesswork.Ast
defmacro __using__(_options) do
quote do
import unquote(__MODULE__)
import Guesswork.Ast
Module.register_attribute(__MODULE__, :facts, accumulate: true)
Module.register_attribute(__MODULE__, :falsehoods, accumulate: true)
Module.register_attribute(__MODULE__, :rules, accumulate: true)
@before_compile unquote(__MODULE__)
@behaviour Guesswork.KnowledgeBase
end
end
defmacro __before_compile__(_env) do
quote do
@store Enum.reduce(@facts, Guesswork.KnowledgeBase.Collection.Store.new(), fn term, kb ->
case Guesswork.KnowledgeBase.Collection.Store.insert_fact(kb, term) do
{:ok, val} ->
val
{:error, error} ->
IO.warn(
"could not build collection store: #{inspect(error)}",
Macro.Env.stacktrace(__ENV__)
)
end
end)
|> then(
&Enum.reduce(@falsehoods, &1, fn term, kb ->
case Guesswork.KnowledgeBase.Collection.Store.insert_falsehood(kb, term) do
{:ok, val} ->
val
{:error, error} ->
IO.warn(
"could not build collection store: #{inspect(error)}",
Macro.Env.stacktrace(__ENV__)
)
end
end)
)
|> then(
&Enum.reduce(@rules, &1, fn term, kb ->
Guesswork.KnowledgeBase.Collection.Store.insert_rule(kb, term)
end)
)
def store, do: @store
@impl true
def get_possible_facts(query),
do: Guesswork.KnowledgeBase.Collection.Store.get_possible_facts(store(), query)
@impl true
def get_possible_falsehoods(query),
do: Guesswork.KnowledgeBase.Collection.Store.get_possible_falsehoods(store(), query)
@impl true
def get_possible_rules(query),
do: Guesswork.KnowledgeBase.Collection.Store.get_possible_rules(store(), query)
end
end
@doc """
Adds a new `Guesswork.Ast.Fact` to the collection as a true fact using the
supplied arguments.
All arguments must concrete.
"""
defmacro deffact(relationship, args) do
quote do
@facts Guesswork.Ast.Fact.new(
unquote(relationship),
unquote(
Enum.map(args, fn
:_ -> quote do: %Guesswork.Ast.Wildcard{}
val -> val
end)
)
)
end
end
@doc """
Adds a new `Guesswork.Ast.Fact` to the collection as a falsehood using the
supplied arguments.
All arguments must concrete.
"""
defmacro deffalsehood(relationship, args) do
quote do
@falsehoods Guesswork.Ast.Fact.new(
unquote(relationship),
unquote(
Enum.map(args, fn
:_ -> quote do: %Guesswork.Ast.Wildcard{}
val -> val
end)
)
)
end
end
@doc """
Adds a new `Guesswork.Ast.Rule` to the collection.
Multiple caluses are joined with `Guesswork.Ast.And`.
"""
defmacro defrule(name, args, do: expr) do
expr = translate_rule_expr(expr)
quote do
rule = Guesswork.Ast.rule(unquote(name), unquote(args), unquote(expr))
metadata = Guesswork.Ast.Rule.get_metadata(rule)
search_fun = fn
%Guesswork.Ast.Fact{} = fact -> Guesswork.Ast.Fact.get_metadata(fact) == metadata
_ -> false
end
if not Enum.empty?(Guesswork.Ast.Statement.find_statements(term(unquote(expr)), search_fun)) and
not Enum.any?(@facts, search_fun) do
IO.warn("No base case (fact) found for recursive rule", Macro.Env.stacktrace(__ENV__))
end
@rules rule
end
end
defp translate_rule_expr({:__block__, _, clauses}) do
quote do
Guesswork.Ast.And.new(unquote(clauses))
end
end
defp translate_rule_expr(clause), do: clause
end