From e8d87dc85366f39ea332055064cf04733daa381f Mon Sep 17 00:00:00 2001 From: l4e21 Date: Mon, 8 Jun 2026 15:58:36 +0100 Subject: [PATCH 01/16] Added async sends --- .mnesiastore/LATEST.LOG | Bin 65643 -> 65579 bytes .mnesiastore/schema.DAT | Bin 9778 -> 9778 bytes lib/AL.ex | 59 +++++++++++++++++++--------------------- lib/AL/application.ex | 29 ++++++++++++++++---- lib/AL/command.ex | 10 +++---- lib/AL/objects.ex | 3 +- lib/AL/scheduler.ex | 52 +++-------------------------------- lib/examples/e_AL.ex | 29 +++++++++++++++----- 8 files changed, 83 insertions(+), 99 deletions(-) diff --git a/.mnesiastore/LATEST.LOG b/.mnesiastore/LATEST.LOG index 3fbfe36e597a367c1b8c1c031b5144bd097fa5bd..20ffbab199ba064b50e7258209c9e34a55f8a652 100644 GIT binary patch delta 3820 zcmc&$c~q3w73U4i3?s~<43(;Ef(r!^m;q)08BiMqAz}qlE5ZznB8Sz%Gf|U@C$?2{ znizk_B^pqo7_BRQMvYWaF^Q&iZ3RW#6OC&;Hfl6g``-Iz=8SD~@W0MK-+k}>?tORe z^1JU&o!90%uO(UPA1od!U4+F$TG8p@A&CRo9fxfaKZ(Z!gialT9M8z+X9Yz0((rlW z!~S~$D!obTeVp>jbb@;B>y1vyD2c~oh8J=?ZA6*)B*NeTzr07m1?;rBWsa~(Y9tbm z=T6H)iaeVrFE5ip6^%c(Q(F4qx3qRMC@ zb-{ytDg3*oOP%o6I~;yksihzWzpork6fXD&sv@H7Xxr#NabEqlwZ5 z_v@KUly@IraKcR@{6H@)1+jme?YF8owreedE-IfOJ8}Wkq0_K<$Wx(W=piB*^dk$n zzt2Rwva+hOs>GUBSyftPtM%~kI7}~zC6TxrZ^DbSGTewAiv6a33U70Hc~!Bw){0A} z-uQ<;0$s`8C`-t|#gTCuyUaGL+B(x_mxpsfQ2lx_;fSKNR%|uIbGbbJ54e~tNAfTo z@qs4MAIDP^2pUtX00tOE;rk3cJJ^? ztG%$wQf8Mju({N6%I|eMl~IVbxE3j6V-zkOBdIc=0QCMt>;y&+BEk#87J&aBA#$Me6EHn(n3%xgfpB#mA#yB(> zgOO@n1+VzQIH8vzaCi)Mq)KsIFGoaL41$ev{7%u-*c&-17xV68)>bwa*Q?<=40SKBf z4Y#7jT3;}Mey7y1CcxJPDs0mhV@09(eaphcC*$LcVG;B*E0)tiKdm3;}$ukyk1RoC&``ZQtQieQ4pAN#_8RX<$$=qkGE zBQRl&hSy40{aGB<`=aw>J(?TBXlKn|zZe^*s;MN~FcKkam^67^G)}JchiR<>ReYzV zDH)^Jva^^q15w|&4?d0J%7nHlekf~7$8^3kdEH-;{|P&rx8@iItQVyrE_?He*iPT3 z6il~psWA&aO-!2p$#vv&sc&O{%vt*(9y|8K(79T}ywN0;dfDa0RTUNH$`W&uIT9^R z{qgpOIXGGr>WGpf^ukvk?(xLf(rd^LF2bt6)c`Z~_Gq^t`xCYMUw_>nHUHSKU3OUCt1PjSz0 zirHn1d-*!XsGnK{<|uG`qnae+?en4k(_tNIidgrfZ+u4|k`xA1_P5A~Zz z)STq5ZL8y4Hsy^i;W+~=38)RiHQr8-6-+IDDB9BxTXy$g^yES_Lr)LTgpK0~3vn4ap~-T0 zCd-_Ku`Sue31E6dC(md(f>nUb_dy zw()UmoBwW_K1XkmT&yfiC%@uXVL7pY_rlx9`6R;JGrVbptdkVL#?q%j|IvE6Io+P z6{Vh~62x2%K=kE4h;E+*TgON|KP$(|i%PtIu{ZX2Dxr+?#{KphjrimL=l!a4o*+c!>6kYX3YZsfy=R-HBL#{w~_j zsYxE%E=1CcSVfkfXY1J)k|4dxq#NgkY4Y6zn{Up;+D;v2UsfxbY1jK3AzfP3cl1Hv zg#fCkJ8#q!xGNS=#YLAx=pRS2p5J>p z@9%xz@6;~yytB;HK0*0ifrqcQy1=7mxq4sgx7a*_(?wv1u^(Nzp*661z)3>Mg0CcW{(rz^#g}zsx%bD;AN_@ zxKbU-k|L-F%1iBf-GK?FwAF5F? zSZ$Z$RAMTgu94xJ^w*L4@d1f$t#OpG_i8%$>NY>PQ6 zF)lhjF}9!}Az`k>CbwG3^Ud>3Rx8~|bu^_GbGgl1$3<||Bx0wgP!-X~FY$Ah9WGXB zNHHxsqk${fe9A4>az+fbLL>0{cd4zvwWP=*y2QTbp=j8s3=Mxa5_da&G$K!h^f=Xf zEh3TV{3?Seks();5*kJ--fI3Crf*S_2wZD^#dX54dYnC%K?ZPaDaEE?#`)>kPO*_V zeLmlHLb1ZTGjH>ROmfF*iyDlA1)`#NyCK2~!{beXN2Gc7kwv30WQb|{bH>F`dUn#ak^thUb zy_G}g*}*MA2wDF=sy2zc3CP~ebQ&w$F#H3y8t~hjc)E+N0xKh__EtE0Z#Cs^Jt8zWjgOvCgbo{wrbuu8asPr zLHJ_rQUqkmRY~ruNmMnkG8J*Bv^@!>Y~a)F3OxMEK-9fF2l<#wip{3-{P~4eUTD_& zCFNF|yA-DV0SMf$MnjGA`7~#NaU9(|gG_Z9clMs5x`GjnkN5Clj8(hExtq05vF^<| z!m7PD39pPH2gC~b{O|*br@yN)^mQ7tke9>4x`}uiiY^^y=k5^&_G-)sIIfTooih_e+sm5Hx6D#rW-^!OvoXQugGgH+ zv5U7WN0kBNp5^UXXsXLT``X_4y@3SvxyDx9IiZH{sce^=g}Uar(k;T^|1-jf@$RV} zZyNvKgLS%cR$0quN;XBvZ?H{MN^l>FMyXVx?OBp3(GMY|j(B~GLlS>eA#w;_L%r;VBl3>M|p{iVW+qtHR z=suH(yJwVyt+bSE3AAT*d>2dzC*Enxd68;?Uqo;mm?q(K(edRiGKJj-7rx+~L-Ihh z<-(y0v~k!&X67dg$j>>T^BX=~@$_r)bC>c53SkeStUrmp*LX?ny$3Dg6pp@9M4)Rm zkQjcW6+B5*tx`N}OXIy(+ggDkJ4c{9N{YAI-XQV#dz~8b?P-W?8_nMb6}tj(mp*%1 z$NIo=;}WWGXwZ67Ni(&wV-)_<#8yR}iHPc8tL7T58@e6M%}n~oj#xDEbN|jMbpLtn zaMoz5DemcU;)u9>_IHu9ZrNx2tpG zdX-d?;P~Etr5l%pMIB1b%x4Q<1f&vYpsY&H%KTknIQLEu&MNZyGk@#~hp$tD+4Q}9 zi&p@7jTegNZIS=u-xiC*_tx!&meJ>WBPK}08>*ghmY|mrR@@V9E|(lPre|LH8H_rvz2%!BkzBUo#rr^cbhLr+-Br8 z|E_5P75e`cqF7?Hm2^2H@BNMln2bGCMrgB@EKo+Wyc8x={|2H5C?gM)c`<1cOlJRU pxQt>qBk%RYJ76;ZUjbz@Hd`szGxEOqb{i&a4-(ej{8h!45dh`2Q$+v( delta 202 zcmdnwv&m;ew-|3!^R7lMsm&639E{cfCtr}rnwVg**-E^Vk$010a~o7<_rJ{-ByKbE z-dxGi4i)WNfxlu4m-c7C!?Mwgw67Z~m%c%Lo88v`xPN diff --git a/lib/AL.ex b/lib/AL.ex index 79802b1..5589a6c 100644 --- a/lib/AL.ex +++ b/lib/AL.ex @@ -70,7 +70,7 @@ 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()} | {:gensym, AL.Var.t()} | {:print, AL.Var.t()} | :fail @@ -174,8 +174,8 @@ 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({:send_async, _, [object, method, args]}), + do: {:send_async, ast_to_pattern(object), ast_to_pattern(method), ast_to_pattern(args)} def ast_to_pattern({:defmethod, _, [class, method_name, head, body]}) do {:oapply, :defmethod, [ @@ -275,7 +275,7 @@ defmodule AL do tx_id: tx_id, trace: [], program: program - }) + }) if result.active_choicepoint.bindings == nil do :mnesia.abort(format_failure(result.trace)) @@ -355,7 +355,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 +394,6 @@ defmodule AL do } result = interp(goal, next_frame) - continue(result) end end @@ -583,9 +582,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 @@ -652,19 +649,19 @@ 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 + }, + choicepoint_stack: + alternative_choicepoints ++ [{:mark, freshener} | state.choicepoint_stack] } end end @@ -673,11 +670,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) } @@ -849,12 +847,11 @@ defmodule AL do 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({:gensym, var}, state) do sym = :crypto.strong_rand_bytes(16) |> Base.encode16(case: :lower) |> String.to_atom() diff --git a/lib/AL/application.ex b/lib/AL/application.ex index d0ae979..315e3f1 100644 --- a/lib/AL/application.ex +++ b/lib/AL/application.ex @@ -66,13 +66,19 @@ defmodule AL.Application do ) do class(self, class) implies( - [lookup(class, method, id)], + [method(self, method, id)], [ - print(["calling", id, "from", class, "with args", [self | args]]), + print(["calling", id, "from", self, "with args", [self | args]]), oapply(id, [self | args]) ], - [:fail] - ) + [implies( + [lookup(class, method, id)], + [ + print(["calling", id, "from", class, "with args", [self | args]]), + oapply(id, [self | args]) + ], + [:fail] + )]) end set_class(:defmethod, :behaviour) @@ -136,11 +142,22 @@ defmodule AL.Application do end send(:class, :new, [%{name: :process, super: :object, slots: []}, _]) - defmethod(:process, :init, [self, args, new_obj]) do + 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) - spawn_process(new_obj, head, body) + + 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 diff --git a/lib/AL/command.ex b/lib/AL/command.ex index 6197531..a1819a5 100644 --- a/lib/AL/command.ex +++ b/lib/AL/command.ex @@ -24,7 +24,7 @@ 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()}} @doc """ Initialise the event log, or re-use the one on disc. @@ -176,11 +176,11 @@ 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 write_command(non_neg_integer(), command()) :: :ok def write_command(tx_id, command) do {t1, _t2} = inc_system_time() diff --git a/lib/AL/objects.ex b/lib/AL/objects.ex index d6ed94a..d80b9b2 100644 --- a/lib/AL/objects.ex +++ b/lib/AL/objects.ex @@ -211,9 +211,8 @@ defmodule AL.Objects do :mnesia.write({:slots, object, merged}) - :spawn_process -> + :send_async -> :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..6ab0279 100644 --- a/lib/AL/scheduler.ex +++ b/lib/AL/scheduler.ex @@ -5,50 +5,19 @@ 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}}, - state - ) do - {:noreply, %{state | processes: Map.put(state.processes, object, start_process(object, head, body, t))}} - end - - @impl true - def handle_info( - {:mnesia_table_event, {:write, :command, {:command, t, _tx_id, command}, _old, _tid}}, + {:write, :command, {:command, _t, _tx_id, {:send_async, {object, method, args}}}, _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 + Task.start(fn -> AL.eval([{:oapply, :send, [object, method, args]}]) end) {:noreply, state} end @@ -56,17 +25,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/examples/e_AL.ex b/lib/examples/e_AL.ex index 000bbd0..c7927ef 100644 --- a/lib/examples/e_AL.ex +++ b/lib/examples/e_AL.ex @@ -216,28 +216,27 @@ 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]) + send(:process, :new, [%{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 - set_class(:test_object, :some_class) + send_async(^new_proc, :handle, [:test_object, :test_class]) end Process.sleep(50) @@ -247,6 +246,22 @@ defmodule Examples.AL do 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 + send_async(proc, :handle, [:test_object_2, :test_class]) + end + + Process.sleep(50) + + {:atomic, results} = + :mnesia.transaction(fn -> AL.Objects.scan_slots(:test_object_2, :"$slots") end) + + assert Enum.any?(results, fn {:slots, _, slots} -> Map.get(slots, :processed) == true end) + end example arithmetic() do {:atomic, {bindings, result}} = run do From cd3140b7f2daacb538087ce68554e3d9988d6dbd Mon Sep 17 00:00:00 2001 From: l4e21 Date: Mon, 8 Jun 2026 16:30:58 +0100 Subject: [PATCH 02/16] Add bridge for elixir processes --- .mnesiastore/LATEST.LOG | Bin 65579 -> 68572 bytes .mnesiastore/schema.DAT | Bin 9778 -> 9778 bytes lib/AL.ex | 11 +++++ lib/AL/application.ex | 14 +++++- lib/AL/command.ex | 6 +++ lib/AL/objects.ex | 3 ++ lib/AL/scheduler.ex | 10 ++++ lib/examples/e_AL_genserver.ex | 88 +++++++++++++++++++++++++++++++++ test/al_test.exs | 4 ++ 9 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 lib/examples/e_AL_genserver.ex diff --git a/.mnesiastore/LATEST.LOG b/.mnesiastore/LATEST.LOG index 20ffbab199ba064b50e7258209c9e34a55f8a652..592031d9702cdfc5c9aaab577d0b44138bf0310a 100644 GIT binary patch literal 68572 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 diff --git a/lib/AL.ex b/lib/AL.ex index 5589a6c..0a16d55 100644 --- a/lib/AL.ex +++ b/lib/AL.ex @@ -71,6 +71,7 @@ defmodule AL do | {:retract_method, AL.Var.t(), AL.Var.t(), AL.Var.t()} | {:retract_oapply, AL.Var.t(), AL.Var.t()} | {: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()} | :fail @@ -177,6 +178,9 @@ defmodule AL do 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, [ ast_to_pattern(class), @@ -191,6 +195,8 @@ defmodule AL do 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 """ @@ -851,6 +857,11 @@ defmodule AL 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() diff --git a/lib/AL/application.ex b/lib/AL/application.ex index 315e3f1..f6c8fdb 100644 --- a/lib/AL/application.ex +++ b/lib/AL/application.ex @@ -141,7 +141,19 @@ defmodule AL.Application do findall([head, body], [clause(self, head, body)], clauses) end - send(:class, :new, [%{name: :process, super: :object, slots: []}, _]) + send(:class, :new, [%{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 + + send(:class, :new, [%{name: :process, super: :object, slots: []}, _]) defmethod(:process, :allocate, [self, args, new_obj]) do class(self, meta) diff --git a/lib/AL/command.ex b/lib/AL/command.ex index a1819a5..2aa1035 100644 --- a/lib/AL/command.ex +++ b/lib/AL/command.ex @@ -25,6 +25,7 @@ defmodule AL.Command do | {:retract_method, {AL.Var.t(), AL.Var.t(), AL.Var.t()}} | {:retract_oapply, {AL.Var.t(), AL.Var.t()}} | {: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. @@ -180,6 +181,11 @@ defmodule AL.Command do 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 def write_command(tx_id, command) do diff --git a/lib/AL/objects.ex b/lib/AL/objects.ex index d80b9b2..8480908 100644 --- a/lib/AL/objects.ex +++ b/lib/AL/objects.ex @@ -213,6 +213,9 @@ defmodule AL.Objects do :send_async -> :ok + + :send_elixir -> + :ok end end diff --git a/lib/AL/scheduler.ex b/lib/AL/scheduler.ex index 6ab0279..e27ce73 100644 --- a/lib/AL/scheduler.ex +++ b/lib/AL/scheduler.ex @@ -21,6 +21,16 @@ defmodule AL.Scheduler do {:noreply, state} end + @impl true + def handle_info( + {:mnesia_table_event, + {:write, :command, {:command, _t, _tx_id, {:send_elixir, {pid, message}}}, _old, _tid}}, + state + ) do + send(pid, message) + {:noreply, state} + end + @impl true def handle_info({:mnesia_table_event, _}, state) do {:noreply, state} diff --git a/lib/examples/e_AL_genserver.ex b/lib/examples/e_AL_genserver.ex new file mode 100644 index 0000000..2e7bf91 --- /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 + send(:elixir_process, :new, [%{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.Objects.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.Objects.scan_class(:my_counter, :"$class") end) + + assert after_stop == [] + + :ok + end +end diff --git a/test/al_test.exs b/test/al_test.exs index ba1a3cc..fa4576a 100644 --- a/test/al_test.exs +++ b/test/al_test.exs @@ -9,3 +9,7 @@ end defmodule AlObjectsTest do use ExExample.ExUnit, for: Examples.ALObjects end + +defmodule ALGenserverTest do + use ExExample.ExUnit, for: Examples.ALGenserver +end From 0437afb7e0e78eb8d6815d868b754e7ffc25ae77 Mon Sep 17 00:00:00 2001 From: l4e21 Date: Mon, 8 Jun 2026 17:43:43 +0100 Subject: [PATCH 03/16] Clean up the bootstrap --- .mnesiastore/LATEST.LOG | Bin 68572 -> 65658 bytes .mnesiastore/schema.DAT | Bin 9778 -> 9778 bytes lib/AL.ex | 9 ++ lib/AL/application.ex | 157 ++--------------------------- lib/AL/bootstrap/core.ex | 109 ++++++++++++++++++++ lib/AL/bootstrap/elixir_process.ex | 19 ++++ lib/AL/bootstrap/lists.ex | 69 +++++++++++++ lib/AL/bootstrap/process.ex | 26 +++++ lib/examples/e_AL.ex | 60 ++--------- 9 files changed, 248 insertions(+), 201 deletions(-) create mode 100644 lib/AL/bootstrap/core.ex create mode 100644 lib/AL/bootstrap/elixir_process.ex create mode 100644 lib/AL/bootstrap/lists.ex create mode 100644 lib/AL/bootstrap/process.ex diff --git a/.mnesiastore/LATEST.LOG b/.mnesiastore/LATEST.LOG index 592031d9702cdfc5c9aaab577d0b44138bf0310a..fc274acde6dcd13748bfadea57439b2f29a69854 100644 GIT binary patch literal 65658 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 LKl&j3Cq(*xx1kUE literal 68572 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 diff --git a/lib/AL.ex b/lib/AL.ex index 0a16d55..ce8718b 100644 --- a/lib/AL.ex +++ b/lib/AL.ex @@ -427,6 +427,14 @@ 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 [] -> @@ -459,6 +467,7 @@ defmodule AL do } end end + end end def interp({:get_super, object_pattern, super_pattern}, state) do diff --git a/lib/AL/application.ex b/lib/AL/application.ex index f6c8fdb..214281a 100644 --- a/lib/AL/application.ex +++ b/lib/AL/application.ex @@ -5,7 +5,6 @@ defmodule AL.Application do """ use Application - use AL @impl true def start(_type, _args) do @@ -22,155 +21,13 @@ 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( - [method(self, method, id)], - [ - print(["calling", id, "from", self, "with args", [self | args]]), - oapply(id, [self | args]) - ], - [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: :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 - - send(:class, :new, [%{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 + 0 -> + AL.Bootstrap.Core.setup() + AL.Bootstrap.Lists.setup() + AL.Bootstrap.ElixirProcess.setup() + AL.Bootstrap.Process.setup() + _ -> + :ok end end end diff --git a/lib/AL/bootstrap/core.ex b/lib/AL/bootstrap/core.ex new file mode 100644 index 0000000..9d82d82 --- /dev/null +++ b/lib/AL/bootstrap/core.ex @@ -0,0 +1,109 @@ +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)], + [ + print(["calling", id, "from", self, "with args", [self | args]]), + oapply(id, [self | args]) + ], + [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 + + 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..596c363 --- /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 + send(:class, :new, [%{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..df2c289 --- /dev/null +++ b/lib/AL/bootstrap/lists.ex @@ -0,0 +1,69 @@ +defmodule AL.Bootstrap.Lists do + use AL + + def setup() do + run do + send(:class, :new, [%{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 + send(ft, :concat, [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 + send(t, :member, [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 + send(t, :reverse, [reversed_tl]) + send(reversed_tl, :concat, [[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, [[fh | ft], func, [sh | st]]) do + send(fh, func, [sh]) + send(ft, :map, [func, 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, [[h | t], func, acc, result]) do + send(acc, func, [h, next_acc]) + send(t, :fold_left, [func, 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, [[h | t], func, acc, result]) do + send(t, :fold_right, [func, acc, next_acc]) + send(next_acc, func, [h, result]) + end + + defmethod(:list, :flatten, [lists, result]) do + send(lists, :fold_left, [: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 + send(ft, :same_length, [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..bface60 --- /dev/null +++ b/lib/AL/bootstrap/process.ex @@ -0,0 +1,26 @@ +defmodule AL.Bootstrap.Process do + use AL + + def setup() do + run do + send(:class, :new, [%{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/examples/e_AL.ex b/lib/examples/e_AL.ex index c7927ef..869bb6d 100644 --- a/lib/examples/e_AL.ex +++ b/lib/examples/e_AL.ex @@ -290,57 +290,15 @@ defmodule Examples.AL do 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]) - end - set_oapply(:flatten_list, [lists, result]) do - oapply(:fold_left, [:concat_list, [], lists, result]) - end - set_oapply(:same_length, [[], []]) do - end - set_oapply(:same_length, [[first_hd | first_tl], [second_hd | second_tl]]) do - oapply(:same_length, [first_tl, second_tl]) - 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]) + send([:w, :x, :y, :z], :hd, [head]) + send([:w, :x, :y, :z], :tl, [tail]) + send([:a, :b, :c], :concat, [[:d, :e, :f], sum]) + send([:b, :c, :d, :e, :f], :reverse, [reversed]) + send([[:a, :b], [:c, :d, :e]], :map, [:reverse, mapped]) + send([[:a], [:b], [:c], [:d]], :fold_left, [:concat, [:starter], folded_left]) + send([[:a], [:b], [:c], [:d]], :fold_right, [:concat, [:starter], folded_right]) + send([[:a, :b], [:c, :d, :e]], :flatten, [flattened]) + send([:c, :d, :e, :f], :same_length, [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] From 56e8bb22c9f88daddb8a5e9fa91651e7b881d705 Mon Sep 17 00:00:00 2001 From: l4e21 Date: Tue, 9 Jun 2026 15:17:34 +0100 Subject: [PATCH 04/16] Add Sussman constraint propagators --- .mnesiastore/LATEST.LOG | Bin 65658 -> 65694 bytes .mnesiastore/schema.DAT | Bin 9778 -> 9778 bytes lib/AL.ex | 55 ++++++++++++++++++++ lib/AL/application.ex | 1 + lib/AL/bootstrap/constraints.ex | 74 +++++++++++++++++++++++++++ lib/AL/bootstrap/core.ex | 8 +-- lib/examples/e_AL.ex | 40 +++++++++++++++ lib/examples/e_AL_constraints.ex | 84 +++++++++++++++++++++++++++++++ test/al_test.exs | 4 ++ 9 files changed, 263 insertions(+), 3 deletions(-) create mode 100644 lib/AL/bootstrap/constraints.ex create mode 100644 lib/examples/e_AL_constraints.ex diff --git a/.mnesiastore/LATEST.LOG b/.mnesiastore/LATEST.LOG index fc274acde6dcd13748bfadea57439b2f29a69854..ef042fe45e4424271a84268b467d23de35205a0c 100644 GIT binary patch literal 65694 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 XXMT1{@p|I^kp?Ys6v)q}a@_v`A41Q` literal 65658 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 LKl&j3Cq(*xx1kUE diff --git a/.mnesiastore/schema.DAT b/.mnesiastore/schema.DAT index 0ce1fe84a4ccd90a359460091b0468a751e5376b..1f4b40a16ec59efdc97135c6c0de9360e508d610 100644 GIT binary patch delta 193 zcmdnwv&m;ew;0o#4<$6Zm@)k#!@Xnt=Vg1ctRcsjn$HPy% delta 197 zcmdnwv&m;ew;1o=IXr)NXl$3r<6zwLbMgg=tceL0o2|q<8QE5z;A7mWF?pg`;btqz zLPnl59UVYESY_qp^Agz;6C^fUNtZM7E^glmli2weVua9UD_NjS 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 diff --git a/lib/AL/application.ex b/lib/AL/application.ex index 214281a..6dd384b 100644 --- a/lib/AL/application.ex +++ b/lib/AL/application.ex @@ -26,6 +26,7 @@ defmodule AL.Application do AL.Bootstrap.Lists.setup() AL.Bootstrap.ElixirProcess.setup() AL.Bootstrap.Process.setup() + AL.Bootstrap.Constraints.setup() _ -> :ok end diff --git a/lib/AL/bootstrap/constraints.ex b/lib/AL/bootstrap/constraints.ex new file mode 100644 index 0000000..bf1c094 --- /dev/null +++ b/lib/AL/bootstrap/constraints.ex @@ -0,0 +1,74 @@ +defmodule AL.Bootstrap.Constraints do + use AL + + def setup() do + run do + send(:class, :new, [%{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), + send(subscribers, :member, [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 + + send(:class, :new, [%{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([send(input_cells, :member, [input_cell])], + [send(input_cell, :subscribe, [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, + [send(input_cells, :member, [input_cell]), + get_slot(input_cell, :value, input_cell_value)], + input_cell_values) + forall([send(input_cell_values, :member, [input_cell_value])], + [not([unify(input_cell_value, :absent)])]) + + send(self, :constrain, [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 index 9d82d82..3f8fdec 100644 --- a/lib/AL/bootstrap/core.ex +++ b/lib/AL/bootstrap/core.ex @@ -77,11 +77,11 @@ defmodule AL.Bootstrap.Core do end defmethod(:object, :allocate, [self, _, self]) do - print(["allocate", self]) + # print(["allocate", self]) end defmethod(:object, :init, [self, _, self]) do - print(["initialise", self]) + # print(["initialise", self]) end defmethod(:class, :new, [self, args, new]) do @@ -95,13 +95,15 @@ defmodule AL.Bootstrap.Core do supers: supers, subs: subs, methods: methods, - clauses: clauses}]) do + 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([head, body], [clause(self, head, body)], clauses) + findall([slot_name, slot_value], [get_slot(self, slot_name, slot_value)], slots) end end diff --git a/lib/examples/e_AL.ex b/lib/examples/e_AL.ex index 869bb6d..5a5750c 100644 --- a/lib/examples/e_AL.ex +++ b/lib/examples/e_AL.ex @@ -288,6 +288,46 @@ defmodule Examples.AL do result end + example not_succeeds_when_goal_fails() do + {:atomic, {_bindings, _}} = + run do + not([class(:nonexistent_xyz, c)]) + end + :ok + end + + example not_fails_when_goal_succeeds() do + {:aborted, _} = + run do + not([class(:object, c)]) + end + :ok + end + + example unify_binds_variable() do + {:atomic, {bindings, _}} = + run do + unify(x, :hello) + end + 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 + assert Map.get(bindings, :"$out") == :hello + :ok + end + example list_tests() do {:atomic, {bindings, result}} = run do send([:w, :x, :y, :z], :hd, [head]) diff --git a/lib/examples/e_AL_constraints.ex b/lib/examples/e_AL_constraints.ex new file mode 100644 index 0000000..f189376 --- /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 + send(:cell, :new, [%{name: :x}, cell]) + send(:propagator, :new, [%{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 + send(:cell, :new, [%{name: :y}, y_cell]) + send(:propagator, :new, [%{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 + send(:cell, :new, [%{name: :a}, a]) + send(:cell, :new, [%{name: :b}, b]) + send(:cell, :new, [%{name: :c}, c]) + + send(:propagator, :new, [%{input_cells: [a, b], output_cell: c}, propagator_ab]) + send(:propagator, :new, [%{input_cells: [a, c], output_cell: b}, propagator_ac]) + send(:propagator, :new, [%{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/test/al_test.exs b/test/al_test.exs index fa4576a..f7430fd 100644 --- a/test/al_test.exs +++ b/test/al_test.exs @@ -13,3 +13,7 @@ end defmodule ALGenserverTest do use ExExample.ExUnit, for: Examples.ALGenserver end + +defmodule ALConstraintsTest do + use ExExample.ExUnit, for: Examples.ALConstraints +end From 3e61eb85f8cb6b8cb2e6120d72f0cc332edbb789 Mon Sep 17 00:00:00 2001 From: l4e21 Date: Wed, 17 Jun 2026 15:14:48 +0100 Subject: [PATCH 05/16] Add GT integration --- .mnesiastore/LATEST.LOG | Bin 65694 -> 65627 bytes .mnesiastore/schema.DAT | Bin 9778 -> 9778 bytes lib/AL.ex | 44 ++++++++++++------ lib/AL/application.ex | 2 +- lib/AL/bootstrap/core.ex | 26 +++++++---- lib/AL/bootstrap/lists.ex | 16 +++++++ lib/AL/command.ex | 37 +++++++++------ lib/AL/{objects.ex => object.ex} | 10 +++- lib/AL/views.ex | 76 +++++++++++++++++++++++++++++++ lib/examples/e_AL.ex | 29 ++++++++++-- lib/examples/e_AL_bootstrap.ex | 8 ++-- lib/examples/e_AL_genserver.ex | 4 +- mix.exs | 3 +- mix.lock | 20 ++++++++ 14 files changed, 226 insertions(+), 49 deletions(-) rename lib/AL/{objects.ex => object.ex} (98%) create mode 100644 lib/AL/views.ex diff --git a/.mnesiastore/LATEST.LOG b/.mnesiastore/LATEST.LOG index ef042fe45e4424271a84268b467d23de35205a0c..2f4f6136ef37703af3bc25cd468b7331315cd929 100644 GIT binary patch literal 65627 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 XXMT1{@p|I^kp?Ys6v)q}a@_v`A41Q` diff --git a/.mnesiastore/schema.DAT b/.mnesiastore/schema.DAT index 1f4b40a16ec59efdc97135c6c0de9360e508d610..3afb06c5809a8de8ee39a340ad3240d9aecb0a27 100644 GIT binary patch delta 202 zcmdnwv&m;ew;1npvnSi{KHe#j$HBPu|Ktl2SrZd1Hd~2zGV;dvgk6Tp{Q1B6g2Zh` z-iF03SD->QP{k6Pt)$BtdDSfU!(@JagBT;U*-92D!?cq9I#kbRs2-q delta 201 zcmdnwv&m;ew;0bMjf$7?`P(J(I2bJ_pO?sz6_K3`5P`H50rVh>Jv=H s5+S45&B*JmEc*^>b>%Oho{Y^_%Jq!Agd;hjH$!up%Ps@O6D0QbsMAOHXW diff --git a/lib/AL.ex b/lib/AL.ex index 8efa9b5..32102b1 100644 --- a/lib/AL.ex +++ b/lib/AL.ex @@ -448,7 +448,7 @@ defmodule AL do } } else - case AL.Objects.scan_class(object_pattern, class_pattern) do + case AL.Object.scan_class(object_pattern, class_pattern) do [] -> backtrack(state) @@ -483,7 +483,7 @@ defmodule AL do 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) @@ -516,7 +516,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) @@ -549,7 +549,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) @@ -628,6 +628,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) @@ -640,7 +655,7 @@ 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 + case AL.Object.scan_oapply(method_id_pattern, :"$head", :"$body") do [] -> backtrack(state) @@ -774,28 +789,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 @@ -842,35 +857,35 @@ 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 @@ -1061,3 +1076,4 @@ defimpl Inspect, for: AL do "#AL<>" end end + diff --git a/lib/AL/application.ex b/lib/AL/application.ex index 6dd384b..cb1c613 100644 --- a/lib/AL/application.ex +++ b/lib/AL/application.ex @@ -9,7 +9,7 @@ defmodule AL.Application do @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) diff --git a/lib/AL/bootstrap/core.ex b/lib/AL/bootstrap/core.ex index 3f8fdec..21ac607 100644 --- a/lib/AL/bootstrap/core.ex +++ b/lib/AL/bootstrap/core.ex @@ -56,9 +56,15 @@ defmodule AL.Bootstrap.Core do set_oapply(impl, head, body) end + set_class(:map, :class) + set_super(:map, :object) + set_class(:map_get, :behaviour) - set_method(:map, :map_get, :map_get) + 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 @@ -90,18 +96,22 @@ defmodule AL.Bootstrap.Core do send(alloc, :init, [args, new]) end - defmethod(:object, :examine, [self, %{classes: classes, - objects: objects, - supers: supers, - subs: subs, - methods: methods, - clauses: clauses, - slots: slots}]) do + 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 diff --git a/lib/AL/bootstrap/lists.ex b/lib/AL/bootstrap/lists.ex index df2c289..595fbcb 100644 --- a/lib/AL/bootstrap/lists.ex +++ b/lib/AL/bootstrap/lists.ex @@ -33,26 +33,42 @@ defmodule AL.Bootstrap.Lists do 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]) send(ft, :map, [func, st]) end + set_oapply(:list_map, [[fh | ft], head, body, [sh | st]]) do + call(head, body, [fh, sh]) + send(ft, :map, [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]) send(t, :fold_left, [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]) + send(t, :fold_left, [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 send(t, :fold_right, [func, acc, next_acc]) send(next_acc, func, [h, result]) end + set_oapply(:list_fold_right, [[h | t], head, body, acc, result]) do + send(t, :fold_right, [head, body, acc, next_acc]) + call(head, body, [next_acc, h, result]) + end defmethod(:list, :flatten, [lists, result]) do send(lists, :fold_left, [:concat, [], result]) diff --git a/lib/AL/command.ex b/lib/AL/command.ex index 2aa1035..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()}} @@ -27,6 +29,7 @@ defmodule AL.Command do | {: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. """ @@ -186,7 +189,7 @@ defmodule AL.Command do 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 def write_command(tx_id, command) do {t1, _t2} = inc_system_time() @@ -204,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 @@ -219,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 @@ -234,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 98% rename from lib/AL/objects.ex rename to lib/AL/object.ex index 8480908..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,7 +216,7 @@ defmodule AL.Objects do end :mnesia.write({:slots, object, merged}) - + :send_async -> :ok diff --git a/lib/AL/views.ex b/lib/AL/views.ex new file mode 100644 index 0000000..9c806e3 --- /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 + send(^self.id, :examine, [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]]) + send(input_cells, :fold_left, [[acc, h, result], + [send(acc, :put, [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 5a5750c..8077244 100644 --- a/lib/examples/e_AL.ex +++ b/lib/examples/e_AL.ex @@ -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]) + send(%{a: 3, b: 4, c: 3}, :get, [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 + send(%{a: 3, b: 4, c: 3}, :put, [:c, 4, m2]) + end + + assert bindings|> Map.get(:"$m2") |> Map.get(:c) == 4 + + program_state + end + example gensym() do {:atomic, {bindings, _}} = run do @@ -242,7 +254,7 @@ defmodule Examples.AL do 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, :"$slots") end) assert Enum.any?(results, fn {:slots, _, slots} -> Map.get(slots, :processed) == true end) end @@ -258,7 +270,7 @@ defmodule Examples.AL do Process.sleep(50) {:atomic, results} = - :mnesia.transaction(fn -> AL.Objects.scan_slots(:test_object_2, :"$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 @@ -327,7 +339,7 @@ defmodule Examples.AL do assert Map.get(bindings, :"$out") == :hello :ok end - + example list_tests() do {:atomic, {bindings, result}} = run do send([:w, :x, :y, :z], :hd, [head]) @@ -351,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 + send([:a, :b, :c], :map, [[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_genserver.ex b/lib/examples/e_AL_genserver.ex index 2e7bf91..ddb19eb 100644 --- a/lib/examples/e_AL_genserver.ex +++ b/lib/examples/e_AL_genserver.ex @@ -62,7 +62,7 @@ defmodule Examples.ALGenserver do {:ok, pid} = CounterService.start_link(:my_counter) {:atomic, results} = - :mnesia.transaction(fn -> AL.Objects.scan_class(:my_counter, :"$class") end) + :mnesia.transaction(fn -> AL.Object.scan_class(:my_counter, :"$class") end) assert Enum.any?(results, fn {:class, _, c} -> c == :elixir_process end) @@ -79,7 +79,7 @@ defmodule Examples.ALGenserver do Process.sleep(50) {:atomic, after_stop} = - :mnesia.transaction(fn -> AL.Objects.scan_class(:my_counter, :"$class") end) + :mnesia.transaction(fn -> AL.Object.scan_class(:my_counter, :"$class") end) assert after_stop == [] 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"}, } From d0274657f47415798b858f4485f7503e8df94b56 Mon Sep 17 00:00:00 2001 From: l4e21 Date: Thu, 18 Jun 2026 12:21:09 +0100 Subject: [PATCH 06/16] Replace oapply default with send default --- .mnesiastore/LATEST.LOG | Bin 65627 -> 65696 bytes .mnesiastore/schema.DAT | Bin 9778 -> 9778 bytes lib/AL.ex | 15 ++++++++++++ lib/AL/bootstrap/constraints.ex | 16 ++++++------- lib/AL/bootstrap/core.ex | 6 ++--- lib/AL/bootstrap/elixir_process.ex | 2 +- lib/AL/bootstrap/lists.ex | 26 ++++++++++---------- lib/AL/bootstrap/process.ex | 2 +- lib/AL/views.ex | 8 +++---- lib/examples/e_AL.ex | 28 +++++++++++----------- lib/examples/e_AL_constraints.ex | 22 ++++++++--------- lib/examples/e_AL_genserver.ex | 2 +- lib/examples/e_AL_objects.ex | 37 ++++++++++++----------------- 13 files changed, 86 insertions(+), 78 deletions(-) diff --git a/.mnesiastore/LATEST.LOG b/.mnesiastore/LATEST.LOG index 2f4f6136ef37703af3bc25cd468b7331315cd929..306c99a3442365878686d72fd27bcbb9eda44619 100644 GIT binary patch literal 65696 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 literal 65627 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 AGynhq diff --git a/lib/AL.ex b/lib/AL.ex index 32102b1..4422f28 100644 --- a/lib/AL.ex +++ b/lib/AL.ex @@ -95,6 +95,9 @@ defmodule AL do end end + @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 @@ -187,6 +190,9 @@ defmodule AL do 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)} @@ -202,6 +208,15 @@ 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)} diff --git a/lib/AL/bootstrap/constraints.ex b/lib/AL/bootstrap/constraints.ex index bf1c094..27260f3 100644 --- a/lib/AL/bootstrap/constraints.ex +++ b/lib/AL/bootstrap/constraints.ex @@ -3,7 +3,7 @@ defmodule AL.Bootstrap.Constraints do def setup() do run do - send(:class, :new, [%{name: :cell, super: :object, slots: [:subscribers, :value, :name]}, _]) + new(:class, %{name: :cell, super: :object, slots: [:subscribers, :value, :name]}, _) defmethod(:cell, :allocate, [self, args, cell_name]) do class(self, meta) @@ -23,7 +23,7 @@ defmodule AL.Bootstrap.Constraints do set_slots(self, %{value: value}) forall([get_slot(self, :subscribers, subscribers), - send(subscribers, :member, [subscriber])], + member(subscribers, subscriber)], [send_async(subscriber, :cell_updated, [self, value])]) cut end @@ -33,7 +33,7 @@ defmodule AL.Bootstrap.Constraints do set_slots(self, %{subscribers: [subscriber | subscribers]}) end - send(:class, :new, [%{name: :propagator, super: :object, slots: [:input_cells, :output_cell]}, _]) + new(:class, %{name: :propagator, super: :object, slots: [:input_cells, :output_cell]}, _) defmethod(:propagator, :allocate, [self, args, propagator]) do class(self, meta) @@ -49,8 +49,8 @@ defmodule AL.Bootstrap.Constraints do set_slots(self, %{input_cells: input_cells, output_cell: output_cell}) - forall([send(input_cells, :member, [input_cell])], - [send(input_cell, :subscribe, [self])]) + forall([member(input_cells, input_cell)], + [subscribe(input_cell, self)]) send_async(self, :cell_updated, [:none, :none]) end @@ -60,13 +60,13 @@ defmodule AL.Bootstrap.Constraints do get_slot(self, :output_cell, output_cell) findall(input_cell_value, - [send(input_cells, :member, [input_cell]), + [member(input_cells, input_cell), get_slot(input_cell, :value, input_cell_value)], input_cell_values) - forall([send(input_cell_values, :member, [input_cell_value])], + forall([member(input_cell_values, input_cell_value)], [not([unify(input_cell_value, :absent)])]) - send(self, :constrain, [input_cell_values, output_value]) + constrain(self, input_cell_values, output_value) send_async(output_cell, :constrain, [output_value]) end end diff --git a/lib/AL/bootstrap/core.ex b/lib/AL/bootstrap/core.ex index 21ac607..1e6ff99 100644 --- a/lib/AL/bootstrap/core.ex +++ b/lib/AL/bootstrap/core.ex @@ -91,9 +91,9 @@ defmodule AL.Bootstrap.Core do end defmethod(:class, :new, [self, args, new]) do - send(self, :construct, [construct]) - send(construct, :allocate, [args, alloc]) - send(alloc, :init, [args, new]) + construct(self, construct) + allocate(construct, args, alloc) + init(alloc, args, new) end defmethod(:object, :examine, [self, %{ diff --git a/lib/AL/bootstrap/elixir_process.ex b/lib/AL/bootstrap/elixir_process.ex index 596c363..ee3597f 100644 --- a/lib/AL/bootstrap/elixir_process.ex +++ b/lib/AL/bootstrap/elixir_process.ex @@ -3,7 +3,7 @@ defmodule AL.Bootstrap.ElixirProcess do def setup() do run do - send(:class, :new, [%{name: :elixir_process, super: :object, slots: []}, _]) + 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) diff --git a/lib/AL/bootstrap/lists.ex b/lib/AL/bootstrap/lists.ex index 595fbcb..2ae5f6b 100644 --- a/lib/AL/bootstrap/lists.ex +++ b/lib/AL/bootstrap/lists.ex @@ -3,7 +3,7 @@ defmodule AL.Bootstrap.Lists do def setup() do run do - send(:class, :new, [%{name: :list, super: :object, slots: []}, _]) + new(:class, %{name: :list, super: :object, slots: []}, _) defmethod(:list, :hd, [[h | _t], h]) do end defmethod(:list, :tl, [[_h | t], t]) do end @@ -12,22 +12,22 @@ defmodule AL.Bootstrap.Lists do set_class(:list_concat, :behaviour) set_oapply(:list_concat, [[], second, second]) do end set_oapply(:list_concat, [[fh | ft], second, [fh | inner]]) do - send(ft, :concat, [second, inner]) + 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 - send(t, :member, [x]) + 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 - send(t, :reverse, [reversed_tl]) - send(reversed_tl, :concat, [[h], reversed]) + reverse(t, reversed_tl) + concat(reversed_tl, [h], reversed) end set_method(:list, :map, :list_map) @@ -36,11 +36,11 @@ defmodule AL.Bootstrap.Lists do set_oapply(:list_map, [[], _head, _body, []]) do end set_oapply(:list_map, [[fh | ft], func, [sh | st]]) do send(fh, func, [sh]) - send(ft, :map, [func, st]) + map(ft, func, st) end set_oapply(:list_map, [[fh | ft], head, body, [sh | st]]) do call(head, body, [fh, sh]) - send(ft, :map, [head, body, st]) + map(ft, head, body, st) end set_method(:list, :fold_left, :list_fold_left) @@ -49,12 +49,12 @@ defmodule AL.Bootstrap.Lists do 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]) - send(t, :fold_left, [func, next_acc, result]) + 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]) - send(t, :fold_left, [head, body, next_acc, result]) + fold_left(t, head, body, next_acc, result) end set_method(:list, :fold_right, :list_fold_right) @@ -62,23 +62,23 @@ defmodule AL.Bootstrap.Lists do 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 - send(t, :fold_right, [func, acc, next_acc]) + 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 - send(t, :fold_right, [head, body, acc, next_acc]) + fold_right(t, head, body, acc, next_acc) call(head, body, [next_acc, h, result]) end defmethod(:list, :flatten, [lists, result]) do - send(lists, :fold_left, [:concat, [], result]) + 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 - send(ft, :same_length, [st]) + same_length(ft, st) end end end diff --git a/lib/AL/bootstrap/process.ex b/lib/AL/bootstrap/process.ex index bface60..d6082c2 100644 --- a/lib/AL/bootstrap/process.ex +++ b/lib/AL/bootstrap/process.ex @@ -3,7 +3,7 @@ defmodule AL.Bootstrap.Process do def setup() do run do - send(:class, :new, [%{name: :process, super: :object, slots: []}, _]) + new(:class, %{name: :process, super: :object, slots: []}, _) defmethod(:process, :allocate, [self, args, new_obj]) do class(self, meta) diff --git a/lib/AL/views.ex b/lib/AL/views.ex index 9c806e3..f5dd6ec 100644 --- a/lib/AL/views.ex +++ b/lib/AL/views.ex @@ -11,7 +11,7 @@ defmodule AL.Views do defview object_examine_view(self = %AL.Object{}, builder) do {:atomic, {bindings, _program_state}} = AL.run do - send(^self.id, :examine, [info]) + examine(^self.id, info) end GtBridge.Views.MapGraph.graph(Map.get(bindings, :"$info"), builder) @@ -49,9 +49,9 @@ defmodule AL.Views do get_slot(^self.id, :input_cells, input_cells) get_slot(^self.id, :output_cell, output_cell) unify(nodes, [^self.id | [output_cell | input_cells]]) - send(input_cells, :fold_left, [[acc, h, result], - [send(acc, :put, [h, [^self.id], result])], - %{^self.id => [output_cell]}, children]) + fold_left(input_cells, [acc, h, result], + [put(acc, h, [^self.id], result)], + %{^self.id => [output_cell]}, children) end IO.inspect(result) diff --git a/lib/examples/e_AL.ex b/lib/examples/e_AL.ex index 8077244..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 @@ -193,7 +193,7 @@ defmodule Examples.AL do example map_get() do {:atomic, {bindings, program_state}} = run do - send(%{a: 3, b: 4, c: 3}, :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 @@ -208,7 +208,7 @@ defmodule Examples.AL do example map_put() do {:atomic, {bindings, program_state}} = run do - send(%{a: 3, b: 4, c: 3}, :put, [:c, 4, m2]) + put(%{a: 3, b: 4, c: 3}, :c, 4, m2) end assert bindings|> Map.get(:"$m2") |> Map.get(:c) == 4 @@ -233,7 +233,7 @@ defmodule Examples.AL do {:atomic, {bindings, _}} = run do - send(:process, :new, [%{method: :handle, head: ^head, body: ^body}, new_proc]) + new(:process, %{method: :handle, head: ^head, body: ^body}, new_proc) cut end @@ -342,15 +342,15 @@ defmodule Examples.AL do example list_tests() do {:atomic, {bindings, result}} = run do - send([:w, :x, :y, :z], :hd, [head]) - send([:w, :x, :y, :z], :tl, [tail]) - send([:a, :b, :c], :concat, [[:d, :e, :f], sum]) - send([:b, :c, :d, :e, :f], :reverse, [reversed]) - send([[:a, :b], [:c, :d, :e]], :map, [:reverse, mapped]) - send([[:a], [:b], [:c], [:d]], :fold_left, [:concat, [:starter], folded_left]) - send([[:a], [:b], [:c], [:d]], :fold_right, [:concat, [:starter], folded_right]) - send([[:a, :b], [:c, :d, :e]], :flatten, [flattened]) - send([:c, :d, :e, :f], :same_length, [of_same_length]) + 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] @@ -367,7 +367,7 @@ defmodule Examples.AL do example call_lambda_map() do {:atomic, {bindings, _}} = run do - send([:a, :b, :c], :map, [[x, %{id: x}], [], out]) + map([:a, :b, :c], [x, %{id: x}], [], out) end assert Map.get(bindings, :"$out") == [%{id: :a}, %{id: :b}, %{id: :c}] :ok diff --git a/lib/examples/e_AL_constraints.ex b/lib/examples/e_AL_constraints.ex index f189376..495b963 100644 --- a/lib/examples/e_AL_constraints.ex +++ b/lib/examples/e_AL_constraints.ex @@ -8,8 +8,8 @@ defmodule Examples.ALConstraints do example constant() do {:atomic, {_bindings, _state}} = run do - send(:cell, :new, [%{name: :x}, cell]) - send(:propagator, :new, [%{input_cells: [], output_cell: cell}, propagator]) + new(:cell, %{name: :x}, cell) + new(:propagator, %{input_cells: [], output_cell: cell}, propagator) defmethod(propagator, :constrain, [_self, [], 2]) do end cut end @@ -29,8 +29,8 @@ defmodule Examples.ALConstraints do constant() {:atomic, {bindings, state}} = run do - send(:cell, :new, [%{name: :y}, y_cell]) - send(:propagator, :new, [%{input_cells: [:x], output_cell: y_cell}, propagator]) + 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 @@ -49,13 +49,13 @@ defmodule Examples.ALConstraints do example bidirectional_adder() do {:atomic, {bindings, state}} = run do - send(:cell, :new, [%{name: :a}, a]) - send(:cell, :new, [%{name: :b}, b]) - send(:cell, :new, [%{name: :c}, c]) - - send(:propagator, :new, [%{input_cells: [a, b], output_cell: c}, propagator_ab]) - send(:propagator, :new, [%{input_cells: [a, c], output_cell: b}, propagator_ac]) - send(:propagator, :new, [%{input_cells: [b, c], output_cell: a}, propagator_bc]) + 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) diff --git a/lib/examples/e_AL_genserver.ex b/lib/examples/e_AL_genserver.ex index ddb19eb..fd837dc 100644 --- a/lib/examples/e_AL_genserver.ex +++ b/lib/examples/e_AL_genserver.ex @@ -19,7 +19,7 @@ defmodule Examples.ALGenserver do def init(object_id) do pid = self() run do - send(:elixir_process, :new, [%{name: ^object_id, pid: ^pid}, _]) + 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}) diff --git a/lib/examples/e_AL_objects.ex b/lib/examples/e_AL_objects.ex index cb2fde3..7121b67 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,24 +53,22 @@ 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) @@ -87,9 +82,7 @@ defmodule Examples.ALObjects do 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 +94,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 +107,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) From 5bcbefe7e8ff2c74716afb9929ab5e55d1349461 Mon Sep 17 00:00:00 2001 From: l4e21 Date: Thu, 18 Jun 2026 13:17:53 +0100 Subject: [PATCH 07/16] Provide proper tracing rather than prints --- .mnesiastore/LATEST.LOG | Bin 65696 -> 65663 bytes .mnesiastore/schema.DAT | Bin 9778 -> 9778 bytes lib/AL.ex | 60 +++++++++++++++++------- lib/AL/bootstrap/core.ex | 2 - lib/AL/trace.ex | 88 +++++++++++++++++++++++++++++++++++ lib/examples/e_AL_objects.ex | 6 ++- lib/examples/e_AL_trace.ex | 29 ++++++++++++ test/al_test.exs | 4 ++ 8 files changed, 167 insertions(+), 22 deletions(-) create mode 100644 lib/AL/trace.ex create mode 100644 lib/examples/e_AL_trace.ex diff --git a/.mnesiastore/LATEST.LOG b/.mnesiastore/LATEST.LOG index 306c99a3442365878686d72fd27bcbb9eda44619..796857cf00c23f3e5be64d25e7d3380d5ec12ce4 100644 GIT binary patch literal 65663 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

Wiwu9SPJ zjSI@ZOo4I(Q;0mDT|}Ru#Xs&UGPT)LCx{Emw@FF4AX?gXH;|5{#B_(KR&oCRAH}K{I11>DfSAeL>F$DOBt#jYHb>ER984eB|z;kJQ54Lu6{xuQVP^cV6H= zVl}m>b`gNN7{J_9V78w-@k#R&V{gO*F-F#|Wrn_!x8%yY0V)T=t zzTSa>LDHScSSIPt_4Ey7dir}!bEtcu&wc$=kPoK1G%h74$~vQR@kwZzZXx-~{gQCf z%hqIRI^78UFwQ=(pQ#8zF|B#q{i!v$fcsP8Aodp$l_F-fg*kPqYPezsxq7N=xur;& z&_k8Y<=^>gON>VfhykS+C$tN78q=F{+g|D3@^3DP|$c0muU$2FE z)ItfhP(X$0WRta99<`H%+DSzDDVgzIjxT2qshmB6a-`%wRbOf33wYk{5^r~oSIlLJ zJzjLPx7E!t$qdT{5`RI!7m^F9-fGgP`C2TNN?9C~!nDR^*=l$6yp+32Qtlc^DJ=Oa zRXuSZP}BQ&ktEqglq5(XRvMWi?RAlaStMYBuFi(4nv{2C!e--m1+>JSk{%zlh4QFJ z2r0hSR$nQ`*2Wd%Y<*KZdj-Qe?H6=F*H&L?FPAp1^sK0jy9A`?eL>v)K-{Gu?lKT} He~$Zq2z>5L literal 65696 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 diff --git a/.mnesiastore/schema.DAT b/.mnesiastore/schema.DAT index 013eb48107ceb37c93f4725e948b616c733e0449..f6a4ac571a127f4d6610ce91628dbb6449fed31a 100644 GIT binary patch delta 199 zcmdnwv&m;ew;1o@AKViJbaqMPaWMY+F!_Q+*2Dyh%~s-_jJzg~tCvD$YW{7$AaR?K zN8ps(GN{n@$rqI}CniX3wvsMqc0Wa%GhkBT+hfW`l}iyy#FguSby_Z6R(+Dz=OOw^&W9 diff --git a/lib/AL.ex b/lib/AL.ex index 4422f28..14cde74 100644 --- a/lib/AL.ex +++ b/lib/AL.ex @@ -87,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 @@ -95,6 +97,11 @@ 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] @@ -307,7 +314,8 @@ 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 @@ -369,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}) @@ -670,6 +678,8 @@ defmodule AL do end def interp({:oapply, method_id_pattern, bind_head_pattern}, state) 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) @@ -717,6 +727,7 @@ defmodule AL do 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] } @@ -1021,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), []) @@ -1041,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) diff --git a/lib/AL/bootstrap/core.ex b/lib/AL/bootstrap/core.ex index 1e6ff99..6c47b45 100644 --- a/lib/AL/bootstrap/core.ex +++ b/lib/AL/bootstrap/core.ex @@ -35,13 +35,11 @@ defmodule AL.Bootstrap.Core do implies( [method(self, method, id)], [ - print(["calling", id, "from", self, "with args", [self | args]]), oapply(id, [self | args]) ], [implies( [lookup(class, method, id)], [ - print(["calling", id, "from", class, "with args", [self | args]]), oapply(id, [self | args]) ], [:fail] 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/examples/e_AL_objects.ex b/lib/examples/e_AL_objects.ex index 7121b67..1808e12 100644 --- a/lib/examples/e_AL_objects.ex +++ b/lib/examples/e_AL_objects.ex @@ -74,8 +74,10 @@ defmodule Examples.ALObjects do 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 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/test/al_test.exs b/test/al_test.exs index f7430fd..1ba0420 100644 --- a/test/al_test.exs +++ b/test/al_test.exs @@ -17,3 +17,7 @@ end defmodule ALConstraintsTest do use ExExample.ExUnit, for: Examples.ALConstraints end + +defmodule ALTraceTest do + use ExExample.ExUnit, for: Examples.ALTrace +end From ba7db35789895d561afb8bede9f39524861f4b33 Mon Sep 17 00:00:00 2001 From: l4e21 Date: Thu, 18 Jun 2026 15:00:48 +0100 Subject: [PATCH 08/16] Add users --- .mnesiastore/LATEST.LOG | Bin 65663 -> 65746 bytes .mnesiastore/schema.DAT | Bin 9778 -> 9778 bytes lib/AL/application.ex | 1 + lib/AL/bootstrap/users.ex | 50 +++++++++++++++++++++++++ lib/examples/e_AL_users.ex | 74 +++++++++++++++++++++++++++++++++++++ test/al_test.exs | 4 ++ 6 files changed, 129 insertions(+) create mode 100644 lib/AL/bootstrap/users.ex create mode 100644 lib/examples/e_AL_users.ex diff --git a/.mnesiastore/LATEST.LOG b/.mnesiastore/LATEST.LOG index 796857cf00c23f3e5be64d25e7d3380d5ec12ce4..7d5f84a0b5e33c3fd7eed73607aef25bc4346b82 100644 GIT binary patch literal 65746 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

Wiwu9SPJ zjSI@ZOo4I(Q;0mDT|}Ru#Xs&UGPT)LCx{Emw@FF4AX?gXH;|5{#B_(KR&oCRAH}K{I11>DfSAeL>F$DOBt#jYHb>ER984eB|z;kJQ54Lu6{xuQVP^cV6H= zVl}m>b`gNN7{J_9V78w-@k#R&V{gO*F-F#|Wrn_!x8%yY0V)T=t zzTSa>LDHScSSIPt_4Ey7dir}!bEtcu&wc$=kPoK1G%h74$~vQR@kwZzZXx-~{gQCf z%hqIRI^78UFwQ=(pQ#8zF|B#q{i!v$fcsP8Aodp$l_F-fg*kPqYPezsxq7N=xur;& z&_k8Y<=^>gON>VfhykS+C$tN78q=F{+g|D3@^3DP|$c0muU$2FE z)ItfhP(X$0WRta99<`H%+DSzDDVgzIjxT2qshmB6a-`%wRbOf33wYk{5^r~oSIlLJ zJzjLPx7E!t$qdT{5`RI!7m^F9-fGgP`C2TNN?9C~!nDR^*=l$6yp+32Qtlc^DJ=Oa zRXuSZP}BQ&ktEqglq5(XRvMWi?RAlaStMYBuFi(4nv{2C!e--m1+>JSk{%zlh4QFJ z2r0hSR$nQ`*2Wd%Y<*KZdj-Qe?H6=F*H&L?FPAp1^sK0jy9A`?eL>v)K-{Gu?lKT} He~$Zq2z>5L diff --git a/.mnesiastore/schema.DAT b/.mnesiastore/schema.DAT index f6a4ac571a127f4d6610ce91628dbb6449fed31a..905ffaba0043d1a8992167182c7e941f1a1739c3 100644 GIT binary patch delta 199 zcmdnwv&m;ew-^s^^ZZ4&`nx3ZI2h|DpO?syE&u=k delta 205 zcmdnwv&m;ew;1o@AKViJbaqMPaWMY+F!_Q+*2Dyh%~s-_j65cftCvD$Y9?P)%G_)r zS AL.Bootstrap.Core.setup() + AL.Bootstrap.Users.setup() AL.Bootstrap.Lists.setup() AL.Bootstrap.ElixirProcess.setup() AL.Bootstrap.Process.setup() 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/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/test/al_test.exs b/test/al_test.exs index 1ba0420..8b3bd39 100644 --- a/test/al_test.exs +++ b/test/al_test.exs @@ -21,3 +21,7 @@ end defmodule ALTraceTest do use ExExample.ExUnit, for: Examples.ALTrace end + +defmodule ALUsersTest do + use ExExample.ExUnit, for: Examples.ALUsers +end From b98a1a6d1557848c2532875b17c1123a01b3a08f Mon Sep 17 00:00:00 2001 From: l4e21 Date: Mon, 22 Jun 2026 11:20:08 +0100 Subject: [PATCH 09/16] Add proper README --- .mnesiastore/LATEST.LOG | Bin 65746 -> 66040 bytes .mnesiastore/schema.DAT | Bin 9778 -> 9778 bytes README.md | 56 +++++++++++++++++++++++++--------------- 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/.mnesiastore/LATEST.LOG b/.mnesiastore/LATEST.LOG index 7d5f84a0b5e33c3fd7eed73607aef25bc4346b82..12da9bad5ec3d4749711199bb020c2cd1c3bb613 100644 GIT binary patch literal 66040 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 HB{BaGUk~c5 literal 65746 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 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. From f3fe2f57247d954b5628530dd5b9cb84a295b5cb Mon Sep 17 00:00:00 2001 From: l4e21 Date: Mon, 22 Jun 2026 22:15:13 +0100 Subject: [PATCH 10/16] Make send a proper VM operation --- .mnesiastore/LATEST.LOG | Bin 66040 -> 87 bytes .mnesiastore/schema.DAT | Bin 9778 -> 9778 bytes config/test.exs | 2 - lib/AL.ex | 66 ++++++++++++++++++++++++++++++- lib/AL/bootstrap/constraints.ex | 11 +++--- lib/AL/bootstrap/core.ex | 22 ++--------- lib/AL/bootstrap/users.ex | 27 +++++-------- lib/AL/scheduler.ex | 2 +- lib/examples/e_AL.ex | 32 +++++++++++++++ lib/examples/e_AL_constraints.ex | 34 +++++++++------- lib/examples/e_AL_objects.ex | 44 --------------------- lib/examples/e_AL_users.ex | 33 ++++------------ 12 files changed, 142 insertions(+), 131 deletions(-) diff --git a/.mnesiastore/LATEST.LOG b/.mnesiastore/LATEST.LOG index 12da9bad5ec3d4749711199bb020c2cd1c3bb613..c4962f0e99ec6b29994e62f9edfd6754a7096fd7 100644 GIT binary patch delta 15 Wcmey-%o0AqpM$CTY7zr)pDX|`Oa&?c literal 66040 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 HB{BaGUk~c5 diff --git a/.mnesiastore/schema.DAT b/.mnesiastore/schema.DAT index ec92f6e9f02c7c0d3c6a1b5816f8f6bca0dae19b..acbdc350f3c6343193297d8dd506ff5e6a51a8b7 100644 GIT binary patch delta 203 zcmdnwv&m;ew;1o?3)}KSdW19|>Fak + dnu(self, method, args, state) + + id -> + if has_matching_clause?(id, call_args, state.active_choicepoint.bindings) do + interp({:oapply, id, call_args}, state) + else + dnu(self, method, args, state) + end + end + end + + defp dnu(_self, :does_not_understand, _args, state), do: backtrack(state) + + defp dnu(self, method, args, state), + do: interp({:send, self, :does_not_understand, [method, args]}, state) + + defp resolve_method_id(self, method) when is_map(self), + do: resolve_in_chain([Map.get(self, :class, :map)], method) + + defp resolve_method_id(self, method) when is_list(self), do: resolve_in_chain([:list], method) + + defp resolve_method_id(self, method) do + case method_ids(self, method) do + [id | _] -> id + [] -> resolve_in_chain(for({:class, _o, c} <- AL.Object.scan_class(self, :"$class"), do: c), method) + end + end + + defp resolve_in_chain(classes, method), + do: Enum.find_value(classes, fn c -> chain_first_id(c, method) end) + + defp chain_first_id(class, method) do + case method_ids(class, method) do + [id | _] -> id + [] -> resolve_in_chain(for({:super, _o, s} <- AL.Object.scan_super(class, :"$super"), do: s), method) + end + end + + defp method_ids(obj, method) do + for {:method, _o, _n, id} <- AL.Object.scan_method(obj, method, :"$id"), do: id + end + + defp has_matching_clause?(id, call_args, bindings) do + id in @primitive_methods or any_clause_matches?(id, call_args, bindings) + end + + defp any_clause_matches?(id, call_args, bindings) do + scope = AL.Command.fresh_scope() + + Enum.any?(AL.Object.scan_oapply(id, :"$head", :"$body"), fn {:oapply, _id, head, _body} -> + AL.Var.unify(AL.Var.freshen(head, scope), call_args, bindings) != nil + end) + end + defp collect_all_solutions(condition, bindings, tx_id) do initial = %AL{ active_choicepoint: %AL.Choicepoint{ diff --git a/lib/AL/bootstrap/constraints.ex b/lib/AL/bootstrap/constraints.ex index 27260f3..94cdd90 100644 --- a/lib/AL/bootstrap/constraints.ex +++ b/lib/AL/bootstrap/constraints.ex @@ -5,17 +5,16 @@ defmodule AL.Bootstrap.Constraints do run do new(:class, %{name: :cell, super: :object, slots: [:subscribers, :value, :name]}, _) - defmethod(:cell, :allocate, [self, args, cell_name]) do + defmethod(:cell, :allocate, [self, args, new]) do class(self, meta) - map_get(args, :name, cell_name) + gensym(new) - set_class(cell_name, meta) - set_super(cell_name, :object) + set_class(new, meta) + set_super(new, :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}) + set_slots(self, %{name: cell_name, subscribers: [], value: :absent}) end defmethod(:cell, :constrain, [self, value]) do diff --git a/lib/AL/bootstrap/core.ex b/lib/AL/bootstrap/core.ex index 6c47b45..4e846b3 100644 --- a/lib/AL/bootstrap/core.ex +++ b/lib/AL/bootstrap/core.ex @@ -11,7 +11,6 @@ defmodule AL.Bootstrap.Core do set_super(:behaviour, :object) set_method(:object, :lookup, :lookup) - set_method(:object, :send, :send) set_method(:object, :meta, :metaclass) set_method(:object, :defmethod, :defmethod) @@ -29,23 +28,6 @@ defmodule AL.Bootstrap.Core do 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) @@ -54,6 +36,10 @@ defmodule AL.Bootstrap.Core do set_oapply(impl, head, body) end + defmethod(:object, :does_not_understand, [self, method, args]) do + :fail + end + set_class(:map, :class) set_super(:map, :object) diff --git a/lib/AL/bootstrap/users.ex b/lib/AL/bootstrap/users.ex index 215d30b..96643ed 100644 --- a/lib/AL/bootstrap/users.ex +++ b/lib/AL/bootstrap/users.ex @@ -3,15 +3,18 @@ defmodule AL.Bootstrap.Users do def setup() do run do - new(:class, %{name: :owned, super: :object, slots: [:owner]}, _) + new(:class, %{name: :durable_object, super: :object, slots: [:owner]}, _) - defmethod(:owned, :allocate, [self, args, new]) do + defmethod(:durable_object, :allocate, [self, args, new]) do class(self, meta) gensym(new) set_class(new, meta) - set_super(new, :object) + set_super(new, :super) end + new(:class, %{name: :user, super: :durable_object, slots: [:name]}, _) + new(:class, %{name: :owned, super: :durable_object, slots: []}, _) + defmethod(:owned, :init, [self, args, self]) do set_slots(self, args) end @@ -25,25 +28,13 @@ defmodule AL.Bootstrap.Users do unify(caller, owner) end - set_class(:guarded_send, :behaviour) - set_oapply(:guarded_send, [caller, self, method, args]) do + defmethod(:owned, :guarded_send, [self, caller, 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}) + defmethod(:owned, :does_not_understand, [self, method, [caller, args]]) do + guarded_send(self, caller, method, args) end end end diff --git a/lib/AL/scheduler.ex b/lib/AL/scheduler.ex index e27ce73..e63551d 100644 --- a/lib/AL/scheduler.ex +++ b/lib/AL/scheduler.ex @@ -17,7 +17,7 @@ defmodule AL.Scheduler do {:write, :command, {:command, _t, _tx_id, {:send_async, {object, method, args}}}, _old, _tid}}, state ) do - Task.start(fn -> AL.eval([{:oapply, :send, [object, method, args]}]) end) + Task.start(fn -> AL.eval([{:send, object, method, args}]) end) {:noreply, state} end diff --git a/lib/examples/e_AL.ex b/lib/examples/e_AL.ex index b6c8100..be222d9 100644 --- a/lib/examples/e_AL.ex +++ b/lib/examples/e_AL.ex @@ -37,6 +37,38 @@ defmodule Examples.AL do result end + example does_not_understand_dispatch() do + {:atomic, {b, _}} = + run do + new(:class, %{name: :gadget, super: :object, slots: []}, _) + + defmethod(:gadget, :poke, [self, x]) do + unify(x, :ok) + end + + defmethod(:gadget, :does_not_understand, [self, _m, _a]) do + end + + new(:gadget, _, g) + end + + g = Map.get(b, :"$g") + + # head matches, body succeeds -> runs + {:atomic, _} = run do poke(^g, :ok) end + + # head matches, body fails -> plain failure, not DNU + {:aborted, _} = run do poke(^g, :bad) end + + # absent selector -> DNU (override succeeds) + {:atomic, _} = run do zap(^g) end + + # wrong arity, no clause head matches -> DNU + {:atomic, _} = run do poke(^g, :a, :b) end + + :ok + end + example get_oapply_command() do run do method(:object, :init, init_method) diff --git a/lib/examples/e_AL_constraints.ex b/lib/examples/e_AL_constraints.ex index 495b963..0484d60 100644 --- a/lib/examples/e_AL_constraints.ex +++ b/lib/examples/e_AL_constraints.ex @@ -7,39 +7,43 @@ defmodule Examples.ALConstraints do import ExUnit.Assertions example constant() do - {:atomic, {_bindings, _state}} = run do - new(:cell, %{name: :x}, cell) - new(:propagator, %{input_cells: [], output_cell: cell}, propagator) + {:atomic, {bindings, _state}} = run do + new(:cell, %{name: :x}, x) + new(:propagator, %{input_cells: [], output_cell: x}, propagator) defmethod(propagator, :constrain, [_self, [], 2]) do end cut end + x = Map.get(bindings, :"$x") + Process.sleep(50) {:atomic, {bindings, _state}} = run do - get_slot(:x, :value, value) + get_slot(^x, :value, value) end assert Map.get(bindings, :"$value") == 2 - bindings + x 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) + x = constant() + + {:atomic, {bindings, _state}} = run do + new(:cell, %{name: :y}, y) + new(:propagator, %{input_cells: [^x], output_cell: y}, propagator) defmethod(propagator, :constrain, [_self, [x_val], y_val]) do is(y_val, 1 + x_val) end end + y = Map.get(bindings, :"$y") + Process.sleep(50) {:atomic, {bindings, _state}} = run do - get_slot(:y, :value, value) + get_slot(^y, :value, value) end assert Map.get(bindings, :"$value") == 3 @@ -48,7 +52,7 @@ defmodule Examples.ALConstraints do end example bidirectional_adder() do - {:atomic, {bindings, state}} = run do + {:atomic, {bindings, _state}} = run do new(:cell, %{name: :a}, a) new(:cell, %{name: :b}, b) new(:cell, %{name: :c}, c) @@ -71,10 +75,12 @@ defmodule Examples.ALConstraints do send_async(c, :constrain, [5]) end - Process.sleep(50) + a = Map.get(bindings, :"$a") + + Process.sleep(100) {:atomic, {bindings, _state}} = run do - get_slot(:a, :value, value) + get_slot(^a, :value, value) end assert Map.get(bindings, :"$value") == 2 diff --git a/lib/examples/e_AL_objects.ex b/lib/examples/e_AL_objects.ex index 1808e12..710c311 100644 --- a/lib/examples/e_AL_objects.ex +++ b/lib/examples/e_AL_objects.ex @@ -37,50 +37,6 @@ defmodule Examples.ALObjects do result end - example metaclass_init_override() do - {:atomic, {b, program_state}} = - run do - new(:class, %{name: :counter_meta, super: :class, slots: []}, _) - set_slots(:counter_meta, %{count: []}) - - defmethod(:counter_meta, :init, [self, args, self]) do - class(self, meta) - get_slot(meta, :count, count) - set_slots(meta, %{count: ["new class!" | count]}) - set_method(self, :init, :initialise_counted_object) - cut - end - - set_class(:initialise_counted_object, :behaviour) - set_oapply(:initialise_counted_object, [self, _, self]) do - 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 - - new(:counter_meta, - %{name: :example_counter_meta_instance, super: :object, slots: []}, - counter_example_meta_instance) - - new(:counter_meta, - %{name: :example_counter_meta_instance_2, super: :object, slots: []}, - counter_example_meta_instance_2) - - new(:example_counter_meta_instance, _, example_ii) - cut - - get_slot(:counter_meta, :count, c) - end - - 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 diff --git a/lib/examples/e_AL_users.ex b/lib/examples/e_AL_users.ex index fd825d5..c77f0a5 100644 --- a/lib/examples/e_AL_users.ex +++ b/lib/examples/e_AL_users.ex @@ -21,21 +21,21 @@ defmodule Examples.ALUsers do :ok end - example owner_gated_update() do + 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) + new(:user, %{name: :charlie}, charlie) + new(:owned, %{owner: charlie, label: :secret}, obj) end - alice = Map.get(b, :"$alice") + charlie = Map.get(b, :"$charlie") bob = Map.get(b, :"$bob") obj = Map.get(b, :"$obj") {:atomic, _} = run do - oapply(:guarded_send, [^alice, ^obj, :update, [%{label: :updated}]]) + update(^obj, ^charlie, [%{label: :updated}]) end {:atomic, {b2, _}} = @@ -47,28 +47,9 @@ defmodule Examples.ALUsers do {:aborted, _} = run do - oapply(:guarded_send, [^bob, ^obj, :update, [%{label: :hacked}]]) + update(^obj, ^bob, [%{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 From c461a1fb373b97c81f6ff3c16179636e42733dfe Mon Sep 17 00:00:00 2001 From: l4e21 Date: Tue, 23 Jun 2026 15:40:05 +0100 Subject: [PATCH 11/16] Add package manager, supporting receipts, configuration, and uninstall semantics --- .mnesiastore/LATEST.LOG | Bin 87 -> 65681 bytes .mnesiastore/schema.DAT | Bin 9778 -> 9778 bytes README.md | 2 +- config/config.exs | 10 + lib/AL.ex | 23 +- lib/AL/application.ex | 14 +- lib/AL/bootstrap/core.ex | 105 --------- lib/AL/bootstrap/lists.ex | 85 -------- lib/AL/command.ex | 7 + lib/AL/object.ex | 22 ++ lib/AL/package.ex | 158 ++++++++++++++ lib/AL/package/bootstrap.ex | 199 ++++++++++++++++++ lib/AL/{bootstrap => package}/constraints.ex | 12 +- .../{bootstrap => package}/elixir_process.ex | 8 +- lib/AL/{bootstrap => package}/process.ex | 8 +- lib/AL/{bootstrap => package}/users.ex | 17 +- 16 files changed, 436 insertions(+), 234 deletions(-) delete mode 100644 lib/AL/bootstrap/core.ex delete mode 100644 lib/AL/bootstrap/lists.ex create mode 100644 lib/AL/package.ex create mode 100644 lib/AL/package/bootstrap.ex rename lib/AL/{bootstrap => package}/constraints.ex (94%) rename lib/AL/{bootstrap => package}/elixir_process.ex (79%) rename lib/AL/{bootstrap => package}/process.ex (83%) rename lib/AL/{bootstrap => package}/users.ex (69%) diff --git a/.mnesiastore/LATEST.LOG b/.mnesiastore/LATEST.LOG index c4962f0e99ec6b29994e62f9edfd6754a7096fd7..7bd086c826c2ed66db8d3aabfc52b655e6824655 100644 GIT binary patch literal 65681 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 QzX6vV2v`wubhGyV0REYc$p8QV delta 15 WcmbQ($PzxmpM$CTY7zr)pDX|=Cj`L& diff --git a/.mnesiastore/schema.DAT b/.mnesiastore/schema.DAT index acbdc350f3c6343193297d8dd506ff5e6a51a8b7..3500644f69ddfb7442cdc4c1fec51607148e38dd 100644 GIT binary patch delta 195 zcmdnwv&m;ew;1oqJ$xR!_w1I)<6x|RIr)M_*2Dyh%~s-_j6Cui_9jAQ{%t-lafgwI zSK(eNROHX(^Agz;6C^fUNtZM7%zJ$^6Dsi;A|bTdN){;be9Po4sDv?GLLMj~!jzZ; rmG}vlQ0!*p37mf!Ch-L*k-6DQxt@_nDnK+Bs{A`p)L`>h6 Enum.map(fn pair -> @@ -917,6 +931,13 @@ defmodule AL do AL.Object.retract_oapply(object, head) state end + + def interp({:retract_slots, object, _slots}, state) when is_map(object), do: state + def interp({:retract_slots, object, slots}, state) do + AL.Command.retract_slots(state.tx_id, object, slots) + AL.Object.retract_slots(object, slots) + state + end def interp({:send_async, object, method, args}, state) do AL.Command.send_async(state.tx_id, object, method, args) diff --git a/lib/AL/application.ex b/lib/AL/application.ex index 54faad3..6f716f1 100644 --- a/lib/AL/application.ex +++ b/lib/AL/application.ex @@ -20,16 +20,8 @@ defmodule AL.Application do end def bootstrap() do - case :mnesia.table_info(:command, :size) do - 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 + :al + |> Application.get_env(:packages, []) + |> AL.Package.install_all() end end diff --git a/lib/AL/bootstrap/core.ex b/lib/AL/bootstrap/core.ex deleted file mode 100644 index 4e846b3..0000000 --- a/lib/AL/bootstrap/core.ex +++ /dev/null @@ -1,105 +0,0 @@ -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, :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(: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 - - defmethod(:object, :does_not_understand, [self, method, args]) do - :fail - 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/lists.ex b/lib/AL/bootstrap/lists.ex deleted file mode 100644 index 2ae5f6b..0000000 --- a/lib/AL/bootstrap/lists.ex +++ /dev/null @@ -1,85 +0,0 @@ -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/command.ex b/lib/AL/command.ex index a1b94e0..b01f2be 100644 --- a/lib/AL/command.ex +++ b/lib/AL/command.ex @@ -13,6 +13,7 @@ defmodule AL.Command do | :retract_super | :retract_method | :retract_oapply + | :retract_slots | :send_async | :send_elixir @@ -26,6 +27,7 @@ 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()}} + | {:retract_slots, {AL.Var.t(), AL.Var.t()}} | {:send_async, {AL.Var.t(), AL.Var.t(), AL.Var.t()}} | {:send_elixir, {AL.Var.t(), AL.Var.t()}} @@ -180,6 +182,11 @@ defmodule AL.Command do write_command(tx_id, {:retract_oapply, {object, head}}) end + @spec retract_slots(non_neg_integer(), AL.Var.t(), AL.Var.t()) :: :ok + def retract_slots(tx_id, object, slots) do + write_command(tx_id, {:retract_slots, {object, slots}}) + end + @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}}) diff --git a/lib/AL/object.ex b/lib/AL/object.ex index c6cef11..cdf0e8a 100644 --- a/lib/AL/object.ex +++ b/lib/AL/object.ex @@ -130,6 +130,24 @@ defmodule AL.Object do :ok end + @spec retract_slots(AL.Var.t(), AL.Var.t()) :: :ok + def retract_slots(object, slots) when is_map(slots) do + case :mnesia.read(:slots, object) do + [{:slots, ^object, existing}] when is_map(existing) -> + case Map.drop(existing, Map.keys(slots)) do + remaining when remaining == %{} -> :mnesia.delete({:slots, object}) + remaining -> :mnesia.write({:slots, object, remaining}) + end + + _ -> + :mnesia.delete({:slots, object}) + end + end + + def retract_slots(object, _slots) do + :mnesia.delete({:slots, object}) + end + @spec set_class(AL.Var.t(), AL.Var.t()) :: :ok def set_class(object_pattern, class_pattern) do :mnesia.write({:class, object_pattern, class_pattern}) @@ -199,6 +217,10 @@ defmodule AL.Object do {object, head} = event retract_oapply(object, head) + :retract_slots -> + {object, slots} = event + retract_slots(object, slots) + :set_slots -> {object, new_slots} = event diff --git a/lib/AL/package.ex b/lib/AL/package.ex new file mode 100644 index 0000000..5728b1d --- /dev/null +++ b/lib/AL/package.ex @@ -0,0 +1,158 @@ +defmodule AL.Package do + @moduledoc """ + I define and install AL packages. A package is a durable object created as the + receipt of running its definitions, authored with `defpackage/3`. + """ + + defmacro __using__(_opts) do + quote do + require AL + import AL.Package, only: [defpackage: 3] + end + end + + defmacro defpackage(name, opts, do: body) do + version = Keyword.get(opts, :version, 1) + deps = Keyword.get(opts, :deps, []) + + statements = + case body do + {:__block__, _, list} -> list + single -> [single] + end + + receipt = + quote do + new(:package, %{name: unquote(name), version: unquote(version), deps: unquote(deps)}, _) + end + + program = {:__block__, [], statements ++ [receipt]} + + quote do + def __package__ do + %{name: unquote(name), version: unquote(version), deps: unquote(deps)} + end + + def install do + AL.run do + unquote(program) + end + end + end + end + + @spec install_all([module()]) :: :ok + def install_all(modules) do + by_name = Map.new(modules, fn m -> {m.__package__().name, m} end) + + modules + |> order(by_name) + |> Enum.each(fn m -> ensure(m.__package__().name, &m.install/0) end) + end + + @spec installed?(atom()) :: boolean() + def installed?(name) do + case :mnesia.transaction(fn -> + Enum.any?(AL.Object.scan_class(:"$p", :package), fn {:class, p, :package} -> + match?([{:slots, ^p, %{name: ^name}}], :mnesia.read(:slots, p)) + end) + end) do + {:atomic, installed?} -> installed? + _ -> false + end + end + + @spec ensure(atom(), (-> any())) :: :ok + def ensure(name, install) do + unless installed?(name), do: install.() + :ok + end + + @doc """ + I retract everything a package installed by reversing the commands of its + install transaction (recorded in the receipt's `:tx` slot). I refuse if another + installed package depends on this one. + """ + @spec uninstall(atom()) :: {:atomic, any()} | {:aborted, term()} | {:error, term()} + def uninstall(name) do + case dependents(name) do + [] -> do_uninstall(name) + deps -> {:error, {:depended_on_by, deps}} + end + end + + defp do_uninstall(name) do + case :mnesia.transaction(fn -> + case find_package(name) do + {_p, %{tx: tx}} -> AL.Command.commands_for_transaction(tx) + _ -> nil + end + end) do + {:atomic, nil} -> {:error, :not_installed} + {:atomic, commands} -> commands |> Enum.reverse() |> Enum.flat_map(&inverse/1) |> AL.eval() + other -> other + end + end + + defp dependents(name) do + {:atomic, names} = + :mnesia.transaction(fn -> + for {:class, p, :package} <- AL.Object.scan_class(:"$p", :package), + {:slots, ^p, %{name: dependent, deps: deps}} <- :mnesia.read(:slots, p), + name in deps, + do: dependent + end) + + names + end + + defp find_package(name) do + Enum.find_value(AL.Object.scan_class(:"$p", :package), fn {:class, p, :package} -> + case :mnesia.read(:slots, p) do + [{:slots, ^p, %{name: ^name} = slots}] -> {p, slots} + _ -> nil + end + end) + end + + defp inverse({:command, _t, _tx, command}) do + case command do + {:set_class, {o, c}} -> [{:retract_class, o, c}] + {:set_super, {o, s}} -> [{:retract_super, o, s}] + {:set_method, {o, n, id}} -> [{:retract_method, o, n, id}] + {:set_oapply, {o, h, _b}} -> [{:retract_oapply, o, h}] + {:set_slots, {o, s}} -> [{:retract_slots, o, s}] + _ -> [] + end + end + + defp order(modules, by_name) do + {ordered, _seen} = + Enum.reduce(modules, {[], MapSet.new()}, fn m, acc -> visit(m, by_name, acc, []) end) + + Enum.reverse(ordered) + end + + defp visit(m, by_name, {ordered, seen}, stack) do + name = m.__package__().name + + cond do + name in seen -> + {ordered, seen} + + name in stack -> + raise "AL package dependency cycle: #{inspect(Enum.reverse([name | stack]))}" + + true -> + {ordered, seen} = + Enum.reduce(m.__package__().deps, {ordered, seen}, fn dep, acc -> + case Map.fetch(by_name, dep) do + {:ok, dep_module} -> visit(dep_module, by_name, acc, [name | stack]) + :error -> raise "AL package #{inspect(name)} depends on unknown #{inspect(dep)}" + end + end) + + {[m | ordered], MapSet.put(seen, name)} + end + end +end diff --git a/lib/AL/package/bootstrap.ex b/lib/AL/package/bootstrap.ex new file mode 100644 index 0000000..949524d --- /dev/null +++ b/lib/AL/package/bootstrap.ex @@ -0,0 +1,199 @@ +defmodule AL.Package.Bootstrap do + use AL.Package + + defpackage :bootstrap, version: 1, deps: [] 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, :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(: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 + + defmethod(:object, :does_not_understand, [self, method, args]) do + :fail + 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 + + new(:class, %{name: :durable_object, super: :object, slots: [:owner]}, _) + + defmethod(:durable_object, :allocate, [self, args, new]) do + class(self, meta) + gensym(new) + set_class(new, meta) + set_super(new, :durable_object) + end + + new(:class, %{name: :package, super: :durable_object, slots: [:name, :version, :deps, :tx]}, _) + + defmethod(:package, :init, [self, args, self]) do + map_get(args, :name, name) + map_get(args, :version, version) + map_get(args, :deps, deps) + current_tx(tx) + set_slots(self, %{name: name, version: version, deps: deps, tx: tx}) + end + + 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 diff --git a/lib/AL/bootstrap/constraints.ex b/lib/AL/package/constraints.ex similarity index 94% rename from lib/AL/bootstrap/constraints.ex rename to lib/AL/package/constraints.ex index 94cdd90..8ba04a6 100644 --- a/lib/AL/bootstrap/constraints.ex +++ b/lib/AL/package/constraints.ex @@ -1,8 +1,7 @@ -defmodule AL.Bootstrap.Constraints do - use AL - - def setup() do - run do +defmodule AL.Package.Constraints do + use AL.Package + + defpackage :constraints, version: 1, deps: [:bootstrap] do new(:class, %{name: :cell, super: :object, slots: [:subscribers, :value, :name]}, _) defmethod(:cell, :allocate, [self, args, new]) do @@ -68,6 +67,5 @@ defmodule AL.Bootstrap.Constraints do constrain(self, input_cell_values, output_value) send_async(output_cell, :constrain, [output_value]) end - end end -end +end diff --git a/lib/AL/bootstrap/elixir_process.ex b/lib/AL/package/elixir_process.ex similarity index 79% rename from lib/AL/bootstrap/elixir_process.ex rename to lib/AL/package/elixir_process.ex index ee3597f..2441d17 100644 --- a/lib/AL/bootstrap/elixir_process.ex +++ b/lib/AL/package/elixir_process.ex @@ -1,8 +1,7 @@ -defmodule AL.Bootstrap.ElixirProcess do - use AL +defmodule AL.Package.ElixirProcess do + use AL.Package - def setup() do - run do + defpackage :elixir_process, version: 1, deps: [:bootstrap] do new(:class, %{name: :elixir_process, super: :object, slots: []}, _) defmethod(:elixir_process, :allocate, [self, args, new_obj]) do class(self, meta) @@ -14,6 +13,5 @@ defmodule AL.Bootstrap.ElixirProcess do map_get(args, :pid, pid) set_slots(self, %{pid: pid}) end - end end end diff --git a/lib/AL/bootstrap/process.ex b/lib/AL/package/process.ex similarity index 83% rename from lib/AL/bootstrap/process.ex rename to lib/AL/package/process.ex index d6082c2..107d700 100644 --- a/lib/AL/bootstrap/process.ex +++ b/lib/AL/package/process.ex @@ -1,8 +1,7 @@ -defmodule AL.Bootstrap.Process do - use AL +defmodule AL.Package.Process do + use AL.Package - def setup() do - run do + defpackage :process, version: 1, deps: [:bootstrap] do new(:class, %{name: :process, super: :object, slots: []}, _) defmethod(:process, :allocate, [self, args, new_obj]) do class(self, meta) @@ -21,6 +20,5 @@ defmodule AL.Bootstrap.Process do set_class(impl, :behaviour) set_oapply(impl, head, body) end - end end end diff --git a/lib/AL/bootstrap/users.ex b/lib/AL/package/users.ex similarity index 69% rename from lib/AL/bootstrap/users.ex rename to lib/AL/package/users.ex index 96643ed..b01c923 100644 --- a/lib/AL/bootstrap/users.ex +++ b/lib/AL/package/users.ex @@ -1,17 +1,7 @@ -defmodule AL.Bootstrap.Users do - use AL - - def setup() do - run do - new(:class, %{name: :durable_object, super: :object, slots: [:owner]}, _) - - defmethod(:durable_object, :allocate, [self, args, new]) do - class(self, meta) - gensym(new) - set_class(new, meta) - set_super(new, :super) - end +defmodule AL.Package.Users do + use AL.Package + defpackage :users, version: 1, deps: [:bootstrap] do new(:class, %{name: :user, super: :durable_object, slots: [:name]}, _) new(:class, %{name: :owned, super: :durable_object, slots: []}, _) @@ -36,6 +26,5 @@ defmodule AL.Bootstrap.Users do defmethod(:owned, :does_not_understand, [self, method, [caller, args]]) do guarded_send(self, caller, method, args) end - end end end From f67a6ddab68186e2418673290e6fe73370dd7783 Mon Sep 17 00:00:00 2001 From: l4e21 Date: Tue, 23 Jun 2026 17:52:21 +0100 Subject: [PATCH 12/16] Add read-only forking capabilities --- lib/AL.ex | 80 ++++++----- lib/AL/command.ex | 10 ++ lib/AL/object.ex | 323 +++++++++++++++++++++---------------------- lib/examples/e_AL.ex | 20 +++ 4 files changed, 229 insertions(+), 204 deletions(-) diff --git a/lib/AL.ex b/lib/AL.ex index 6525500..41c7ee9 100644 --- a/lib/AL.ex +++ b/lib/AL.ex @@ -91,6 +91,7 @@ defmodule AL do field(:program, [goal()], enforce: true, default: []) field(:tracepoints, MapSet.t(), enforce: true, default: %MapSet{}) field(:traced_calls, %{optional(scope()) => tuple()}, default: %{}) + field(:store, AL.Object.store(), default: :main) end defmacro __using__(_opts) do @@ -241,17 +242,22 @@ defmodule AL do def ast_to_pattern(x), do: x @doc """ - I provide the DSL for the AL interpreter + I provide the DSL for the AL interpreter. I run against the live store by + default; `run store: s do ... end` runs against store `s` (e.g. a `fork`). """ - defmacro run(do: program) do + defmacro run(opts \\ [], do: program) do goals = case ast_to_pattern(program) do list when is_list(list) -> list goal -> [goal] end - quote do - AL.eval(unquote(Macro.escape(goals, unquote: true))) + escaped = Macro.escape(goals, unquote: true) + + if Keyword.has_key?(opts, :store) do + quote do: AL.eval(unquote(escaped), nil, unquote(opts[:store])) + else + quote do: AL.eval(unquote(escaped)) end end @@ -301,8 +307,9 @@ defmodule AL do TODO fix leakiness on -> marks? Or maybe not necessary TODO Make fresheners deterministic """ - @spec eval([goal()], AL.Var.bindings()) :: {:atomic, t() | nil} | {:aborted, term()} - def eval(program, initial_bindings \\ nil) do + @spec eval([goal()], AL.Var.bindings(), AL.Object.store()) :: + {:atomic, t() | nil} | {:aborted, term()} + def eval(program, initial_bindings \\ nil, store \\ :main) do bindings = initial_bindings || AL.Var.empty_bindings() input_vars = AL.Var.find_vars(program) @@ -320,6 +327,7 @@ defmodule AL do }, choicepoint_stack: [{:mark, 0}], tx_id: tx_id, + store: store, trace: [], program: program, tracepoints: AL.Trace.tracepoints() @@ -478,7 +486,7 @@ defmodule AL do } } else - case AL.Object.scan_class(object_pattern, class_pattern) do + case AL.Object.scan_class(object_pattern, class_pattern, state.store) do [] -> backtrack(state) @@ -513,7 +521,7 @@ defmodule AL do end def interp({:get_super, object_pattern, super_pattern}, state) do - case AL.Object.scan_super(object_pattern, super_pattern) do + case AL.Object.scan_super(object_pattern, super_pattern, state.store) do [] -> backtrack(state) @@ -546,7 +554,7 @@ defmodule AL do end def interp({:get_method, object_pattern, method_name_pattern, method_id_pattern}, state) do - case AL.Object.scan_method(object_pattern, method_name_pattern, method_id_pattern) do + case AL.Object.scan_method(object_pattern, method_name_pattern, method_id_pattern, state.store) do [] -> backtrack(state) @@ -579,7 +587,7 @@ defmodule AL do end def interp({:get_oapply, object_pattern, head_pattern, body_pattern}, state) do - case AL.Object.scan_oapply(object_pattern, head_pattern, body_pattern) do + case AL.Object.scan_oapply(object_pattern, head_pattern, body_pattern, state.store) do [] -> backtrack(state) @@ -697,7 +705,7 @@ defmodule AL do def interp({:oapply, method_id_pattern, bind_head_pattern}, state) do trace_info = trace_call(state, method_id_pattern, bind_head_pattern) - case AL.Object.scan_oapply(method_id_pattern, :"$head", :"$body") do + case AL.Object.scan_oapply(method_id_pattern, :"$head", :"$body", state.store) do [] -> backtrack(state) @@ -859,7 +867,7 @@ defmodule AL do def interp({:get_slot, object, key, value}, state) do entries = - case :mnesia.read(:slots, object) do + case AL.Object.read_slots(object, state.store) do [{:slots, ^object, m}] when is_map(m) -> if AL.Var.var?(key) do Map.to_list(m) @@ -968,7 +976,7 @@ defmodule AL do end def interp({:forall, condition, body}, state) do - solutions = collect_all_solutions(condition, state.active_choicepoint.bindings, state.tx_id) + solutions = collect_all_solutions(condition, state.active_choicepoint.bindings, state.tx_id, state.store) body_goals = Enum.flat_map(solutions, fn bindings -> @@ -984,7 +992,7 @@ defmodule AL do end def interp({:findall, template, condition, result}, state) do - solutions = collect_all_solutions(condition, state.active_choicepoint.bindings, state.tx_id) + solutions = collect_all_solutions(condition, state.active_choicepoint.bindings, state.tx_id, state.store) collected = Enum.map(solutions, fn bindings -> AL.Var.subst(template, bindings) end) @@ -1034,7 +1042,7 @@ defmodule AL do end def interp({:not, condition}, state) do - case collect_all_solutions(condition, state.active_choicepoint.bindings, state.tx_id) do + case collect_all_solutions(condition, state.active_choicepoint.bindings, state.tx_id, state.store) do [] -> state _ -> backtrack(state) end @@ -1047,12 +1055,12 @@ defmodule AL do def interp({:send, self, method, args}, state) do call_args = [self | args] - case resolve_method_id(self, method) do + case resolve_method_id(self, method, state.store) do nil -> dnu(self, method, args, state) id -> - if has_matching_clause?(id, call_args, state.active_choicepoint.bindings) do + if has_matching_clause?(id, call_args, state.active_choicepoint.bindings, state.store) do interp({:oapply, id, call_args}, state) else dnu(self, method, args, state) @@ -1065,45 +1073,46 @@ defmodule AL do defp dnu(self, method, args, state), do: interp({:send, self, :does_not_understand, [method, args]}, state) - defp resolve_method_id(self, method) when is_map(self), - do: resolve_in_chain([Map.get(self, :class, :map)], method) + defp resolve_method_id(self, method, store) when is_map(self), + do: resolve_in_chain([Map.get(self, :class, :map)], method, store) - defp resolve_method_id(self, method) when is_list(self), do: resolve_in_chain([:list], method) + defp resolve_method_id(self, method, store) when is_list(self), + do: resolve_in_chain([:list], method, store) - defp resolve_method_id(self, method) do - case method_ids(self, method) do + defp resolve_method_id(self, method, store) do + case method_ids(self, method, store) do [id | _] -> id - [] -> resolve_in_chain(for({:class, _o, c} <- AL.Object.scan_class(self, :"$class"), do: c), method) + [] -> resolve_in_chain(for({:class, _o, c} <- AL.Object.scan_class(self, :"$class", store), do: c), method, store) end end - defp resolve_in_chain(classes, method), - do: Enum.find_value(classes, fn c -> chain_first_id(c, method) end) + defp resolve_in_chain(classes, method, store), + do: Enum.find_value(classes, fn c -> chain_first_id(c, method, store) end) - defp chain_first_id(class, method) do - case method_ids(class, method) do + defp chain_first_id(class, method, store) do + case method_ids(class, method, store) do [id | _] -> id - [] -> resolve_in_chain(for({:super, _o, s} <- AL.Object.scan_super(class, :"$super"), do: s), method) + [] -> resolve_in_chain(for({:super, _o, s} <- AL.Object.scan_super(class, :"$super", store), do: s), method, store) end end - defp method_ids(obj, method) do - for {:method, _o, _n, id} <- AL.Object.scan_method(obj, method, :"$id"), do: id + defp method_ids(obj, method, store) do + for {:method, _o, _n, id} <- AL.Object.scan_method(obj, method, :"$id", store), do: id end - defp has_matching_clause?(id, call_args, bindings) do - id in @primitive_methods or any_clause_matches?(id, call_args, bindings) + defp has_matching_clause?(id, call_args, bindings, store) do + id in @primitive_methods or any_clause_matches?(id, call_args, bindings, store) end - defp any_clause_matches?(id, call_args, bindings) do + defp any_clause_matches?(id, call_args, bindings, store) do scope = AL.Command.fresh_scope() - Enum.any?(AL.Object.scan_oapply(id, :"$head", :"$body"), fn {:oapply, _id, head, _body} -> + Enum.any?(AL.Object.scan_oapply(id, :"$head", :"$body", store), fn {:oapply, _id, head, _body} -> AL.Var.unify(AL.Var.freshen(head, scope), call_args, bindings) != nil end) end - defp collect_all_solutions(condition, bindings, tx_id) do + defp collect_all_solutions(condition, bindings, tx_id, store) do initial = %AL{ active_choicepoint: %AL.Choicepoint{ goals: condition, @@ -1114,6 +1123,7 @@ defmodule AL do }, choicepoint_stack: [], tx_id: tx_id, + store: store, trace: [], program: condition, tracepoints: AL.Trace.tracepoints() diff --git a/lib/AL/command.ex b/lib/AL/command.ex index b01f2be..51f325b 100644 --- a/lib/AL/command.ex +++ b/lib/AL/command.ex @@ -113,6 +113,16 @@ defmodule AL.Command do ]) end + @doc """ + Read all commands up to and including time t + """ + @spec commands_until(non_neg_integer()) :: [command()] + def commands_until(t) do + :mnesia.select(:command, [ + {{:command, :"$1", :"$2", :"$3"}, [{:"=<", :"$1", t}], [:"$_"]} + ]) + end + @doc """ Read all commands for a given transaction """ diff --git a/lib/AL/object.ex b/lib/AL/object.ex index cdf0e8a..c6ca9a2 100644 --- a/lib/AL/object.ex +++ b/lib/AL/object.ex @@ -1,259 +1,244 @@ defmodule AL.Object do @moduledoc """ - I am the in-memory store for AL objects + I am the in-memory store for AL objects. State is a materialised view of the + command log. I am parameterised by a `store` (a namespace): `:main` is the live + store (base table names); any other store uses suffixed tables (`:class@name`, + ...) created with `record_name:` the base relation, so record tags — and every + scan pattern — are identical across stores. """ 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()]} + @type store() :: atom() + + @relations %{ + class: [:object, :class], + super: [:object, :super], + slots: [:object, :slots], + method: [:object, :method_name, :method_id], + oapply: [:object, :head, :body] + } + @bags [:class, :super, :method, :oapply] typedstruct enforce: true do field(:id, any(), enforce: true) end - + + @spec table(atom(), store()) :: atom() + def table(relation, store \\ :main) + def table(relation, :main), do: relation + def table(relation, store), do: :"#{relation}@#{store}" + def setup() do - case :mnesia.create_table(:class, - attributes: [:object, :class], - type: :bag, - ram_copies: [node()] - ) do - {:atomic, :ok} -> :ok - {:aborted, {:already_exists, _}} -> :ok - end + create_store(:main) + hydrate_since(0) + end - case :mnesia.create_table(:super, - attributes: [:object, :super], - type: :bag, - ram_copies: [node()] - ) do - {:atomic, :ok} -> :ok - {:aborted, {:already_exists, _}} -> :ok - end + @doc "Create the table set for a store. Idempotent." + @spec create_store(store()) :: :ok + def create_store(store) do + for relation <- Map.keys(@relations), do: create_table(relation, store) + :mnesia.wait_for_tables(Enum.map(Map.keys(@relations), &table(&1, store)), 5_000) + :ok + end - case :mnesia.create_table(:slots, - attributes: [:object, :slots], - type: :set, - ram_copies: [node()] - ) do - {:atomic, :ok} -> :ok - {:aborted, {:already_exists, _}} -> :ok - end + @doc "Delete a store's table set." + @spec drop_store(store()) :: :ok + def drop_store(store) do + for relation <- Map.keys(@relations), do: :mnesia.delete_table(table(relation, store)) + :ok + end - case :mnesia.create_table(:method, - attributes: [:object, :method_name, :method_id], - type: :bag, - ram_copies: [node()] - ) do - {:atomic, :ok} -> :ok - {:aborted, {:already_exists, _}} -> :ok - end + @doc """ + Fork a new store, materialised from the log as of time `at` (default `:tip`, + i.e. now). Returns the new store's name. Forks currently derive from the main + log; drop one with `drop_store/1`. + """ + @spec fork(non_neg_integer() | :tip) :: store() + def fork(at \\ :tip) do + store = :"fork_#{System.unique_integer([:positive])}" + create_store(store) + hydrate_until(at_time(at), store) + store + end + + defp at_time(:tip), do: AL.Command.system_time() + defp at_time(t) when is_integer(t), do: t - case :mnesia.create_table(:oapply, - attributes: [:object, :head, :body], - type: :bag, - ram_copies: [node()] - ) do + defp create_table(relation, store) do + opts = [attributes: @relations[relation], type: type(relation), ram_copies: [node()]] + opts = if store == :main, do: opts, else: [{:record_name, relation} | opts] + + case :mnesia.create_table(table(relation, store), opts) do {:atomic, :ok} -> :ok {:aborted, {:already_exists, _}} -> :ok end - - :mnesia.wait_for_tables([:class, :super, :slots, :method, :oapply], 5_000) - - hydrate_since(0) end - @spec scan_class(AL.Var.t(), AL.Var.t()) :: [class_record()] - def scan_class(self_pattern, class_pattern) do - :mnesia.select(:class, [ + defp type(relation) when relation in @bags, do: :bag + defp type(_relation), do: :set + + @spec scan_class(AL.Var.t(), AL.Var.t(), store()) :: [class_record()] + def scan_class(self_pattern, class_pattern, store \\ :main) do + :mnesia.select(table(:class, store), [ {AL.Var.to_mnesia_pattern({:class, self_pattern, class_pattern}), [], [:"$_"]} ]) end - @spec scan_super(AL.Var.t(), AL.Var.t()) :: [super_record()] - def scan_super(self_pattern, super_pattern) do - :mnesia.select(:super, [ + @spec scan_super(AL.Var.t(), AL.Var.t(), store()) :: [super_record()] + def scan_super(self_pattern, super_pattern, store \\ :main) do + :mnesia.select(table(:super, store), [ {AL.Var.to_mnesia_pattern({:super, self_pattern, super_pattern}), [], [:"$_"]} ]) end - @spec scan_slots(AL.Var.t(), AL.Var.t()) :: [slots_record()] - def scan_slots(self_pattern, slots_pattern) do - :mnesia.select(:slots, [ + @spec scan_slots(AL.Var.t(), AL.Var.t(), store()) :: [slots_record()] + def scan_slots(self_pattern, slots_pattern, store \\ :main) do + :mnesia.select(table(:slots, store), [ {AL.Var.to_mnesia_pattern({:slots, self_pattern, slots_pattern}), [], [:"$_"]} ]) end - @spec scan_method(AL.Var.t(), AL.Var.t(), AL.Var.t()) :: [method_record()] - def scan_method(self_pattern, method_name_pattern, method_id_pattern) do - :mnesia.select(:method, [ + @spec scan_method(AL.Var.t(), AL.Var.t(), AL.Var.t(), store()) :: [method_record()] + def scan_method(self_pattern, method_name_pattern, method_id_pattern, store \\ :main) do + :mnesia.select(table(:method, store), [ {AL.Var.to_mnesia_pattern({:method, self_pattern, method_name_pattern, method_id_pattern}), [], [:"$_"]} ]) end - @spec scan_oapply(AL.Var.t(), AL.Var.t(), AL.Var.t()) :: [oapply_record()] - def scan_oapply(self_pattern, head_pattern, body_pattern) do - :mnesia.select(:oapply, [ + @spec scan_oapply(AL.Var.t(), AL.Var.t(), AL.Var.t(), store()) :: [oapply_record()] + def scan_oapply(self_pattern, head_pattern, body_pattern, store \\ :main) do + :mnesia.select(table(:oapply, store), [ {AL.Var.to_mnesia_pattern({:oapply, self_pattern, head_pattern, body_pattern}), [], [:"$_"]} ]) end - @spec retract_class(AL.Var.t(), AL.Var.t()) :: :ok - def retract_class(object_pattern, class_pattern) do - for record <- scan_class(object_pattern, class_pattern), do: :mnesia.delete_object(record) - :ok + @spec read_slots(AL.Var.t(), store()) :: [slots_record()] + def read_slots(object, store \\ :main) do + :mnesia.read(table(:slots, store), object) end - @spec retract_super(AL.Var.t(), AL.Var.t()) :: :ok - def retract_super(object_pattern, super_pattern) do - for record <- scan_super(object_pattern, super_pattern), do: :mnesia.delete_object(record) - :ok + @spec retract_class(AL.Var.t(), AL.Var.t(), store()) :: :ok + def retract_class(object_pattern, class_pattern, store \\ :main) do + delete_all(:class, scan_class(object_pattern, class_pattern, store), store) end - @spec retract_method(AL.Var.t(), AL.Var.t(), AL.Var.t()) :: :ok - def retract_method(object_pattern, method_name_pattern, method_id_pattern) do - for record <- scan_method(object_pattern, method_name_pattern, method_id_pattern), - do: :mnesia.delete_object(record) + @spec retract_super(AL.Var.t(), AL.Var.t(), store()) :: :ok + def retract_super(object_pattern, super_pattern, store \\ :main) do + delete_all(:super, scan_super(object_pattern, super_pattern, store), store) + end - :ok + @spec retract_method(AL.Var.t(), AL.Var.t(), AL.Var.t(), store()) :: :ok + def retract_method(object_pattern, method_name_pattern, method_id_pattern, store \\ :main) do + delete_all(:method, scan_method(object_pattern, method_name_pattern, method_id_pattern, store), store) end - @spec retract_oapply(AL.Var.t(), AL.Var.t()) :: :ok - def retract_oapply(object_pattern, head_pattern) do - for record <- scan_oapply(object_pattern, head_pattern, :"$body"), - do: :mnesia.delete_object(record) + @spec retract_oapply(AL.Var.t(), AL.Var.t(), store()) :: :ok + def retract_oapply(object_pattern, head_pattern, store \\ :main) do + delete_all(:oapply, scan_oapply(object_pattern, head_pattern, :"$body", store), store) + end + defp delete_all(relation, records, store) do + for record <- records, do: :mnesia.delete_object(table(relation, store), record, :write) :ok end - @spec retract_slots(AL.Var.t(), AL.Var.t()) :: :ok - def retract_slots(object, slots) when is_map(slots) do - case :mnesia.read(:slots, object) do + @spec retract_slots(AL.Var.t(), AL.Var.t(), store()) :: :ok + def retract_slots(object, slots, store \\ :main) + + def retract_slots(object, slots, store) when is_map(slots) do + case read_slots(object, store) do [{:slots, ^object, existing}] when is_map(existing) -> case Map.drop(existing, Map.keys(slots)) do - remaining when remaining == %{} -> :mnesia.delete({:slots, object}) - remaining -> :mnesia.write({:slots, object, remaining}) + remaining when remaining == %{} -> :mnesia.delete(table(:slots, store), object, :write) + remaining -> :mnesia.write(table(:slots, store), {:slots, object, remaining}, :write) end _ -> - :mnesia.delete({:slots, object}) + :mnesia.delete(table(:slots, store), object, :write) end end - def retract_slots(object, _slots) do - :mnesia.delete({:slots, object}) + def retract_slots(object, _slots, store) do + :mnesia.delete(table(:slots, store), object, :write) end - @spec set_class(AL.Var.t(), AL.Var.t()) :: :ok - def set_class(object_pattern, class_pattern) do - :mnesia.write({:class, object_pattern, class_pattern}) + @spec set_class(AL.Var.t(), AL.Var.t(), store()) :: :ok + def set_class(object, class, store \\ :main) do + :mnesia.write(table(:class, store), {:class, object, class}, :write) end - @spec set_super(AL.Var.t(), AL.Var.t()) :: :ok - def set_super(object_pattern, super_pattern) do - :mnesia.write({:super, object_pattern, super_pattern}) + @spec set_super(AL.Var.t(), AL.Var.t(), store()) :: :ok + def set_super(object, super, store \\ :main) do + :mnesia.write(table(:super, store), {:super, object, super}, :write) end - @spec set_method(AL.Var.t(), AL.Var.t(), AL.Var.t()) :: :ok - def set_method(object_pattern, method_name_pattern, method_id_pattern) do - :mnesia.write({:method, object_pattern, method_name_pattern, method_id_pattern}) + @spec set_method(AL.Var.t(), AL.Var.t(), AL.Var.t(), store()) :: :ok + def set_method(object, method_name, method_id, store \\ :main) do + :mnesia.write(table(:method, store), {:method, object, method_name, method_id}, :write) end - @spec set_oapply(AL.Var.t(), AL.Var.t(), [AL.goal()]) :: :ok - def set_oapply(object_pattern, head_pattern, body_pattern) do - :mnesia.write({:oapply, object_pattern, head_pattern, body_pattern}) + @spec set_oapply(AL.Var.t(), AL.Var.t(), [AL.goal()], store()) :: :ok + def set_oapply(object, head, body, store \\ :main) do + :mnesia.write(table(:oapply, store), {:oapply, object, head, body}, :write) end - @spec set_slots(AL.Var.t(), AL.Var.t()) :: :ok - def set_slots(object, new_slots) when is_map(new_slots) do + @spec set_slots(AL.Var.t(), AL.Var.t(), store()) :: :ok + def set_slots(object, new_slots, store \\ :main) + + def set_slots(object, new_slots, store) when is_map(new_slots) do existing = - case :mnesia.read(:slots, object) do + case read_slots(object, store) do [{:slots, _, slots}] when is_map(slots) -> slots _ -> %{} end - :mnesia.write({:slots, object, Map.merge(existing, new_slots)}) + :mnesia.write(table(:slots, store), {:slots, object, Map.merge(existing, new_slots)}, :write) end - def set_slots(object, slots) do - :mnesia.write({:slots, object, slots}) + def set_slots(object, slots, store) do + :mnesia.write(table(:slots, store), {:slots, object, slots}, :write) end - def hydrate_event(op, event) do + @spec hydrate_event(AL.Command.command_op(), tuple(), store()) :: any() + def hydrate_event(op, event, store \\ :main) do case op do - :set_class -> - {object, class} = event - :mnesia.write({:class, object, class}) - - :set_super -> - {object, super} = event - :mnesia.write({:super, object, super}) - - :set_method -> - {object, method_name, method_id} = event - :mnesia.write({:method, object, method_name, method_id}) - - :set_oapply -> - {object, head, body} = event - :mnesia.write({:oapply, object, head, body}) - - :retract_class -> - {object, class} = event - retract_class(object, class) - - :retract_super -> - {object, super} = event - retract_super(object, super) - - :retract_method -> - {object, name, id} = event - retract_method(object, name, id) - - :retract_oapply -> - {object, head} = event - retract_oapply(object, head) - - :retract_slots -> - {object, slots} = event - retract_slots(object, slots) - - :set_slots -> - {object, new_slots} = event - - merged = - if is_map(new_slots) do - existing = - case :mnesia.read(:slots, object) do - [{:slots, _, slots}] when is_map(slots) -> slots - _ -> %{} - end - - Map.merge(existing, new_slots) - else - new_slots - end - - :mnesia.write({:slots, object, merged}) - - :send_async -> - :ok - - :send_elixir -> - :ok + :set_class -> with {o, c} <- event, do: set_class(o, c, store) + :set_super -> with {o, s} <- event, do: set_super(o, s, store) + :set_method -> with {o, n, id} <- event, do: set_method(o, n, id, store) + :set_oapply -> with {o, h, b} <- event, do: set_oapply(o, h, b, store) + :set_slots -> with {o, s} <- event, do: set_slots(o, s, store) + :retract_class -> with {o, c} <- event, do: retract_class(o, c, store) + :retract_super -> with {o, s} <- event, do: retract_super(o, s, store) + :retract_method -> with {o, n, id} <- event, do: retract_method(o, n, id, store) + :retract_oapply -> with {o, h} <- event, do: retract_oapply(o, h, store) + :retract_slots -> with {o, s} <- event, do: retract_slots(o, s, store) + :send_async -> :ok + :send_elixir -> :ok end end - def hydrate_since(t) do - f = fn -> - for {:command, _, _, {op, event}} <- AL.Command.commands_since(t) do - hydrate_event(op, event) - end - end + @doc "Replay commands at or after time `t` into `store`." + @spec hydrate_since(non_neg_integer(), store()) :: {:atomic, any()} | {:aborted, term()} + def hydrate_since(t, store \\ :main) do + hydrate(fn -> AL.Command.commands_since(t) end, store) + end + + @doc "Replay commands up to and including time `t` into `store`." + @spec hydrate_until(non_neg_integer(), store()) :: {:atomic, any()} | {:aborted, term()} + def hydrate_until(t, store \\ :main) do + hydrate(fn -> AL.Command.commands_until(t) end, store) + end - :mnesia.transaction(f) + defp hydrate(fetch, store) do + :mnesia.transaction(fn -> + for {:command, _, _, {op, event}} <- fetch.(), do: hydrate_event(op, event, store) + end) end end diff --git a/lib/examples/e_AL.ex b/lib/examples/e_AL.ex index be222d9..c946007 100644 --- a/lib/examples/e_AL.ex +++ b/lib/examples/e_AL.ex @@ -404,4 +404,24 @@ defmodule Examples.AL do assert Map.get(bindings, :"$out") == [%{id: :a}, %{id: :b}, %{id: :c}] :ok end + + example time_travel_fork() do + # time just before we introduce :tt_thing + before = AL.Command.system_time() + {:atomic, _} = run do set_class(:tt_thing, :object) end + + past = AL.Object.fork(before - 1) + tip = AL.Object.fork() + + # the tip fork sees :tt_thing; the past fork does not + {:atomic, _} = run store: tip do class(:tt_thing, :object) end + {:aborted, _} = run store: past do class(:tt_thing, :object) end + + # both forks still carry the bootstrap + {:atomic, _} = run store: past do class(:object, :class) end + + AL.Object.drop_store(past) + AL.Object.drop_store(tip) + :ok + end end From fe7693f91300e19dbe12fcc46bf409d7f1371a6d Mon Sep 17 00:00:00 2001 From: l4e21 Date: Tue, 23 Jun 2026 18:01:22 +0100 Subject: [PATCH 13/16] Clean up examples --- .mnesiastore/LATEST.LOG | Bin 65681 -> 66460 bytes .mnesiastore/schema.DAT | Bin 9778 -> 15932 bytes lib/examples/e_AL.ex | 68 ----------------------------- lib/examples/e_AL_bitemporality.ex | 30 +++++++++++++ lib/examples/e_AL_processes.ex | 57 ++++++++++++++++++++++++ test/al_test.exs | 9 ++++ 6 files changed, 96 insertions(+), 68 deletions(-) create mode 100644 lib/examples/e_AL_bitemporality.ex create mode 100644 lib/examples/e_AL_processes.ex diff --git a/.mnesiastore/LATEST.LOG b/.mnesiastore/LATEST.LOG index 7bd086c826c2ed66db8d3aabfc52b655e6824655..755eab74551fe142f58572cd46eedae911c412d9 100644 GIT binary patch literal 66460 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 JaX(>k{~zbIV@Lo1 literal 65681 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 QzX6vV2v`wubhGyV0REYc$p8QV diff --git a/.mnesiastore/schema.DAT b/.mnesiastore/schema.DAT index 3500644f69ddfb7442cdc4c1fec51607148e38dd..021953c8eef98a95c3c64157148be6859bcfa8f2 100644 GIT binary patch delta 1353 zcmdnwv!{lIfx+g_MwVOrO!5|+9T}6DCOZjnd#006$R`TVH zJd3}#z$D5b5+a+eWXlyievT`9aP4_7^CVvDr!* zsDWeuPMEM6NEo6)xt@`?J?kD!rtA+yhRcQV81w40?->{v_cdoQm$MZoXQbvPmh%_q zV<{GH8Hva2 z=5S+Bve2_4Kf>T4J#A(Z%urKMNYlj7tjz(89ZYVFx1eEh2|1hvkmC?Dkzh!JW0B{~ zEW2iCNbd#a!%$rL11T&q&E`jwg$MQ7$jLC%|ANv;h8yEKsN0XBxE(naV45$0E)7W~ xRe3vMrtkj%G(EwI@d(XQiN1^g#B2s$1_oCPVEF*!^D!`}ZWdIPXJoRo0RVnZ+{*v} delta 251 zcmdl}v&n~rfkEZdMwVOrOo|qp9T}6DCOZj>oY{maQ0B(f$ZSZuZu?_^|?->^4v_nyrsB_1*I@G9I(g{t~9`MgB- z!~}`WR*L0}Jo8@P%!EpOhDZo)wvq)(Jl`@o3o2m@myiZZh%hDQKqY>{CFHvqc>?EO zhDm$@N@Q-fQm$v@l?o8eg)0C4fAR$zD?wEY1_lNJ=GAB418F`62Gh;bw(^WjYDNIm C)m) 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 - send_async(proc, :handle, [:test_object_2, :test_class]) - end - - Process.sleep(50) - - {:atomic, results} = - :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 example arithmetic() do {:atomic, {bindings, result}} = run do @@ -404,24 +356,4 @@ defmodule Examples.AL do assert Map.get(bindings, :"$out") == [%{id: :a}, %{id: :b}, %{id: :c}] :ok end - - example time_travel_fork() do - # time just before we introduce :tt_thing - before = AL.Command.system_time() - {:atomic, _} = run do set_class(:tt_thing, :object) end - - past = AL.Object.fork(before - 1) - tip = AL.Object.fork() - - # the tip fork sees :tt_thing; the past fork does not - {:atomic, _} = run store: tip do class(:tt_thing, :object) end - {:aborted, _} = run store: past do class(:tt_thing, :object) end - - # both forks still carry the bootstrap - {:atomic, _} = run store: past do class(:object, :class) end - - AL.Object.drop_store(past) - AL.Object.drop_store(tip) - :ok - end end diff --git a/lib/examples/e_AL_bitemporality.ex b/lib/examples/e_AL_bitemporality.ex new file mode 100644 index 0000000..62655f6 --- /dev/null +++ b/lib/examples/e_AL_bitemporality.ex @@ -0,0 +1,30 @@ +defmodule Examples.ALBitemporality do + @moduledoc """ + I provide bitemporality feature and GIT-like behaviour examples for AL + """ + + use ExExample + use AL + import ExUnit.Assertions + + + example time_travel_fork() do + # time just before we introduce :tt_thing + before = AL.Command.system_time() + {:atomic, _} = run do set_class(:tt_thing, :object) end + + past = AL.Object.fork(before - 1) + tip = AL.Object.fork() + + # the tip fork sees :tt_thing; the past fork does not + {:atomic, _} = run store: tip do class(:tt_thing, :object) end + {:aborted, _} = run store: past do class(:tt_thing, :object) end + + # both forks still carry the bootstrap + {:atomic, _} = run store: past do class(:object, :class) end + + AL.Object.drop_store(past) + AL.Object.drop_store(tip) + :ok + end +end diff --git a/lib/examples/e_AL_processes.ex b/lib/examples/e_AL_processes.ex new file mode 100644 index 0000000..57086e5 --- /dev/null +++ b/lib/examples/e_AL_processes.ex @@ -0,0 +1,57 @@ +defmodule Examples.ALProcesses do + @moduledoc """ + I provide task and process examples for AL + """ + + use ExExample + use AL + import ExUnit.Assertions + +example create_process() do + head = [:"$self", :"$object", :"$class"] + body = [{:set_slots, :"$object", %{processed: true}}] + + {:atomic, {bindings, _}} = + run do + new(:process, %{method: :handle, head: ^head, body: ^body}, new_proc) + cut + end + + new_proc = Map.get(bindings, :"$new_proc") + assert is_atom(new_proc) + + bindings + end + + example process_handles_command() do + 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 + send_async(proc, :handle, [:test_object_2, :test_class]) + end + + Process.sleep(50) + + {:atomic, results} = + :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 +end diff --git a/test/al_test.exs b/test/al_test.exs index 8b3bd39..7f145b9 100644 --- a/test/al_test.exs +++ b/test/al_test.exs @@ -10,6 +10,14 @@ defmodule AlObjectsTest do use ExExample.ExUnit, for: Examples.ALObjects end +defmodule ALProcessTest do + use ExExample.ExUnit, for: Examples.ALProcesses +end + +defmodule ALBitemporalityTest do + use ExExample.ExUnit, for: Examples.ALBitemporality +end + defmodule ALGenserverTest do use ExExample.ExUnit, for: Examples.ALGenserver end @@ -25,3 +33,4 @@ end defmodule ALUsersTest do use ExExample.ExUnit, for: Examples.ALUsers end + From a583e002662451d27b0d0ddac98d54dfe40899b9 Mon Sep 17 00:00:00 2001 From: l4e21 Date: Tue, 23 Jun 2026 20:08:19 +0100 Subject: [PATCH 14/16] Ensure forks also fork the command log and work cleanly --- lib/AL.ex | 44 ++++----- lib/AL/command.ex | 144 +++++++++++++++++++---------- lib/AL/object.ex | 23 +++-- lib/examples/e_AL_bitemporality.ex | 30 ------ lib/examples/e_AL_log.ex | 49 ++++++++++ test/al_test.exs | 4 +- 6 files changed, 184 insertions(+), 110 deletions(-) delete mode 100644 lib/examples/e_AL_bitemporality.ex create mode 100644 lib/examples/e_AL_log.ex diff --git a/lib/AL.ex b/lib/AL.ex index 41c7ee9..a29df94 100644 --- a/lib/AL.ex +++ b/lib/AL.ex @@ -839,29 +839,29 @@ 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.Object.set_class(object_pattern, class_pattern) + AL.Command.set_class(state.tx_id, object_pattern, class_pattern, state.store) + AL.Object.set_class(object_pattern, class_pattern, state.store) 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.Object.set_super(object_pattern, super_pattern) + AL.Command.set_super(state.tx_id, object_pattern, super_pattern, state.store) + AL.Object.set_super(object_pattern, super_pattern, state.store) 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.Object.set_method(object_pattern, method_name_pattern, method_id_pattern) + AL.Command.set_method(state.tx_id, object_pattern, method_name_pattern, method_id_pattern, state.store) + AL.Object.set_method(object_pattern, method_name_pattern, method_id_pattern, state.store) 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.Object.set_oapply(object_pattern, head_pattern, body_pattern) + AL.Command.set_oapply(state.tx_id, object_pattern, head_pattern, body_pattern, state.store) + AL.Object.set_oapply(object_pattern, head_pattern, body_pattern, state.store) state end @@ -907,53 +907,53 @@ 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.Object.set_slots(object_pattern, slots_pattern) + AL.Command.set_slots(state.tx_id, object_pattern, slots_pattern, state.store) + AL.Object.set_slots(object_pattern, slots_pattern, state.store) 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.Object.retract_class(object, class) + AL.Command.retract_class(state.tx_id, object, class, state.store) + AL.Object.retract_class(object, class, state.store) 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.Object.retract_super(object, super) + AL.Command.retract_super(state.tx_id, object, super, state.store) + AL.Object.retract_super(object, super, state.store) 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.Object.retract_method(object, name, id) + AL.Command.retract_method(state.tx_id, object, name, id, state.store) + AL.Object.retract_method(object, name, id, state.store) 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.Object.retract_oapply(object, head) + AL.Command.retract_oapply(state.tx_id, object, head, state.store) + AL.Object.retract_oapply(object, head, state.store) state end def interp({:retract_slots, object, _slots}, state) when is_map(object), do: state def interp({:retract_slots, object, slots}, state) do - AL.Command.retract_slots(state.tx_id, object, slots) - AL.Object.retract_slots(object, slots) + AL.Command.retract_slots(state.tx_id, object, slots, state.store) + AL.Object.retract_slots(object, slots, state.store) state end def interp({:send_async, object, method, args}, state) do - AL.Command.send_async(state.tx_id, object, method, args) + AL.Command.send_async(state.tx_id, object, method, args, state.store) state end def interp({:send_elixir, pid, message}, state) do - AL.Command.send_elixir(state.tx_id, pid, message) + AL.Command.send_elixir(state.tx_id, pid, message, state.store) state end diff --git a/lib/AL/command.ex b/lib/AL/command.ex index 51f325b..dbeed36 100644 --- a/lib/AL/command.ex +++ b/lib/AL/command.ex @@ -92,12 +92,21 @@ defmodule AL.Command do end end + @doc """ + Table name for a store's command log. `:main` is the live log; a fork uses a + suffixed table created with `record_name: :command`. + """ + @spec log(AL.Object.store()) :: atom() + def log(store \\ :main) + def log(:main), do: :command + def log(store), do: :"command@#{store}" + @doc """ Read a command at time t """ - @spec command(non_neg_integer()) :: command() | :absent - def command(t) do - case :mnesia.read(:command, t) do + @spec command(non_neg_integer(), AL.Object.store()) :: command() | :absent + def command(t, store \\ :main) do + case :mnesia.read(log(store), t) do [{_, ^t, _tx_id, command}] -> command [] -> :absent end @@ -106,9 +115,9 @@ defmodule AL.Command do @doc """ Read all commands since time t """ - @spec commands_since(non_neg_integer()) :: [command()] - def commands_since(t) do - :mnesia.select(:command, [ + @spec commands_since(non_neg_integer(), AL.Object.store()) :: [command()] + def commands_since(t, store \\ :main) do + :mnesia.select(log(store), [ {{:command, :"$1", :"$2", :"$3"}, [{:>=, :"$1", t}], [:"$_"]} ]) end @@ -116,9 +125,9 @@ defmodule AL.Command do @doc """ Read all commands up to and including time t """ - @spec commands_until(non_neg_integer()) :: [command()] - def commands_until(t) do - :mnesia.select(:command, [ + @spec commands_until(non_neg_integer(), AL.Object.store()) :: [command()] + def commands_until(t, store \\ :main) do + :mnesia.select(log(store), [ {{:command, :"$1", :"$2", :"$3"}, [{:"=<", :"$1", t}], [:"$_"]} ]) end @@ -126,91 +135,126 @@ defmodule AL.Command do @doc """ Read all commands for a given transaction """ - def commands_for_transaction(tx_id) do - :mnesia.select(:command, [ + def commands_for_transaction(tx_id, store \\ :main) do + :mnesia.select(log(store), [ {{:command, :"$1", tx_id, :"$3"}, [], [:"$_"]} ]) end + @doc "Create a fork's command log. Idempotent." + @spec create_log(AL.Object.store()) :: :ok + def create_log(store) do + case :mnesia.create_table(log(store), + attributes: [:t, :tx_id, :command], + type: :ordered_set, + ram_copies: [node()], + record_name: :command + ) do + {:atomic, :ok} -> :ok + {:aborted, {:already_exists, _}} -> :ok + end + + :mnesia.wait_for_tables([log(store)], 5_000) + :ok + end + + @doc "Delete a fork's command log." + @spec drop_log(AL.Object.store()) :: :ok + def drop_log(store) do + :mnesia.delete_table(log(store)) + :ok + end + + @doc "Copy `src`'s commands up to and including time `t` into `dst`'s log." + @spec copy_prefix(AL.Object.store(), AL.Object.store(), non_neg_integer()) :: + {:atomic, any()} | {:aborted, term()} + def copy_prefix(src, dst, t) do + :mnesia.transaction(fn -> + for {:command, ct, tx, cmd} <- commands_until(t, src) do + :mnesia.write(log(dst), {:command, ct, tx, cmd}, :write) + end + end) + end + @doc """ Write a command that says a class of an object was set """ - @spec set_class(non_neg_integer(), AL.Var.t(), AL.Var.t()) :: :ok - def set_class(tx_id, object, class) do - write_command(tx_id, {:set_class, {object, class}}) + @spec set_class(non_neg_integer(), AL.Var.t(), AL.Var.t(), AL.Object.store()) :: :ok + def set_class(tx_id, object, class, store \\ :main) do + write_command(tx_id, {:set_class, {object, class}}, store) end @doc """ Write a command that says a superclass of an object was set """ - @spec set_super(non_neg_integer(), AL.Var.t(), AL.Var.t()) :: :ok - def set_super(tx_id, object, super) do - write_command(tx_id, {:set_super, {object, super}}) + @spec set_super(non_neg_integer(), AL.Var.t(), AL.Var.t(), AL.Object.store()) :: :ok + def set_super(tx_id, object, super, store \\ :main) do + write_command(tx_id, {:set_super, {object, super}}, store) end @doc """ Write a command that says a method was set for an object """ - @spec set_method(non_neg_integer(), AL.Var.t(), AL.Var.t(), AL.Var.t()) :: :ok - def set_method(tx_id, object, method_name, method_id) do - write_command(tx_id, {:set_method, {object, method_name, method_id}}) + @spec set_method(non_neg_integer(), AL.Var.t(), AL.Var.t(), AL.Var.t(), AL.Object.store()) :: :ok + def set_method(tx_id, object, method_name, method_id, store \\ :main) do + write_command(tx_id, {:set_method, {object, method_name, method_id}}, store) end @doc """ Write a command that says the object was given a run method """ - @spec set_oapply(non_neg_integer(), AL.Var.t(), AL.Var.t(), [AL.goal()]) :: :ok - def set_oapply(tx_id, object, head, body) do - write_command(tx_id, {:set_oapply, {object, head, body}}) + @spec set_oapply(non_neg_integer(), AL.Var.t(), AL.Var.t(), [AL.goal()], AL.Object.store()) :: :ok + def set_oapply(tx_id, object, head, body, store \\ :main) do + write_command(tx_id, {:set_oapply, {object, head, body}}, store) end @doc """ Write a command that says slots were set for an object """ - @spec set_slots(non_neg_integer(), AL.Var.t(), AL.Var.t()) :: :ok - def set_slots(tx_id, object, slots) do - write_command(tx_id, {:set_slots, {object, slots}}) + @spec set_slots(non_neg_integer(), AL.Var.t(), AL.Var.t(), AL.Object.store()) :: :ok + def set_slots(tx_id, object, slots, store \\ :main) do + write_command(tx_id, {:set_slots, {object, slots}}, store) end - @spec retract_class(non_neg_integer(), AL.Var.t(), AL.Var.t()) :: :ok - def retract_class(tx_id, object, class) do - write_command(tx_id, {:retract_class, {object, class}}) + @spec retract_class(non_neg_integer(), AL.Var.t(), AL.Var.t(), AL.Object.store()) :: :ok + def retract_class(tx_id, object, class, store \\ :main) do + write_command(tx_id, {:retract_class, {object, class}}, store) end - @spec retract_super(non_neg_integer(), AL.Var.t(), AL.Var.t()) :: :ok - def retract_super(tx_id, object, super) do - write_command(tx_id, {:retract_super, {object, super}}) + @spec retract_super(non_neg_integer(), AL.Var.t(), AL.Var.t(), AL.Object.store()) :: :ok + def retract_super(tx_id, object, super, store \\ :main) do + write_command(tx_id, {:retract_super, {object, super}}, store) end - @spec retract_method(non_neg_integer(), AL.Var.t(), AL.Var.t(), AL.Var.t()) :: :ok - def retract_method(tx_id, object, name, id) do - write_command(tx_id, {:retract_method, {object, name, id}}) + @spec retract_method(non_neg_integer(), AL.Var.t(), AL.Var.t(), AL.Var.t(), AL.Object.store()) :: :ok + def retract_method(tx_id, object, name, id, store \\ :main) do + write_command(tx_id, {:retract_method, {object, name, id}}, store) end - @spec retract_oapply(non_neg_integer(), AL.Var.t(), AL.Var.t()) :: :ok - def retract_oapply(tx_id, object, head) do - write_command(tx_id, {:retract_oapply, {object, head}}) + @spec retract_oapply(non_neg_integer(), AL.Var.t(), AL.Var.t(), AL.Object.store()) :: :ok + def retract_oapply(tx_id, object, head, store \\ :main) do + write_command(tx_id, {:retract_oapply, {object, head}}, store) end - @spec retract_slots(non_neg_integer(), AL.Var.t(), AL.Var.t()) :: :ok - def retract_slots(tx_id, object, slots) do - write_command(tx_id, {:retract_slots, {object, slots}}) + @spec retract_slots(non_neg_integer(), AL.Var.t(), AL.Var.t(), AL.Object.store()) :: :ok + def retract_slots(tx_id, object, slots, store \\ :main) do + write_command(tx_id, {:retract_slots, {object, slots}}, store) end - @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}}) + @spec send_async(non_neg_integer(), AL.Var.t(), AL.Var.t(), AL.Var.t(), AL.Object.store()) :: :ok + def send_async(tx_id, object, method, args, store \\ :main) do + write_command(tx_id, {:send_async, {object, method, args}}, store) 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}}) + @spec send_elixir(non_neg_integer(), pid(), term(), AL.Object.store()) :: :ok + def send_elixir(tx_id, pid, message, store \\ :main) do + write_command(tx_id, {:send_elixir, {pid, message}}, store) end - @spec write_command(non_neg_integer(), command()) :: :ok - def write_command(tx_id, command) do + @spec write_command(non_neg_integer(), command(), AL.Object.store()) :: :ok + def write_command(tx_id, command, store \\ :main) do {t1, _t2} = inc_system_time() - :mnesia.write({:command, t1, tx_id, command}) + :mnesia.write(log(store), {:command, t1, tx_id, command}, :write) end @doc """ diff --git a/lib/AL/object.ex b/lib/AL/object.ex index c6ca9a2..e838977 100644 --- a/lib/AL/object.ex +++ b/lib/AL/object.ex @@ -55,21 +55,32 @@ defmodule AL.Object do end @doc """ - Fork a new store, materialised from the log as of time `at` (default `:tip`, - i.e. now). Returns the new store's name. Forks currently derive from the main - log; drop one with `drop_store/1`. + Fork a new store from `:main` as of time `at` (default `:tip`, i.e. now). The + fork gets its OWN command log (the parent's prefix copied in) and its own + projection; subsequent writes against it diverge. Returns the fork's name; + remove it with `discard/1`. """ @spec fork(non_neg_integer() | :tip) :: store() def fork(at \\ :tip) do store = :"fork_#{System.unique_integer([:positive])}" + AL.Command.create_log(store) + AL.Command.copy_prefix(:main, store, at_time(at)) create_store(store) - hydrate_until(at_time(at), store) + hydrate_since(0, store) store end defp at_time(:tip), do: AL.Command.system_time() defp at_time(t) when is_integer(t), do: t + @doc "Discard a fork: drop its projection and its command log." + @spec discard(store()) :: :ok + def discard(store) do + drop_store(store) + AL.Command.drop_log(store) + :ok + end + defp create_table(relation, store) do opts = [attributes: @relations[relation], type: type(relation), ram_copies: [node()]] opts = if store == :main, do: opts, else: [{:record_name, relation} | opts] @@ -227,13 +238,13 @@ defmodule AL.Object do @doc "Replay commands at or after time `t` into `store`." @spec hydrate_since(non_neg_integer(), store()) :: {:atomic, any()} | {:aborted, term()} def hydrate_since(t, store \\ :main) do - hydrate(fn -> AL.Command.commands_since(t) end, store) + hydrate(fn -> AL.Command.commands_since(t, store) end, store) end @doc "Replay commands up to and including time `t` into `store`." @spec hydrate_until(non_neg_integer(), store()) :: {:atomic, any()} | {:aborted, term()} def hydrate_until(t, store \\ :main) do - hydrate(fn -> AL.Command.commands_until(t) end, store) + hydrate(fn -> AL.Command.commands_until(t, store) end, store) end defp hydrate(fetch, store) do diff --git a/lib/examples/e_AL_bitemporality.ex b/lib/examples/e_AL_bitemporality.ex deleted file mode 100644 index 62655f6..0000000 --- a/lib/examples/e_AL_bitemporality.ex +++ /dev/null @@ -1,30 +0,0 @@ -defmodule Examples.ALBitemporality do - @moduledoc """ - I provide bitemporality feature and GIT-like behaviour examples for AL - """ - - use ExExample - use AL - import ExUnit.Assertions - - - example time_travel_fork() do - # time just before we introduce :tt_thing - before = AL.Command.system_time() - {:atomic, _} = run do set_class(:tt_thing, :object) end - - past = AL.Object.fork(before - 1) - tip = AL.Object.fork() - - # the tip fork sees :tt_thing; the past fork does not - {:atomic, _} = run store: tip do class(:tt_thing, :object) end - {:aborted, _} = run store: past do class(:tt_thing, :object) end - - # both forks still carry the bootstrap - {:atomic, _} = run store: past do class(:object, :class) end - - AL.Object.drop_store(past) - AL.Object.drop_store(tip) - :ok - end -end diff --git a/lib/examples/e_AL_log.ex b/lib/examples/e_AL_log.ex new file mode 100644 index 0000000..0ac092d --- /dev/null +++ b/lib/examples/e_AL_log.ex @@ -0,0 +1,49 @@ +defmodule Examples.ALLog do + @moduledoc """ + I provide command-log forking (Git-like) examples for AL: a `fork` is a + divergent command log materialised into its own store. + """ + + use ExExample + use AL + import ExUnit.Assertions + + example read_from_fork() do + # time just before we introduce :tt_thing + before = AL.Command.system_time() + {:atomic, _} = run do set_class(:tt_thing, :object) end + + past = AL.Object.fork(before - 1) + tip = AL.Object.fork() + + # the tip fork sees :tt_thing; the past fork does not + {:atomic, _} = run store: tip do class(:tt_thing, :object) end + {:aborted, _} = run store: past do class(:tt_thing, :object) end + + # both forks still carry the bootstrap + {:atomic, _} = run store: past do class(:object, :class) end + + AL.Object.discard(past) + AL.Object.discard(tip) + :ok + end + + example write_to_fork() do + tip = AL.Object.fork() + + # write only into the fork, then read it back from the fork's projection + {:atomic, {bindings, _}} = + run store: tip do + set_slots(:widget, %{x: 3}) + get_slot(:widget, :x, x) + end + + assert Map.get(bindings, :"$x") == 3 + + # main never saw :widget — the write stayed in the fork's log + {:aborted, _} = run do get_slot(:widget, :x, x) end + + AL.Object.discard(tip) + :ok + end +end diff --git a/test/al_test.exs b/test/al_test.exs index 7f145b9..fdec415 100644 --- a/test/al_test.exs +++ b/test/al_test.exs @@ -14,8 +14,8 @@ defmodule ALProcessTest do use ExExample.ExUnit, for: Examples.ALProcesses end -defmodule ALBitemporalityTest do - use ExExample.ExUnit, for: Examples.ALBitemporality +defmodule ALLogTest do + use ExExample.ExUnit, for: Examples.ALLog end defmodule ALGenserverTest do From 3686a97f5eeef10b1b9553683468ae64917bcb65 Mon Sep 17 00:00:00 2001 From: l4e21 Date: Tue, 23 Jun 2026 20:29:20 +0100 Subject: [PATCH 15/16] Refactor branch code, persist forks --- .mnesiastore/LATEST.LOG | Bin 66460 -> 65675 bytes .mnesiastore/meta.DCD | Bin 8 -> 240 bytes .mnesiastore/schema.DAT | Bin 15932 -> 18022 bytes lib/AL.ex | 2 +- lib/AL/application.ex | 2 +- lib/AL/branch.ex | 96 +++++++++++++++++++ lib/AL/command.ex | 4 +- lib/AL/object.ex | 32 ------- lib/examples/{e_AL_log.ex => e_AL_branch.ex} | 32 +++++-- test/al_test.exs | 4 +- 10 files changed, 126 insertions(+), 46 deletions(-) create mode 100644 lib/AL/branch.ex rename lib/examples/{e_AL_log.ex => e_AL_branch.ex} (58%) diff --git a/.mnesiastore/LATEST.LOG b/.mnesiastore/LATEST.LOG index 755eab74551fe142f58572cd46eedae911c412d9..2e930d530e543be41fa30aab24ff8098ca316e36 100644 GIT binary patch literal 65675 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 literal 66460 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 JaX(>k{~zbIV@Lo1 diff --git a/.mnesiastore/meta.DCD b/.mnesiastore/meta.DCD index f8dd237a910bc930fcf122e55ae74c880d89a452..3c6dc33dd14cd1b0b562ffa86eb9dccf18b2166f 100644 GIT binary patch literal 240 zcmZQ%VrEH>@a1M;U~o+e_i=2_U@Pa!$xn~ZNKH&hEh=YENlu9e^0OJ34fPDN8CXp8 zj7-XT^YZiZQ&JuB@-y;_OEQ>~7#P^TB{48>Wk_P+xcV1pm@LFF=5m(Y)RM$<-s0r^ zg4For{L;LV)FPmA#xp=gqNs{kKz0H(BxdFTC52EW*@{c@i&BfTfHD%OGTg@Z|;o1APG= diff --git a/.mnesiastore/schema.DAT b/.mnesiastore/schema.DAT index 021953c8eef98a95c3c64157148be6859bcfa8f2..04312a61030776d84fce4b147ab3bb43dd110025 100644 GIT binary patch delta 1318 zcmdl}^Q?!Fg@M79Z6nJqekOa1&5n#oOp~32rZ72LOgvb$*-O}wnYYFKb6xY9-4b~m zjO8yUUy#U}m|(HlO1zViCxO#v7F6cf=JOJF7AA^18kIa5!D8>A+ICL9sGvLffr`y$E7@{J9wQU6 z6;K%qh=D?rtxQ$Gx)V)hK=kGY(?~|%z>ENx5jMYpt_<01r3loosQDhIe+SUy%!vsi zo2``V8F?4}6I=;3_{SfJuecnT`U@(N z1_lP945o6Hyu{p8G&zWeEGl_buI_vQHGSWY$rtqWtb-8 z#DapHO6+!{Np5bi^kw8_OSu8_&A;!!u+lL@HjlSBC%*(&@Sw;|woq4LF)}hS1||S0 z2m_V^EsjtuLW?65xy^N| z?uMt`$b47bB%v^higMgQw!W3so>P?LcNu7SPt(9Kbk_X|k23n2{iA zoC6assw*+&!ST*}P>=O3G-UPw6SkiIWM@?=aQaVB1?M7&hpZ&7!jzdn61o8sbIW8K zQ$E&`*aGSpX8M@h7I**ZOdEUw8P`my@a+||sD|2OtZRSXM)}oR} ze0vEj!_|O&`_IaDv$cE|MpR{Oww5l#Fk5%>1~D;=NF+MUEiBa_Wq>8L3;?Cm+fLJ9 zPTKPmm`?R=WFQ%Ufs27b+Jb>W9*Av$*cpf!1fX&}3=F0qz6g}h%fO%tr0g@M85&qkJ8{7mu|n;jXGm?k?3O<|I@n0T;gvzM?VGf&K}X<9``c1z@O zFjh>ysG~bsKvrww1)t4U;+>2WB*Q=s2NbScC(doJtJ>>);*YL*`LW1Y)U3uNlWnME~C+8H#a!@ zGV*Rvh^U4dUHp6U1&Jgl<_4(Qr~#6klUNK5P0Rp6l>`TXk6k6Nb=+QeIl?nVSO`qnIXJ$*X|lHBnv$oN&N#ab}iXGc>^VLesd$WGh=` zND8n;qyS(5@SKgD3{(6U7;#yXt(_qX6P=+(g5sbmZzoLE{tv)lN^oL4LbEu~ml1%N b&A`jR;A#O(*f2gH1B2>jS66vPCOaDd{_Jvu diff --git a/lib/AL.ex b/lib/AL.ex index a29df94..ad1acfe 100644 --- a/lib/AL.ex +++ b/lib/AL.ex @@ -257,7 +257,7 @@ defmodule AL do if Keyword.has_key?(opts, :store) do quote do: AL.eval(unquote(escaped), nil, unquote(opts[:store])) else - quote do: AL.eval(unquote(escaped)) + quote do: AL.eval(unquote(escaped), nil, AL.Branch.head()) end end diff --git a/lib/AL/application.ex b/lib/AL/application.ex index 6f716f1..d46d44d 100644 --- a/lib/AL/application.ex +++ b/lib/AL/application.ex @@ -9,7 +9,7 @@ defmodule AL.Application do @impl true def start(_type, _args) do AL.Command.setup() - AL.Object.setup() + AL.Branch.setup() opts = [strategy: :one_for_one, name: Al.Supervisor] {:ok, pid} = Supervisor.start_link([AL.Scheduler], opts) diff --git a/lib/AL/branch.ex b/lib/AL/branch.ex new file mode 100644 index 0000000..30c095d --- /dev/null +++ b/lib/AL/branch.ex @@ -0,0 +1,96 @@ +defmodule AL.Branch do + @moduledoc """ + I manage branches of the command log. A branch is a fork: its own command log + (the parent's prefix copied in) and its own object projection. `:main` is the + root branch. I also track which branch is checked out (HEAD). + + Branch metadata lives in the `:meta` table: `:stores` (the list of forks) and + `:head` (the checked-out branch). `AL.Command` owns command-log primitives and + `AL.Object` owns projection primitives; I orchestrate both. + """ + + @doc """ + Bring up every branch: for `:main` and each persisted fork, create its + projection tables and replay its command log. Run at startup. + """ + @spec setup() :: :ok + def setup() do + init_meta() + + for branch <- [:main | list()] do + AL.Object.create_store(branch) + AL.Object.hydrate_since(0, branch) + end + + :ok + end + + @doc """ + Fork a new branch from `:main` as of time `at` (default `:tip`, i.e. now). The + branch gets its own (disc) command log with the parent's prefix copied in, plus + its own projection; subsequent writes against it diverge. Returns its name. + """ + @spec fork(non_neg_integer() | :tip) :: AL.Object.store() + def fork(at \\ :tip) do + branch = :"fork_#{System.unique_integer([:positive])}" + AL.Command.create_log(branch) + AL.Command.copy_prefix(:main, branch, at_time(at)) + AL.Object.create_store(branch) + AL.Object.hydrate_since(0, branch) + register(branch) + branch + end + + @doc "Discard a branch: drop its projection and command log, untrack it." + @spec discard(AL.Object.store()) :: :ok + def discard(branch) do + unregister(branch) + if head() == branch, do: set_head(:main) + AL.Object.drop_store(branch) + AL.Command.drop_log(branch) + :ok + end + + @doc "Check out a branch (Git HEAD-style): `run do ... end` now acts against it." + @spec checkout(AL.Object.store()) :: :ok + def checkout(branch), do: set_head(branch) + + @doc "The currently checked-out branch (default `:main`)." + @spec head() :: AL.Object.store() + def head() do + case :mnesia.dirty_read(:meta, :head) do + [{_, :head, branch}] -> branch + [] -> :main + end + end + + @doc "All forks (not including `:main`)." + @spec list() :: [AL.Object.store()] + def list() do + case :mnesia.dirty_read(:meta, :stores) do + [{_, :stores, names}] -> names + [] -> [] + end + end + + defp at_time(:tip), do: AL.Command.system_time() + defp at_time(t) when is_integer(t), do: t + + defp set_head(branch), do: :mnesia.dirty_write({:meta, :head, branch}) + + defp register(name), do: :mnesia.dirty_write({:meta, :stores, Enum.uniq([name | list()])}) + + defp unregister(name), do: :mnesia.dirty_write({:meta, :stores, list() -- [name]}) + + defp init_meta() do + case :mnesia.dirty_read(:meta, :stores) do + [] -> :mnesia.dirty_write({:meta, :stores, []}) + _ -> :ok + end + + case :mnesia.dirty_read(:meta, :head) do + [] -> :mnesia.dirty_write({:meta, :head, :main}) + _ -> :ok + end + end +end diff --git a/lib/AL/command.ex b/lib/AL/command.ex index dbeed36..55fcb65 100644 --- a/lib/AL/command.ex +++ b/lib/AL/command.ex @@ -141,13 +141,13 @@ defmodule AL.Command do ]) end - @doc "Create a fork's command log. Idempotent." + @doc "Create a fork's command log (persisted to disc). Idempotent." @spec create_log(AL.Object.store()) :: :ok def create_log(store) do case :mnesia.create_table(log(store), attributes: [:t, :tx_id, :command], type: :ordered_set, - ram_copies: [node()], + disc_copies: [node()], record_name: :command ) do {:atomic, :ok} -> :ok diff --git a/lib/AL/object.ex b/lib/AL/object.ex index e838977..c118f97 100644 --- a/lib/AL/object.ex +++ b/lib/AL/object.ex @@ -34,11 +34,6 @@ defmodule AL.Object do def table(relation, :main), do: relation def table(relation, store), do: :"#{relation}@#{store}" - def setup() do - create_store(:main) - hydrate_since(0) - end - @doc "Create the table set for a store. Idempotent." @spec create_store(store()) :: :ok def create_store(store) do @@ -54,33 +49,6 @@ defmodule AL.Object do :ok end - @doc """ - Fork a new store from `:main` as of time `at` (default `:tip`, i.e. now). The - fork gets its OWN command log (the parent's prefix copied in) and its own - projection; subsequent writes against it diverge. Returns the fork's name; - remove it with `discard/1`. - """ - @spec fork(non_neg_integer() | :tip) :: store() - def fork(at \\ :tip) do - store = :"fork_#{System.unique_integer([:positive])}" - AL.Command.create_log(store) - AL.Command.copy_prefix(:main, store, at_time(at)) - create_store(store) - hydrate_since(0, store) - store - end - - defp at_time(:tip), do: AL.Command.system_time() - defp at_time(t) when is_integer(t), do: t - - @doc "Discard a fork: drop its projection and its command log." - @spec discard(store()) :: :ok - def discard(store) do - drop_store(store) - AL.Command.drop_log(store) - :ok - end - defp create_table(relation, store) do opts = [attributes: @relations[relation], type: type(relation), ram_copies: [node()]] opts = if store == :main, do: opts, else: [{:record_name, relation} | opts] diff --git a/lib/examples/e_AL_log.ex b/lib/examples/e_AL_branch.ex similarity index 58% rename from lib/examples/e_AL_log.ex rename to lib/examples/e_AL_branch.ex index 0ac092d..babe6e6 100644 --- a/lib/examples/e_AL_log.ex +++ b/lib/examples/e_AL_branch.ex @@ -1,6 +1,6 @@ -defmodule Examples.ALLog do +defmodule Examples.ALBranch do @moduledoc """ - I provide command-log forking (Git-like) examples for AL: a `fork` is a + I provide branch (Git-like command-log fork) examples for AL: a branch is a divergent command log materialised into its own store. """ @@ -13,8 +13,8 @@ defmodule Examples.ALLog do before = AL.Command.system_time() {:atomic, _} = run do set_class(:tt_thing, :object) end - past = AL.Object.fork(before - 1) - tip = AL.Object.fork() + past = AL.Branch.fork(before - 1) + tip = AL.Branch.fork() # the tip fork sees :tt_thing; the past fork does not {:atomic, _} = run store: tip do class(:tt_thing, :object) end @@ -23,13 +23,13 @@ defmodule Examples.ALLog do # both forks still carry the bootstrap {:atomic, _} = run store: past do class(:object, :class) end - AL.Object.discard(past) - AL.Object.discard(tip) + AL.Branch.discard(past) + AL.Branch.discard(tip) :ok end example write_to_fork() do - tip = AL.Object.fork() + tip = AL.Branch.fork() # write only into the fork, then read it back from the fork's projection {:atomic, {bindings, _}} = @@ -43,7 +43,23 @@ defmodule Examples.ALLog do # main never saw :widget — the write stayed in the fork's log {:aborted, _} = run do get_slot(:widget, :x, x) end - AL.Object.discard(tip) + AL.Branch.discard(tip) + :ok + end + + example checkout_switches_head() do + branch = AL.Branch.fork() + AL.Branch.checkout(branch) + + # with the branch checked out, plain `run` acts against it + {:atomic, _} = run do set_class(:on_branch, :object) end + {:atomic, _} = run do class(:on_branch, :object) end + + # back on main, the branch's write is invisible + AL.Branch.checkout(:main) + {:aborted, _} = run do class(:on_branch, :object) end + + AL.Branch.discard(branch) :ok end end diff --git a/test/al_test.exs b/test/al_test.exs index fdec415..4622230 100644 --- a/test/al_test.exs +++ b/test/al_test.exs @@ -14,8 +14,8 @@ defmodule ALProcessTest do use ExExample.ExUnit, for: Examples.ALProcesses end -defmodule ALLogTest do - use ExExample.ExUnit, for: Examples.ALLog +defmodule ALBranchTest do + use ExExample.ExUnit, for: Examples.ALBranch end defmodule ALGenserverTest do From 932012b3bb71af45cf2ef6edc6e9a898189b875b Mon Sep 17 00:00:00 2001 From: l4e21 Date: Wed, 24 Jun 2026 10:34:05 +0100 Subject: [PATCH 16/16] Make objects durable by default, other improvements and fixes --- .mnesiastore/LATEST.LOG | Bin 65675 -> 65741 bytes .mnesiastore/meta.DCD | Bin 240 -> 438 bytes .mnesiastore/schema.DAT | Bin 18022 -> 21912 bytes lib/AL.ex | 12 ------------ lib/AL/command.ex | 13 +++++++++---- lib/AL/package/bootstrap.ex | 27 +++++++++++++-------------- lib/AL/package/constraints.ex | 22 +++------------------- lib/AL/package/elixir_process.ex | 3 ++- lib/AL/package/users.ex | 4 ++-- lib/examples/e_AL.ex | 2 +- lib/examples/e_AL_branch.ex | 9 ++++++--- lib/examples/e_AL_constraints.ex | 30 ++++++++++++------------------ lib/examples/e_AL_objects.ex | 4 ++-- 13 files changed, 50 insertions(+), 76 deletions(-) diff --git a/.mnesiastore/LATEST.LOG b/.mnesiastore/LATEST.LOG index 2e930d530e543be41fa30aab24ff8098ca316e36..aa61266f0cc8f603489fa2e6fb35e66014eda505 100644 GIT binary patch literal 65741 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 literal 65675 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 diff --git a/.mnesiastore/meta.DCD b/.mnesiastore/meta.DCD index 3c6dc33dd14cd1b0b562ffa86eb9dccf18b2166f..e468e1754f05888bed9d2734b8808f09fb62d717 100644 GIT binary patch delta 259 zcmajYJ&M9W6ae5+g9sjBQ36(W^FNbDvDZ?zl=o)lVIXPNK+wj*3z*8Acn{Cu1w6nm zY`u+pf$w|u?t|-Ql%1Z)#$U^BJL?a>yHma>@N>Po><4XDrv~=@c2ve)1(>Qf#{d|F zL=4&&Iv`n+#tRbUB^q!r>28z!RnUT2^s7CbdTv-GsfOc0yz2*xs7B6 ok&G(E1kPF$bzyh}^$%c^;nty44mA~?lvfy(Qj%MYK64!Azx!xUb^rhX delta 88 zcmdnS{DE27AN94i8tkc;kyUXs(GBagaEQCaP zvht!35&};|qo7e@2v&ipwGTvoqVbPJejW@8BGE(xMZh~VcV_Or)2%zZvmKbsCQbL; zJ9lR9`Mz`Rx#!$n0M>Ma11GNm03iOGX1@gy)YfABL0!Ptpnns-v}+sknh!Qyj(j}) z$v@|Ab@RC^{SA4DtDDAGy1G2PIMhI>fdFZsJ+%J-p&3pk)Ig|#Py?X`LJfo(@InJ{ z6o9TNh>_qh0JG|N4jcjCkvhhEp{qvR1C9akPypxE2wjEutX%`}AOLd-p20%^EFk3C z?_AqR?fLV3c)s@B`93_iHZ6st>eT=o?p(HZ6Uvpo_G%_tYEt-&P~gn}d35(=CQ^!t zTtP4!HCZheg;G=zR3x;@Tp`6v#jK#@kl2>aDm<38@vzh`N}`k&`a~%sDXMl&QolwL z^JQXSYhL2gDM?NXG6%F9aXc?5N~(ayqK62mvm|F#0d>W*Vp`aw-6^Jsg`)PRJumTG z9^Fn<1yL=<2e`bVUD_rKJi2F!nL#V3s&ck}P!$yIL!w)%Tz_6DHS>xp5Ayn_ObjZ5 zoGQvvQIJ(UyTpLZtz+7ncu88HMGwnF#TKx7Z{N4Wa1`VJ8Hs;0FBJ-$nAUXDsi|34pV_K4=nQDn(Gt=r--GK~GQgUXg91%be@@_$+3T9{oCUA% zdFFS_g6lW^WI-kT2hpNOJAk;Nwb}UhbL)^mtp4{T{&-H4;ICcbIsVDhdrbUS-i`S4 zc}`*NUvCQ-{^p!2mO zpnOy*)-R>Yp8J981LG#;ISA?Ukm{5H-vBLVVN4*C6E0!-^Njs4Q(`#WL4aA<%uRXzAtGAzj(^&s| zU9JDMWqyrOa0JKyhe-QxGCUv+J8ldX+V-S;@%5!kv#;Vj(BrOReyg5>;Bdx&UfTZz z@jqezT5qrz5%C$L!~| ze14^9E#XnwA;-V==|@d_KISUNtJRMGzU5}OS9=5y9m4iMLazVq z1S5L8=`euqvweTa6;u(P;CMh)k^WCdau;x;ZgKn~jssS?$_LbXmJp6&{HgPQ9fXhXanum# z&S2s1+wOnE$<^mg)@S*~X!t$0|5N1ppQNHl#|q%+ws;iAk>16T<)g`;aXn~-t2k22{!f zsZ{?>Q0G5bC&Knx=m#SLH0F*b#iSDK>i<-o2;1Gg&t$+BSJ7bgo*}vw+kaU9qrVbl zqut1}9yQnhkmZh&9y>wq*c=XmI%=;HiEA4fayUFGL_gg_Ho`3E%UIX0b zg{$)c@h8Ig|E`Gt3u}&>EGoOL05I`CQR`!uZsz=-?FYi@O&!&n?8TQ%>^9bkon4`E zIPs2f@792C;tX|dF|j-Bs(yo3V8-rj?3OQCMi)&<>`x(CB1J-k1T^up z!Iu!@O2PsS3(!X*L}J25W1_K;keIkYT^Ly)3l}OW#P`~nCI;i;WoCNrJ@>qGPajOf zulJxFfJVhrIkT71^T-WX6z~-Qhk5LV7a3HSox|wp&jSkh3;;Jjw$y`p23sz$xW-v2I}-#Z+Zzr>GWJ=l4O@YF9oMAwET+M1rB1Q1}c5gksom z96ctuQbdI1Flyk6{_=JR7JtbHe~^al{G@JZMgp>^BGMM{2Ypd4 z(jD%NxH`f;U9D87Lqq#{eUaR1wcCiRn#4=OShjF-@QXOLk7^>m)+o{h+4Q?j?9$zY z>|Dlc$((+)Twy|6mD%h+2H+DmvZqJm=TgeHP^`|3wky^77JD}UAUnWBBD5 zEGE}$D5R{yO*%vK!mX@NNzPP#qgTQ8+7M)? zLT{$TIoqkYH6X#9NUWJmL$jXuG!tCz^Ij=&gXB&mw6hI0SkT?w?JD_CU|xHDH$950 zlqg=QDfq94WWB}`8h2wN?ldDG>&B(wi@%AXW$hj-1v*rm5o(I`nca diff --git a/lib/AL.ex b/lib/AL.ex index ad1acfe..c0bac78 100644 --- a/lib/AL.ex +++ b/lib/AL.ex @@ -619,18 +619,6 @@ defmodule AL do end end - def interp({:oapply, :gensym, [result]}, state) do - fresh = :"gensym_#{System.unique_integer([:monotonic, :positive])}" - - %AL{ - state - | active_choicepoint: %AL.Choicepoint{ - state.active_choicepoint - | bindings: AL.Var.unify(result, fresh, state.active_choicepoint.bindings) - } - } - end - def interp({:oapply, :fresh_id, [result]}, state) do %AL{ state diff --git a/lib/AL/command.ex b/lib/AL/command.ex index 55fcb65..61b34e3 100644 --- a/lib/AL/command.ex +++ b/lib/AL/command.ex @@ -261,15 +261,20 @@ defmodule AL.Command do I increase the monotonic system time of the log """ def inc_system_time() do - t = system_time() - :mnesia.dirty_write({:meta, :system_time, t + 1}) + t = + case :mnesia.read(:meta, :system_time, :write) do + [{_, :system_time, t}] -> t + [] -> 0 + end + + :mnesia.write({:meta, :system_time, t + 1}) {t, t + 1} end @spec fresh_id() :: atom() def fresh_id() do label = - case :mnesia.dirty_read(:meta, :id_counter) do + case :mnesia.read(:meta, :id_counter) do [{:meta, :id_counter, n}] -> n [] -> 0 end @@ -286,7 +291,7 @@ defmodule AL.Command do @spec fresh_scope() :: String.t() def fresh_scope() do n = - case :mnesia.dirty_read(:meta, :scope_counter) do + case :mnesia.read(:meta, :scope_counter) do [{:meta, :scope_counter, n}] -> n [] -> 0 end diff --git a/lib/AL/package/bootstrap.ex b/lib/AL/package/bootstrap.ex index 949524d..c51f6c7 100644 --- a/lib/AL/package/bootstrap.ex +++ b/lib/AL/package/bootstrap.ex @@ -40,7 +40,7 @@ defmodule AL.Package.Bootstrap do end set_class(:map, :class) - set_super(:map, :object) + set_super(:map, :ephemeral) set_class(:map_get, :behaviour) set_method(:map, :get, :map_get) @@ -65,8 +65,10 @@ defmodule AL.Package.Bootstrap do set_slots(name, slots) end - defmethod(:object, :allocate, [self, _, self]) do - # print(["allocate", self]) + defmethod(:object, :allocate, [self, args, name]) do + class(self, meta) + alternative([map_get(args, :name, name)], [gensym(name)]) + set_class(name, meta) end defmethod(:object, :init, [self, _, self]) do @@ -79,6 +81,12 @@ defmodule AL.Package.Bootstrap do init(alloc, args, new) end + new(:class, %{name: :ephemeral, super: :object, slots: []}, _) + + defmethod(:ephemeral, :allocate, [self, _, self]) do + # print(["allocate", self]) + end + defmethod(:object, :examine, [self, %{ id: self, classes: classes, @@ -99,16 +107,7 @@ defmodule AL.Package.Bootstrap do findall([slot_name, slot_value], [get_slot(self, slot_name, slot_value)], slots) end - new(:class, %{name: :durable_object, super: :object, slots: [:owner]}, _) - - defmethod(:durable_object, :allocate, [self, args, new]) do - class(self, meta) - gensym(new) - set_class(new, meta) - set_super(new, :durable_object) - end - - new(:class, %{name: :package, super: :durable_object, slots: [:name, :version, :deps, :tx]}, _) + new(:class, %{name: :package, super: :object, slots: [:name, :version, :deps, :tx]}, _) defmethod(:package, :init, [self, args, self]) do map_get(args, :name, name) @@ -118,7 +117,7 @@ defmodule AL.Package.Bootstrap do set_slots(self, %{name: name, version: version, deps: deps, tx: tx}) end - new(:class, %{name: :list, super: :object, slots: []}, _) + new(:class, %{name: :list, super: :ephemeral, slots: []}, _) defmethod(:list, :hd, [[h | _t], h]) do end defmethod(:list, :tl, [[_h | t], t]) do end diff --git a/lib/AL/package/constraints.ex b/lib/AL/package/constraints.ex index 8ba04a6..7a17658 100644 --- a/lib/AL/package/constraints.ex +++ b/lib/AL/package/constraints.ex @@ -4,16 +4,8 @@ defmodule AL.Package.Constraints do defpackage :constraints, version: 1, deps: [:bootstrap] do new(:class, %{name: :cell, super: :object, slots: [:subscribers, :value, :name]}, _) - defmethod(:cell, :allocate, [self, args, new]) do - class(self, meta) - gensym(new) - - set_class(new, meta) - set_super(new, :object) - end - defmethod(:cell, :init, [self, args, self]) do - set_slots(self, %{name: cell_name, subscribers: [], value: :absent}) + set_slots(self, %{name: self, subscribers: [], value: :absent}) end defmethod(:cell, :constrain, [self, value]) do @@ -31,21 +23,13 @@ defmodule AL.Package.Constraints do 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 + new(:class, %{name: :propagator, super: :object, slots: [:input_cells, :output_cell, :name]}, _) 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}) + set_slots(self, %{input_cells: input_cells, output_cell: output_cell, name: self}) forall([member(input_cells, input_cell)], [subscribe(input_cell, self)]) diff --git a/lib/AL/package/elixir_process.ex b/lib/AL/package/elixir_process.ex index 2441d17..0216bd0 100644 --- a/lib/AL/package/elixir_process.ex +++ b/lib/AL/package/elixir_process.ex @@ -7,8 +7,9 @@ defmodule AL.Package.ElixirProcess do class(self, meta) map_get(args, :name, new_obj) set_class(new_obj, meta) - set_super(new_obj, :elixir_process) + set_super(new_obj, :object) end + defmethod(:elixir_process, :init, [self, args, self]) do map_get(args, :pid, pid) set_slots(self, %{pid: pid}) diff --git a/lib/AL/package/users.ex b/lib/AL/package/users.ex index b01c923..df9598a 100644 --- a/lib/AL/package/users.ex +++ b/lib/AL/package/users.ex @@ -2,8 +2,8 @@ defmodule AL.Package.Users do use AL.Package defpackage :users, version: 1, deps: [:bootstrap] do - new(:class, %{name: :user, super: :durable_object, slots: [:name]}, _) - new(:class, %{name: :owned, super: :durable_object, slots: []}, _) + new(:class, %{name: :user, super: :object, slots: [:name]}, _) + new(:class, %{name: :owned, super: :object, slots: [:name, :owner]}, _) defmethod(:owned, :init, [self, args, self]) do set_slots(self, args) diff --git a/lib/examples/e_AL.ex b/lib/examples/e_AL.ex index 671a0a7..2299957 100644 --- a/lib/examples/e_AL.ex +++ b/lib/examples/e_AL.ex @@ -40,7 +40,7 @@ defmodule Examples.AL do example does_not_understand_dispatch() do {:atomic, {b, _}} = run do - new(:class, %{name: :gadget, super: :object, slots: []}, _) + new(:class, %{name: :gadget, super: :ephemeral, slots: []}, _) defmethod(:gadget, :poke, [self, x]) do unify(x, :ok) diff --git a/lib/examples/e_AL_branch.ex b/lib/examples/e_AL_branch.ex index babe6e6..36ef8b7 100644 --- a/lib/examples/e_AL_branch.ex +++ b/lib/examples/e_AL_branch.ex @@ -11,14 +11,17 @@ defmodule Examples.ALBranch do example read_from_fork() do # time just before we introduce :tt_thing before = AL.Command.system_time() - {:atomic, _} = run do set_class(:tt_thing, :object) end + + sym = :crypto.strong_rand_bytes(16) |> Base.encode16(case: :lower) |> String.to_atom() + + {:atomic, _} = run do set_class(^sym, :object) end past = AL.Branch.fork(before - 1) tip = AL.Branch.fork() # the tip fork sees :tt_thing; the past fork does not - {:atomic, _} = run store: tip do class(:tt_thing, :object) end - {:aborted, _} = run store: past do class(:tt_thing, :object) end + {:atomic, _} = run store: tip do class(^sym, :object) end + {:aborted, _} = run store: past do class(^sym, :object) end # both forks still carry the bootstrap {:atomic, _} = run store: past do class(:object, :class) end diff --git a/lib/examples/e_AL_constraints.ex b/lib/examples/e_AL_constraints.ex index 0484d60..4cee767 100644 --- a/lib/examples/e_AL_constraints.ex +++ b/lib/examples/e_AL_constraints.ex @@ -7,43 +7,39 @@ defmodule Examples.ALConstraints do import ExUnit.Assertions example constant() do - {:atomic, {bindings, _state}} = run do + {:atomic, {_bindings, _state}} = run do new(:cell, %{name: :x}, x) new(:propagator, %{input_cells: [], output_cell: x}, propagator) defmethod(propagator, :constrain, [_self, [], 2]) do end cut end - x = Map.get(bindings, :"$x") - Process.sleep(50) {:atomic, {bindings, _state}} = run do - get_slot(^x, :value, value) + get_slot(:x, :value, value) end assert Map.get(bindings, :"$value") == 2 - x + bindings end example inc() do - x = constant() + constant() - {:atomic, {bindings, _state}} = run do + {:atomic, {_bindings, _state}} = run do new(:cell, %{name: :y}, y) - new(:propagator, %{input_cells: [^x], output_cell: y}, propagator) + new(:propagator, %{input_cells: [:x], output_cell: y}, propagator) defmethod(propagator, :constrain, [_self, [x_val], y_val]) do is(y_val, 1 + x_val) end end - y = Map.get(bindings, :"$y") - Process.sleep(50) {:atomic, {bindings, _state}} = run do - get_slot(^y, :value, value) + get_slot(:y, :value, value) end assert Map.get(bindings, :"$value") == 3 @@ -52,14 +48,14 @@ defmodule Examples.ALConstraints do end example bidirectional_adder() do - {:atomic, {bindings, _state}} = run 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) + new(:propagator, %{input_cells: [a, b], output_cell: c, name: :ab_c}, propagator_ab) + new(:propagator, %{input_cells: [a, c], output_cell: b, name: :ac_b}, propagator_ac) + new(:propagator, %{input_cells: [b, c], output_cell: a, name: :bc_a}, propagator_bc) defmethod(propagator_ab, :constrain, [_self, [a_val, b_val], c_val]) do is(c_val, a_val + b_val) @@ -75,12 +71,10 @@ defmodule Examples.ALConstraints do send_async(c, :constrain, [5]) end - a = Map.get(bindings, :"$a") - Process.sleep(100) {:atomic, {bindings, _state}} = run do - get_slot(^a, :value, value) + get_slot(:a, :value, value) end assert Map.get(bindings, :"$value") == 2 diff --git a/lib/examples/e_AL_objects.ex b/lib/examples/e_AL_objects.ex index 710c311..5e0850c 100644 --- a/lib/examples/e_AL_objects.ex +++ b/lib/examples/e_AL_objects.ex @@ -10,7 +10,7 @@ defmodule Examples.ALObjects do example defmethod() do {:atomic, {bindings, _}} = run do - new(:class, %{name: :greeter, super: :object, slots: []}, _) + new(:class, %{name: :greeter, super: :ephemeral, slots: []}, _) defmethod(:greeter, :greet, [self, name]) do end @@ -26,7 +26,7 @@ defmodule Examples.ALObjects do example make_point_object() do {:atomic, {bindings, result}} = run do - new(:class, %{name: :point, super: :object, slots: []}, new_point_class) + new(:class, %{name: :point, super: :ephemeral, slots: []}, new_point_class) new(new_point_class, _, new_point_object) cut end