diff options
Diffstat (limited to 'ocaml/lib')
-rw-r--r-- | ocaml/lib/dune | 2 | ||||
-rw-r--r-- | ocaml/lib/nock_iter.ml | 231 | ||||
-rw-r--r-- | ocaml/lib/nock_tail.ml | 223 |
3 files changed, 455 insertions, 1 deletions
diff --git a/ocaml/lib/dune b/ocaml/lib/dune index a0cb4b8..91e51bc 100644 --- a/ocaml/lib/dune +++ b/ocaml/lib/dune @@ -1,4 +1,4 @@ (library (name nock_lib) - (modules noun nock bitstream serial eventlog state effects boot runtime domain_pool nock_parallel) + (modules noun nock nock_iter nock_tail bitstream serial eventlog state effects boot runtime domain_pool nock_parallel) (libraries zarith eio eio.unix domainslib murmur3)) diff --git a/ocaml/lib/nock_iter.ml b/ocaml/lib/nock_iter.ml new file mode 100644 index 0000000..48094ba --- /dev/null +++ b/ocaml/lib/nock_iter.ml @@ -0,0 +1,231 @@ +(** Fully iterative Nock interpreter using explicit stack + + No recursion at all - uses a work stack and result stack. + This will never overflow regardless of computation depth. +*) + +open Noun + +type frame = + | EvalFrame of noun * noun (* Need to evaluate nock_on(bus, fol) *) + | DistLeft of noun * noun (* After computing left of [a b], compute right *) + | DistBoth of noun (* After both sides, construct cell *) + | Op2Formula of noun * noun (* After formula, compute subject *) + | Op2Subject of noun (* After subject, tail-call *) + | Op3Apply (* Apply cell test *) + | Op4Apply (* Apply increment *) + | Op5Apply (* Apply equality *) + | Op6Test of noun * noun * noun (* After test, select branch (bus, c, d) *) + | Op7Subject of noun (* After subject, tail-call with formula *) + | Op8Pin of noun * noun (* After pin, extend and tail-call (old_bus, c) *) + | Op9Core of noun * noun (* After core, extract slot (bus, b_gal/axis) *) + +let nock_on init_bus init_fol = + let work_stack = ref [EvalFrame (init_bus, init_fol)] in + let result_stack = ref [] in + + let rec loop () = + match !work_stack with + | [] -> + (match !result_stack with + | [result] -> result + | _ -> raise Exit) (* Should have exactly one result *) + + | EvalFrame (bus, fol) :: work_rest -> + work_stack := work_rest; + + (match fol with + | Cell { h = hib; t = gal; _ } when is_cell hib -> + (* Distribution: push frames in reverse order *) + work_stack := EvalFrame (bus, hib) :: DistLeft (bus, gal) :: !work_stack; + loop () + + | Cell { h = Atom { z = op; _ }; t = gal; _ } -> + if Z.compare op (Z.of_int max_int) > 0 then raise Exit; + + (match Z.to_int op with + | 0 -> + if not (is_atom gal) then raise Exit; + let axis = (match gal with Atom { z = n; _ } -> n | _ -> raise Exit) in + let res = slot axis bus in + result_stack := res :: !result_stack; + loop () + + | 1 -> + result_stack := gal :: !result_stack; + loop () + + | 2 -> + if not (is_cell gal) then raise Exit; + let c_gal = tail gal in + let b_gal = head gal in + work_stack := EvalFrame (bus, c_gal) :: Op2Formula (bus, b_gal) :: !work_stack; + loop () + + | 3 -> + work_stack := EvalFrame (bus, gal) :: Op3Apply :: !work_stack; + loop () + + | 4 -> + work_stack := EvalFrame (bus, gal) :: Op4Apply :: !work_stack; + loop () + + | 5 -> + work_stack := EvalFrame (bus, gal) :: Op5Apply :: !work_stack; + loop () + + | 6 -> + if not (is_cell gal) then raise Exit; + let b_gal = head gal in + let cd_gal = tail gal in + if not (is_cell cd_gal) then raise Exit; + let c_gal = head cd_gal in + let d_gal = tail cd_gal in + work_stack := EvalFrame (bus, b_gal) :: Op6Test (bus, c_gal, d_gal) :: !work_stack; + loop () + + | 7 -> + if not (is_cell gal) then raise Exit; + let b_gal = head gal in + let c_gal = tail gal in + work_stack := EvalFrame (bus, b_gal) :: Op7Subject c_gal :: !work_stack; + loop () + + | 8 -> + if not (is_cell gal) then raise Exit; + let b_gal = head gal in + let c_gal = tail gal in + work_stack := EvalFrame (bus, b_gal) :: Op8Pin (bus, c_gal) :: !work_stack; + loop () + + | 9 -> + if not (is_cell gal) then raise Exit; + let b_gal = head gal in + let c_gal = tail gal in + if not (is_atom b_gal) then raise Exit; + work_stack := EvalFrame (bus, c_gal) :: Op9Core (bus, b_gal) :: !work_stack; + loop () + + | 10 -> + (* Hint - just evaluate tail, ignore head *) + if not (is_cell gal) then raise Exit; + let q_gal = tail gal in + work_stack := EvalFrame (bus, q_gal) :: !work_stack; + loop () + + | 11 -> + if not (is_cell gal) then raise Exit; + let q_gal = tail gal in + work_stack := EvalFrame (bus, q_gal) :: !work_stack; + loop () + + | _ -> raise Exit) + + | _ -> raise Exit) + + | DistLeft (bus, gal) :: work_rest -> + (match !result_stack with + | left :: result_rest -> + result_stack := result_rest; + work_stack := EvalFrame (bus, gal) :: DistBoth left :: work_rest; + loop () + | _ -> raise Exit) + + | DistBoth left :: work_rest -> + (match !result_stack with + | right :: result_rest -> + result_stack := cell left right :: result_rest; + work_stack := work_rest; + loop () + | _ -> raise Exit) + + | Op2Formula (bus, b_gal) :: work_rest -> + (match !result_stack with + | nex :: result_rest -> + result_stack := result_rest; + work_stack := EvalFrame (bus, b_gal) :: Op2Subject nex :: work_rest; + loop () + | _ -> raise Exit) + + | Op2Subject nex :: work_rest -> + (match !result_stack with + | seb :: result_rest -> + result_stack := result_rest; + (* Tail call: push new eval frame *) + work_stack := EvalFrame (seb, nex) :: work_rest; + loop () + | _ -> raise Exit) + + | Op3Apply :: work_rest -> + (match !result_stack with + | gof :: result_rest -> + let res = if is_cell gof then atom 0 else atom 1 in + result_stack := res :: result_rest; + work_stack := work_rest; + loop () + | _ -> raise Exit) + + | Op4Apply :: work_rest -> + (match !result_stack with + | gof :: result_rest -> + result_stack := inc gof :: result_rest; + work_stack := work_rest; + loop () + | _ -> raise Exit) + + | Op5Apply :: work_rest -> + (match !result_stack with + | wim :: result_rest -> + if not (is_cell wim) then raise Exit; + let a = head wim in + let b = tail wim in + let res = if equal a b then atom 0 else atom 1 in + result_stack := res :: result_rest; + work_stack := work_rest; + loop () + | _ -> raise Exit) + + | Op6Test (bus, c_gal, d_gal) :: work_rest -> + (match !result_stack with + | tys :: result_rest -> + result_stack := result_rest; + let nex = match tys with + | Atom { z = n; _ } when Z.equal n Z.zero -> c_gal + | Atom { z = n; _ } when Z.equal n Z.one -> d_gal + | _ -> raise Exit + in + work_stack := EvalFrame (bus, nex) :: work_rest; + loop () + | _ -> raise Exit) + + | Op7Subject c_gal :: work_rest -> + (match !result_stack with + | bod :: result_rest -> + result_stack := result_rest; + work_stack := EvalFrame (bod, c_gal) :: work_rest; + loop () + | _ -> raise Exit) + + | Op8Pin (old_bus, c_gal) :: work_rest -> + (match !result_stack with + | heb :: result_rest -> + result_stack := result_rest; + let new_bus = cell heb old_bus in + work_stack := EvalFrame (new_bus, c_gal) :: work_rest; + loop () + | _ -> raise Exit) + + | Op9Core (_bus, b_gal) :: work_rest -> + (match !result_stack with + | cor :: result_rest -> + result_stack := result_rest; + if not (is_atom b_gal) then raise Exit; + let axis = (match b_gal with Atom { z = n; _ } -> n | _ -> raise Exit) in + let arm = slot axis cor in + work_stack := EvalFrame (cor, arm) :: work_rest; + loop () + | _ -> raise Exit) + + in + + loop () diff --git a/ocaml/lib/nock_tail.ml b/ocaml/lib/nock_tail.ml new file mode 100644 index 0000000..cdc1167 --- /dev/null +++ b/ocaml/lib/nock_tail.ml @@ -0,0 +1,223 @@ +open Noun + +(** Fully tail-recursive Nock using explicit continuations + + Instead of using OCaml's call stack, we use heap-allocated + continuations that represent "what to do with a result". + + This is constant stack space - O(1) - no matter how deep + the Nock computation goes. +*) + +(* Continuation: what to do with a computed noun *) +type cont = + | Done (* Final result *) + | AfterDistLeft of noun * noun * cont (* After left, compute right (bus, gal) *) + | AfterDistRight of noun * cont (* After right, construct cell (left) *) + | AfterOp2Form of noun * noun * cont (* After formula, compute subject (bus, b_gal) *) + | AfterOp2Subj of noun * cont (* After subject, eval (formula) *) + | AfterOp3 of cont (* Apply cell test *) + | AfterOp4 of cont (* Apply increment *) + | AfterOp5 of cont (* Apply equality *) + | AfterOp6 of noun * noun * noun * cont (* Select branch (bus, then, else) *) + | AfterOp7 of noun * cont (* Tail-call with new bus (formula) *) + | AfterOp8 of noun * noun * cont (* Extend and tail-call (old_bus, formula) *) + | AfterOp9Core of noun * cont (* Extract slot and tail-call (axis) *) + +(* The work queue: either compute or apply continuation *) +type work = + | Eval of noun * noun * cont (* Evaluate nock(bus, fol) with continuation k *) + | Cont of noun * cont (* Apply continuation k to result *) + +(** Main interpreter loop - fully tail-recursive *) +let nock_on init_bus init_fol = + let queue = ref [Eval (init_bus, init_fol, Done)] in + + let rec loop () = + match !queue with + | [] -> raise Exit (* Should never happen *) + + | Eval (bus, fol, k) :: rest -> + queue := rest; + + (match fol with + (* Distribution: [a b] where a is cell *) + | Cell { h = hib; t = gal; _ } when is_cell hib -> + (* Compute left first, continuation will compute right *) + queue := Eval (bus, hib, AfterDistLeft (bus, gal, k)) :: !queue; + loop () + + | Cell { h = Atom { z = op; _ }; t = gal; _ } when Z.fits_int op -> + (match Z.to_int op with + + (* 0: slot *) + | 0 -> + if not (is_atom gal) then raise Exit; + let axis = (match gal with Atom { z; _ } -> z | _ -> raise Exit) in + let result = slot axis bus in + queue := Cont (result, k) :: !queue; + loop () + + (* 1: constant *) + | 1 -> + queue := Cont (gal, k) :: !queue; + loop () + + (* 2: eval *) + | 2 -> + if not (is_cell gal) then raise Exit; + let b_gal = head gal in + let c_gal = tail gal in + (* Compute formula first, then subject, then eval *) + queue := Eval (bus, c_gal, AfterOp2Form (bus, b_gal, k)) :: !queue; + loop () + + (* 3: cell test *) + | 3 -> + queue := Eval (bus, gal, AfterOp3 k) :: !queue; + loop () + + (* 4: increment *) + | 4 -> + queue := Eval (bus, gal, AfterOp4 k) :: !queue; + loop () + + (* 5: equality *) + | 5 -> + queue := Eval (bus, gal, AfterOp5 k) :: !queue; + loop () + + (* 6: if-then-else *) + | 6 -> + if not (is_cell gal) then raise Exit; + let b_gal = head gal in + let cd_gal = tail gal in + if not (is_cell cd_gal) then raise Exit; + let c_gal = head cd_gal in + let d_gal = tail cd_gal in + queue := Eval (bus, b_gal, AfterOp6 (bus, c_gal, d_gal, k)) :: !queue; + loop () + + (* 7: compose *) + | 7 -> + if not (is_cell gal) then raise Exit; + let b_gal = head gal in + let c_gal = tail gal in + queue := Eval (bus, b_gal, AfterOp7 (c_gal, k)) :: !queue; + loop () + + (* 8: extend *) + | 8 -> + if not (is_cell gal) then raise Exit; + let b_gal = head gal in + let c_gal = tail gal in + queue := Eval (bus, b_gal, AfterOp8 (bus, c_gal, k)) :: !queue; + loop () + + (* 9: invoke *) + | 9 -> + if not (is_cell gal) then raise Exit; + let b_gal = head gal in + let c_gal = tail gal in + if not (is_atom b_gal) then raise Exit; + queue := Eval (bus, c_gal, AfterOp9Core (b_gal, k)) :: !queue; + loop () + + (* 10: hint - ignore *) + | 10 -> + if not (is_cell gal) then raise Exit; + let q_gal = tail gal in + queue := Eval (bus, q_gal, k) :: !queue; + loop () + + (* 11: hint - ignore *) + | 11 -> + if not (is_cell gal) then raise Exit; + let q_gal = tail gal in + queue := Eval (bus, q_gal, k) :: !queue; + loop () + + | _ -> raise Exit) + + | _ -> raise Exit) + + | Cont (result, k) :: rest -> + queue := rest; + + (match k with + (* Done - return final result *) + | Done -> + result + + (* After computing left side of distribution, compute right *) + | AfterDistLeft (bus, gal, k') -> + queue := Eval (bus, gal, AfterDistRight (result, k')) :: !queue; + loop () + + (* After computing both sides, construct cell *) + | AfterDistRight (left, k') -> + queue := Cont (cell left result, k') :: !queue; + loop () + + (* After computing formula for op2, compute subject *) + | AfterOp2Form (bus, b_gal, k') -> + let formula = result in + queue := Eval (bus, b_gal, AfterOp2Subj (formula, k')) :: !queue; + loop () + + (* After computing subject for op2, eval formula with new subject *) + | AfterOp2Subj (formula, k') -> + let subject = result in + queue := Eval (subject, formula, k') :: !queue; + loop () + + (* After computing test for op6, select branch *) + | AfterOp6 (bus, c_gal, d_gal, k') -> + let branch = match result with + | Atom { z; _ } when Z.equal z Z.zero -> c_gal + | Atom { z; _ } when Z.equal z Z.one -> d_gal + | _ -> raise Exit + in + queue := Eval (bus, branch, k') :: !queue; + loop () + + (* After computing subject for op7, eval formula with new subject *) + | AfterOp7 (c_gal, k') -> + queue := Eval (result, c_gal, k') :: !queue; + loop () + + (* After computing pin for op8, extend and eval *) + | AfterOp8 (old_bus, c_gal, k') -> + let new_bus = cell result old_bus in + queue := Eval (new_bus, c_gal, k') :: !queue; + loop () + + (* After computing core for op9, extract arm and eval *) + | AfterOp9Core (b_gal, k') -> + if not (is_atom b_gal) then raise Exit; + let axis = (match b_gal with Atom { z; _ } -> z | _ -> raise Exit) in + let core = result in + let arm = slot axis core in + queue := Eval (core, arm, k') :: !queue; + loop () + + (* Apply operations *) + | AfterOp3 k' -> + let res = if is_cell result then atom 0 else atom 1 in + queue := Cont (res, k') :: !queue; + loop () + + | AfterOp4 k' -> + queue := Cont (inc result, k') :: !queue; + loop () + + | AfterOp5 k' -> + if not (is_cell result) then raise Exit; + let a = head result in + let b = tail result in + let res = if equal a b then atom 0 else atom 1 in + queue := Cont (res, k') :: !queue; + loop ()) + + in + loop () |