From e8d87dc85366f39ea332055064cf04733daa381f Mon Sep 17 00:00:00 2001 From: l4e21 Date: Mon, 8 Jun 2026 15:58:36 +0100 Subject: [PATCH 1/4] 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 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 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 2/4] 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 zcmeHQXPX;Ga$bbZBt@NarnFF^BuWfoP@-}Ur2rFP#BicTv^Eu~y=bW?8 zKId$ob57@+&*${rKfu+wx~DM!L{7D(6Q4&~%wYO`tE#K3t9!Z|rE;aZu(-55H#he{ zg_mc#lv(t8Akn49qyc%s~p;Q@Wfp8(7pqQ(w+T5f6$8W><`+5 zQPQq7=H?duw=uWt)@vGb^Pl_#z;y)NQo7I_^m?7dDU$Bm8pfmTUR)PnZ+4+fsafy51?$ z?Hn}+TTFYK(Z21H7H`y&ZPelk%hZGwYQict;XF0r0yW_xHQ^F9;W9Ph3N_&>HQ^3w z!kyHFyQm4zpeEc+O?W0X;rplw&$0<=@){@2C^~m@=!(y3&bEzOJmERigy&Kdo<~i1 zJ~iP5HX%(*bn@?_TcSf(e8afVHfr&N7f};lY!lMBjWdcW+HD-V;%o5|+o;78UP?`P znN3LJHqKbMXt#0bim$~zwo!{Gyqucw3Y(C|ZJZg6qTR-!E4~)jY@-%WSfeJq(k7&F z8)sgMhSZsxq9JwWr)Wr>IVu`bXP%0N)S0WIAtj%$y2@^&#Yds5sR^&K32ExlnFgdG zb*2JoNS*0G8d7IUkcQNm7A!g;aXPZ%TjsTPLn)qcFE!zHHX)7MIFq|YyN#2I6km(i z+eR&(@CItaeKsMD+XR`Scc!puGnnbM{qb*8n84+=NhZM1m8 zo2Uu*+k`ar=**(R$p+|X2N31kHdfbU%FGR(~KH}VZv8wI*)JiwVQ|d z+Rgb8IMLl;oRAj;_{~#2GQ15_hecf#r;;hnp)S43X0c8ad$mktihtt z_MipV(QsqL8Owmayb&k$W;Ys*)RPM}ehFxco!(Zr6OY8o(rUWGp4BimY^By|Nl&yH>&HyK~M6r>*chQwD&N%gn6XQQdE#q>BRtik0=Rk ziA}kM?qG0Ydy72=H-&=6BwY3xg_j_wPx`dF9(B5~64zJ1<+a18cHv5BV%bUBQ`fAr z*T;U?f=_*!4_mBp5m zF0*CH%D0qs=BAR5`<1GrdFHl})@i)ATAu8bwuBdI?uxsew>ramrkOck*tH>je__zr zjGGCo5&KRm{Y?KA>#Kzo>_ryuKaVE)^pnVUT zQ4*)+HI-RSt8}LiOWVt9s-Ky%*kb%v)a&#ap^D#@uK}RdU=CQ`Rj$=>Ybn8+c@whA ztWB>lu{u$=Gm6!dmMch~$6;Eu0(HmJ4rG~K%2*=ovIsAVI$xyAjkq1X*%@pPafQl< z%WF58l5jERY1PciYey`59I@;n0&Tif1B2GVxp{kAWq~2nJBS7eUnyj7cu_vPL-soL ze$|NqCt3*@OyQ*ogb1lRcvZ6Q9L3VO*^n6k*LpeLLFT zf?XQsTh+FWs7vdx&!Sy79S`djH^G`K3|+Py17N8PWbA20CPWeUP=zTh*_q4lg&mZJ z9v3kF0r=7_%sq7nHDv8N`%MAXU~!>r*k+z)G7Gq3t^*B*=M|!(kig=W(}PI^iANZa zbt7GF4*H{HxZPwyKn(+}ATX|^2|8}WK`Ps9S^5fiJP@m-(}^MbEyed+ZDw)|F#_fp zrUKnFl8arYs!f>>NTqqO{t|`5F&5?5jl}R+!4Fst8Yns_ap4=BWadH1k4@ zn|or2l9@X^&T@L5XpN7jC{_t+KqyvIpnyovc%YJBNeT3Ke9U0J<8+?tQx-6_q3(=} z0kW~GUJW^_x|JF?hfc;rYLW^=YwnF9mfpj#mh;kb*(!w-Fb5F9 zPz4}QAQTTcn5LxjMNTYb<1*x*hsSxGY(@?+YE8~J#vh<_x*0br)uA~Ba-g_(0L)JVEMI0Z?^#H7%Flx%>{wFwk$#GQS`=Y`J*7TXN81fP>&5;oa8H1VH{oPrf@}fASzPb*TM&lR zGBC2x4mxFHA$++hWMXWSi^22KSy(YCkzGtqyZKr|xi;F=lHyWTkiKC_DIA>N*M#6r zpfV~a>|Cz(aijINRQ-&xb_T~DxCm(^m>%KCE*3WoVLn#5zL7TTwX`#_nCOpGI<6hT zPanxX%_EbTa$RH_=p(4)_c2xUMKlMaN;Jj;z{$zEF1{CI?KN1!0)PcNFw=leB4)e; zJ6^5q^;p^Kxw0mm1uL@7$6sF>;yes-UKYYh`zOK8VYs<0ob(g*#|&GvKv!dgs|^Cr z;MR49pMsoXw04*|+aaJBwGkQ;iF>=~8#dQNYkZ*X=pDqR;VG^lX#F8u@aJe7P6- zT|P^rO>qS_#TCXBVvGX`r0N64y$aJv*I=Y;JV;I|Gk)?xLtcs@FEx-TuQ}as+%z|P zv1cwgx07so(tSIIUxy*CGvaI8x)$-yp=craFj0*g7CdmOs`!{V`-WEoorUE(%L634 ze>2$#skQaeL_=MMp)MN_<$D35L8=&}%0bLrG;|6mr&lVXi2x0=h+!5zvZ%sUsDN=V zvNhV380|_UlIMvPgOLg~@t~_vw4gh&pgXg=ibjWA)n~AM7-U}-M9&cBxWaCXvD;%Y zQ)w*Gc{j`!>Pom6>wdA(T}M>alvE_O)_Z7}b1}@h9vEqU^Ulc`%-}h0jd}q_y}*O2 zgfp+goiW_Tt=J7&B>i(CrDE+N9(#jwvdQPgJi!jnf#xA;%f^DtVd8%tUw9;2$=Btbj z$}`;DL{*TlLUv)ST^=oX$7bpA-Xh<3;v!$|J!j(@KihbZY5^?Q$6=gL{>j68kBepN zGp|mEp#(czIEh>Q4B+C|_p7(rQtsjj)q8I;Le-54!qP`(-%zk4yP=?T4WtbG-_#ol z;7ui)F%}D+m)W0s60@_Wv-j9CzH(ZMqGnoZL{Lb$77s4=(3!16*A9C)I>bhSdi6NJ zP&{I3AjkBDH?{~x1Km6Ykde!lh`lq`K!>IPiX!JU!|`n=zapHrL$l#1$f;A#sZ2DQ zIwv#HkY1sDA6hRo3L4V25Tvybq(M$Ga*k!8sZ;W!S&rKES$xNO%qA>)>!Wj6Qt|rT zV(V8t;jPq!x7ma=4I{`ALe6n4G@9-WVP5-Kmg>W^XP;+#vd?`Y_ne!XI{TdGS(;is zSzbE|X&Sn6#;tOZuAJ!7%SKIV3CrEYGa{{AmK*nC*u@CDM`CMAK~54fPPAm~Xm;!* z6J)-!e$UYZhYue-a%}xbbnr;?z~RPCjr}(tYpfq^G!MiFjy9V-Z=UW^{ca8=zEY^E zXvF;7#7E6U6XH<|geR6}^&#ft(uvQe3vE~+*Ja!7bT`9lP*k$czF8fci|B|R9LUux zQ}9SfKniKVV;KDyN4KZDrhw0=VUH!)COOS_#3-Of;FB5D9G7hRHvFv^{#K&_nwUCT zWW&&^-GT+VC95Z^=GkW&@&ku6S}MLfz1Oc<=i8dxTaPO{3b{X)?>srb{-q6f2R7Ut(H&rYh?o6VJsO?tv+itiG?QVUS8ck= zW{=qRVA%VfuJ8*U7ggUY=DUc7%+5h+GzlH~VQF_-PEoo%-zj`&lbq*?DEnuQDArUW z_5(ASi0}GyG^GpkCIP5j^*DCbr(C0CnZ$}jF6H#ugPfY)Ol4#Lu$V+ zBT66iC|0yl8cm%EQyNn8S$CB?at^6?^q$Yfdn(%3A!e^)npQR>zj+*d!YQh(&316fp ze2JRyWop7#s0m-CCVb5%q!~*%>x^hf?X^ZkZ%v#vDm0qfYaoa;b>899Xd2`_uJgKv zMpNgN4GpRD+J=VId38fW>b$<8A$4Bi(2zQ>acD@LS2;AK_Ujy?FzC!$(P(PVToGw% z&vX!xI-_YC^ExAH8d7IeO+)I8tZ7J{(KQXJGs31Jbw=4Vq|Qj2hSV8t(~vqNZW>Z& z)J;R`jJ#<`ozXW9sWSqnA$3OKG^9aB;`I>2|JOs1s{J~oYf_!9H8hUY3}N0@2+}wN z>3Rs#jS!^m5Tu-wHu`G6d;ogdqLQ5Tu_Kg7gC+NIyFS>F0zX{oD|w zpBIAk^Fxq+Fa+rrgdqJ;2+}VMLHb1@NWVA)>6e5c{n8MmUlxM&%R`WUMF`Tb3_AenSY-Zwx{DO(96XIRxp4Ly&$X1nIYgApO=5q~B(d z?*5vyny&pJLJbsWD{A75$1^-LBa761c9&L9MwexN|sYKc3T0ed(sWT~IJC;&MNY8lS z_mK1xf>N`2)ma@yy|~_u`x{9c*$3Zduw^#2E$i1Z2YxWGys*J?ZM`#u!|}ip>SNLs zU#-B;dp8Gg>K=TjKPO$akxZ+K3Atnl$tvg-7d9@w84pLGaR-hfF#cfO&sErZG8Z97 zWsq44(#kl2MUc^Ohb~wL;~*DU1>-fNge%5MRp6&i6$d=(3bTntuuLVl_+dP=@ekk4 zo_uO~Mmgys^S~9?MaW^=0&5U{&syq5TW&S!dqP9u_TyYrKa9lC_b`BvNjc<L zwQrhc2*37WTwS|!yM=4aD~m1%aYC0HXQCbJ-6%=ozH27yKGUw8F4UOEkL=h3rweM? zmwBA*#_I_yP)%`7Z)S^IE~CPb!9XTAHwEz2k=7KP5>3>j@hu(sxCxN1rk*My)Qd=I zA}6jUBZeivdDsc+Udxo*Eu;ioagkhsndtt;fNKK*;oA(XvI!L|;;8;`YJfsS(i!D3c*yhlLevt6i%0ma(X*=t=bD%wHG|9jd5zi}^B!e%S*}-6?Cyp2dygasdt+_*Du#R21DC1V zQ*vXRqhye$%9gYt9>5}n*4bU z^215&<=>?nW}m3J+A~pe3z&faoBF43#Np=h#KWEZhZi?ubLNrwK}P#NJJYV5R{Jn% zk=?{b;{lO5VKNN!WheMG5NC?s&4MF;2AxM8b1nEa@ z(&CSKw09GhuFlV;d_Oa^*ONHkF4E>1elyZeoUP0X#)c}Pw(g6=pxvK?R0i~9&9CDu z@Ac^QOx8RD{G^mQ-zrYZOp~kqgfb0fg7}jmNIw>W^ru3Q{&Wb^p9w+wvo>k*kLvg8U~!kp7iTT6}#v2Wb^gnAjT9)a74?ApM&Vq<A!~{{Zt6j{|G_)pCL&9D+K9(+oZ+kNA`K2 zyKkR;71MstDyI8pUd1G~bjsCAm&9*Hy{&FsZw|KmNj$9g;v}kf`lBT3H)FG^DgQb3 z8cA$K2Z;8dm0p$$T|WQ|off8iG_7{}og}@0{l5d-58xmHA#Y3mcCM^T&aZwe7i+{6 zkw2k}Z>yW)$XQGJe1(zh`47Gw$@3A7D*4Z)J8Vt)sk+!e6YEFYY|@kCsZ<49vLNGG z_oc1MYsakW9BpsK!!%!6?x<5d2ixu&n{iVORfIVN!6YRO#iS>_B*Oc~d$6h2@z0Fe zQ7xPDB(b;x-2og%FzHFFInN98ST$vAVcqsLC)w(tPK|=Bf|na{J9@J-*jCdHvMRWI z7N`;1x-Aw~&T}7U%P1>7_}^+jPWOl!zMNgJuxoC4_tH{Pouj~{G-s2*6$gehYDRGv zRy)+9;RcMV`4!(BKM%4kv#nF2a`kg8h62zM`-*LHf->wDn1=;f*l5rHvcecWS$r$E6wf1tND1JLk1cDkY%TG4K}(NBUM39W#H;4?(-uU zAhZahR+-kUcv)HiPTyd+xJ5TNEi0mLa!rYz^V$=8i85%4O~WhqZ0?bI>tY{7F&9xJ zc3-I)03GuK)J*+==GnreTbxw53=4H2!K8-li$ogy1O`8m1)rdhCz5LD4Gg_ujHQou z_;hGn+8WG@&4n>E_i?Dj35K@99_|@6CObjOTx1ExH{3CCScCO2a?c>EJ)D+o-o}PP zuoZbUMT{w8<0~SxLBp~*{5gUKYhtkG1bO}PtYe6E1EFebeD9^DU(5{$3} kBQ62TN1)M8G}pvj#pk;nOxrQEx?dR|>E3NA3M|kFr?t*&LyIX5>XL5u8mcEp;MkD8R>TTFutdzq{x5MJ%Fe}8yR`d9|-mR=+txiHxTI?on2{};VwA*UQL!^~A zvQ~V8-No5ml4v8=?BI;}9DSfWVXVvLuo&uGoRhdLcO$&ZwM1_*d7UPDnK~!u(APTn zTF&9}c*#^b*o6VKzD^g9shmH#(5*7^e2oPgZI;_Zf;zL_&%71?-T-?DYP;S^e!(mX zfC(QOyqI%1ExaA?lCd&22-fpM9)PFD@*9~fh61j*58yu#z$<$JxWdM{SaPuxZTTaK zvgAVm#4@+MhQm>`n6=h%|2wu_(2k?nX5LQB@?hI}t(Vx|A3BX?>4w%@GFd!@S9u?J z?-6*5djYS~Xl0$wE_@B_LcH+FVkv8VGnw4g|1*ml1JgJli@i>C16aiysyQQ0Wa{RB zi*cDxuKVZj#ot#*80YpDa4@|KI)K4PCOV=D)H+>I?!Dw zLE3n=cWLWI%m>6^3Zk<2AX<5sPhNJbyN^U(Iz3MO0*FN7*9YEUoSh@;~D z2d1v3N_*;=gMXE9?fj;fxJn>WToWm-p%mB1UgOHMwYApzN4&(Uyk0yISA`i5-fo3~ zH`PBJmcCHV3LJg|u)O-iISI?$-eM^MhbJPTkL>p2vL}8dfP!LVAsPibT^>vyIU25w z4u&r?65vu%B)oYq815vc3dQ52W$^6C5V$t>9wd!!hWSZh@RzJq;nS2?5n%|k)5{Pz zb-D0nh8DJ_gu*My9B5MtVc__5_(D65s#=or1gy&qhU>afP|_9x=hF1h)EW$XGjGF{ zoa-<@vz+pmX;WeAm=H)v+d=o4bH~6ty78b%Plk&b!SLJUK|<7+<%lwWP96iz*{QJe zo(lGlO%_&KaI#{A!)>A@nO-t z_vDnKHQEqtj$a5%Y|WJLRIObU`N<)Qz=Y)@N4|FOAn15shoVSg!r(JnsM!+* zh}ymkoUn| zSlSv1&i#o{x&Icu>0SGD;$^C#vnmFMOh@|~Q06oEY44$l>I}2O`_ji|u%lq}@h4$V zVHDIXBqE)Mb0B;Yd14Mb(0Fe)406ua39Gha1PIpq3=I9>WMQDO?*h0G zw+JGtqbS}}tRio|Jql{qEfoIn1_pGCNr(E<@|lOrXDU*{t?ZmRL()>R)48l{oym}H zN;RgYS83Bys&tu#jI2~sN_J)%w%XHA+&#*=Rc8F9!OFR1Lo;C8y?(_#?!ezK#7Vzr z<-nMuXDI0m*|RXw`;elrHM>8p2vte3FyCAxhM5xL%rV*=+%ZD&B&U~Nx_Wd7TNw*6 zHB8r5X)f-0D={QxW+)W$49bBk(=^zeifQA0<7PZdObR0=c}5j^&zIo`%jZSWu*pge zh|V7l- ztQhK_$r2F=ft-dId~%gXi+2;LfuQr6khCmgI@9S5`1^tvL-!oBJb4!)x&UN(L1} zzttrkI+484ES`E}bt7oz>d-KHYLN$)NZ3Aic(j3)xe?16-Aeiijy54Ve7{Nq^2#Cj z{glMOjzbJ8X{pRsPxJMB8jEQfsICm7CQ8c5!5{yw5e8N3&gS;J0E5b4 zbWJ*xVwL164mQ*zdgbt5Gy{sPPs7=xOxkoYeC;MZ>#VmMah~>^7w4^;pnW%uDl`-H z_I!vr+aHyBT@K|Vpwm7H#uvXYsDWr^*sJS=&+bLJFQgYovItB+5?|wH~B`; zO{S~P1?u&V`bzk7b~u?(8UsbhW?I;~sdYz~>Kx=-GO04Gza7+8KYo`Beq=xUaSwqT zqm{$`Byi+Oy~U3XC{q*XN9i(EBEQ@bj0TMI6nTkA&F#7qKQgZK(R5IIDK{Q2jg5rY zQ-((`X-6ZCWOX|;s7u=OYVTDH{PaOj zR{CatCF_-z8eX7o|Hz<~WZ4mBV7GnJDFh}B96#b~w#Se_)zDG)7_2?c;9FiXm9{1j zbI6mE=q;LpZ=V(W3B2&3SPAP-(rE{=gbns{;!K=zUYv<9;&t+cIyxg&vx}hYGjRr1 zT@+`e%1g9O!7{uimCcn4RJKdxaWLAxoqRzeYhr@MW-IYdM&A1=Y_%|%H=8d=+-Br0 zdNUO!RQnpDSYor4bU7o>7wKa#nZ1)QDrHVg5ZP=cTh7S4bIW6x#NU??Ekc{EhmY|mrR@@V9E|(lPre|LH8H_rvz2%!BkzBUo#rr^cbhLr+-Br8 z|E_5P75e`cqF7?Hm2^2H@BNMln2bGCMrgB@Y&j#3WO*q}rhf8yiR_69KnZ!EmKT#I w!6f#-h8PBvQS4^qy?%HHOy>V9piIVQE9H7d-Z$TF!-VZY!up%Ps@O6D0BB}XSO5S3 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 3/4] 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 4/4] 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