diff --git a/.mnesiastore/LATEST.LOG b/.mnesiastore/LATEST.LOG index 3fbfe36..12da9ba 100644 Binary files a/.mnesiastore/LATEST.LOG and b/.mnesiastore/LATEST.LOG differ diff --git a/.mnesiastore/schema.DAT b/.mnesiastore/schema.DAT index 12e5d60..ec92f6e 100644 Binary files a/.mnesiastore/schema.DAT and b/.mnesiastore/schema.DAT differ diff --git a/README.md b/README.md index 5771e2e..d8a4775 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,49 @@ # AL -**TODO: Add description** +AL is a live, ACID, (eventually) bitemporal, relational-object operating system built around an append-only command log. It combines inspiration from: -## Installation +- XTDB -If [available in Hex](https://hex.pm/docs/publish), the package can be installed -by adding `al` to your list of dependencies in `mix.exs`: +- GlamorousToolkit/Pharo -```elixir -def deps do - [ - {:al, "~> 0.1.0"} - ] -end -``` +- Git -Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) -and published on [HexDocs](https://hexdocs.pm). Once published, the docs can -be found at . +- PROLOG +- LISP -TODO +- BEAM -- Re-evaluate this list! +- Urbit -- Loads more docs -- concurrency +The goal of the system is to be the first truly principled object-oriented PROLOG, and the personal computing environment of the future. +This runtime is the first version of AL, written in Elixir. The irony of the first Erlang interpreter having been written in PROLOG is not lost on us. -- constraint processing +## Features -- more bootstrapping for diff kinds of objects +- Live Smalltalk-style objects, defined relationally. No more faux-ADTs. Define protocols and their implementations. Mix and match at your leisure. +- Bidirectional Execution thanks to WAM semantics. +- Shutdown your system, continue later. All transactions are backed up by an on-disk database, hydrated at startup. +- ACID transactions ensure your work is safe and easy to reason about. +- Constraint Processing -- system wipes and rollbacks +And to come: + +- Bitemporality features: Model temporal systems. Spin off new branches of your system at different points in time and move between them easily. + +For discussion of the design philosophy of AL and resources that were consulted during its design, please see: +https://forum.anoma.net/t/design-philosophy-of-al-bibliography/2698 + +## Getting Started + +Install from terminal using `iex -S mix` or as a mix dependency. +From IEx, you can run `require AL`. + +`lib/examples` contains examples. +`lib/AL/bootstrap` contains the bootstrap code. +`lib/AL` contains the runtime code. + +Tips: + +- Use `examine(:my_object_id_here, info)` in order to get quick information about an object via its ID, such as its class(es!), superclass(es!), methods, and in the case of method objects, relevant clauses. diff --git a/lib/AL.ex b/lib/AL.ex index 79802b1..14cde74 100644 --- a/lib/AL.ex +++ b/lib/AL.ex @@ -70,9 +70,13 @@ defmodule AL do | {:retract_super, AL.Var.t(), AL.Var.t()} | {:retract_method, AL.Var.t(), AL.Var.t(), AL.Var.t()} | {:retract_oapply, AL.Var.t(), AL.Var.t()} - | {:spawn_process, AL.Var.t(), AL.Var.t(), [goal()]} + | {:send_async, AL.Var.t(), AL.Var.t(), AL.Var.t()} + | {:send_elixir, AL.Var.t(), AL.Var.t()} | {:gensym, AL.Var.t()} | {:print, AL.Var.t()} + | {:not, [goal()]} + | {:unify, AL.Var.t(), AL.Var.t()} + | {:call, [AL.Var.t()], [goal()], [AL.Var.t()]} | :fail @type stack_entry() :: AL.Choicepoint.t() | {:mark, scope()} | :implies_mark @@ -83,6 +87,8 @@ defmodule AL do field(:tx_id, non_neg_integer(), enforce: true, default: 0) field(:trace, [goal()], enforce: true, default: []) field(:program, [goal()], enforce: true, default: []) + field(:tracepoints, MapSet.t(), enforce: true, default: %MapSet{}) + field(:traced_calls, %{optional(scope()) => tuple()}, default: %{}) end defmacro __using__(_opts) do @@ -91,6 +97,14 @@ defmodule AL do end end + defdelegate trace(point), to: AL.Trace + defdelegate untrace(point), to: AL.Trace + defdelegate notrace(), to: AL.Trace + defdelegate tracepoints(), to: AL.Trace + + @arithmetic_ops [:+, :-, :*, :/, :**] + @oapply_primitives [:is, :map_get, :map_put, :lookup, :fresh_id] + def ast_to_pattern([{:do, {:__block__, _, goals}}]), do: ast_to_pattern(goals) def ast_to_pattern([{:do, nil}]), do: nil @@ -174,8 +188,23 @@ defmodule AL do def ast_to_pattern({:findall, _, [template, condition, result]}), do: {:findall, ast_to_pattern(template), ast_to_pattern(condition), ast_to_pattern(result)} - def ast_to_pattern({:spawn_process, _, [object, head, body]}), - do: {:spawn_process, ast_to_pattern(object), ast_to_pattern(head), ast_to_pattern(body)} + + def ast_to_pattern({:not, _, [goals]}), + do: {:not, ast_to_pattern(goals)} + + def ast_to_pattern({:unify, _, [a, b]}), + do: {:unify, ast_to_pattern(a), ast_to_pattern(b)} + + def ast_to_pattern({:call, _, [head, body, args]}), + do: {:call, ast_to_pattern(head), ast_to_pattern(body), ast_to_pattern(args)} + def ast_to_pattern({:send, _, [receiver, method, args]}), + do: {:oapply, :send, [ast_to_pattern(receiver), ast_to_pattern(method), ast_to_pattern(args)]} + + def ast_to_pattern({:send_async, _, [object, method, args]}), + do: {:send_async, ast_to_pattern(object), ast_to_pattern(method), ast_to_pattern(args)} + + def ast_to_pattern({:send_elixir, _, [pid, message]}), + do: {:send_elixir, ast_to_pattern(pid), ast_to_pattern(message)} def ast_to_pattern({:defmethod, _, [class, method_name, head, body]}) do {:oapply, :defmethod, [ @@ -186,11 +215,22 @@ defmodule AL do } end + def ast_to_pattern({op, _, args}) when op in @arithmetic_ops and is_list(args), + do: {:oapply, op, Enum.map(args, &ast_to_pattern/1)} + + def ast_to_pattern({fun, _, args}) when fun in @oapply_primitives and is_list(args), + do: {:oapply, fun, Enum.map(args, &ast_to_pattern/1)} + + def ast_to_pattern({method, _, [receiver | args]}) when is_atom(method) and is_list(args), + do: {:oapply, :send, [ast_to_pattern(receiver), method, Enum.map(args, &ast_to_pattern/1)]} + def ast_to_pattern({fun, _, args}) when is_atom(fun) and is_list(args), do: {:oapply, fun, Enum.map(args, &ast_to_pattern/1)} def ast_to_pattern({name, _, _module}), do: AL.Var.var(name) + def ast_to_pattern({a, b}), do: {ast_to_pattern(a), ast_to_pattern(b)} + def ast_to_pattern(x), do: x @doc """ @@ -274,8 +314,9 @@ defmodule AL do choicepoint_stack: [{:mark, 0}], tx_id: tx_id, trace: [], - program: program - }) + program: program, + tracepoints: AL.Trace.tracepoints() + }) if result.active_choicepoint.bindings == nil do :mnesia.abort(format_failure(result.trace)) @@ -336,8 +377,8 @@ defmodule AL do } } - [{:mark, _} | rest_choices] -> - backtrack(%AL{state | choicepoint_stack: rest_choices}) + [{:mark, f} | rest_choices] -> + backtrack(%AL{trace_fail(state, f) | choicepoint_stack: rest_choices}) [:implies_mark | rest_choices] -> backtrack(%AL{state | choicepoint_stack: rest_choices}) @@ -355,7 +396,7 @@ defmodule AL do @spec continue(t()) :: t() | nil def continue(nil), do: nil - def continue(state) do + def continue(state) do cond do state.active_choicepoint.bindings == nil -> backtrack(state) @@ -394,7 +435,6 @@ defmodule AL do } result = interp(goal, next_frame) - continue(result) end end @@ -422,8 +462,16 @@ defmodule AL do } } end + else if is_list(object_pattern) do + %AL{ + state + | active_choicepoint: %AL.Choicepoint{ + state.active_choicepoint + | bindings: AL.Var.unify(:list, class_pattern, state.active_choicepoint.bindings) + } + } else - case AL.Objects.scan_class(object_pattern, class_pattern) do + case AL.Object.scan_class(object_pattern, class_pattern) do [] -> backtrack(state) @@ -454,10 +502,11 @@ defmodule AL do } end end + end end def interp({:get_super, object_pattern, super_pattern}, state) do - case AL.Objects.scan_super(object_pattern, super_pattern) do + case AL.Object.scan_super(object_pattern, super_pattern) do [] -> backtrack(state) @@ -490,7 +539,7 @@ defmodule AL do end def interp({:get_method, object_pattern, method_name_pattern, method_id_pattern}, state) do - case AL.Objects.scan_method(object_pattern, method_name_pattern, method_id_pattern) do + case AL.Object.scan_method(object_pattern, method_name_pattern, method_id_pattern) do [] -> backtrack(state) @@ -523,7 +572,7 @@ defmodule AL do end def interp({:get_oapply, object_pattern, head_pattern, body_pattern}, state) do - case AL.Objects.scan_oapply(object_pattern, head_pattern, body_pattern) do + case AL.Object.scan_oapply(object_pattern, head_pattern, body_pattern) do [] -> backtrack(state) @@ -583,9 +632,7 @@ defmodule AL do AL.Var.unify({k_pattern, v_pattern}, pair, state.active_choicepoint.bindings) end) |> Enum.filter(fn t -> t end) do - [] -> - backtrack(state) - + [] -> backtrack(state) [choice | next_choices] -> %AL{ state @@ -604,6 +651,21 @@ defmodule AL do end end + def interp({:oapply, :map_put, [m1, k_pattern, v_pattern, m2]}, state) do + case AL.Var.unify(m2, Map.put(m1, k_pattern, v_pattern), state.active_choicepoint.bindings) do + nil -> backtrack(state) + choice -> + %AL{ + state + | active_choicepoint: %AL.Choicepoint{ + state.active_choicepoint + | bindings: choice + }, + choicepoint_stack: state.choicepoint_stack + } + end + end + def interp({:oapply, :is, [a, b]}, state) do a_deref = AL.Var.deref(state.active_choicepoint.bindings, a) expr = interp_is(b, state.active_choicepoint.bindings) @@ -616,7 +678,9 @@ defmodule AL do end def interp({:oapply, method_id_pattern, bind_head_pattern}, state) do - case AL.Objects.scan_oapply(method_id_pattern, :"$head", :"$body") do + trace_info = trace_call(state, method_id_pattern, bind_head_pattern) + + case AL.Object.scan_oapply(method_id_pattern, :"$head", :"$body") do [] -> backtrack(state) @@ -652,19 +716,20 @@ defmodule AL do %AL{ state | active_choicepoint: %AL.Choicepoint{ - goals: body_pattern, - bindings: - AL.Var.unify( - {head_pattern, id}, - {bind_head_pattern, method_id_pattern}, - state.active_choicepoint.bindings - ), - continuations: [continuation | state.active_choicepoint.continuations], - goal_pointer: 0, - scope_pointer: freshener - }, - choicepoint_stack: - alternative_choicepoints ++ [{:mark, freshener} | state.choicepoint_stack] + goals: body_pattern, + bindings: + AL.Var.unify( + {head_pattern, id}, + {bind_head_pattern, method_id_pattern}, + state.active_choicepoint.bindings + ), + continuations: [continuation | state.active_choicepoint.continuations], + goal_pointer: 0, + scope_pointer: freshener + }, + traced_calls: record_traced_call(state.traced_calls, freshener, trace_info), + choicepoint_stack: + alternative_choicepoints ++ [{:mark, freshener} | state.choicepoint_stack] } end end @@ -673,11 +738,12 @@ defmodule AL do %AL{ state | active_choicepoint: state.active_choicepoint, - choicepoint_stack: - Enum.drop_while(state.choicepoint_stack, fn choice -> - case choice do - {:mark, f} -> f != state.active_choicepoint.scope_pointer - _choice -> true + choicepoint_stack: + Enum.drop_while(state.choicepoint_stack, fn choice -> + case choice do + {:mark, f} -> + f != state.active_choicepoint.scope_pointer + _choice -> true end end) } @@ -749,28 +815,28 @@ defmodule AL do def interp({:set_class, object, _class}, state) when is_map(object), do: state def interp({:set_class, object_pattern, class_pattern}, state) do AL.Command.set_class(state.tx_id, object_pattern, class_pattern) - AL.Objects.set_class(object_pattern, class_pattern) + AL.Object.set_class(object_pattern, class_pattern) state end def interp({:set_super, object, _super}, state) when is_map(object), do: state def interp({:set_super, object_pattern, super_pattern}, state) do AL.Command.set_super(state.tx_id, object_pattern, super_pattern) - AL.Objects.set_super(object_pattern, super_pattern) + AL.Object.set_super(object_pattern, super_pattern) state end def interp({:set_method, object, _name, _id}, state) when is_map(object), do: state def interp({:set_method, object_pattern, method_name_pattern, method_id_pattern}, state) do AL.Command.set_method(state.tx_id, object_pattern, method_name_pattern, method_id_pattern) - AL.Objects.set_method(object_pattern, method_name_pattern, method_id_pattern) + AL.Object.set_method(object_pattern, method_name_pattern, method_id_pattern) state end def interp({:set_oapply, object, _head, _body}, state) when is_map(object), do: state def interp({:set_oapply, object_pattern, head_pattern, body_pattern}, state) do AL.Command.set_oapply(state.tx_id, object_pattern, head_pattern, body_pattern) - AL.Objects.set_oapply(object_pattern, head_pattern, body_pattern) + AL.Object.set_oapply(object_pattern, head_pattern, body_pattern) state end @@ -817,44 +883,48 @@ defmodule AL do def interp({:set_slots, object, _slots}, state) when is_map(object), do: state def interp({:set_slots, object_pattern, slots_pattern}, state) do AL.Command.set_slots(state.tx_id, object_pattern, slots_pattern) - AL.Objects.set_slots(object_pattern, slots_pattern) + AL.Object.set_slots(object_pattern, slots_pattern) state end def interp({:retract_class, object, _class}, state) when is_map(object), do: state def interp({:retract_class, object, class}, state) do AL.Command.retract_class(state.tx_id, object, class) - AL.Objects.retract_class(object, class) + AL.Object.retract_class(object, class) state end def interp({:retract_super, object, _super}, state) when is_map(object), do: state def interp({:retract_super, object, super}, state) do AL.Command.retract_super(state.tx_id, object, super) - AL.Objects.retract_super(object, super) + AL.Object.retract_super(object, super) state end def interp({:retract_method, object, _name, _id}, state) when is_map(object), do: state def interp({:retract_method, object, name, id}, state) do AL.Command.retract_method(state.tx_id, object, name, id) - AL.Objects.retract_method(object, name, id) + AL.Object.retract_method(object, name, id) state end def interp({:retract_oapply, object, _head}, state) when is_map(object), do: state def interp({:retract_oapply, object, head}, state) do AL.Command.retract_oapply(state.tx_id, object, head) - AL.Objects.retract_oapply(object, head) + AL.Object.retract_oapply(object, head) state end - def interp({:spawn_process, object, head, body}, state) do - AL.Command.spawn_process(state.tx_id, object, head, body) - + def interp({:send_async, object, method, args}, state) do + AL.Command.send_async(state.tx_id, object, method, args) state end + def interp({:send_elixir, pid, message}, state) do + AL.Command.send_elixir(state.tx_id, pid, message) + state + end + def interp({:gensym, var}, state) do sym = :crypto.strong_rand_bytes(16) |> Base.encode16(case: :lower) |> String.to_atom() @@ -903,6 +973,49 @@ defmodule AL do } end + def interp({:call, head, body, args}, state) do + freshener = AL.Command.fresh_scope() + fresh_head = AL.Var.freshen(head, freshener) + fresh_body = AL.Var.freshen(body, freshener) + + bindings = AL.Var.unify(fresh_head, args, state.active_choicepoint.bindings) + + if bindings == nil do + backtrack(state) + else + continuation = %AL.Continuation{ + goals: state.active_choicepoint.goals, + goal_pointer: state.active_choicepoint.goal_pointer, + scope_pointer: state.active_choicepoint.scope_pointer + } + + %AL{state | + active_choicepoint: %AL.Choicepoint{ + goals: fresh_body, + bindings: bindings, + continuations: [continuation | state.active_choicepoint.continuations], + goal_pointer: 0, + scope_pointer: freshener + }, + choicepoint_stack: [{:mark, freshener} | state.choicepoint_stack] + } + end + end + + def interp({:unify, a, b}, state) do + case AL.Var.unify(a, b, state.active_choicepoint.bindings) do + nil -> backtrack(state) + bindings -> %AL{state | active_choicepoint: %AL.Choicepoint{state.active_choicepoint | bindings: bindings}} + end + end + + def interp({:not, condition}, state) do + case collect_all_solutions(condition, state.active_choicepoint.bindings, state.tx_id) do + [] -> state + _ -> backtrack(state) + end + end + def interp(:fail, state) do backtrack(state) end @@ -919,7 +1032,8 @@ defmodule AL do choicepoint_stack: [], tx_id: tx_id, trace: [], - program: condition + program: condition, + tracepoints: AL.Trace.tracepoints() } do_collect(continue(initial), []) @@ -939,31 +1053,43 @@ defmodule AL do end defp format_failure(trace) do - steps = trace |> Enum.reverse() |> Enum.map(&normalize_term/1) + steps = trace |> Enum.reverse() |> Enum.map(&AL.Trace.pretty/1) %{failed_on: List.last(steps), trace: steps} end - defp normalize_term(a) when is_atom(a) do - s = Atom.to_string(a) + defp trace_call(state, method_id, bind_head) do + {receiver, args} = + case bind_head do + [r | rest] -> {r, rest} + other -> {other, []} + end - cond do - Regex.match?(~r/^[0-9a-f]{32}$/, s) -> - :"##{AL.Command.id_label(a)}" + traced? = + MapSet.member?(state.tracepoints, method_id) or + (method_id != :send and MapSet.member?(state.tracepoints, receiver)) - true -> - a + if traced? do + depth = length(state.active_choicepoint.continuations) + AL.Trace.call(depth, receiver, method_id, args) + {depth, receiver, method_id} end end - defp normalize_term(t) when is_tuple(t), - do: t |> Tuple.to_list() |> Enum.map(&normalize_term/1) |> List.to_tuple() + defp record_traced_call(traced_calls, _freshener, nil), do: traced_calls - defp normalize_term(l) when is_list(l), do: Enum.map(l, &normalize_term/1) + defp record_traced_call(traced_calls, freshener, info), + do: Map.put(traced_calls, freshener, info) - defp normalize_term(m) when is_map(m), - do: Map.new(m, fn {k, v} -> {normalize_term(k), normalize_term(v)} end) + defp trace_fail(state, freshener) do + case Map.pop(state.traced_calls, freshener) do + {nil, _} -> + state - defp normalize_term(x), do: x + {{depth, receiver, method}, rest} -> + AL.Trace.fail(depth, receiver, method) + %AL{state | traced_calls: rest} + end + end def interp_is({:oapply, :+, [a, b]}, bindings), do: interp_is(a, bindings) + interp_is(b, bindings) @@ -989,3 +1115,4 @@ defimpl Inspect, for: AL do "#AL<>" end end + diff --git a/lib/AL/application.ex b/lib/AL/application.ex index d0ae979..54faad3 100644 --- a/lib/AL/application.ex +++ b/lib/AL/application.ex @@ -5,12 +5,11 @@ defmodule AL.Application do """ use Application - use AL @impl true def start(_type, _args) do AL.Command.setup() - AL.Objects.setup() + AL.Object.setup() opts = [strategy: :one_for_one, name: Al.Supervisor] {:ok, pid} = Supervisor.start_link([AL.Scheduler], opts) @@ -22,126 +21,15 @@ defmodule AL.Application do def bootstrap() do case :mnesia.table_info(:command, :size) do - 0 -> do_bootstrap() - _ -> :ok - end - end - - defp do_bootstrap() do - run do - set_class(:class, :class) - set_class(:object, :class) - set_class(:behaviour, :class) - - set_super(:class, :object) - set_super(:behaviour, :object) - - set_method(:object, :lookup, :lookup) - set_method(:object, :send, :send) - set_method(:object, :meta, :metaclass) - set_method(:object, :defmethod, :defmethod) - - set_class(:metaclass, :behaviour) - - set_oapply(:metaclass, [self, class, meta]) do - class(self, class) - class(class, meta) - end - - set_class(:lookup, :behaviour) - set_oapply( - :lookup, - [self, name, id] - ) do - alternative([method(self, name, id)], - [super(self, super), - lookup(super, name, id)]) - end - - set_class(:send, :behaviour) - - set_oapply( - :send, - [self, method, args] - ) do - class(self, class) - implies( - [lookup(class, method, id)], - [ - print(["calling", id, "from", class, "with args", [self | args]]), - oapply(id, [self | args]) - ], - [:fail] - ) - end - - set_class(:defmethod, :behaviour) - set_oapply(:defmethod, [self, method_name, head, body]) do - fresh_id(impl) - set_method(self, method_name, impl) - set_class(impl, :behaviour) - set_oapply(impl, head, body) - end - - set_class(:map_get, :behaviour) - set_method(:map, :map_get, :map_get) - - defmethod(:class, :construct, [self, %{class: self}]) do - end - - set_method(:class, :allocate, :allocate_class) - set_class(:allocate_class, :behaviour) - - set_oapply( - :allocate_class, - [self, args, name] - ) do - map_get(args, :name, name) - map_get(args, :super, super) - map_get(args, :slots, slots) - - class(self, meta) - - set_class(name, meta) - set_super(name, super) - set_slots(name, slots) - end - - defmethod(:object, :allocate, [self, _, self]) do - print(["allocate", self]) - end - - defmethod(:object, :init, [self, _, self]) do - print(["initialise", self]) - end - - defmethod(:class, :new, [self, args, new]) do - send(self, :construct, [construct]) - send(construct, :allocate, [args, alloc]) - send(alloc, :init, [args, new]) - end - - defmethod(:object, :examine, [self, %{classes: classes, - objects: objects, - supers: supers, - subs: subs, - methods: methods, - clauses: clauses}]) do - findall(c, [class(self, c)], classes) - findall(c, [class(c, self)], objects) - findall(s, [super(self, s)], supers) - findall(sub, [super(sub, self)], subs) - findall([n, id], [method(self, n, id)], methods) - findall([head, body], [clause(self, head, body)], clauses) - end - - send(:class, :new, [%{name: :process, super: :object, slots: []}, _]) - defmethod(:process, :init, [self, args, new_obj]) do - map_get(args, :head, head) - map_get(args, :body, body) - gensym(new_obj) - spawn_process(new_obj, head, body) - end + 0 -> + AL.Bootstrap.Core.setup() + AL.Bootstrap.Users.setup() + AL.Bootstrap.Lists.setup() + AL.Bootstrap.ElixirProcess.setup() + AL.Bootstrap.Process.setup() + AL.Bootstrap.Constraints.setup() + _ -> + :ok end end end diff --git a/lib/AL/bootstrap/constraints.ex b/lib/AL/bootstrap/constraints.ex new file mode 100644 index 0000000..27260f3 --- /dev/null +++ b/lib/AL/bootstrap/constraints.ex @@ -0,0 +1,74 @@ +defmodule AL.Bootstrap.Constraints do + use AL + + def setup() do + run do + new(:class, %{name: :cell, super: :object, slots: [:subscribers, :value, :name]}, _) + + defmethod(:cell, :allocate, [self, args, cell_name]) do + class(self, meta) + map_get(args, :name, cell_name) + + set_class(cell_name, meta) + set_super(cell_name, :object) + end + + defmethod(:cell, :init, [self, args, self]) do + map_get(args, :name, cell_name) + set_slots(cell_name, %{name: cell_name, subscribers: [], value: :absent}) + end + + defmethod(:cell, :constrain, [self, value]) do + get_slot(self, :value, :absent) + set_slots(self, %{value: value}) + + forall([get_slot(self, :subscribers, subscribers), + member(subscribers, subscriber)], + [send_async(subscriber, :cell_updated, [self, value])]) + cut + end + + defmethod(:cell, :subscribe, [self, subscriber]) do + get_slot(self, :subscribers, subscribers) + set_slots(self, %{subscribers: [subscriber | subscribers]}) + end + + new(:class, %{name: :propagator, super: :object, slots: [:input_cells, :output_cell]}, _) + + defmethod(:propagator, :allocate, [self, args, propagator]) do + class(self, meta) + gensym(propagator) + + set_class(propagator, meta) + set_super(propagator, :object) + end + + defmethod(:propagator, :init, [self, args, self]) do + map_get(args, :input_cells, input_cells) + map_get(args, :output_cell, output_cell) + + set_slots(self, %{input_cells: input_cells, output_cell: output_cell}) + + forall([member(input_cells, input_cell)], + [subscribe(input_cell, self)]) + + send_async(self, :cell_updated, [:none, :none]) + end + + defmethod(:propagator, :cell_updated, [self, _cell_name, _value]) do + get_slot(self, :input_cells, input_cells) + get_slot(self, :output_cell, output_cell) + + findall(input_cell_value, + [member(input_cells, input_cell), + get_slot(input_cell, :value, input_cell_value)], + input_cell_values) + forall([member(input_cell_values, input_cell_value)], + [not([unify(input_cell_value, :absent)])]) + + constrain(self, input_cell_values, output_value) + send_async(output_cell, :constrain, [output_value]) + end + end + end +end diff --git a/lib/AL/bootstrap/core.ex b/lib/AL/bootstrap/core.ex new file mode 100644 index 0000000..6c47b45 --- /dev/null +++ b/lib/AL/bootstrap/core.ex @@ -0,0 +1,119 @@ +defmodule AL.Bootstrap.Core do + use AL + + def setup() do + run do + set_class(:class, :class) + set_class(:object, :class) + set_class(:behaviour, :class) + + set_super(:class, :object) + set_super(:behaviour, :object) + + set_method(:object, :lookup, :lookup) + set_method(:object, :send, :send) + set_method(:object, :meta, :metaclass) + set_method(:object, :defmethod, :defmethod) + + set_class(:metaclass, :behaviour) + + set_oapply(:metaclass, [self, class, meta]) do + class(self, class) + class(class, meta) + end + + set_class(:lookup, :behaviour) + set_oapply(:lookup, [self, name, id]) do + alternative([method(self, name, id)], + [super(self, super), + lookup(super, name, id)]) + end + + set_class(:send, :behaviour) + set_oapply(:send, [self, method, args]) do + class(self, class) + implies( + [method(self, method, id)], + [ + oapply(id, [self | args]) + ], + [implies( + [lookup(class, method, id)], + [ + oapply(id, [self | args]) + ], + [:fail] + )]) + end + + set_class(:defmethod, :behaviour) + set_oapply(:defmethod, [self, method_name, head, body]) do + fresh_id(impl) + set_method(self, method_name, impl) + set_class(impl, :behaviour) + set_oapply(impl, head, body) + end + + set_class(:map, :class) + set_super(:map, :object) + + set_class(:map_get, :behaviour) + set_method(:map, :get, :map_get) + + set_class(:map_put, :behaviour) + set_method(:map, :put, :map_put) + + defmethod(:class, :construct, [self, %{class: self}]) do + end + + set_method(:class, :allocate, :allocate_class) + set_class(:allocate_class, :behaviour) + set_oapply(:allocate_class, [self, args, name]) do + map_get(args, :name, name) + map_get(args, :super, super) + map_get(args, :slots, slots) + + class(self, meta) + + set_class(name, meta) + set_super(name, super) + set_slots(name, slots) + end + + defmethod(:object, :allocate, [self, _, self]) do + # print(["allocate", self]) + end + + defmethod(:object, :init, [self, _, self]) do + # print(["initialise", self]) + end + + defmethod(:class, :new, [self, args, new]) do + construct(self, construct) + allocate(construct, args, alloc) + init(alloc, args, new) + end + + defmethod(:object, :examine, [self, %{ + id: self, + classes: classes, + objects: objects, + supers: supers, + subs: subs, + methods: methods, + providers: providers, + clauses: clauses, + slots: slots}]) do + findall(c, [class(self, c)], classes) + findall(c, [class(c, self)], objects) + findall(s, [super(self, s)], supers) + findall(sub, [super(sub, self)], subs) + findall([n, id], [method(self, n, id)], methods) + findall([provider, n], [method(provider, n, self)], providers) + findall([head, body], [clause(self, head, body)], clauses) + findall([slot_name, slot_value], [get_slot(self, slot_name, slot_value)], slots) + end + + end + end +end diff --git a/lib/AL/bootstrap/elixir_process.ex b/lib/AL/bootstrap/elixir_process.ex new file mode 100644 index 0000000..ee3597f --- /dev/null +++ b/lib/AL/bootstrap/elixir_process.ex @@ -0,0 +1,19 @@ +defmodule AL.Bootstrap.ElixirProcess do + use AL + + def setup() do + run do + new(:class, %{name: :elixir_process, super: :object, slots: []}, _) + defmethod(:elixir_process, :allocate, [self, args, new_obj]) do + class(self, meta) + map_get(args, :name, new_obj) + set_class(new_obj, meta) + set_super(new_obj, :elixir_process) + end + defmethod(:elixir_process, :init, [self, args, self]) do + map_get(args, :pid, pid) + set_slots(self, %{pid: pid}) + end + end + end +end diff --git a/lib/AL/bootstrap/lists.ex b/lib/AL/bootstrap/lists.ex new file mode 100644 index 0000000..2ae5f6b --- /dev/null +++ b/lib/AL/bootstrap/lists.ex @@ -0,0 +1,85 @@ +defmodule AL.Bootstrap.Lists do + use AL + + def setup() do + run do + new(:class, %{name: :list, super: :object, slots: []}, _) + + defmethod(:list, :hd, [[h | _t], h]) do end + defmethod(:list, :tl, [[_h | t], t]) do end + + set_method(:list, :concat, :list_concat) + set_class(:list_concat, :behaviour) + set_oapply(:list_concat, [[], second, second]) do end + set_oapply(:list_concat, [[fh | ft], second, [fh | inner]]) do + concat(ft, second, inner) + end + + set_method(:list, :member, :list_member) + set_class(:list_member, :behaviour) + set_oapply(:list_member, [[x | _t], x]) do end + set_oapply(:list_member, [[_h | t], x]) do + member(t, x) + end + + set_method(:list, :reverse, :list_reverse) + set_class(:list_reverse, :behaviour) + set_oapply(:list_reverse, [[], []]) do end + set_oapply(:list_reverse, [[h | t], reversed]) do + reverse(t, reversed_tl) + concat(reversed_tl, [h], reversed) + end + + set_method(:list, :map, :list_map) + set_class(:list_map, :behaviour) + set_oapply(:list_map, [[], _func, []]) do end + set_oapply(:list_map, [[], _head, _body, []]) do end + set_oapply(:list_map, [[fh | ft], func, [sh | st]]) do + send(fh, func, [sh]) + map(ft, func, st) + end + set_oapply(:list_map, [[fh | ft], head, body, [sh | st]]) do + call(head, body, [fh, sh]) + map(ft, head, body, st) + end + + set_method(:list, :fold_left, :list_fold_left) + set_class(:list_fold_left, :behaviour) + set_oapply(:list_fold_left, [[], _func, acc, acc]) do end + set_oapply(:list_fold_left, [[], _head, _body, acc, acc]) do end + set_oapply(:list_fold_left, [[h | t], func, acc, result]) do + send(acc, func, [h, next_acc]) + fold_left(t, func, next_acc, result) + end + set_oapply(:list_fold_left, [[h | t], head, body, acc, result]) do + print(acc) + call(head, body, [acc, h, next_acc]) + fold_left(t, head, body, next_acc, result) + end + + set_method(:list, :fold_right, :list_fold_right) + set_class(:list_fold_right, :behaviour) + set_oapply(:list_fold_right, [[], _func, acc, acc]) do end + set_oapply(:list_fold_right, [[], _head, _body, acc, acc]) do end + set_oapply(:list_fold_right, [[h | t], func, acc, result]) do + fold_right(t, func, acc, next_acc) + send(next_acc, func, [h, result]) + end + set_oapply(:list_fold_right, [[h | t], head, body, acc, result]) do + fold_right(t, head, body, acc, next_acc) + call(head, body, [next_acc, h, result]) + end + + defmethod(:list, :flatten, [lists, result]) do + fold_left(lists, :concat, [], result) + end + + set_method(:list, :same_length, :list_same_length) + set_class(:list_same_length, :behaviour) + set_oapply(:list_same_length, [[], []]) do end + set_oapply(:list_same_length, [[_fh | ft], [_sh | st]]) do + same_length(ft, st) + end + end + end +end diff --git a/lib/AL/bootstrap/process.ex b/lib/AL/bootstrap/process.ex new file mode 100644 index 0000000..d6082c2 --- /dev/null +++ b/lib/AL/bootstrap/process.ex @@ -0,0 +1,26 @@ +defmodule AL.Bootstrap.Process do + use AL + + def setup() do + run do + new(:class, %{name: :process, super: :object, slots: []}, _) + defmethod(:process, :allocate, [self, args, new_obj]) do + class(self, meta) + + map_get(args, :method, method_name) + map_get(args, :head, head) + map_get(args, :body, body) + + gensym(new_obj) + + set_class(new_obj, meta) + set_super(new_obj, :object) + + fresh_id(impl) + set_method(new_obj, method_name, impl) + set_class(impl, :behaviour) + set_oapply(impl, head, body) + end + end + end +end diff --git a/lib/AL/bootstrap/users.ex b/lib/AL/bootstrap/users.ex new file mode 100644 index 0000000..215d30b --- /dev/null +++ b/lib/AL/bootstrap/users.ex @@ -0,0 +1,50 @@ +defmodule AL.Bootstrap.Users do + use AL + + def setup() do + run do + new(:class, %{name: :owned, super: :object, slots: [:owner]}, _) + + defmethod(:owned, :allocate, [self, args, new]) do + class(self, meta) + gensym(new) + set_class(new, meta) + set_super(new, :object) + end + + defmethod(:owned, :init, [self, args, self]) do + set_slots(self, args) + end + + defmethod(:owned, :update, [self, slots]) do + set_slots(self, slots) + end + + defmethod(:owned, :may, [self, caller, _method, _args]) do + get_slot(self, :owner, owner) + unify(caller, owner) + end + + set_class(:guarded_send, :behaviour) + set_oapply(:guarded_send, [caller, self, method, args]) do + may(self, caller, method, args) + send(self, method, args) + end + + new(:class, %{name: :user, super: :owned, slots: [:name]}, _) + + new(:class, %{name: :owned_class, super: :class, slots: []}, _) + + defmethod(:owned_class, :allocate, [self, args, name]) do + map_get(args, :name, name) + map_get(args, :super, super) + map_get(args, :owner, owner) + + class(self, meta) + set_class(name, meta) + set_super(name, super) + set_slots(name, %{owner: owner}) + end + end + end +end diff --git a/lib/AL/command.ex b/lib/AL/command.ex index 6197531..a1b94e0 100644 --- a/lib/AL/command.ex +++ b/lib/AL/command.ex @@ -13,6 +13,8 @@ defmodule AL.Command do | :retract_super | :retract_method | :retract_oapply + | :send_async + | :send_elixir @type command() :: {:set_class, {AL.Var.t(), AL.Var.t()}} @@ -24,8 +26,10 @@ defmodule AL.Command do | {:retract_super, {AL.Var.t(), AL.Var.t()}} | {:retract_method, {AL.Var.t(), AL.Var.t(), AL.Var.t()}} | {:retract_oapply, {AL.Var.t(), AL.Var.t()}} - | {:spawn_process, {AL.Var.t(), AL.Var.t(), [AL.goal()]}} + | {:send_async, {AL.Var.t(), AL.Var.t(), AL.Var.t()}} + | {:send_elixir, {AL.Var.t(), AL.Var.t()}} + @doc """ Initialise the event log, or re-use the one on disc. """ @@ -176,9 +180,14 @@ defmodule AL.Command do write_command(tx_id, {:retract_oapply, {object, head}}) end - @spec spawn_process(non_neg_integer(), AL.Var.t(), AL.Var.t(), [AL.goal()]) :: :ok - def spawn_process(tx_id, object, head, body) do - write_command(tx_id, {:spawn_process, {object, head, body}}) + @spec send_async(non_neg_integer(), AL.Var.t(), AL.Var.t(), AL.Var.t()) :: :ok + def send_async(tx_id, object, method, args) do + write_command(tx_id, {:send_async, {object, method, args}}) + end + + @spec send_elixir(non_neg_integer(), pid(), term()) :: :ok + def send_elixir(tx_id, pid, message) do + write_command(tx_id, {:send_elixir, {pid, message}}) end @spec write_command(non_neg_integer(), command()) :: :ok @@ -198,10 +207,12 @@ defmodule AL.Command do @spec fresh_id() :: atom() def fresh_id() do - label = case :mnesia.dirty_read(:meta, :id_counter) do - [{:meta, :id_counter, n}] -> n - [] -> 0 - end + label = + case :mnesia.dirty_read(:meta, :id_counter) do + [{:meta, :id_counter, n}] -> n + [] -> 0 + end + :mnesia.dirty_write({:meta, :id_counter, label + 1}) :"##{label}" end @@ -213,10 +224,12 @@ defmodule AL.Command do @spec fresh_scope() :: String.t() def fresh_scope() do - n = case :mnesia.dirty_read(:meta, :scope_counter) do - [{:meta, :scope_counter, n}] -> n - [] -> 0 - end + n = + case :mnesia.dirty_read(:meta, :scope_counter) do + [{:meta, :scope_counter, n}] -> n + [] -> 0 + end + :mnesia.dirty_write({:meta, :scope_counter, n + 1}) Integer.to_string(n) end @@ -228,14 +241,18 @@ defmodule AL.Command do defp meta_label(namespace, counter_key, key) do meta_key = {namespace, key} + case :mnesia.dirty_read(:meta, meta_key) do [{:meta, ^meta_key, label}] -> label + [] -> - label = case :mnesia.dirty_read(:meta, counter_key) do - [{:meta, ^counter_key, n}] -> n - [] -> 0 - end + label = + case :mnesia.dirty_read(:meta, counter_key) do + [{:meta, ^counter_key, n}] -> n + [] -> 0 + end + :mnesia.dirty_write({:meta, counter_key, label + 1}) :mnesia.dirty_write({:meta, meta_key, label}) label diff --git a/lib/AL/objects.ex b/lib/AL/object.ex similarity index 97% rename from lib/AL/objects.ex rename to lib/AL/object.ex index d6ed94a..c6cef11 100644 --- a/lib/AL/objects.ex +++ b/lib/AL/object.ex @@ -1,14 +1,20 @@ -defmodule AL.Objects do +defmodule AL.Object do @moduledoc """ I am the in-memory store for AL objects """ + use TypedStruct + @type class_record() :: {:class, AL.Var.t(), AL.Var.t()} @type super_record() :: {:super, AL.Var.t(), AL.Var.t()} @type slots_record() :: {:slots, AL.Var.t(), AL.Var.t()} @type method_record() :: {:method, AL.Var.t(), AL.Var.t(), AL.Var.t()} @type oapply_record() :: {:oapply, AL.Var.t(), AL.Var.t(), [AL.goal()]} + typedstruct enforce: true do + field(:id, any(), enforce: true) + end + def setup() do case :mnesia.create_table(:class, attributes: [:object, :class], @@ -210,10 +216,12 @@ defmodule AL.Objects do end :mnesia.write({:slots, object, merged}) - - :spawn_process -> + + :send_async -> + :ok + + :send_elixir -> :ok - # TODO Consider whether this should be where the scheduler hydration occurs end end diff --git a/lib/AL/scheduler.ex b/lib/AL/scheduler.ex index 482c914..e27ce73 100644 --- a/lib/AL/scheduler.ex +++ b/lib/AL/scheduler.ex @@ -5,50 +5,29 @@ defmodule AL.Scheduler do GenServer.start_link(__MODULE__, args, name: __MODULE__) end - def processes() do - GenServer.call(__MODULE__, :processes) - end - - @impl true - def handle_call(:processes, _from, state) do - {:reply, state.processes, state} - end - @impl true def init(_opts) do :mnesia.subscribe({:table, :command, :detailed}) - - {:atomic, commands} = :mnesia.transaction(fn -> AL.Command.commands_since(0) end) - - processes = - for {:command, t, _, {:spawn_process, {object, head, body}}} <- commands, into: %{} do - {object, start_process(object, head, body, t)} - end - - {:ok, %{processes: processes}} + {:ok, %{}} end @impl true def handle_info( {:mnesia_table_event, - {:write, :command, {:command, t, _tx_id, {:spawn_process, {object, head, body}}}, _old, _tid}}, + {:write, :command, {:command, _t, _tx_id, {:send_async, {object, method, args}}}, _old, _tid}}, state - ) do - {:noreply, %{state | processes: Map.put(state.processes, object, start_process(object, head, body, t))}} + ) do + Task.start(fn -> AL.eval([{:oapply, :send, [object, method, args]}]) end) + {:noreply, state} end @impl true def handle_info( - {:mnesia_table_event, {:write, :command, {:command, t, _tx_id, command}, _old, _tid}}, + {:mnesia_table_event, + {:write, :command, {:command, _t, _tx_id, {:send_elixir, {pid, message}}}, _old, _tid}}, state - ) do - Enum.each(state.processes, fn {_object, {head, pid}} -> - case AL.Var.unify(command, head, AL.Var.empty_bindings()) do - nil -> :ok - bindings -> send(pid, {:dispatch, t, bindings}) - end - end) - + ) do + send(pid, message) {:noreply, state} end @@ -56,17 +35,4 @@ defmodule AL.Scheduler do def handle_info({:mnesia_table_event, _}, state) do {:noreply, state} end - - defp start_process(_object, head, body, _spawn_t) do - pid = spawn_link(fn -> worker_loop(body) end) - {head, pid} - end - - defp worker_loop(body) do - receive do - {:dispatch, _t, bindings} -> - AL.eval(body, bindings) - worker_loop(body) - end - end end diff --git a/lib/AL/trace.ex b/lib/AL/trace.ex new file mode 100644 index 0000000..02042f7 --- /dev/null +++ b/lib/AL/trace.ex @@ -0,0 +1,88 @@ +defmodule AL.Trace do + @moduledoc """ + I am the tracing module. I provide tracepoint functionality and readable + rendering of AL terms. + """ + + @spec trace(atom()) :: :ok + def trace(point) do + Application.put_env(:al, :tracepoints, MapSet.put(tracepoints(), point)) + end + + @spec untrace(atom()) :: :ok + def untrace(point) do + Application.put_env(:al, :tracepoints, MapSet.delete(tracepoints(), point)) + end + + @spec notrace() :: :ok + def notrace() do + Application.put_env(:al, :tracepoints, MapSet.new()) + end + + @spec tracepoints() :: MapSet.t() + def tracepoints() do + Application.get_env(:al, :tracepoints, MapSet.new()) + end + + @spec call(non_neg_integer(), term(), term(), [term()]) :: :ok + def call(depth, receiver, method, args) do + IO.puts([ + String.duplicate(" ", depth), + "Call: ", + inspect(pretty(receiver)), + " <- ", + inspect(pretty(method)), + "(", + args |> Enum.map(&inspect(pretty(&1))) |> Enum.join(", "), + ")" + ]) + end + + @spec fail(non_neg_integer(), term(), term()) :: :ok + def fail(depth, receiver, method) do + IO.puts([ + String.duplicate(" ", depth), + "Fail: ", + inspect(pretty(receiver)), + " ", + inspect(pretty(method)) + ]) + end + + @spec pretty(term()) :: term() + def pretty(a) when is_atom(a) do + s = Atom.to_string(a) + + cond do + hash?(s) -> :"##{AL.Command.id_label(a)}" + AL.Var.var?(a) -> :"#{strip_freshener(s)}" + true -> a + end + end + + def pretty(t) when is_tuple(t), + do: t |> Tuple.to_list() |> Enum.map(&pretty/1) |> List.to_tuple() + + def pretty(l) when is_list(l), do: Enum.map(l, &pretty/1) + + def pretty(m) when is_map(m), + do: Map.new(m, fn {k, v} -> {pretty(k), pretty(v)} end) + + def pretty(x), do: x + + defp hash?(s) do + byte_size(s) == 32 and Enum.all?(String.to_charlist(s), &(&1 in ?0..?9 or &1 in ?a..?f)) + end + + defp strip_freshener(s) do + s + |> String.split("_") + |> Enum.reverse() + |> Enum.drop_while(&integer_segment?/1) + |> Enum.reverse() + |> Enum.join("_") + end + + defp integer_segment?(""), do: false + defp integer_segment?(s), do: Enum.all?(String.to_charlist(s), &(&1 in ?0..?9)) +end diff --git a/lib/AL/views.ex b/lib/AL/views.ex new file mode 100644 index 0000000..f5dd6ec --- /dev/null +++ b/lib/AL/views.ex @@ -0,0 +1,76 @@ +defmodule AL.Views do + @doc """ + I define GlamorousToolkit views for AL + """ + + + use AL + use GtBridge.View + + alias GtBridge.Phlow.Mondrian + + defview object_examine_view(self = %AL.Object{}, builder) do + {:atomic, {bindings, _program_state}} = AL.run do + examine(^self.id, info) + end + + GtBridge.Views.MapGraph.graph(Map.get(bindings, :"$info"), builder) + |> Mondrian.title("Examine") + end + + defview cell_view(self = %AL.Object{}, builder) do + result = AL.run do + class(^self.id, :cell) + get_slot(^self.id, :subscribers, subscribers) + get_slot(^self.id, :value, value) + unify(nodes, [^self.id | subscribers]) + unify(children, %{^self.id => subscribers}) + end + case result do + {:atomic, {bindings, _program_state}} -> + builder.mondrian() + |> Mondrian.title("Cell View") + |> Mondrian.nodes(Enum.map(Map.get(bindings, :"$nodes"), + fn x -> %AL.Object{id: x} end)) + |> Mondrian.node_label(fn node -> Atom.to_string(node.id) end) + |> Mondrian.edges(fn node -> case Map.get(Map.get(bindings, :"$children"), node.id) do + nil -> [] + children -> Enum.map(children, fn c -> %AL.Object{id: c} end) + end + end) + |> Mondrian.layout(:horizontal_tree) + _e -> builder.empty() + end + end + + defview propagator_view(self = %AL.Object{}, builder) do + result = AL.run do + class(^self.id, :propagator) + get_slot(^self.id, :input_cells, input_cells) + get_slot(^self.id, :output_cell, output_cell) + unify(nodes, [^self.id | [output_cell | input_cells]]) + fold_left(input_cells, [acc, h, result], + [put(acc, h, [^self.id], result)], + %{^self.id => [output_cell]}, children) + end + + IO.inspect(result) + + case result do + {:atomic, {bindings, _program_state}} -> + + builder.mondrian() + |> Mondrian.title("Propagator View") + |> Mondrian.nodes(Enum.map(Map.get(bindings, :"$nodes"), + fn x -> %AL.Object{id: x} end)) + |> Mondrian.node_label(fn node -> Atom.to_string(node.id) end) + |> Mondrian.edges(fn node -> case Map.get(Map.get(bindings, :"$children"), node.id) do + nil -> [] + children -> Enum.map(children, fn c -> %AL.Object{id: c} end) + end + end) + |> Mondrian.layout(:horizontal_tree) + _e -> builder.empty() + end + end +end diff --git a/lib/examples/e_AL.ex b/lib/examples/e_AL.ex index 000bbd0..b6c8100 100644 --- a/lib/examples/e_AL.ex +++ b/lib/examples/e_AL.ex @@ -48,7 +48,7 @@ defmodule Examples.AL do {:atomic, {bindings, result}} = run do method(:object, :init, init_method) - send(init_method, :meta, [:"$class", :"$metaclass"]) + meta(init_method, :"$class", :"$metaclass") end assert Map.get(bindings, :"$class") == :behaviour @@ -189,10 +189,11 @@ defmodule Examples.AL do assert slots == %{a: 99, b: 2} slots end + example map_get() do {:atomic, {bindings, program_state}} = run do - send(%{a: 3, b: 4, c: 3}, :map_get, [k, 3]) + get(%{a: 3, b: 4, c: 3}, k, 3) end assert Map.get(bindings, :"$k") == :c or Map.get(bindings, :"$k") == :a @@ -204,6 +205,17 @@ defmodule Examples.AL do program_state end + example map_put() do + {:atomic, {bindings, program_state}} = + run do + put(%{a: 3, b: 4, c: 3}, :c, 4, m2) + end + + assert bindings|> Map.get(:"$m2") |> Map.get(:c) == 4 + + program_state + end + example gensym() do {:atomic, {bindings, _}} = run do @@ -216,34 +228,49 @@ defmodule Examples.AL do end example create_process() do - head = {:set_class, {:"$object", :"$class"}} + head = [:"$self", :"$object", :"$class"] body = [{:set_slots, :"$object", %{processed: true}}] {:atomic, {bindings, _}} = run do - send(:process, :new, [%{head: ^head, body: ^body}, new_proc]) + new(:process, %{method: :handle, head: ^head, body: ^body}, new_proc) cut - end - + end + new_proc = Map.get(bindings, :"$new_proc") assert is_atom(new_proc) - assert Enum.any?(AL.Scheduler.processes(), fn {_t, {h, _pid}} -> h == head end) bindings end example process_handles_command() do - create_process() + new_proc = Map.get(create_process(), :"$new_proc") + + {:atomic, _} = + run do + send_async(^new_proc, :handle, [:test_object, :test_class]) + end + + Process.sleep(50) + + {:atomic, results} = + :mnesia.transaction(fn -> AL.Object.scan_slots(:test_object, :"$slots") end) + + assert Enum.any?(results, fn {:slots, _, slots} -> Map.get(slots, :processed) == true end) + end + + example process_called_by_var() do + _new_proc = Map.get(create_process(), :"$new_proc") {:atomic, _} = run do - set_class(:test_object, :some_class) + send_async(proc, :handle, [:test_object_2, :test_class]) end Process.sleep(50) {:atomic, results} = - :mnesia.transaction(fn -> AL.Objects.scan_slots(:test_object, :"$slots") end) + :mnesia.transaction(fn -> AL.Object.scan_slots(:test_object_2, :"$slots") end) assert Enum.any?(results, fn {:slots, _, slots} -> Map.get(slots, :processed) == true end) end @@ -273,59 +300,57 @@ defmodule Examples.AL do result end - example list_tests() do - {:atomic, {bindings, result}} = run do - set_oapply(:hd, [[hd | tl], hd]) do - end - set_oapply(:tl, [[hd | tl], tl]) do - end - set_class(:concat_list, :behaviour) - set_oapply(:concat_list, [[], second, second]) do - end - set_oapply(:concat_list, [[first_hd | first_tl], second, [first_hd | inner]]) do - oapply(:concat_list, [first_tl, second, inner]) - end - set_oapply(:reverse_list, [[], []]) do - end - set_oapply(:reverse_list, [[first_hd | first_tl], reversed]) do - oapply(:reverse_list, [first_tl, reversed_tl]) - oapply(:concat_list, [reversed_tl, [first_hd], reversed]) - end - set_oapply(:map_list, [func, [], []]) do - end - set_oapply(:map_list, [func, [first_hd | first_tl], [second_hd | second_tl]]) do - oapply(func, [first_hd, second_hd]) - oapply(:map_list, [func, first_tl, second_tl]) - end - set_oapply(:fold_left, [func, acc, [], acc]) do - end - set_oapply(:fold_left, [func, acc, [hd | tl], result]) do - oapply(func, [acc, hd, next_acc]) - oapply(:fold_left, [func, next_acc, tl, result]) - end - set_oapply(:fold_right, [func, acc, [], acc]) do - end - set_oapply(:fold_right, [func, acc, [hd | tl], result]) do - oapply(:fold_right, [func, acc, tl, next_acc]) - oapply(func, [next_acc, hd, result]) + example not_succeeds_when_goal_fails() do + {:atomic, {_bindings, _}} = + run do + not([class(:nonexistent_xyz, c)]) end - set_oapply(:flatten_list, [lists, result]) do - oapply(:fold_left, [:concat_list, [], lists, result]) + :ok + end + + example not_fails_when_goal_succeeds() do + {:aborted, _} = + run do + not([class(:object, c)]) end - set_oapply(:same_length, [[], []]) do + :ok + end + + example unify_binds_variable() do + {:atomic, {bindings, _}} = + run do + unify(x, :hello) end - set_oapply(:same_length, [[first_hd | first_tl], [second_hd | second_tl]]) do - oapply(:same_length, [first_tl, second_tl]) + assert Map.get(bindings, :"$x") == :hello + :ok + end + + example unify_checks_equality() do + {:aborted, _} = run do unify(:foo, :bar) end + {:atomic, _} = run do unify(:foo, :foo) end + :ok + end + + example call_lambda() do + {:atomic, {bindings, _}} = + run do + call([x, result], [unify(result, x)], [:hello, out]) end - oapply(:hd, [[:w, :x, :y, :z], head]) - oapply(:tl, [[:w, :x, :y, :z], tail]) - oapply(:concat_list, [[:a, :b, :c], [:d, :e, :f], sum]) - oapply(:reverse_list, [[:b, :c, :d, :e, :f], reversed]) - oapply(:map_list, [:reverse_list, [[:a, :b], [:c, :d, :e]], mapped]) - oapply(:fold_left, [:concat_list, [:starter], [[:a], [:b], [:c], [:d]], folded_left]) - oapply(:fold_right, [:concat_list, [:starter], [[:a], [:b], [:c], [:d]], folded_right]) - oapply(:flatten_list, [[[:a, :b], [:c, :d, :e]], flattened]) - oapply(:same_length, [[:c, :d, :e, :f], of_same_length]) + assert Map.get(bindings, :"$out") == :hello + :ok + end + + example list_tests() do + {:atomic, {bindings, result}} = run do + hd([:w, :x, :y, :z], head) + tl([:w, :x, :y, :z], tail) + concat([:a, :b, :c], [:d, :e, :f], sum) + reverse([:b, :c, :d, :e, :f], reversed) + map([[:a, :b], [:c, :d, :e]], :reverse, mapped) + fold_left([[:a], [:b], [:c], [:d]], :concat, [:starter], folded_left) + fold_right([[:a], [:b], [:c], [:d]], :concat, [:starter], folded_right) + flatten([[:a, :b], [:c, :d, :e]], flattened) + same_length([:c, :d, :e, :f], of_same_length) end assert Map.get(bindings, :"$sum") == [:a, :b, :c, :d, :e, :f] assert Map.get(bindings, :"$reversed") == [:f, :e, :d, :c, :b] @@ -338,4 +363,13 @@ defmodule Examples.AL do assert length(Map.get(bindings, :"$of_same_length")) == 4 result end + + example call_lambda_map() do + {:atomic, {bindings, _}} = + run do + map([:a, :b, :c], [x, %{id: x}], [], out) + end + assert Map.get(bindings, :"$out") == [%{id: :a}, %{id: :b}, %{id: :c}] + :ok + end end diff --git a/lib/examples/e_AL_bootstrap.ex b/lib/examples/e_AL_bootstrap.ex index 84e0c06..83e7edc 100644 --- a/lib/examples/e_AL_bootstrap.ex +++ b/lib/examples/e_AL_bootstrap.ex @@ -9,7 +9,7 @@ defmodule Examples.ALBootstrap do example bootstrapped_classes() do :mnesia.transaction(fn -> - class_results = AL.Objects.scan_class(:"$object", :"$class") + class_results = AL.Object.scan_class(:"$object", :"$class") Enum.take(class_results, 3) end) @@ -17,7 +17,7 @@ defmodule Examples.ALBootstrap do example bootstrapped_supers() do :mnesia.transaction(fn -> - super_results = AL.Objects.scan_super(:"$object", :"$super") + super_results = AL.Object.scan_super(:"$object", :"$super") Enum.take(super_results, 3) end) @@ -25,7 +25,7 @@ defmodule Examples.ALBootstrap do example bootstrapped_methods() do :mnesia.transaction(fn -> - method_results = AL.Objects.scan_method(:"$object", :"$method_name", :"$method_id") + method_results = AL.Object.scan_method(:"$object", :"$method_name", :"$method_id") Enum.take(method_results, 1) end) @@ -33,7 +33,7 @@ defmodule Examples.ALBootstrap do example bootstrapped_oapply() do :mnesia.transaction(fn -> - oapply_results = AL.Objects.scan_oapply(:"$object", :"$head", :"$body") + oapply_results = AL.Object.scan_oapply(:"$object", :"$head", :"$body") Enum.take(oapply_results, 1) end) diff --git a/lib/examples/e_AL_constraints.ex b/lib/examples/e_AL_constraints.ex new file mode 100644 index 0000000..495b963 --- /dev/null +++ b/lib/examples/e_AL_constraints.ex @@ -0,0 +1,84 @@ +defmodule Examples.ALConstraints do + @moduledoc """ + """ + + use ExExample + use AL + import ExUnit.Assertions + + example constant() do + {:atomic, {_bindings, _state}} = run do + new(:cell, %{name: :x}, cell) + new(:propagator, %{input_cells: [], output_cell: cell}, propagator) + defmethod(propagator, :constrain, [_self, [], 2]) do end + cut + end + + Process.sleep(50) + + {:atomic, {bindings, _state}} = run do + get_slot(:x, :value, value) + end + + assert Map.get(bindings, :"$value") == 2 + + bindings + end + + example inc() do + constant() + + {:atomic, {bindings, state}} = run do + new(:cell, %{name: :y}, y_cell) + new(:propagator, %{input_cells: [:x], output_cell: y_cell}, propagator) + defmethod(propagator, :constrain, [_self, [x_val], y_val]) do + is(y_val, 1 + x_val) + end + end + + Process.sleep(50) + + {:atomic, {bindings, _state}} = run do + get_slot(:y, :value, value) + end + + assert Map.get(bindings, :"$value") == 3 + + bindings + end + + example bidirectional_adder() do + {:atomic, {bindings, state}} = run do + new(:cell, %{name: :a}, a) + new(:cell, %{name: :b}, b) + new(:cell, %{name: :c}, c) + + new(:propagator, %{input_cells: [a, b], output_cell: c}, propagator_ab) + new(:propagator, %{input_cells: [a, c], output_cell: b}, propagator_ac) + new(:propagator, %{input_cells: [b, c], output_cell: a}, propagator_bc) + + defmethod(propagator_ab, :constrain, [_self, [a_val, b_val], c_val]) do + is(c_val, a_val + b_val) + end + defmethod(propagator_ac, :constrain, [_self, [a_val, c_val], b_val]) do + is(b_val, c_val - a_val) + end + defmethod(propagator_bc, :constrain, [_self, [b_val, c_val], a_val]) do + is(a_val, c_val - b_val) + end + + send_async(b, :constrain, [3]) + send_async(c, :constrain, [5]) + end + + Process.sleep(50) + + {:atomic, {bindings, _state}} = run do + get_slot(:a, :value, value) + end + + assert Map.get(bindings, :"$value") == 2 + + bindings + end +end diff --git a/lib/examples/e_AL_genserver.ex b/lib/examples/e_AL_genserver.ex new file mode 100644 index 0000000..fd837dc --- /dev/null +++ b/lib/examples/e_AL_genserver.ex @@ -0,0 +1,88 @@ +defmodule Examples.ALGenserver do + @moduledoc """ + I demonstrate the pattern of registering an Elixir GenServer as an AL object. + """ + + use ExExample + use AL + import ExUnit.Assertions + + defmodule CounterService do + use GenServer + use AL + + def start_link(object_id) do + GenServer.start_link(__MODULE__, object_id) + end + + @impl true + def init(object_id) do + pid = self() + run do + new(:elixir_process, %{name: ^object_id, pid: ^pid}, _) + defmethod(^object_id, :increment, [self, amount]) do + get_slot(self, :pid, p) + send_elixir(p, {:increment, amount}) + end + end + {:ok, %{object_id: object_id, count: 0}} + end + + @impl true + def handle_info({:increment, amount}, state) do + {:noreply, %{state | count: state.count + amount}} + end + + @impl true + def handle_info({:get_count, reply_to}, state) do + send(reply_to, {:count, state.count}) + {:noreply, state} + end + + @impl true + def terminate(_reason, state) do + object_id = state.object_id + run do + retract_class(^object_id, c) + retract_super(^object_id, s) + end + end + + def count(pid) do + send(pid, {:get_count, self()}) + receive do + {:count, n} -> n + after + 1000 -> :timeout + end + end + end + + example genserver_registers_as_al_object() do + {:ok, pid} = CounterService.start_link(:my_counter) + + {:atomic, results} = + :mnesia.transaction(fn -> AL.Object.scan_class(:my_counter, :"$class") end) + + assert Enum.any?(results, fn {:class, _, c} -> c == :elixir_process end) + + {:atomic, _} = + run do + send_async(:my_counter, :increment, [5]) + end + + Process.sleep(50) + + assert CounterService.count(pid) == 5 + + GenServer.stop(pid) + Process.sleep(50) + + {:atomic, after_stop} = + :mnesia.transaction(fn -> AL.Object.scan_class(:my_counter, :"$class") end) + + assert after_stop == [] + + :ok + end +end diff --git a/lib/examples/e_AL_objects.ex b/lib/examples/e_AL_objects.ex index cb2fde3..1808e12 100644 --- a/lib/examples/e_AL_objects.ex +++ b/lib/examples/e_AL_objects.ex @@ -10,13 +10,13 @@ defmodule Examples.ALObjects do example defmethod() do {:atomic, {bindings, _}} = run do - send(:class, :new, [%{name: :greeter, super: :object, slots: []}, _]) + new(:class, %{name: :greeter, super: :object, slots: []}, _) defmethod(:greeter, :greet, [self, name]) do end - send(:greeter, :new, [_, instance]) - send(instance, :greet, [:world]) + new(:greeter, _, instance) + greet(instance, :world) end assert Map.get(bindings, :"$instance") == %{class: :greeter} @@ -26,8 +26,8 @@ defmodule Examples.ALObjects do example make_point_object() do {:atomic, {bindings, result}} = run do - send(:class, :new, [%{name: :point, super: :object, slots: []}, new_point_class]) - send(new_point_class, :new, [_, new_point_object]) + new(:class, %{name: :point, super: :object, slots: []}, new_point_class) + new(new_point_class, _, new_point_object) cut end @@ -40,10 +40,7 @@ defmodule Examples.ALObjects do example metaclass_init_override() do {:atomic, {b, program_state}} = run do - send(:class, :new, [ - %{name: :counter_meta, super: :class, slots: []}, - _ - ]) + new(:class, %{name: :counter_meta, super: :class, slots: []}, _) set_slots(:counter_meta, %{count: []}) defmethod(:counter_meta, :init, [self, args, self]) do @@ -56,40 +53,38 @@ defmodule Examples.ALObjects do set_class(:initialise_counted_object, :behaviour) set_oapply(:initialise_counted_object, [self, _, self]) do - send(self, :meta, [meta, metaclass]) + meta(self, meta, metaclass) get_slot(metaclass, :count, count) set_slots(metaclass, %{count: ["new object!" | count]}) # TODO find a good way to do call next method cut end - send(:counter_meta, :new, [ + new(:counter_meta, %{name: :example_counter_meta_instance, super: :object, slots: []}, - counter_example_meta_instance - ]) + counter_example_meta_instance) - send(:counter_meta, :new, [ + new(:counter_meta, %{name: :example_counter_meta_instance_2, super: :object, slots: []}, - counter_example_meta_instance_2 - ]) + counter_example_meta_instance_2) - send(:example_counter_meta_instance, :new, [_, example_ii]) + new(:example_counter_meta_instance, _, example_ii) cut get_slot(:counter_meta, :count, c) end - assert Map.get(b, :"$c") == ["new object!", "new class!", "new class!"] - + c = Map.get(b, :"$c") + assert List.first(c) == "new object!" + assert "new class!" in c + program_state end example metaclass_alloc_override() do {:atomic, {b, program_state}} = run do - send(:class, :new, [ - %{name: :durable_meta, super: :object, slots: []}, - _]) + new(:class, %{name: :durable_meta, super: :object, slots: []}, _) defmethod(:durable_meta, :allocate, [self, args, name]) do map_get(args, :name, name) map_get(args, :slots, slots) @@ -101,7 +96,7 @@ defmodule Examples.ALObjects do set_slots(name, slots) end - send(:durable_meta, :new, [%{slots: [], name: :alloc_overriden}, obj]) + new(:durable_meta, %{slots: [], name: :alloc_overriden}, obj) class(obj, obj_class) end @@ -114,7 +109,7 @@ defmodule Examples.ALObjects do example examine() do {:atomic, {bindings, program_state}} = run do - send(:class, :examine, [info]) + examine(:class, info) map_get(info, :methods, methods) map_get(info, :classes, classes) map_get(info, :supers, supers) diff --git a/lib/examples/e_AL_trace.ex b/lib/examples/e_AL_trace.ex new file mode 100644 index 0000000..e0fbd59 --- /dev/null +++ b/lib/examples/e_AL_trace.ex @@ -0,0 +1,29 @@ +defmodule Examples.ALTrace do + @moduledoc """ + I provide tracing examples for AL + """ + + use ExExample + use AL + import ExUnit.Assertions + import ExUnit.CaptureIO + + example trace_object() do + AL.trace(:cell) + output = capture_io(fn -> run do new(:cell, %{name: :traced}, c) end end) + AL.notrace() + + assert String.contains?(output, "Call: :cell") + output + end + + example trace_clause_fail() do + AL.trace(:list_member) + output = capture_io(fn -> run do member([:a, :b], :z) end end) + AL.notrace() + + assert String.contains?(output, "Call:") + assert String.contains?(output, "Fail:") + output + end +end diff --git a/lib/examples/e_AL_users.ex b/lib/examples/e_AL_users.ex new file mode 100644 index 0000000..fd825d5 --- /dev/null +++ b/lib/examples/e_AL_users.ex @@ -0,0 +1,74 @@ +defmodule Examples.ALUsers do + @moduledoc """ + I provide user and ownership examples for AL + """ + + use ExExample + use AL + import ExUnit.Assertions + + example owner_is_a_slot() do + {:atomic, {b, _}} = + run do + new(:user, %{name: :alice}, alice) + new(:owned, %{owner: alice, label: :thing}, obj) + get_slot(obj, :owner, owner) + class(obj, c) + end + + assert Map.get(b, :"$owner") == Map.get(b, :"$alice") + assert Map.get(b, :"$c") == :owned + :ok + end + + example owner_gated_update() do + {:atomic, {b, _}} = + run do + new(:user, %{name: :alice}, alice) + new(:user, %{name: :bob}, bob) + new(:owned, %{owner: alice, label: :secret}, obj) + end + + alice = Map.get(b, :"$alice") + bob = Map.get(b, :"$bob") + obj = Map.get(b, :"$obj") + + {:atomic, _} = + run do + oapply(:guarded_send, [^alice, ^obj, :update, [%{label: :updated}]]) + end + + {:atomic, {b2, _}} = + run do + get_slot(^obj, :label, l) + end + + assert Map.get(b2, :"$l") == :updated + + {:aborted, _} = + run do + oapply(:guarded_send, [^bob, ^obj, :update, [%{label: :hacked}]]) + end + + :ok + end + + example owned_class() do + {:atomic, {b, _}} = + run do + new(:user, %{name: :alice}, alice) + gensym(widget) + new(:owned_class, %{name: widget, super: :owned, owner: alice}, _) + new(widget, %{owner: alice, color: :red}, w) + get_slot(widget, :owner, class_owner) + get_slot(w, :owner, instance_owner) + class(w, wc) + end + + alice = Map.get(b, :"$alice") + assert Map.get(b, :"$class_owner") == alice + assert Map.get(b, :"$instance_owner") == alice + assert Map.get(b, :"$wc") == Map.get(b, :"$widget") + :ok + end +end diff --git a/mix.exs b/mix.exs index 2300f85..dc2f8a0 100644 --- a/mix.exs +++ b/mix.exs @@ -24,7 +24,8 @@ defmodule AL.MixProject do defp deps do [ {:typed_struct, "~> 0.3.0"}, - {:ex_example, "~> 0.1.1"} + {:ex_example, "~> 0.1.1"}, + {:gt_bridge, git: "https://github.com/mariari/ElixirGtBridge.git"} ] end end diff --git a/mix.lock b/mix.lock index e4a9aac..0fe955b 100644 --- a/mix.lock +++ b/mix.lock @@ -1,11 +1,31 @@ %{ "cachex": {:hex, :cachex, "4.1.1", "574c5cd28473db313a0a76aac8c945fe44191659538ca6a1e8946ec300b1a19f", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:ex_hash_ring, "~> 6.0", [hex: :ex_hash_ring, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "d6b7449ff98d6bb92dda58bd4fc3189cae9f99e7042054d669596f56dc503cd8"}, + "cowboy": {:hex, :cowboy, "2.16.1", "fa04080b602ff25c40a7700f2dc0152dbc1ba26b42093ae0fa9bb7a337d5a242", [:make, :rebar3], [{:cowlib, ">= 2.16.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, ">= 1.8.0 and < 3.0.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "b8ea4dd317a043e3177ec840cfa3bcb47cfb41035d3abb24d954dc7d51def399"}, + "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, + "cowlib": {:hex, :cowlib, "2.17.1", "3e6053016d1ab245730f0af688755476dcedb1c25ed8fb5751f59a2bfdc0c9af", [:make, :rebar3], [], "hexpm", "ff08bd17e6dd931445b18af77315b9b5fe052407110964ad2588c686b57b5e3f"}, "eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"}, + "event_broker": {:hex, :event_broker, "1.1.1", "40788d9131e5ff87ed97765dd8818186ada9b0cc816af9811a9270b0bfeff5a8", [:make, :mix], [{:ex_example, "~> 0.1.1", [hex: :ex_example, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:typed_struct, "~> 0.3.0", [hex: :typed_struct, repo: "hexpm", optional: false]}], "hexpm", "15c9d400542f5f544eeea90979cf108b5b3220172d925eb33143b14af44b106b"}, "ex_example": {:hex, :ex_example, "0.1.1", "6651636401262149399420415b97b3dbcbe034aaf84e2336696eef4a780a6ee8", [:mix], [{:cachex, "~> 4.1.1", [hex: :cachex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16.0", [hex: :libgraph, repo: "hexpm", optional: false]}, {:typed_struct, "~> 0.3.0", [hex: :typed_struct, repo: "hexpm", optional: false]}], "hexpm", "df2730873fa1ce4e2650693a67026517aadd4ac1a0011afd4e17b348474bbd23"}, "ex_hash_ring": {:hex, :ex_hash_ring, "6.0.4", "bef9d2d796afbbe25ab5b5a7ed746e06b99c76604f558113c273466d52fa6d6b", [:mix], [], "hexpm", "89adabf31f7d3dfaa36802ce598ce918e9b5b33bae8909ac1a4d052e1e567d18"}, + "finch": {:hex, :finch, "0.22.0", "5c48fa6f9706a78eb9036cacb67b8b996b4e66d111c543f4c29bb0f879a6806b", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.8", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b94e83c47780fc6813f746a1f1a34ee65cda42da4c5ea26a68f0acc4498e23dc"}, + "gt_bridge": {:git, "https://github.com/mariari/ElixirGtBridge.git", "97ceed38b42515911a7e01f5a314f6d944e4bed8", []}, + "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, + "jason": {:hex, :jason, "1.4.5", "2e3a008590b0b8d7388c20293e9dcc9cf3e5d642fd2a114e4cbbb52e595d940a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b0c823996102bcd0239b3c2444eb00409b72f6a140c1950bc8b457d836b30684"}, + "jexon": {:hex, :jexon, "0.9.5", "fa4c851c0576b6f6e3d563fdccea6172bc32bc6fac1d7f34b5b47cad6c699359", [:mix], [{:jason, "~> 1.4.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "76e4dea871745d5dc49515c74d3b287fb41fb742aca7d1c4b146cc8b8f70e2ba"}, "jumper": {:hex, :jumper, "1.0.2", "68cdcd84472a00ac596b4e6459a41b3062d4427cbd4f1e8c8793c5b54f1406a7", [:mix], [], "hexpm", "9b7782409021e01ab3c08270e26f36eb62976a38c1aa64b2eaf6348422f165e1"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, + "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, + "mint": {:hex, :mint, "1.9.0", "d6f534c2a3e98b2a8cc749b4796eb77e9e3af79a76f96e4c74035a827de0d318", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "007154c7d8c43916aed3c93afd1f11aebbaa9c5ff4b7ba55ebe0d17ee0296042"}, + "msgpax": {:hex, :msgpax, "2.4.0", "4647575c87cb0c43b93266438242c21f71f196cafa268f45f91498541148c15d", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "ca933891b0e7075701a17507c61642bf6e0407bb244040d5d0a58597a06369d2"}, + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, + "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, + "plug": {:hex, :plug, "1.19.2", "e4950525b22c6789dfb38a3f95d47171ba159da3fc5a33be9643b43d5e8adb98", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b6fce20a56af5e60fa5dfecf3f907bb98ec981be43c79a3809a499bc3d133de0"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.7.5", "261f21b67aea8162239b2d6d3b4c31efde4daa22a20d80b19c2c0f21b34b270e", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "20884bf58a90ff5a5663420f5d2c368e9e15ed1ad5e911daf0916ea3c57f77ac"}, + "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"}, + "ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"}, + "req": {:hex, :req, "0.5.18", "48e6431cb4135e8a7815e745177485369a9b4a9924d5fe68ca00eb09ceaed1ef", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.21.0 or ~> 0.22.0", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "fa03812c440a9754bf34355e0c5d4f3ed316458db62e3284b7a352ef8dc0b996"}, "sleeplocks": {:hex, :sleeplocks, "1.1.3", "96a86460cc33b435c7310dbd27ec82ca2c1f24ae38e34f8edde97f756503441a", [:rebar3], [], "hexpm", "d3b3958552e6eb16f463921e70ae7c767519ef8f5be46d7696cc1ed649421321"}, + "telemetry": {:hex, :telemetry, "1.4.2", "a0cb522801dffb1c49fe6e30561badffc7b6d0e180db1300df759faa22062855", [:rebar3], [], "hexpm", "928f6495066506077862c0d1646609eed891a4326bee3126ba54b60af61febb1"}, "typed_struct": {:hex, :typed_struct, "0.3.0", "939789e3c1dca39d7170c87f729127469d1315dcf99fee8e152bb774b17e7ff7", [:mix], [], "hexpm", "c50bd5c3a61fe4e198a8504f939be3d3c85903b382bde4865579bc23111d1b6d"}, "unsafe": {:hex, :unsafe, "1.0.2", "23c6be12f6c1605364801f4b47007c0c159497d0446ad378b5cf05f1855c0581", [:mix], [], "hexpm", "b485231683c3ab01a9cd44cb4a79f152c6f3bb87358439c6f68791b85c2df675"}, } diff --git a/test/al_test.exs b/test/al_test.exs index ba1a3cc..8b3bd39 100644 --- a/test/al_test.exs +++ b/test/al_test.exs @@ -9,3 +9,19 @@ end defmodule AlObjectsTest do use ExExample.ExUnit, for: Examples.ALObjects end + +defmodule ALGenserverTest do + use ExExample.ExUnit, for: Examples.ALGenserver +end + +defmodule ALConstraintsTest do + use ExExample.ExUnit, for: Examples.ALConstraints +end + +defmodule ALTraceTest do + use ExExample.ExUnit, for: Examples.ALTrace +end + +defmodule ALUsersTest do + use ExExample.ExUnit, for: Examples.ALUsers +end