diff options
Diffstat (limited to 'modal/ocaml/_build/default/src')
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 [] [] + + |