Packages

Guesswork is a logic programming library for Elixir. It is heavily inspired by Prolog, but attempts to use idiomatic Elixir when expressing problems and their solutions.

Current section

Files

Jump to
guesswork lib guesswork knowledge_base collection.ex
Raw

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