about summary refs log tree commit diff stats
path: root/modal/ocaml/_build/default/src
diff options
context:
space:
mode:
Diffstat (limited to 'modal/ocaml/_build/default/src')
-rw-r--r--modal/ocaml/_build/default/src/.merlin-conf/lib-modalbin0 -> 1072 bytes
-rw-r--r--modal/ocaml/_build/default/src/.modal.objs/byte/modal.cmibin0 -> 357 bytes
-rw-r--r--modal/ocaml/_build/default/src/.modal.objs/byte/modal.cmobin0 -> 1376 bytes
-rw-r--r--modal/ocaml/_build/default/src/.modal.objs/byte/modal.cmtbin0 -> 2382 bytes
-rw-r--r--modal/ocaml/_build/default/src/.modal.objs/byte/modal__Ast.cmibin0 -> 876 bytes
-rw-r--r--modal/ocaml/_build/default/src/.modal.objs/byte/modal__Ast.cmobin0 -> 2913 bytes
-rw-r--r--modal/ocaml/_build/default/src/.modal.objs/byte/modal__Ast.cmtbin0 -> 6474 bytes
-rw-r--r--modal/ocaml/_build/default/src/.modal.objs/byte/modal__Eval.cmibin0 -> 4276 bytes
-rw-r--r--modal/ocaml/_build/default/src/.modal.objs/byte/modal__Eval.cmobin0 -> 27322 bytes
-rw-r--r--modal/ocaml/_build/default/src/.modal.objs/byte/modal__Eval.cmtbin0 -> 54564 bytes
-rw-r--r--modal/ocaml/_build/default/src/.modal.objs/byte/modal__Parse.cmibin0 -> 875 bytes
-rw-r--r--modal/ocaml/_build/default/src/.modal.objs/byte/modal__Parse.cmobin0 -> 5896 bytes
-rw-r--r--modal/ocaml/_build/default/src/.modal.objs/byte/modal__Parse.cmtbin0 -> 10774 bytes
-rw-r--r--modal/ocaml/_build/default/src/.modal.objs/byte/modal__Program.cmibin0 -> 834 bytes
-rw-r--r--modal/ocaml/_build/default/src/.modal.objs/byte/modal__Program.cmobin0 -> 7401 bytes
-rw-r--r--modal/ocaml/_build/default/src/.modal.objs/byte/modal__Program.cmtbin0 -> 14571 bytes
-rw-r--r--modal/ocaml/_build/default/src/.modal.objs/modal__Ast.impl.all-deps0
-rw-r--r--modal/ocaml/_build/default/src/.modal.objs/modal__Ast.impl.d1
-rw-r--r--modal/ocaml/_build/default/src/.modal.objs/modal__Eval.impl.all-deps2
-rw-r--r--modal/ocaml/_build/default/src/.modal.objs/modal__Eval.impl.d1
-rw-r--r--modal/ocaml/_build/default/src/.modal.objs/modal__Parse.impl.all-deps1
-rw-r--r--modal/ocaml/_build/default/src/.modal.objs/modal__Parse.impl.d1
-rw-r--r--modal/ocaml/_build/default/src/.modal.objs/modal__Program.impl.all-deps2
-rw-r--r--modal/ocaml/_build/default/src/.modal.objs/modal__Program.impl.d1
-rw-r--r--modal/ocaml/_build/default/src/.modal.objs/native/modal.cmxbin0 -> 159 bytes
-rw-r--r--modal/ocaml/_build/default/src/.modal.objs/native/modal__Ast.cmxbin0 -> 510 bytes
-rw-r--r--modal/ocaml/_build/default/src/.modal.objs/native/modal__Eval.cmxbin0 -> 717 bytes
-rw-r--r--modal/ocaml/_build/default/src/.modal.objs/native/modal__Parse.cmxbin0 -> 609 bytes
-rw-r--r--modal/ocaml/_build/default/src/.modal.objs/native/modal__Program.cmxbin0 -> 656 bytes
-rw-r--r--modal/ocaml/_build/default/src/ast.ml22
-rw-r--r--modal/ocaml/_build/default/src/eval.ml231
-rw-r--r--modal/ocaml/_build/default/src/modal.abin0 -> 60672 bytes
-rw-r--r--modal/ocaml/_build/default/src/modal.cmabin0 -> 44783 bytes
-rw-r--r--modal/ocaml/_build/default/src/modal.cmxabin0 -> 2543 bytes
-rwxr-xr-xmodal/ocaml/_build/default/src/modal.cmxsbin0 -> 81624 bytes
-rw-r--r--modal/ocaml/_build/default/src/modal.ml-gen13
-rw-r--r--modal/ocaml/_build/default/src/parse.ml50
-rw-r--r--modal/ocaml/_build/default/src/program.ml70
38 files changed, 395 insertions, 0 deletions
diff --git a/modal/ocaml/_build/default/src/.merlin-conf/lib-modal b/modal/ocaml/_build/default/src/.merlin-conf/lib-modal
new file mode 100644
index 0000000..152452b
--- /dev/null
+++ b/modal/ocaml/_build/default/src/.merlin-conf/lib-modal
Binary files differdiff --git a/modal/ocaml/_build/default/src/.modal.objs/byte/modal.cmi b/modal/ocaml/_build/default/src/.modal.objs/byte/modal.cmi
new file mode 100644
index 0000000..9e89401
--- /dev/null
+++ b/modal/ocaml/_build/default/src/.modal.objs/byte/modal.cmi
Binary files differdiff --git a/modal/ocaml/_build/default/src/.modal.objs/byte/modal.cmo b/modal/ocaml/_build/default/src/.modal.objs/byte/modal.cmo
new file mode 100644
index 0000000..cee1f8e
--- /dev/null
+++ b/modal/ocaml/_build/default/src/.modal.objs/byte/modal.cmo
Binary files differdiff --git a/modal/ocaml/_build/default/src/.modal.objs/byte/modal.cmt b/modal/ocaml/_build/default/src/.modal.objs/byte/modal.cmt
new file mode 100644
index 0000000..8bc3753
--- /dev/null
+++ b/modal/ocaml/_build/default/src/.modal.objs/byte/modal.cmt
Binary files differdiff --git a/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Ast.cmi b/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Ast.cmi
new file mode 100644
index 0000000..54c31bb
--- /dev/null
+++ b/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Ast.cmi
Binary files differdiff --git a/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Ast.cmo b/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Ast.cmo
new file mode 100644
index 0000000..5b3d01b
--- /dev/null
+++ b/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Ast.cmo
Binary files differdiff --git a/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Ast.cmt b/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Ast.cmt
new file mode 100644
index 0000000..bb640a3
--- /dev/null
+++ b/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Ast.cmt
Binary files differdiff --git a/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Eval.cmi b/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Eval.cmi
new file mode 100644
index 0000000..de78c80
--- /dev/null
+++ b/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Eval.cmi
Binary files differdiff --git a/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Eval.cmo b/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Eval.cmo
new file mode 100644
index 0000000..d5ceed9
--- /dev/null
+++ b/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Eval.cmo
Binary files differdiff --git a/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Eval.cmt b/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Eval.cmt
new file mode 100644
index 0000000..71d8e88
--- /dev/null
+++ b/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Eval.cmt
Binary files differdiff --git a/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Parse.cmi b/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Parse.cmi
new file mode 100644
index 0000000..e258c76
--- /dev/null
+++ b/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Parse.cmi
Binary files differdiff --git a/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Parse.cmo b/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Parse.cmo
new file mode 100644
index 0000000..0da04e7
--- /dev/null
+++ b/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Parse.cmo
Binary files differdiff --git a/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Parse.cmt b/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Parse.cmt
new file mode 100644
index 0000000..6200a93
--- /dev/null
+++ b/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Parse.cmt
Binary files differdiff --git a/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Program.cmi b/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Program.cmi
new file mode 100644
index 0000000..7795ec6
--- /dev/null
+++ b/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Program.cmi
Binary files differdiff --git a/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Program.cmo b/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Program.cmo
new file mode 100644
index 0000000..24658cf
--- /dev/null
+++ b/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Program.cmo
Binary files differdiff --git a/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Program.cmt b/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Program.cmt
new file mode 100644
index 0000000..9b54705
--- /dev/null
+++ b/modal/ocaml/_build/default/src/.modal.objs/byte/modal__Program.cmt
Binary files differdiff --git a/modal/ocaml/_build/default/src/.modal.objs/modal__Ast.impl.all-deps b/modal/ocaml/_build/default/src/.modal.objs/modal__Ast.impl.all-deps
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/modal/ocaml/_build/default/src/.modal.objs/modal__Ast.impl.all-deps
diff --git a/modal/ocaml/_build/default/src/.modal.objs/modal__Ast.impl.d b/modal/ocaml/_build/default/src/.modal.objs/modal__Ast.impl.d
new file mode 100644
index 0000000..b0f9a19
--- /dev/null
+++ b/modal/ocaml/_build/default/src/.modal.objs/modal__Ast.impl.d
@@ -0,0 +1 @@
+src/ast.ml: Format List
diff --git a/modal/ocaml/_build/default/src/.modal.objs/modal__Eval.impl.all-deps b/modal/ocaml/_build/default/src/.modal.objs/modal__Eval.impl.all-deps
new file mode 100644
index 0000000..676ec7a
--- /dev/null
+++ b/modal/ocaml/_build/default/src/.modal.objs/modal__Eval.impl.all-deps
@@ -0,0 +1,2 @@
+modal__Ast
+modal__Parse
diff --git a/modal/ocaml/_build/default/src/.modal.objs/modal__Eval.impl.d b/modal/ocaml/_build/default/src/.modal.objs/modal__Eval.impl.d
new file mode 100644
index 0000000..5b67388
--- /dev/null
+++ b/modal/ocaml/_build/default/src/.modal.objs/modal__Eval.impl.d
@@ -0,0 +1 @@
+src/eval.ml: Ast List Map Parse String
diff --git a/modal/ocaml/_build/default/src/.modal.objs/modal__Parse.impl.all-deps b/modal/ocaml/_build/default/src/.modal.objs/modal__Parse.impl.all-deps
new file mode 100644
index 0000000..ec81abe
--- /dev/null
+++ b/modal/ocaml/_build/default/src/.modal.objs/modal__Parse.impl.all-deps
@@ -0,0 +1 @@
+modal__Ast
diff --git a/modal/ocaml/_build/default/src/.modal.objs/modal__Parse.impl.d b/modal/ocaml/_build/default/src/.modal.objs/modal__Parse.impl.d
new file mode 100644
index 0000000..d178fee
--- /dev/null
+++ b/modal/ocaml/_build/default/src/.modal.objs/modal__Parse.impl.d
@@ -0,0 +1 @@
+src/parse.ml: Ast Buffer List String
diff --git a/modal/ocaml/_build/default/src/.modal.objs/modal__Program.impl.all-deps b/modal/ocaml/_build/default/src/.modal.objs/modal__Program.impl.all-deps
new file mode 100644
index 0000000..676ec7a
--- /dev/null
+++ b/modal/ocaml/_build/default/src/.modal.objs/modal__Program.impl.all-deps
@@ -0,0 +1,2 @@
+modal__Ast
+modal__Parse
diff --git a/modal/ocaml/_build/default/src/.modal.objs/modal__Program.impl.d b/modal/ocaml/_build/default/src/.modal.objs/modal__Program.impl.d
new file mode 100644
index 0000000..06fcfc2
--- /dev/null
+++ b/modal/ocaml/_build/default/src/.modal.objs/modal__Program.impl.d
@@ -0,0 +1 @@
+src/program.ml: Ast List Parse String
diff --git a/modal/ocaml/_build/default/src/.modal.objs/native/modal.cmx b/modal/ocaml/_build/default/src/.modal.objs/native/modal.cmx
new file mode 100644
index 0000000..f32e2c0
--- /dev/null
+++ b/modal/ocaml/_build/default/src/.modal.objs/native/modal.cmx
Binary files differdiff --git a/modal/ocaml/_build/default/src/.modal.objs/native/modal__Ast.cmx b/modal/ocaml/_build/default/src/.modal.objs/native/modal__Ast.cmx
new file mode 100644
index 0000000..f7833b5
--- /dev/null
+++ b/modal/ocaml/_build/default/src/.modal.objs/native/modal__Ast.cmx
Binary files differdiff --git a/modal/ocaml/_build/default/src/.modal.objs/native/modal__Eval.cmx b/modal/ocaml/_build/default/src/.modal.objs/native/modal__Eval.cmx
new file mode 100644
index 0000000..16e6d8e
--- /dev/null
+++ b/modal/ocaml/_build/default/src/.modal.objs/native/modal__Eval.cmx
Binary files differdiff --git a/modal/ocaml/_build/default/src/.modal.objs/native/modal__Parse.cmx b/modal/ocaml/_build/default/src/.modal.objs/native/modal__Parse.cmx
new file mode 100644
index 0000000..846d16c
--- /dev/null
+++ b/modal/ocaml/_build/default/src/.modal.objs/native/modal__Parse.cmx
Binary files differdiff --git a/modal/ocaml/_build/default/src/.modal.objs/native/modal__Program.cmx b/modal/ocaml/_build/default/src/.modal.objs/native/modal__Program.cmx
new file mode 100644
index 0000000..30ee954
--- /dev/null
+++ b/modal/ocaml/_build/default/src/.modal.objs/native/modal__Program.cmx
Binary files differdiff --git a/modal/ocaml/_build/default/src/ast.ml b/modal/ocaml/_build/default/src/ast.ml
new file mode 100644
index 0000000..bf2f275
--- /dev/null
+++ b/modal/ocaml/_build/default/src/ast.ml
@@ -0,0 +1,22 @@
+(* AST for Modal trees and rules *)
+
+type node =
+  | Atom of string
+  | List of node list
+
+type rule = { left : node; right : node }
+
+let atom s = Atom s
+let list xs = List xs
+
+let rec pp fmt = function
+  | Atom s -> Format.fprintf fmt "%s" s
+  | List xs ->
+      Format.fprintf fmt "(";
+      List.iteri
+        (fun i n ->
+          if i > 0 then Format.fprintf fmt " "; pp fmt n)
+        xs;
+      Format.fprintf fmt ")"
+
+
diff --git a/modal/ocaml/_build/default/src/eval.ml b/modal/ocaml/_build/default/src/eval.ml
new file mode 100644
index 0000000..c22a40c
--- /dev/null
+++ b/modal/ocaml/_build/default/src/eval.ml
@@ -0,0 +1,231 @@
+(* Evaluator: matching, substitution, rewriting driver *)
+
+open Ast
+
+module Env = Map.Make (String)
+
+type env = node Env.t
+
+let is_register = function
+  | Atom s when String.length s > 0 && s.[0] = '?' -> true
+  | _ -> false
+
+let reg_name = function
+  | Atom s -> String.sub s 1 (String.length s - 1)
+  | _ -> invalid_arg "reg_name"
+
+let rec equal_node a b =
+  match (a, b) with
+  | Atom x, Atom y -> String.equal x y
+  | List xs, List ys ->
+      List.length xs = List.length ys
+      && List.for_all2 equal_node xs ys
+  | _ -> false
+
+(* Attempt to match pattern against tree, accumulating bindings in env *)
+let rec match_pattern env pattern tree =
+  match pattern with
+  | p when is_register p -> (
+      let name = reg_name p in
+      match Env.find_opt name env with
+      | None -> Ok (Env.add name tree env)
+      | Some bound -> if equal_node bound tree then Ok env else Error ())
+  | Atom x -> (
+      match tree with Atom y when String.equal x y -> Ok env | _ -> Error ())
+  | List pxs -> (
+      match tree with
+      | List txs when List.length pxs = List.length txs ->
+          List.fold_left2
+            (fun acc p t -> match acc with Ok e -> match_pattern e p t | _ -> acc)
+            (Ok env) pxs txs
+      | _ -> Error ())
+
+let rec substitute env = function
+  | a when is_register a -> (
+      let name = reg_name a in
+      match Env.find_opt name env with
+      | Some v -> v
+      | None -> a)
+  | Atom _ as a -> a
+  | List xs -> List (List.map (substitute env) xs)
+
+(* Configuration for evaluation behavior *)
+type config = { mutable quiet : bool; mutable allow_access : bool; mutable cycles : int }
+
+let default_config () = { quiet = false; allow_access = false; cycles = 0x200000 }
+
+(* Devices and dynamic forms (AST-based) *)
+
+let rec node_to_string = function
+  | Atom s -> s
+  | List xs -> String.concat "" (List.map node_to_string xs)
+
+let parse_int_atom s =
+  try
+    if String.length s > 1 && s.[0] = '#' then int_of_string_opt ("0x" ^ String.sub s 1 (String.length s - 1))
+    else int_of_string_opt s
+  with _ -> None
+
+let rec flatten_atoms acc = function
+  | Atom s -> s :: acc
+  | List xs -> List.fold_left flatten_atoms acc xs
+
+let device_eval ~(cfg : config) (node : node) : node option =
+  match node with
+  | List (Atom "?:" :: args) ->
+      (* Print or ALU: if first arg is an operator [+ - * / % & ^ | = ! > <], perform arithmetic; else print all args *)
+      let alu_ops = [ "+"; "-"; "*"; "/"; "%"; "&"; "^"; "|"; "="; "!"; ">"; "<" ] in
+      let result_opt =
+        match args with
+        | Atom op :: rest when List.mem op alu_ops ->
+            let nums =
+              List.filter_map
+                (fun a ->
+                  match a with Atom s -> parse_int_atom s | List _ -> parse_int_atom (node_to_string a))
+                rest
+            in
+            (match nums with
+            | [] -> None
+            | acc :: tl ->
+                let f acc n =
+                  match op with
+                  | "+" -> acc + n
+                  | "-" -> acc - n
+                  | "*" -> acc * n
+                  | "/" -> (try acc / n with _ -> acc)
+                  | "%" -> (try acc mod n with _ -> acc)
+                  | "&" -> acc land n
+                  | "^" -> acc lxor n
+                  | "|" -> acc lor n
+                  | "=" -> if acc = n then 1 else 0
+                  | "!" -> if acc <> n then 1 else 0
+                  | ">" -> if acc > n then 1 else 0
+                  | "<" -> if acc < n then 1 else 0
+                  | _ -> acc
+                in
+                let res = List.fold_left f acc tl in
+                Some (Atom (string_of_int res)))
+        | _ ->
+            let s = String.concat "" (List.map node_to_string args) in
+            if not cfg.quiet then output_string stdout s;
+            Some (List [])
+      in
+      result_opt
+  | List [ Atom "?_"; path ] ->
+      if not cfg.allow_access then Some (Atom "NAF")
+      else (
+        let filepath = node_to_string path in
+        try
+          let ch = open_in filepath in
+          let len = in_channel_length ch in
+          let buf = really_input_string ch len in
+          close_in ch;
+          (* Parse the imported string as a node; on failure, return Atom *)
+          try Some (Parse.parse buf) with _ -> Some (Atom buf)
+        with _ -> Some (Atom "NAF"))
+  | List [ Atom "?~" ] ->
+      if not cfg.allow_access then Some (Atom "EOF")
+      else (
+        try
+          let buf = really_input_string stdin (in_channel_length stdin) in
+          Some (Atom buf)
+        with _ -> Some (Atom "EOF"))
+  | List [ Atom "?^"; arg ] ->
+      let atoms = List.rev (flatten_atoms [] arg) in
+      Some (Atom (String.concat "" atoms))
+  | List [ Atom "?."; arg ] -> (
+      match arg with List xs -> Some (List xs) | _ -> Some arg)
+  | List [ Atom "?*"; arg ] -> (
+      match arg with
+      | Atom s -> Some (List (List.init (String.length s) (fun i -> Atom (String.make 1 s.[i]))))
+      | List xs -> Some (List xs))
+  | _ -> None
+
+let is_define = function List [ Atom "<>"; _; _ ] -> true | _ -> false
+let is_undefine = function List [ Atom "><"; _; _ ] -> true | _ -> false
+
+let extract_lr = function List [ _; l; r ] -> (l, r) | _ -> failwith "extract_lr"
+
+(* Walk positions in a tree in preorder, returning a zipper-like path *)
+type path = (node list * node list) list
+
+let nodes_with_paths tree =
+  let rec go acc path = function
+    | Atom _ as a -> (List.rev path, a) :: acc
+    | List xs as l ->
+        let acc' = (List.rev path, l) :: acc in
+        let rec children acc left right =
+          match right with
+          | [] -> acc
+          | n :: rs ->
+              let path' = (left, rs) :: path in
+              let acc'' = go acc path' n in
+              children acc'' (n :: left) rs
+        in
+        children acc' [] xs
+  in
+  List.rev (go [] [] tree)
+
+let replace_at_path root path new_subtree =
+  let rec rebuild = function
+    | [] -> new_subtree
+    | (left, right) :: rest ->
+        let rebuilt_child = rebuild rest in
+        let lst = List.rev left @ (rebuilt_child :: right) in
+        List lst
+  in
+  match path with [] -> new_subtree | _ -> rebuild (List.rev path)
+
+let try_dynamic ~(cfg : config) ~(rules : rule list ref) tree =
+  let candidates = nodes_with_paths tree in
+  let rec loop = function
+    | [] -> None
+    | (path, node) :: rest -> (
+        (* Devices *)
+        match device_eval ~cfg node with
+        | Some replacement -> Some (replace_at_path tree path replacement)
+        | None ->
+            (* Define / Undefine *)
+            if is_define node then (
+              let l, r = extract_lr node in
+              rules := !rules @ [ { left = l; right = r } ];
+              Some (replace_at_path tree path (List []))
+            ) else if is_undefine node then (
+              let l, r = extract_lr node in
+              rules := List.filter (fun ru -> not (equal_node ru.left l && equal_node ru.right r)) !rules;
+              Some (replace_at_path tree path (List []))
+            ) else loop rest)
+  in
+  loop candidates
+
+let try_apply_rule rule tree =
+  let candidates = nodes_with_paths tree in
+  let rec loop = function
+    | [] -> None
+    | (path, node) :: rest -> (
+        match match_pattern Env.empty rule.left node with
+        | Ok env ->
+            let rhs = substitute env rule.right in
+            Some (replace_at_path tree path rhs)
+        | Error () -> loop rest)
+  in
+  loop candidates
+
+let eval ?(config = default_config ()) rules tree =
+  let rules_ref = ref rules in
+  let rec loop cycles_left current =
+    if cycles_left <= 0 then current
+    else
+      match try_dynamic ~cfg:config ~rules:rules_ref current with
+      | Some t' -> loop (cycles_left - 1) t'
+      | None ->
+          let rec scan = function
+            | [] -> None
+            | r :: rs -> (
+                match try_apply_rule r current with Some t' -> Some t' | None -> scan rs)
+          in
+          (match scan !rules_ref with Some t' -> loop (cycles_left - 1) t' | None -> current)
+  in
+  loop config.cycles tree
+
+
diff --git a/modal/ocaml/_build/default/src/modal.a b/modal/ocaml/_build/default/src/modal.a
new file mode 100644
index 0000000..2e7d3d6
--- /dev/null
+++ b/modal/ocaml/_build/default/src/modal.a
Binary files differdiff --git a/modal/ocaml/_build/default/src/modal.cma b/modal/ocaml/_build/default/src/modal.cma
new file mode 100644
index 0000000..302cf00
--- /dev/null
+++ b/modal/ocaml/_build/default/src/modal.cma
Binary files differdiff --git a/modal/ocaml/_build/default/src/modal.cmxa b/modal/ocaml/_build/default/src/modal.cmxa
new file mode 100644
index 0000000..a7f909b
--- /dev/null
+++ b/modal/ocaml/_build/default/src/modal.cmxa
Binary files differdiff --git a/modal/ocaml/_build/default/src/modal.cmxs b/modal/ocaml/_build/default/src/modal.cmxs
new file mode 100755
index 0000000..ac14596
--- /dev/null
+++ b/modal/ocaml/_build/default/src/modal.cmxs
Binary files differdiff --git a/modal/ocaml/_build/default/src/modal.ml-gen b/modal/ocaml/_build/default/src/modal.ml-gen
new file mode 100644
index 0000000..61874fe
--- /dev/null
+++ b/modal/ocaml/_build/default/src/modal.ml-gen
@@ -0,0 +1,13 @@
+(* generated by dune *)
+
+(** @canonical Modal.Ast *)
+module Ast = Modal__Ast
+
+(** @canonical Modal.Eval *)
+module Eval = Modal__Eval
+
+(** @canonical Modal.Parse *)
+module Parse = Modal__Parse
+
+(** @canonical Modal.Program *)
+module Program = Modal__Program
diff --git a/modal/ocaml/_build/default/src/parse.ml b/modal/ocaml/_build/default/src/parse.ml
new file mode 100644
index 0000000..a415a80
--- /dev/null
+++ b/modal/ocaml/_build/default/src/parse.ml
@@ -0,0 +1,50 @@
+(* Minimal s-expression parser for Modal syntax *)
+
+open Ast
+
+exception Parse_error of string
+
+let is_space = function ' ' | '\n' | '\t' | '\r' -> true | _ -> false
+
+let tokenize s =
+  let buf = Buffer.create 16 in
+  let tokens = ref [] in
+  let flush () =
+    if Buffer.length buf > 0 then (
+      tokens := Buffer.contents buf :: !tokens;
+      Buffer.clear buf)
+  in
+  String.iter
+    (fun c ->
+      match c with
+      | '(' | ')' -> flush (); tokens := (String.make 1 c) :: !tokens
+      | _ when is_space c -> flush ()
+      | _ -> Buffer.add_char buf c)
+    s;
+  flush (); List.rev !tokens
+
+let parse_tokens tokens =
+  let rec parse_list i =
+    match List.nth_opt tokens i with
+    | Some ")" -> ([], i + 1)
+    | None -> raise (Parse_error "Unclosed list")
+    | _ ->
+        let (node, i') = parse_node i in
+        let (rest, j) = parse_list i' in
+        (node :: rest, j)
+  and parse_node i =
+    match List.nth_opt tokens i with
+    | Some "(" ->
+        let (nodes, j) = parse_list (i + 1) in
+        (List nodes, j)
+    | Some ")" -> raise (Parse_error "Unexpected )")
+    | Some tok -> (Atom tok, i + 1)
+    | None -> raise (Parse_error "Unexpected end")
+  in
+  let (node, i) = parse_node 0 in
+  if i <> List.length tokens then raise (Parse_error "Trailing tokens");
+  node
+
+let parse s = parse_tokens (tokenize s)
+
+
diff --git a/modal/ocaml/_build/default/src/program.ml b/modal/ocaml/_build/default/src/program.ml
new file mode 100644
index 0000000..8e209dd
--- /dev/null
+++ b/modal/ocaml/_build/default/src/program.ml
@@ -0,0 +1,70 @@
+(* Loading rules and inputs from a simple .modal text format *)
+
+open Ast
+
+(* Lines beginning with "<>" are rules: "<> left right". Lines beginning with ".." are inputs to evaluate, concatenated into a single input tree. *)
+
+let trim s = String.trim s
+
+let starts_with pref s =
+  let n = String.length pref in
+  String.length s >= n && String.sub s 0 n = pref
+
+let load_file path : rule list * node option =
+  let ic = open_in path in
+  let rec loop rules inputs =
+    match input_line ic with
+    | line ->
+        let line = trim line in
+        if line = "" then loop rules inputs
+        else if starts_with "<>" line then (
+          let rest = String.sub line 2 (String.length line - 2) |> trim in
+          (* split rest into two trees by parsing greedily twice *)
+          (* strategy: parse first tree, then parse second from remaining tokens *)
+          let tokens = Parse.tokenize rest in
+          let parse_one toks =
+            let rec take_tree i depth acc =
+              match List.nth_opt toks i with
+              | None -> acc, i
+              | Some tok ->
+                  let depth' =
+                    if tok = "(" then depth + 1
+                    else if tok = ")" then depth - 1
+                    else depth
+                  in
+                  let acc' = acc @ [ tok ] in
+                  if depth' = 0 && acc' <> [] then acc', i + 1
+                  else take_tree (i + 1) depth' acc'
+            in
+            take_tree 0 0 []
+          in
+          let left_toks, i = parse_one tokens in
+          let rec drop k xs = match (k, xs) with 0, _ -> xs | _, [] -> [] | k, _ :: tl -> drop (k - 1) tl in
+          let right_toks = drop i tokens in
+          let left = Parse.parse_tokens left_toks in
+          let right = Parse.parse_tokens right_toks in
+          let rules = rules @ [ { left; right } ] in
+          loop rules inputs)
+        else if starts_with ".." line then (
+          let rest = String.sub line 2 (String.length line - 2) |> trim in
+          let node = Parse.parse rest in
+          loop rules (inputs @ [ node ]))
+        else (
+          (* Treat any other non-empty line as an input expression *)
+          try
+            let node = Parse.parse line in
+            loop rules (inputs @ [ node ])
+          with _ -> loop rules inputs)
+    | exception End_of_file ->
+        close_in ic;
+        let input =
+          match inputs with
+          | [] -> None
+          | [ x ] -> Some x
+          | xs -> Some (List xs)
+        in
+        (rules, input)
+  in
+  loop [] []
+
+