OCaml bootstrapper: transpiler compiles full CEK evaluator (61/61 tests)
SX-to-OCaml transpiler (transpiler.sx) generates sx_ref.ml (~90KB, ~135 mutually recursive functions) from the spec evaluator. Foundation tests all pass: parser, primitives, env operations, type system. Key design decisions: - Env variant added to value type for CEK state dict storage - Continuation carries optional data dict for captured frames - Dynamic var tracking distinguishes OCaml fn calls from SX value dispatch - Single let rec...and block for forward references between all defines - Unused ref pre-declarations eliminated via let-bound name detection Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
3
hosts/ocaml/bin/dune
Normal file
3
hosts/ocaml/bin/dune
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
(executable
|
||||||
|
(name run_tests)
|
||||||
|
(libraries sx))
|
||||||
177
hosts/ocaml/bin/run_tests.ml
Normal file
177
hosts/ocaml/bin/run_tests.ml
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
(** Minimal test runner — verifies the OCaml foundation (types, parser, primitives).
|
||||||
|
|
||||||
|
Eventually this will load test-framework.sx and run the full spec test
|
||||||
|
suite against the transpiled evaluator. For now it exercises the parser
|
||||||
|
and primitives directly. *)
|
||||||
|
|
||||||
|
open Sx.Sx_types
|
||||||
|
open Sx.Sx_parser
|
||||||
|
open Sx.Sx_primitives
|
||||||
|
|
||||||
|
let pass_count = ref 0
|
||||||
|
let fail_count = ref 0
|
||||||
|
|
||||||
|
let assert_eq name expected actual =
|
||||||
|
if expected = actual then begin
|
||||||
|
incr pass_count;
|
||||||
|
Printf.printf " PASS: %s\n" name
|
||||||
|
end else begin
|
||||||
|
incr fail_count;
|
||||||
|
Printf.printf " FAIL: %s — expected %s, got %s\n" name (inspect expected) (inspect actual)
|
||||||
|
end
|
||||||
|
|
||||||
|
let assert_true name v =
|
||||||
|
if sx_truthy v then begin
|
||||||
|
incr pass_count;
|
||||||
|
Printf.printf " PASS: %s\n" name
|
||||||
|
end else begin
|
||||||
|
incr fail_count;
|
||||||
|
Printf.printf " FAIL: %s — expected truthy, got %s\n" name (inspect v)
|
||||||
|
end
|
||||||
|
|
||||||
|
let call name args =
|
||||||
|
match Hashtbl.find_opt primitives name with
|
||||||
|
| Some f -> f args
|
||||||
|
| None -> failwith ("Unknown primitive: " ^ name)
|
||||||
|
|
||||||
|
let () =
|
||||||
|
Printf.printf "=== SX OCaml Foundation Tests ===\n\n";
|
||||||
|
|
||||||
|
(* --- Parser tests --- *)
|
||||||
|
Printf.printf "Suite: parser\n";
|
||||||
|
|
||||||
|
let exprs = parse_all "42" in
|
||||||
|
assert_eq "number" (Number 42.0) (List.hd exprs);
|
||||||
|
|
||||||
|
let exprs = parse_all "\"hello\"" in
|
||||||
|
assert_eq "string" (String "hello") (List.hd exprs);
|
||||||
|
|
||||||
|
let exprs = parse_all "true" in
|
||||||
|
assert_eq "bool true" (Bool true) (List.hd exprs);
|
||||||
|
|
||||||
|
let exprs = parse_all "nil" in
|
||||||
|
assert_eq "nil" Nil (List.hd exprs);
|
||||||
|
|
||||||
|
let exprs = parse_all ":class" in
|
||||||
|
assert_eq "keyword" (Keyword "class") (List.hd exprs);
|
||||||
|
|
||||||
|
let exprs = parse_all "foo" in
|
||||||
|
assert_eq "symbol" (Symbol "foo") (List.hd exprs);
|
||||||
|
|
||||||
|
let exprs = parse_all "(+ 1 2)" in
|
||||||
|
assert_eq "list" (List [Symbol "+"; Number 1.0; Number 2.0]) (List.hd exprs);
|
||||||
|
|
||||||
|
let exprs = parse_all "(div :class \"card\" (p \"hi\"))" in
|
||||||
|
(match List.hd exprs with
|
||||||
|
| List [Symbol "div"; Keyword "class"; String "card"; List [Symbol "p"; String "hi"]] ->
|
||||||
|
incr pass_count; Printf.printf " PASS: nested list\n"
|
||||||
|
| v -> incr fail_count; Printf.printf " FAIL: nested list — got %s\n" (inspect v));
|
||||||
|
|
||||||
|
let exprs = parse_all "'(1 2 3)" in
|
||||||
|
(match List.hd exprs with
|
||||||
|
| List [Symbol "quote"; List [Number 1.0; Number 2.0; Number 3.0]] ->
|
||||||
|
incr pass_count; Printf.printf " PASS: quote sugar\n"
|
||||||
|
| v -> incr fail_count; Printf.printf " FAIL: quote sugar — got %s\n" (inspect v));
|
||||||
|
|
||||||
|
let exprs = parse_all "{:a 1 :b 2}" in
|
||||||
|
(match List.hd exprs with
|
||||||
|
| Dict d when dict_has d "a" && dict_has d "b" ->
|
||||||
|
incr pass_count; Printf.printf " PASS: dict literal\n"
|
||||||
|
| v -> incr fail_count; Printf.printf " FAIL: dict literal — got %s\n" (inspect v));
|
||||||
|
|
||||||
|
let exprs = parse_all ";; comment\n42" in
|
||||||
|
assert_eq "comment" (Number 42.0) (List.hd exprs);
|
||||||
|
|
||||||
|
let exprs = parse_all "(fn (x) (+ x 1))" in
|
||||||
|
(match List.hd exprs with
|
||||||
|
| List [Symbol "fn"; List [Symbol "x"]; List [Symbol "+"; Symbol "x"; Number 1.0]] ->
|
||||||
|
incr pass_count; Printf.printf " PASS: fn form\n"
|
||||||
|
| v -> incr fail_count; Printf.printf " FAIL: fn form — got %s\n" (inspect v));
|
||||||
|
|
||||||
|
let exprs = parse_all "\"hello\\nworld\"" in
|
||||||
|
assert_eq "string escape" (String "hello\nworld") (List.hd exprs);
|
||||||
|
|
||||||
|
let exprs = parse_all "(1 2 3) (4 5)" in
|
||||||
|
assert_eq "multiple exprs" (Number 2.0) (Number (float_of_int (List.length exprs)));
|
||||||
|
|
||||||
|
Printf.printf "\nSuite: primitives\n";
|
||||||
|
|
||||||
|
(* --- Primitive tests --- *)
|
||||||
|
assert_eq "+" (Number 6.0) (call "+" [Number 1.0; Number 2.0; Number 3.0]);
|
||||||
|
assert_eq "-" (Number 3.0) (call "-" [Number 5.0; Number 2.0]);
|
||||||
|
assert_eq "*" (Number 12.0) (call "*" [Number 3.0; Number 4.0]);
|
||||||
|
assert_eq "/" (Number 2.5) (call "/" [Number 5.0; Number 2.0]);
|
||||||
|
assert_eq "mod" (Number 1.0) (call "mod" [Number 5.0; Number 2.0]);
|
||||||
|
assert_eq "inc" (Number 6.0) (call "inc" [Number 5.0]);
|
||||||
|
assert_eq "abs" (Number 5.0) (call "abs" [Number (-5.0)]);
|
||||||
|
|
||||||
|
assert_true "=" (call "=" [Number 1.0; Number 1.0]);
|
||||||
|
assert_true "!=" (call "!=" [Number 1.0; Number 2.0]);
|
||||||
|
assert_true "<" (call "<" [Number 1.0; Number 2.0]);
|
||||||
|
assert_true ">" (call ">" [Number 2.0; Number 1.0]);
|
||||||
|
|
||||||
|
assert_true "nil?" (call "nil?" [Nil]);
|
||||||
|
assert_true "number?" (call "number?" [Number 1.0]);
|
||||||
|
assert_true "string?" (call "string?" [String "hi"]);
|
||||||
|
assert_true "list?" (call "list?" [List [Number 1.0]]);
|
||||||
|
assert_true "empty? list" (call "empty?" [List []]);
|
||||||
|
assert_true "empty? string" (call "empty?" [String ""]);
|
||||||
|
|
||||||
|
assert_eq "str" (String "hello42") (call "str" [String "hello"; Number 42.0]);
|
||||||
|
assert_eq "upper" (String "HI") (call "upper" [String "hi"]);
|
||||||
|
assert_eq "trim" (String "hi") (call "trim" [String " hi "]);
|
||||||
|
assert_eq "join" (String "a,b,c") (call "join" [String ","; List [String "a"; String "b"; String "c"]]);
|
||||||
|
assert_true "starts-with?" (call "starts-with?" [String "hello"; String "hel"]);
|
||||||
|
assert_true "contains?" (call "contains?" [List [Number 1.0; Number 2.0; Number 3.0]; Number 2.0]);
|
||||||
|
|
||||||
|
assert_eq "list" (List [Number 1.0; Number 2.0]) (call "list" [Number 1.0; Number 2.0]);
|
||||||
|
assert_eq "len" (Number 3.0) (call "len" [List [Number 1.0; Number 2.0; Number 3.0]]);
|
||||||
|
assert_eq "first" (Number 1.0) (call "first" [List [Number 1.0; Number 2.0]]);
|
||||||
|
assert_eq "rest" (List [Number 2.0; Number 3.0]) (call "rest" [List [Number 1.0; Number 2.0; Number 3.0]]);
|
||||||
|
assert_eq "nth" (Number 2.0) (call "nth" [List [Number 1.0; Number 2.0]; Number 1.0]);
|
||||||
|
assert_eq "cons" (List [Number 0.0; Number 1.0]) (call "cons" [Number 0.0; List [Number 1.0]]);
|
||||||
|
assert_eq "append" (List [Number 1.0; Number 2.0; Number 3.0])
|
||||||
|
(call "append" [List [Number 1.0]; List [Number 2.0; Number 3.0]]);
|
||||||
|
assert_eq "reverse" (List [Number 3.0; Number 2.0; Number 1.0])
|
||||||
|
(call "reverse" [List [Number 1.0; Number 2.0; Number 3.0]]);
|
||||||
|
assert_eq "range" (List [Number 0.0; Number 1.0; Number 2.0]) (call "range" [Number 3.0]);
|
||||||
|
assert_eq "slice" (List [Number 2.0; Number 3.0]) (call "slice" [List [Number 1.0; Number 2.0; Number 3.0]; Number 1.0]);
|
||||||
|
|
||||||
|
assert_eq "type-of" (String "number") (call "type-of" [Number 1.0]);
|
||||||
|
assert_eq "type-of nil" (String "nil") (call "type-of" [Nil]);
|
||||||
|
|
||||||
|
Printf.printf "\nSuite: env\n";
|
||||||
|
|
||||||
|
(* --- Environment tests --- *)
|
||||||
|
let e = make_env () in
|
||||||
|
ignore (env_bind e "x" (Number 42.0));
|
||||||
|
assert_eq "env-bind + get" (Number 42.0) (env_get e "x");
|
||||||
|
assert_true "env-has" (Bool (env_has e "x"));
|
||||||
|
|
||||||
|
let child = env_extend e in
|
||||||
|
ignore (env_bind child "y" (Number 10.0));
|
||||||
|
assert_eq "child sees parent" (Number 42.0) (env_get child "x");
|
||||||
|
assert_eq "child own binding" (Number 10.0) (env_get child "y");
|
||||||
|
|
||||||
|
ignore (env_set child "x" (Number 99.0));
|
||||||
|
assert_eq "set! walks chain" (Number 99.0) (env_get e "x");
|
||||||
|
|
||||||
|
Printf.printf "\nSuite: types\n";
|
||||||
|
|
||||||
|
(* --- Type tests --- *)
|
||||||
|
assert_true "sx_truthy true" (Bool (sx_truthy (Bool true)));
|
||||||
|
assert_true "sx_truthy 0" (Bool (sx_truthy (Number 0.0)));
|
||||||
|
assert_true "sx_truthy \"\"" (Bool (sx_truthy (String "")));
|
||||||
|
assert_eq "not truthy nil" (Bool false) (Bool (sx_truthy Nil));
|
||||||
|
assert_eq "not truthy false" (Bool false) (Bool (sx_truthy (Bool false)));
|
||||||
|
|
||||||
|
let l = { l_params = ["x"]; l_body = Symbol "x"; l_closure = make_env (); l_name = None } in
|
||||||
|
assert_true "is_lambda" (Bool (is_lambda (Lambda l)));
|
||||||
|
ignore (Sx.Sx_types.set_lambda_name (Lambda l) "my-fn");
|
||||||
|
assert_eq "lambda name mutated" (String "my-fn") (lambda_name (Lambda l));
|
||||||
|
|
||||||
|
(* --- Summary --- *)
|
||||||
|
Printf.printf "\n============================================================\n";
|
||||||
|
Printf.printf "Results: %d passed, %d failed\n" !pass_count !fail_count;
|
||||||
|
Printf.printf "============================================================\n";
|
||||||
|
if !fail_count > 0 then exit 1
|
||||||
150
hosts/ocaml/bootstrap.py
Normal file
150
hosts/ocaml/bootstrap.py
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Bootstrap compiler: SX spec -> OCaml.
|
||||||
|
|
||||||
|
Loads the SX-to-OCaml transpiler (transpiler.sx), feeds it the spec files,
|
||||||
|
and produces sx_ref.ml — the transpiled evaluator as native OCaml.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python3 hosts/ocaml/bootstrap.py --output hosts/ocaml/lib/sx_ref.ml
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
_PROJECT = os.path.abspath(os.path.join(_HERE, "..", ".."))
|
||||||
|
sys.path.insert(0, _PROJECT)
|
||||||
|
|
||||||
|
from shared.sx.parser import parse_all
|
||||||
|
from shared.sx.types import Symbol
|
||||||
|
|
||||||
|
|
||||||
|
def extract_defines(source: str) -> list[tuple[str, list]]:
|
||||||
|
"""Parse .sx source, return list of (name, define-expr) for top-level defines."""
|
||||||
|
exprs = parse_all(source)
|
||||||
|
defines = []
|
||||||
|
for expr in exprs:
|
||||||
|
if isinstance(expr, list) and expr and isinstance(expr[0], Symbol):
|
||||||
|
if expr[0].name == "define":
|
||||||
|
name = expr[1].name if isinstance(expr[1], Symbol) else str(expr[1])
|
||||||
|
defines.append((name, expr))
|
||||||
|
return defines
|
||||||
|
|
||||||
|
|
||||||
|
# OCaml preamble — opens and runtime helpers
|
||||||
|
PREAMBLE = """\
|
||||||
|
(* sx_ref.ml — Auto-generated from SX spec by hosts/ocaml/bootstrap.py *)
|
||||||
|
(* Do not edit — regenerate with: python3 hosts/ocaml/bootstrap.py *)
|
||||||
|
|
||||||
|
[@@@warning "-26-27"]
|
||||||
|
|
||||||
|
open Sx_types
|
||||||
|
open Sx_runtime
|
||||||
|
|
||||||
|
(* Trampoline — evaluates thunks via the CEK machine.
|
||||||
|
eval_expr is defined in the transpiled block below. *)
|
||||||
|
let trampoline v = v (* CEK machine doesn't produce thunks *)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# OCaml fixups — override iterative CEK run
|
||||||
|
FIXUPS = """\
|
||||||
|
|
||||||
|
(* Override recursive cek_run with iterative loop *)
|
||||||
|
let cek_run_iterative state =
|
||||||
|
let s = ref state in
|
||||||
|
while not (match cek_terminal_p !s with Bool true -> true | _ -> false) do
|
||||||
|
s := cek_step !s
|
||||||
|
done;
|
||||||
|
cek_value !s
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def compile_spec_to_ml(spec_dir: str | None = None) -> str:
|
||||||
|
"""Compile the SX spec to OCaml source."""
|
||||||
|
from shared.sx.ref.sx_ref import eval_expr, trampoline, make_env, sx_parse
|
||||||
|
|
||||||
|
if spec_dir is None:
|
||||||
|
spec_dir = os.path.join(_PROJECT, "spec")
|
||||||
|
|
||||||
|
# Load the transpiler
|
||||||
|
env = make_env()
|
||||||
|
transpiler_path = os.path.join(_HERE, "transpiler.sx")
|
||||||
|
with open(transpiler_path) as f:
|
||||||
|
transpiler_src = f.read()
|
||||||
|
for expr in sx_parse(transpiler_src):
|
||||||
|
trampoline(eval_expr(expr, env))
|
||||||
|
|
||||||
|
# Spec files to transpile (in dependency order)
|
||||||
|
sx_files = [
|
||||||
|
("evaluator.sx", "evaluator (frames + eval + CEK)"),
|
||||||
|
]
|
||||||
|
|
||||||
|
parts = [PREAMBLE]
|
||||||
|
|
||||||
|
for filename, label in sx_files:
|
||||||
|
filepath = os.path.join(spec_dir, filename)
|
||||||
|
if not os.path.exists(filepath):
|
||||||
|
print(f"Warning: {filepath} not found, skipping", file=sys.stderr)
|
||||||
|
continue
|
||||||
|
|
||||||
|
with open(filepath) as f:
|
||||||
|
src = f.read()
|
||||||
|
defines = extract_defines(src)
|
||||||
|
|
||||||
|
# Skip defines provided by preamble or fixups
|
||||||
|
skip = {"trampoline"}
|
||||||
|
defines = [(n, e) for n, e in defines if n not in skip]
|
||||||
|
|
||||||
|
# Deduplicate — keep last definition for each name (CEK overrides tree-walk)
|
||||||
|
seen = {}
|
||||||
|
for i, (n, e) in enumerate(defines):
|
||||||
|
seen[n] = i
|
||||||
|
defines = [(n, e) for i, (n, e) in enumerate(defines) if seen[n] == i]
|
||||||
|
|
||||||
|
# Build the defines list for the transpiler
|
||||||
|
defines_list = [[name, expr] for name, expr in defines]
|
||||||
|
env["_defines"] = defines_list
|
||||||
|
|
||||||
|
# Pass known define names so the transpiler can distinguish
|
||||||
|
# static (OCaml fn) calls from dynamic (SX value) calls
|
||||||
|
env["_known_defines"] = [name for name, _ in defines]
|
||||||
|
|
||||||
|
# Call ml-translate-file — emits as single let rec block
|
||||||
|
translate_expr = sx_parse("(ml-translate-file _defines)")[0]
|
||||||
|
result = trampoline(eval_expr(translate_expr, env))
|
||||||
|
|
||||||
|
parts.append(f"\n(* === Transpiled from {label} === *)\n")
|
||||||
|
parts.append(result)
|
||||||
|
|
||||||
|
parts.append(FIXUPS)
|
||||||
|
return "\n".join(parts)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
import argparse
|
||||||
|
parser = argparse.ArgumentParser(description="Bootstrap SX spec -> OCaml")
|
||||||
|
parser.add_argument(
|
||||||
|
"--output", "-o",
|
||||||
|
default=None,
|
||||||
|
help="Output file (default: stdout)",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
result = compile_spec_to_ml()
|
||||||
|
|
||||||
|
if args.output:
|
||||||
|
with open(args.output, "w") as f:
|
||||||
|
f.write(result)
|
||||||
|
size = os.path.getsize(args.output)
|
||||||
|
print(f"Wrote {args.output} ({size} bytes)", file=sys.stderr)
|
||||||
|
else:
|
||||||
|
print(result)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
2
hosts/ocaml/dune-project
Normal file
2
hosts/ocaml/dune-project
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
(lang dune 3.0)
|
||||||
|
(name sx)
|
||||||
2
hosts/ocaml/lib/dune
Normal file
2
hosts/ocaml/lib/dune
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
(library
|
||||||
|
(name sx))
|
||||||
181
hosts/ocaml/lib/sx_parser.ml
Normal file
181
hosts/ocaml/lib/sx_parser.ml
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
(** S-expression parser.
|
||||||
|
|
||||||
|
Recursive descent over a string, producing [Sx_types.value list].
|
||||||
|
Supports: lists, dicts, symbols, keywords, strings (with escapes),
|
||||||
|
numbers, booleans, nil, comments, quote/quasiquote/unquote sugar. *)
|
||||||
|
|
||||||
|
open Sx_types
|
||||||
|
|
||||||
|
type state = {
|
||||||
|
src : string;
|
||||||
|
len : int;
|
||||||
|
mutable pos : int;
|
||||||
|
}
|
||||||
|
|
||||||
|
let make_state src = { src; len = String.length src; pos = 0 }
|
||||||
|
|
||||||
|
let peek s = if s.pos < s.len then Some s.src.[s.pos] else None
|
||||||
|
let advance s = s.pos <- s.pos + 1
|
||||||
|
let at_end s = s.pos >= s.len
|
||||||
|
|
||||||
|
let skip_whitespace_and_comments s =
|
||||||
|
let rec go () =
|
||||||
|
if at_end s then ()
|
||||||
|
else match s.src.[s.pos] with
|
||||||
|
| ' ' | '\t' | '\n' | '\r' | ',' -> advance s; go ()
|
||||||
|
| ';' ->
|
||||||
|
while s.pos < s.len && s.src.[s.pos] <> '\n' do advance s done;
|
||||||
|
if s.pos < s.len then advance s;
|
||||||
|
go ()
|
||||||
|
| _ -> ()
|
||||||
|
in go ()
|
||||||
|
|
||||||
|
let is_symbol_char = function
|
||||||
|
| '(' | ')' | '[' | ']' | '{' | '}' | '"' | '\'' | '`'
|
||||||
|
| ' ' | '\t' | '\n' | '\r' | ',' | ';' -> false
|
||||||
|
| _ -> true
|
||||||
|
|
||||||
|
let read_string s =
|
||||||
|
(* s.pos is on the opening quote *)
|
||||||
|
advance s;
|
||||||
|
let buf = Buffer.create 64 in
|
||||||
|
let rec go () =
|
||||||
|
if at_end s then raise (Parse_error "Unterminated string");
|
||||||
|
let c = s.src.[s.pos] in
|
||||||
|
advance s;
|
||||||
|
if c = '"' then Buffer.contents buf
|
||||||
|
else if c = '\\' then begin
|
||||||
|
if at_end s then raise (Parse_error "Unterminated string escape");
|
||||||
|
let esc = s.src.[s.pos] in
|
||||||
|
advance s;
|
||||||
|
(match esc with
|
||||||
|
| 'n' -> Buffer.add_char buf '\n'
|
||||||
|
| 't' -> Buffer.add_char buf '\t'
|
||||||
|
| 'r' -> Buffer.add_char buf '\r'
|
||||||
|
| '"' -> Buffer.add_char buf '"'
|
||||||
|
| '\\' -> Buffer.add_char buf '\\'
|
||||||
|
| 'u' ->
|
||||||
|
(* \uXXXX — read 4 hex digits, encode as UTF-8 *)
|
||||||
|
if s.pos + 4 > s.len then raise (Parse_error "Incomplete \\u escape");
|
||||||
|
let hex = String.sub s.src s.pos 4 in
|
||||||
|
s.pos <- s.pos + 4;
|
||||||
|
let code = int_of_string ("0x" ^ hex) in
|
||||||
|
let ubuf = Buffer.create 4 in
|
||||||
|
Buffer.add_utf_8_uchar ubuf (Uchar.of_int code);
|
||||||
|
Buffer.add_string buf (Buffer.contents ubuf)
|
||||||
|
| _ -> Buffer.add_char buf '\\'; Buffer.add_char buf esc);
|
||||||
|
go ()
|
||||||
|
end else begin
|
||||||
|
Buffer.add_char buf c;
|
||||||
|
go ()
|
||||||
|
end
|
||||||
|
in go ()
|
||||||
|
|
||||||
|
let read_symbol s =
|
||||||
|
let start = s.pos in
|
||||||
|
while s.pos < s.len && is_symbol_char s.src.[s.pos] do advance s done;
|
||||||
|
String.sub s.src start (s.pos - start)
|
||||||
|
|
||||||
|
let try_number str =
|
||||||
|
match float_of_string_opt str with
|
||||||
|
| Some n -> Some (Number n)
|
||||||
|
| None -> None
|
||||||
|
|
||||||
|
let rec read_value s : value =
|
||||||
|
skip_whitespace_and_comments s;
|
||||||
|
if at_end s then raise (Parse_error "Unexpected end of input");
|
||||||
|
match s.src.[s.pos] with
|
||||||
|
| '(' -> read_list s ')'
|
||||||
|
| '[' -> read_list s ']'
|
||||||
|
| '{' -> read_dict s
|
||||||
|
| '"' -> String (read_string s)
|
||||||
|
| '\'' -> advance s; List [Symbol "quote"; read_value s]
|
||||||
|
| '`' -> advance s; List [Symbol "quasiquote"; read_value s]
|
||||||
|
| '~' when s.pos + 1 < s.len && s.src.[s.pos + 1] = '@' ->
|
||||||
|
advance s; advance s; (* skip ~@ *)
|
||||||
|
List [Symbol "splice-unquote"; read_value s]
|
||||||
|
| _ ->
|
||||||
|
(* Check for unquote: , followed by non-whitespace *)
|
||||||
|
if s.src.[s.pos] = ',' && s.pos + 1 < s.len &&
|
||||||
|
s.src.[s.pos + 1] <> ' ' && s.src.[s.pos + 1] <> '\n' then begin
|
||||||
|
advance s;
|
||||||
|
if s.pos < s.len && s.src.[s.pos] = '@' then begin
|
||||||
|
advance s;
|
||||||
|
List [Symbol "splice-unquote"; read_value s]
|
||||||
|
end else
|
||||||
|
List [Symbol "unquote"; read_value s]
|
||||||
|
end else begin
|
||||||
|
(* Symbol, keyword, number, or boolean *)
|
||||||
|
let token = read_symbol s in
|
||||||
|
if token = "" then raise (Parse_error ("Unexpected char: " ^ String.make 1 s.src.[s.pos]));
|
||||||
|
match token with
|
||||||
|
| "true" -> Bool true
|
||||||
|
| "false" -> Bool false
|
||||||
|
| "nil" -> Nil
|
||||||
|
| _ when token.[0] = ':' ->
|
||||||
|
Keyword (String.sub token 1 (String.length token - 1))
|
||||||
|
| _ ->
|
||||||
|
match try_number token with
|
||||||
|
| Some n -> n
|
||||||
|
| None -> Symbol token
|
||||||
|
end
|
||||||
|
|
||||||
|
and read_list s close_char =
|
||||||
|
advance s; (* skip opening paren/bracket *)
|
||||||
|
let items = ref [] in
|
||||||
|
let rec go () =
|
||||||
|
skip_whitespace_and_comments s;
|
||||||
|
if at_end s then raise (Parse_error "Unterminated list");
|
||||||
|
if s.src.[s.pos] = close_char then begin
|
||||||
|
advance s;
|
||||||
|
List (List.rev !items)
|
||||||
|
end else begin
|
||||||
|
items := read_value s :: !items;
|
||||||
|
go ()
|
||||||
|
end
|
||||||
|
in go ()
|
||||||
|
|
||||||
|
and read_dict s =
|
||||||
|
advance s; (* skip { *)
|
||||||
|
let d = make_dict () in
|
||||||
|
let rec go () =
|
||||||
|
skip_whitespace_and_comments s;
|
||||||
|
if at_end s then raise (Parse_error "Unterminated dict");
|
||||||
|
if s.src.[s.pos] = '}' then begin
|
||||||
|
advance s;
|
||||||
|
Dict d
|
||||||
|
end else begin
|
||||||
|
let key = read_value s in
|
||||||
|
let key_str = match key with
|
||||||
|
| Keyword k -> k
|
||||||
|
| String k -> k
|
||||||
|
| Symbol k -> k
|
||||||
|
| _ -> raise (Parse_error "Dict key must be keyword, string, or symbol")
|
||||||
|
in
|
||||||
|
let v = read_value s in
|
||||||
|
dict_set d key_str v;
|
||||||
|
go ()
|
||||||
|
end
|
||||||
|
in go ()
|
||||||
|
|
||||||
|
|
||||||
|
(** Parse a string into a list of SX values. *)
|
||||||
|
let parse_all src =
|
||||||
|
let s = make_state src in
|
||||||
|
let results = ref [] in
|
||||||
|
let rec go () =
|
||||||
|
skip_whitespace_and_comments s;
|
||||||
|
if at_end s then List.rev !results
|
||||||
|
else begin
|
||||||
|
results := read_value s :: !results;
|
||||||
|
go ()
|
||||||
|
end
|
||||||
|
in go ()
|
||||||
|
|
||||||
|
(** Parse a file into a list of SX values. *)
|
||||||
|
let parse_file path =
|
||||||
|
let ic = open_in path in
|
||||||
|
let n = in_channel_length ic in
|
||||||
|
let src = really_input_string ic n in
|
||||||
|
close_in ic;
|
||||||
|
parse_all src
|
||||||
524
hosts/ocaml/lib/sx_primitives.ml
Normal file
524
hosts/ocaml/lib/sx_primitives.ml
Normal file
@@ -0,0 +1,524 @@
|
|||||||
|
(** Built-in primitive functions (~80 pure functions).
|
||||||
|
|
||||||
|
Registered in a global table; the evaluator checks this table
|
||||||
|
when a symbol isn't found in the lexical environment. *)
|
||||||
|
|
||||||
|
open Sx_types
|
||||||
|
|
||||||
|
let primitives : (string, value list -> value) Hashtbl.t = Hashtbl.create 128
|
||||||
|
|
||||||
|
let register name fn = Hashtbl.replace primitives name fn
|
||||||
|
|
||||||
|
let is_primitive name = Hashtbl.mem primitives name
|
||||||
|
|
||||||
|
let get_primitive name =
|
||||||
|
match Hashtbl.find_opt primitives name with
|
||||||
|
| Some fn -> NativeFn (name, fn)
|
||||||
|
| None -> raise (Eval_error ("Unknown primitive: " ^ name))
|
||||||
|
|
||||||
|
(* --- Helpers --- *)
|
||||||
|
|
||||||
|
let as_number = function
|
||||||
|
| Number n -> n
|
||||||
|
| v -> raise (Eval_error ("Expected number, got " ^ type_of v))
|
||||||
|
|
||||||
|
let as_string = function
|
||||||
|
| String s -> s
|
||||||
|
| v -> raise (Eval_error ("Expected string, got " ^ type_of v))
|
||||||
|
|
||||||
|
let as_list = function
|
||||||
|
| List l -> l
|
||||||
|
| v -> raise (Eval_error ("Expected list, got " ^ type_of v))
|
||||||
|
|
||||||
|
let as_bool = function
|
||||||
|
| Bool b -> b
|
||||||
|
| v -> sx_truthy v
|
||||||
|
|
||||||
|
let to_string = function
|
||||||
|
| String s -> s
|
||||||
|
| Number n ->
|
||||||
|
if Float.is_integer n then string_of_int (int_of_float n)
|
||||||
|
else Printf.sprintf "%g" n
|
||||||
|
| Bool true -> "true"
|
||||||
|
| Bool false -> "false"
|
||||||
|
| Nil -> ""
|
||||||
|
| Symbol s -> s
|
||||||
|
| Keyword k -> k
|
||||||
|
| v -> inspect v
|
||||||
|
|
||||||
|
let () =
|
||||||
|
(* === Arithmetic === *)
|
||||||
|
register "+" (fun args ->
|
||||||
|
Number (List.fold_left (fun acc a -> acc +. as_number a) 0.0 args));
|
||||||
|
register "-" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [] -> Number 0.0
|
||||||
|
| [a] -> Number (-. (as_number a))
|
||||||
|
| a :: rest -> Number (List.fold_left (fun acc x -> acc -. as_number x) (as_number a) rest));
|
||||||
|
register "*" (fun args ->
|
||||||
|
Number (List.fold_left (fun acc a -> acc *. as_number a) 1.0 args));
|
||||||
|
register "/" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [a; b] -> Number (as_number a /. as_number b)
|
||||||
|
| _ -> raise (Eval_error "/: expected 2 args"));
|
||||||
|
register "mod" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [a; b] -> Number (Float.rem (as_number a) (as_number b))
|
||||||
|
| _ -> raise (Eval_error "mod: expected 2 args"));
|
||||||
|
register "inc" (fun args ->
|
||||||
|
match args with [a] -> Number (as_number a +. 1.0) | _ -> raise (Eval_error "inc: 1 arg"));
|
||||||
|
register "dec" (fun args ->
|
||||||
|
match args with [a] -> Number (as_number a -. 1.0) | _ -> raise (Eval_error "dec: 1 arg"));
|
||||||
|
register "abs" (fun args ->
|
||||||
|
match args with [a] -> Number (Float.abs (as_number a)) | _ -> raise (Eval_error "abs: 1 arg"));
|
||||||
|
register "floor" (fun args ->
|
||||||
|
match args with [a] -> Number (Float.of_int (int_of_float (Float.round (as_number a -. 0.5))))
|
||||||
|
| _ -> raise (Eval_error "floor: 1 arg"));
|
||||||
|
register "ceil" (fun args ->
|
||||||
|
match args with [a] -> Number (Float.of_int (int_of_float (Float.round (as_number a +. 0.5))))
|
||||||
|
| _ -> raise (Eval_error "ceil: 1 arg"));
|
||||||
|
register "round" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [a] -> Number (Float.round (as_number a))
|
||||||
|
| [a; b] ->
|
||||||
|
let n = as_number a and places = int_of_float (as_number b) in
|
||||||
|
let factor = 10.0 ** float_of_int places in
|
||||||
|
Number (Float.round (n *. factor) /. factor)
|
||||||
|
| _ -> raise (Eval_error "round: 1-2 args"));
|
||||||
|
register "min" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [] -> raise (Eval_error "min: at least 1 arg")
|
||||||
|
| _ -> Number (List.fold_left (fun acc a -> Float.min acc (as_number a)) Float.infinity args));
|
||||||
|
register "max" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [] -> raise (Eval_error "max: at least 1 arg")
|
||||||
|
| _ -> Number (List.fold_left (fun acc a -> Float.max acc (as_number a)) Float.neg_infinity args));
|
||||||
|
register "sqrt" (fun args ->
|
||||||
|
match args with [a] -> Number (Float.sqrt (as_number a)) | _ -> raise (Eval_error "sqrt: 1 arg"));
|
||||||
|
register "pow" (fun args ->
|
||||||
|
match args with [a; b] -> Number (as_number a ** as_number b)
|
||||||
|
| _ -> raise (Eval_error "pow: 2 args"));
|
||||||
|
register "clamp" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [x; lo; hi] ->
|
||||||
|
let x = as_number x and lo = as_number lo and hi = as_number hi in
|
||||||
|
Number (Float.max lo (Float.min hi x))
|
||||||
|
| _ -> raise (Eval_error "clamp: 3 args"));
|
||||||
|
register "parse-int" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [String s] -> (match int_of_string_opt s with Some n -> Number (float_of_int n) | None -> Nil)
|
||||||
|
| [Number n] -> Number (float_of_int (int_of_float n))
|
||||||
|
| _ -> Nil);
|
||||||
|
register "parse-float" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [String s] -> (match float_of_string_opt s with Some n -> Number n | None -> Nil)
|
||||||
|
| [Number n] -> Number n
|
||||||
|
| _ -> Nil);
|
||||||
|
|
||||||
|
(* === Comparison === *)
|
||||||
|
register "=" (fun args ->
|
||||||
|
match args with [a; b] -> Bool (a = b) | _ -> raise (Eval_error "=: 2 args"));
|
||||||
|
register "!=" (fun args ->
|
||||||
|
match args with [a; b] -> Bool (a <> b) | _ -> raise (Eval_error "!=: 2 args"));
|
||||||
|
register "<" (fun args ->
|
||||||
|
match args with [a; b] -> Bool (as_number a < as_number b) | _ -> raise (Eval_error "<: 2 args"));
|
||||||
|
register ">" (fun args ->
|
||||||
|
match args with [a; b] -> Bool (as_number a > as_number b) | _ -> raise (Eval_error ">: 2 args"));
|
||||||
|
register "<=" (fun args ->
|
||||||
|
match args with [a; b] -> Bool (as_number a <= as_number b) | _ -> raise (Eval_error "<=: 2 args"));
|
||||||
|
register ">=" (fun args ->
|
||||||
|
match args with [a; b] -> Bool (as_number a >= as_number b) | _ -> raise (Eval_error ">=: 2 args"));
|
||||||
|
|
||||||
|
(* === Logic === *)
|
||||||
|
register "not" (fun args ->
|
||||||
|
match args with [a] -> Bool (not (sx_truthy a)) | _ -> raise (Eval_error "not: 1 arg"));
|
||||||
|
|
||||||
|
(* === Predicates === *)
|
||||||
|
register "nil?" (fun args ->
|
||||||
|
match args with [a] -> Bool (is_nil a) | _ -> raise (Eval_error "nil?: 1 arg"));
|
||||||
|
register "number?" (fun args ->
|
||||||
|
match args with [Number _] -> Bool true | [_] -> Bool false | _ -> raise (Eval_error "number?: 1 arg"));
|
||||||
|
register "string?" (fun args ->
|
||||||
|
match args with [String _] -> Bool true | [_] -> Bool false | _ -> raise (Eval_error "string?: 1 arg"));
|
||||||
|
register "boolean?" (fun args ->
|
||||||
|
match args with [Bool _] -> Bool true | [_] -> Bool false | _ -> raise (Eval_error "boolean?: 1 arg"));
|
||||||
|
register "list?" (fun args ->
|
||||||
|
match args with [List _] -> Bool true | [_] -> Bool false | _ -> raise (Eval_error "list?: 1 arg"));
|
||||||
|
register "dict?" (fun args ->
|
||||||
|
match args with [Dict _] -> Bool true | [_] -> Bool false | _ -> raise (Eval_error "dict?: 1 arg"));
|
||||||
|
register "symbol?" (fun args ->
|
||||||
|
match args with [Symbol _] -> Bool true | [_] -> Bool false | _ -> raise (Eval_error "symbol?: 1 arg"));
|
||||||
|
register "keyword?" (fun args ->
|
||||||
|
match args with [Keyword _] -> Bool true | [_] -> Bool false | _ -> raise (Eval_error "keyword?: 1 arg"));
|
||||||
|
register "empty?" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [List []] -> Bool true | [List _] -> Bool false
|
||||||
|
| [String ""] -> Bool true | [String _] -> Bool false
|
||||||
|
| [Dict d] -> Bool (Hashtbl.length d = 0)
|
||||||
|
| [Nil] -> Bool true
|
||||||
|
| [_] -> Bool false
|
||||||
|
| _ -> raise (Eval_error "empty?: 1 arg"));
|
||||||
|
register "odd?" (fun args ->
|
||||||
|
match args with [a] -> Bool (int_of_float (as_number a) mod 2 <> 0) | _ -> raise (Eval_error "odd?: 1 arg"));
|
||||||
|
register "even?" (fun args ->
|
||||||
|
match args with [a] -> Bool (int_of_float (as_number a) mod 2 = 0) | _ -> raise (Eval_error "even?: 1 arg"));
|
||||||
|
register "zero?" (fun args ->
|
||||||
|
match args with [a] -> Bool (as_number a = 0.0) | _ -> raise (Eval_error "zero?: 1 arg"));
|
||||||
|
|
||||||
|
(* === Strings === *)
|
||||||
|
register "str" (fun args -> String (String.concat "" (List.map to_string args)));
|
||||||
|
register "upper" (fun args ->
|
||||||
|
match args with [a] -> String (String.uppercase_ascii (as_string a)) | _ -> raise (Eval_error "upper: 1 arg"));
|
||||||
|
register "upcase" (fun args ->
|
||||||
|
match args with [a] -> String (String.uppercase_ascii (as_string a)) | _ -> raise (Eval_error "upcase: 1 arg"));
|
||||||
|
register "lower" (fun args ->
|
||||||
|
match args with [a] -> String (String.lowercase_ascii (as_string a)) | _ -> raise (Eval_error "lower: 1 arg"));
|
||||||
|
register "downcase" (fun args ->
|
||||||
|
match args with [a] -> String (String.lowercase_ascii (as_string a)) | _ -> raise (Eval_error "downcase: 1 arg"));
|
||||||
|
register "trim" (fun args ->
|
||||||
|
match args with [a] -> String (String.trim (as_string a)) | _ -> raise (Eval_error "trim: 1 arg"));
|
||||||
|
register "string-length" (fun args ->
|
||||||
|
match args with [a] -> Number (float_of_int (String.length (as_string a)))
|
||||||
|
| _ -> raise (Eval_error "string-length: 1 arg"));
|
||||||
|
register "string-contains?" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [String haystack; String needle] ->
|
||||||
|
let rec find i =
|
||||||
|
if i + String.length needle > String.length haystack then false
|
||||||
|
else if String.sub haystack i (String.length needle) = needle then true
|
||||||
|
else find (i + 1)
|
||||||
|
in Bool (find 0)
|
||||||
|
| _ -> raise (Eval_error "string-contains?: 2 string args"));
|
||||||
|
register "starts-with?" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [String s; String prefix] ->
|
||||||
|
Bool (String.length s >= String.length prefix &&
|
||||||
|
String.sub s 0 (String.length prefix) = prefix)
|
||||||
|
| _ -> raise (Eval_error "starts-with?: 2 string args"));
|
||||||
|
register "ends-with?" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [String s; String suffix] ->
|
||||||
|
let sl = String.length s and xl = String.length suffix in
|
||||||
|
Bool (sl >= xl && String.sub s (sl - xl) xl = suffix)
|
||||||
|
| _ -> raise (Eval_error "ends-with?: 2 string args"));
|
||||||
|
register "index-of" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [String haystack; String needle] ->
|
||||||
|
let nl = String.length needle and hl = String.length haystack in
|
||||||
|
let rec find i =
|
||||||
|
if i + nl > hl then Number (-1.0)
|
||||||
|
else if String.sub haystack i nl = needle then Number (float_of_int i)
|
||||||
|
else find (i + 1)
|
||||||
|
in find 0
|
||||||
|
| _ -> raise (Eval_error "index-of: 2 string args"));
|
||||||
|
register "substring" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [String s; Number start; Number end_] ->
|
||||||
|
let i = int_of_float start and j = int_of_float end_ in
|
||||||
|
let len = String.length s in
|
||||||
|
let i = max 0 (min i len) and j = max 0 (min j len) in
|
||||||
|
String (String.sub s i (max 0 (j - i)))
|
||||||
|
| _ -> raise (Eval_error "substring: 3 args"));
|
||||||
|
register "substr" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [String s; Number start; Number len] ->
|
||||||
|
let i = int_of_float start and n = int_of_float len in
|
||||||
|
let sl = String.length s in
|
||||||
|
let i = max 0 (min i sl) in
|
||||||
|
let n = max 0 (min n (sl - i)) in
|
||||||
|
String (String.sub s i n)
|
||||||
|
| [String s; Number start] ->
|
||||||
|
let i = int_of_float start in
|
||||||
|
let sl = String.length s in
|
||||||
|
let i = max 0 (min i sl) in
|
||||||
|
String (String.sub s i (sl - i))
|
||||||
|
| _ -> raise (Eval_error "substr: 2-3 args"));
|
||||||
|
register "split" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [String s; String sep] ->
|
||||||
|
List (List.map (fun p -> String p) (String.split_on_char sep.[0] s))
|
||||||
|
| _ -> raise (Eval_error "split: 2 args"));
|
||||||
|
register "join" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [String sep; List items] -> String (String.concat sep (List.map to_string items))
|
||||||
|
| _ -> raise (Eval_error "join: 2 args"));
|
||||||
|
register "replace" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [String s; String old_s; String new_s] ->
|
||||||
|
let ol = String.length old_s in
|
||||||
|
if ol = 0 then String s
|
||||||
|
else begin
|
||||||
|
let buf = Buffer.create (String.length s) in
|
||||||
|
let rec go i =
|
||||||
|
if i >= String.length s then ()
|
||||||
|
else if i + ol <= String.length s && String.sub s i ol = old_s then begin
|
||||||
|
Buffer.add_string buf new_s;
|
||||||
|
go (i + ol)
|
||||||
|
end else begin
|
||||||
|
Buffer.add_char buf s.[i];
|
||||||
|
go (i + 1)
|
||||||
|
end
|
||||||
|
in go 0;
|
||||||
|
String (Buffer.contents buf)
|
||||||
|
end
|
||||||
|
| _ -> raise (Eval_error "replace: 3 string args"));
|
||||||
|
register "char-from-code" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [Number n] ->
|
||||||
|
let buf = Buffer.create 4 in
|
||||||
|
Buffer.add_utf_8_uchar buf (Uchar.of_int (int_of_float n));
|
||||||
|
String (Buffer.contents buf)
|
||||||
|
| _ -> raise (Eval_error "char-from-code: 1 arg"));
|
||||||
|
|
||||||
|
(* === Collections === *)
|
||||||
|
register "list" (fun args -> List args);
|
||||||
|
register "len" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [List l] -> Number (float_of_int (List.length l))
|
||||||
|
| [String s] -> Number (float_of_int (String.length s))
|
||||||
|
| [Dict d] -> Number (float_of_int (Hashtbl.length d))
|
||||||
|
| _ -> raise (Eval_error "len: 1 arg"));
|
||||||
|
register "first" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [List (x :: _)] -> x | [List []] -> Nil
|
||||||
|
| _ -> raise (Eval_error "first: 1 list arg"));
|
||||||
|
register "rest" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [List (_ :: xs)] -> List xs | [List []] -> List []
|
||||||
|
| _ -> raise (Eval_error "rest: 1 list arg"));
|
||||||
|
register "last" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [List l] -> (match List.rev l with x :: _ -> x | [] -> Nil)
|
||||||
|
| _ -> raise (Eval_error "last: 1 list arg"));
|
||||||
|
register "nth" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [List l; Number n] -> (try List.nth l (int_of_float n) with _ -> Nil)
|
||||||
|
| _ -> raise (Eval_error "nth: list and number"));
|
||||||
|
register "cons" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [x; List l] -> List (x :: l)
|
||||||
|
| _ -> raise (Eval_error "cons: value and list"));
|
||||||
|
register "append" (fun args ->
|
||||||
|
let all = List.concat_map (fun a -> as_list a) args in
|
||||||
|
List all);
|
||||||
|
register "reverse" (fun args ->
|
||||||
|
match args with [List l] -> List (List.rev l) | _ -> raise (Eval_error "reverse: 1 list"));
|
||||||
|
register "flatten" (fun args ->
|
||||||
|
let rec flat = function
|
||||||
|
| List items -> List.concat_map flat items
|
||||||
|
| x -> [x]
|
||||||
|
in
|
||||||
|
match args with [List l] -> List (List.concat_map flat l) | _ -> raise (Eval_error "flatten: 1 list"));
|
||||||
|
register "concat" (fun args -> List (List.concat_map as_list args));
|
||||||
|
register "contains?" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [List l; item] -> Bool (List.mem item l)
|
||||||
|
| [String s; String sub] ->
|
||||||
|
let rec find i =
|
||||||
|
if i + String.length sub > String.length s then false
|
||||||
|
else if String.sub s i (String.length sub) = sub then true
|
||||||
|
else find (i + 1)
|
||||||
|
in Bool (find 0)
|
||||||
|
| _ -> raise (Eval_error "contains?: 2 args"));
|
||||||
|
register "range" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [Number stop] ->
|
||||||
|
let n = int_of_float stop in
|
||||||
|
List (List.init (max 0 n) (fun i -> Number (float_of_int i)))
|
||||||
|
| [Number start; Number stop] ->
|
||||||
|
let s = int_of_float start and e = int_of_float stop in
|
||||||
|
let len = max 0 (e - s) in
|
||||||
|
List (List.init len (fun i -> Number (float_of_int (s + i))))
|
||||||
|
| _ -> raise (Eval_error "range: 1-2 args"));
|
||||||
|
register "slice" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [List l; Number start] ->
|
||||||
|
let i = max 0 (int_of_float start) in
|
||||||
|
let rec drop n = function _ :: xs when n > 0 -> drop (n-1) xs | l -> l in
|
||||||
|
List (drop i l)
|
||||||
|
| [List l; Number start; Number end_] ->
|
||||||
|
let i = max 0 (int_of_float start) and j = int_of_float end_ in
|
||||||
|
let len = List.length l in
|
||||||
|
let j = min j len in
|
||||||
|
let rec take_range idx = function
|
||||||
|
| [] -> []
|
||||||
|
| x :: xs ->
|
||||||
|
if idx >= j then []
|
||||||
|
else if idx >= i then x :: take_range (idx+1) xs
|
||||||
|
else take_range (idx+1) xs
|
||||||
|
in List (take_range 0 l)
|
||||||
|
| [String s; Number start] ->
|
||||||
|
let i = max 0 (int_of_float start) in
|
||||||
|
String (String.sub s i (max 0 (String.length s - i)))
|
||||||
|
| [String s; Number start; Number end_] ->
|
||||||
|
let i = max 0 (int_of_float start) and j = int_of_float end_ in
|
||||||
|
let sl = String.length s in
|
||||||
|
let j = min j sl in
|
||||||
|
String (String.sub s i (max 0 (j - i)))
|
||||||
|
| _ -> raise (Eval_error "slice: 2-3 args"));
|
||||||
|
register "sort" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [List l] -> List (List.sort compare l)
|
||||||
|
| _ -> raise (Eval_error "sort: 1 list"));
|
||||||
|
register "zip" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [List a; List b] ->
|
||||||
|
let rec go l1 l2 acc = match l1, l2 with
|
||||||
|
| x :: xs, y :: ys -> go xs ys (List [x; y] :: acc)
|
||||||
|
| _ -> List.rev acc
|
||||||
|
in List (go a b [])
|
||||||
|
| _ -> raise (Eval_error "zip: 2 lists"));
|
||||||
|
register "zip-pairs" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [List l] ->
|
||||||
|
let rec go = function
|
||||||
|
| a :: b :: rest -> List [a; b] :: go rest
|
||||||
|
| _ -> []
|
||||||
|
in List (go l)
|
||||||
|
| _ -> raise (Eval_error "zip-pairs: 1 list"));
|
||||||
|
register "take" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [List l; Number n] ->
|
||||||
|
let rec take_n i = function
|
||||||
|
| x :: xs when i > 0 -> x :: take_n (i-1) xs
|
||||||
|
| _ -> []
|
||||||
|
in List (take_n (int_of_float n) l)
|
||||||
|
| _ -> raise (Eval_error "take: list and number"));
|
||||||
|
register "drop" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [List l; Number n] ->
|
||||||
|
let rec drop_n i = function
|
||||||
|
| _ :: xs when i > 0 -> drop_n (i-1) xs
|
||||||
|
| l -> l
|
||||||
|
in List (drop_n (int_of_float n) l)
|
||||||
|
| _ -> raise (Eval_error "drop: list and number"));
|
||||||
|
register "chunk-every" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [List l; Number n] ->
|
||||||
|
let size = int_of_float n in
|
||||||
|
let rec go = function
|
||||||
|
| [] -> []
|
||||||
|
| l ->
|
||||||
|
let rec take_n i = function
|
||||||
|
| x :: xs when i > 0 -> x :: take_n (i-1) xs
|
||||||
|
| _ -> []
|
||||||
|
in
|
||||||
|
let rec drop_n i = function
|
||||||
|
| _ :: xs when i > 0 -> drop_n (i-1) xs
|
||||||
|
| l -> l
|
||||||
|
in
|
||||||
|
List (take_n size l) :: go (drop_n size l)
|
||||||
|
in List (go l)
|
||||||
|
| _ -> raise (Eval_error "chunk-every: list and number"));
|
||||||
|
register "unique" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [List l] ->
|
||||||
|
let seen = Hashtbl.create 16 in
|
||||||
|
let result = List.filter (fun x ->
|
||||||
|
let key = inspect x in
|
||||||
|
if Hashtbl.mem seen key then false
|
||||||
|
else (Hashtbl.replace seen key true; true)
|
||||||
|
) l in
|
||||||
|
List result
|
||||||
|
| _ -> raise (Eval_error "unique: 1 list"));
|
||||||
|
|
||||||
|
(* === Dict === *)
|
||||||
|
register "dict" (fun args ->
|
||||||
|
let d = make_dict () in
|
||||||
|
let rec go = function
|
||||||
|
| [] -> Dict d
|
||||||
|
| Keyword k :: v :: rest -> dict_set d k v; go rest
|
||||||
|
| String k :: v :: rest -> dict_set d k v; go rest
|
||||||
|
| _ -> raise (Eval_error "dict: pairs of key value")
|
||||||
|
in go args);
|
||||||
|
register "get" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [Dict d; String k] -> dict_get d k
|
||||||
|
| [Dict d; Keyword k] -> dict_get d k
|
||||||
|
| [List l; Number n] -> (try List.nth l (int_of_float n) with _ -> Nil)
|
||||||
|
| _ -> raise (Eval_error "get: dict+key or list+index"));
|
||||||
|
register "has-key?" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [Dict d; String k] -> Bool (dict_has d k)
|
||||||
|
| [Dict d; Keyword k] -> Bool (dict_has d k)
|
||||||
|
| _ -> raise (Eval_error "has-key?: dict and key"));
|
||||||
|
register "assoc" (fun args ->
|
||||||
|
match args with
|
||||||
|
| Dict d :: rest ->
|
||||||
|
let d2 = Hashtbl.copy d in
|
||||||
|
let rec go = function
|
||||||
|
| [] -> Dict d2
|
||||||
|
| String k :: v :: rest -> Hashtbl.replace d2 k v; go rest
|
||||||
|
| Keyword k :: v :: rest -> Hashtbl.replace d2 k v; go rest
|
||||||
|
| _ -> raise (Eval_error "assoc: pairs")
|
||||||
|
in go rest
|
||||||
|
| _ -> raise (Eval_error "assoc: dict + pairs"));
|
||||||
|
register "dissoc" (fun args ->
|
||||||
|
match args with
|
||||||
|
| Dict d :: keys ->
|
||||||
|
let d2 = Hashtbl.copy d in
|
||||||
|
List.iter (fun k -> Hashtbl.remove d2 (to_string k)) keys;
|
||||||
|
Dict d2
|
||||||
|
| _ -> raise (Eval_error "dissoc: dict + keys"));
|
||||||
|
register "merge" (fun args ->
|
||||||
|
let d = make_dict () in
|
||||||
|
List.iter (function
|
||||||
|
| Dict src -> Hashtbl.iter (fun k v -> Hashtbl.replace d k v) src
|
||||||
|
| _ -> raise (Eval_error "merge: all args must be dicts")
|
||||||
|
) args;
|
||||||
|
Dict d);
|
||||||
|
register "keys" (fun args ->
|
||||||
|
match args with [Dict d] -> List (dict_keys d) | _ -> raise (Eval_error "keys: 1 dict"));
|
||||||
|
register "vals" (fun args ->
|
||||||
|
match args with [Dict d] -> List (dict_vals d) | _ -> raise (Eval_error "vals: 1 dict"));
|
||||||
|
register "dict-set!" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [Dict d; String k; v] -> dict_set d k v; v
|
||||||
|
| [Dict d; Keyword k; v] -> dict_set d k v; v
|
||||||
|
| _ -> raise (Eval_error "dict-set!: dict key val"));
|
||||||
|
register "dict-get" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [Dict d; String k] -> dict_get d k
|
||||||
|
| [Dict d; Keyword k] -> dict_get d k
|
||||||
|
| _ -> raise (Eval_error "dict-get: dict and key"));
|
||||||
|
register "dict-has?" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [Dict d; String k] -> Bool (dict_has d k)
|
||||||
|
| _ -> raise (Eval_error "dict-has?: dict and key"));
|
||||||
|
register "dict-delete!" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [Dict d; String k] -> dict_delete d k; Nil
|
||||||
|
| _ -> raise (Eval_error "dict-delete!: dict and key"));
|
||||||
|
|
||||||
|
(* === Misc === *)
|
||||||
|
register "type-of" (fun args ->
|
||||||
|
match args with [a] -> String (type_of a) | _ -> raise (Eval_error "type-of: 1 arg"));
|
||||||
|
register "inspect" (fun args ->
|
||||||
|
match args with [a] -> String (inspect a) | _ -> raise (Eval_error "inspect: 1 arg"));
|
||||||
|
register "error" (fun args ->
|
||||||
|
match args with [String msg] -> raise (Eval_error msg)
|
||||||
|
| [a] -> raise (Eval_error (to_string a))
|
||||||
|
| _ -> raise (Eval_error "error: 1 arg"));
|
||||||
|
register "apply" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [NativeFn (_, f); List a] -> f a
|
||||||
|
| _ -> raise (Eval_error "apply: function and list"));
|
||||||
|
register "identical?" (fun args ->
|
||||||
|
match args with [a; b] -> Bool (a == b) | _ -> raise (Eval_error "identical?: 2 args"));
|
||||||
|
register "make-spread" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [Dict d] ->
|
||||||
|
let pairs = Hashtbl.fold (fun k v acc -> (k, v) :: acc) d [] in
|
||||||
|
Spread pairs
|
||||||
|
| _ -> raise (Eval_error "make-spread: 1 dict"));
|
||||||
|
register "spread?" (fun args ->
|
||||||
|
match args with [Spread _] -> Bool true | [_] -> Bool false
|
||||||
|
| _ -> raise (Eval_error "spread?: 1 arg"));
|
||||||
|
register "spread-attrs" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [Spread pairs] ->
|
||||||
|
let d = make_dict () in
|
||||||
|
List.iter (fun (k, v) -> dict_set d k v) pairs;
|
||||||
|
Dict d
|
||||||
|
| _ -> raise (Eval_error "spread-attrs: 1 spread"));
|
||||||
|
()
|
||||||
565
hosts/ocaml/lib/sx_ref.ml
Normal file
565
hosts/ocaml/lib/sx_ref.ml
Normal file
File diff suppressed because one or more lines are too long
347
hosts/ocaml/lib/sx_runtime.ml
Normal file
347
hosts/ocaml/lib/sx_runtime.ml
Normal file
@@ -0,0 +1,347 @@
|
|||||||
|
(** Runtime helpers for transpiled code.
|
||||||
|
|
||||||
|
These bridge the gap between the transpiler's output and the
|
||||||
|
foundation types/primitives. The transpiled evaluator calls these
|
||||||
|
functions directly. *)
|
||||||
|
|
||||||
|
open Sx_types
|
||||||
|
|
||||||
|
(** Call a registered primitive by name. *)
|
||||||
|
let prim_call name args =
|
||||||
|
match Hashtbl.find_opt Sx_primitives.primitives name with
|
||||||
|
| Some f -> f args
|
||||||
|
| None -> raise (Eval_error ("Unknown primitive: " ^ name))
|
||||||
|
|
||||||
|
(** Convert any SX value to an OCaml string (internal). *)
|
||||||
|
let value_to_str = function
|
||||||
|
| String s -> s
|
||||||
|
| Number n ->
|
||||||
|
if Float.is_integer n then string_of_int (int_of_float n)
|
||||||
|
else Printf.sprintf "%g" n
|
||||||
|
| Bool true -> "true"
|
||||||
|
| Bool false -> "false"
|
||||||
|
| Nil -> ""
|
||||||
|
| Symbol s -> s
|
||||||
|
| Keyword k -> k
|
||||||
|
| v -> inspect v
|
||||||
|
|
||||||
|
(** sx_to_string returns a value (String) for transpiled code. *)
|
||||||
|
let sx_to_string v = String (value_to_str v)
|
||||||
|
|
||||||
|
(** String concatenation helper — [sx_str] takes a list of values. *)
|
||||||
|
let sx_str args =
|
||||||
|
String.concat "" (List.map value_to_str args)
|
||||||
|
|
||||||
|
(** Convert a value to a list. *)
|
||||||
|
let sx_to_list = function
|
||||||
|
| List l -> l
|
||||||
|
| Nil -> []
|
||||||
|
| v -> raise (Eval_error ("Expected list, got " ^ type_of v))
|
||||||
|
|
||||||
|
(** Call an SX callable (lambda, native fn, continuation). *)
|
||||||
|
let sx_call f args =
|
||||||
|
match f with
|
||||||
|
| NativeFn (_, fn) -> fn args
|
||||||
|
| Lambda l ->
|
||||||
|
let local = Sx_types.env_extend l.l_closure in
|
||||||
|
List.iter2 (fun p a -> ignore (Sx_types.env_bind local p a)) l.l_params args;
|
||||||
|
(* Return the body + env for the trampoline to evaluate *)
|
||||||
|
Thunk (l.l_body, local)
|
||||||
|
| Continuation (k, _) ->
|
||||||
|
k (match args with x :: _ -> x | [] -> Nil)
|
||||||
|
| _ -> raise (Eval_error ("Not callable: " ^ inspect f))
|
||||||
|
|
||||||
|
(** Apply a function to a list of args. *)
|
||||||
|
let sx_apply f args_list =
|
||||||
|
sx_call f (sx_to_list args_list)
|
||||||
|
|
||||||
|
(** Mutable append — add item to a list ref or accumulator.
|
||||||
|
In transpiled code, lists that get appended to are mutable refs. *)
|
||||||
|
let sx_append_b lst item =
|
||||||
|
match lst with
|
||||||
|
| List items -> List (items @ [item])
|
||||||
|
| _ -> raise (Eval_error ("append!: expected list, got " ^ type_of lst))
|
||||||
|
|
||||||
|
(** Mutable dict-set — set key in dict, return value. *)
|
||||||
|
let sx_dict_set_b d k v =
|
||||||
|
match d, k with
|
||||||
|
| Dict tbl, String key -> Hashtbl.replace tbl key v; v
|
||||||
|
| Dict tbl, Keyword key -> Hashtbl.replace tbl key v; v
|
||||||
|
| _ -> raise (Eval_error "dict-set!: expected dict and string key")
|
||||||
|
|
||||||
|
(** Get from dict or list. *)
|
||||||
|
let get_val container key =
|
||||||
|
match container, key with
|
||||||
|
| Dict d, String k -> dict_get d k
|
||||||
|
| Dict d, Keyword k -> dict_get d k
|
||||||
|
| List l, Number n -> (try List.nth l (int_of_float n) with _ -> Nil)
|
||||||
|
| _ -> raise (Eval_error ("get: unsupported " ^ type_of container ^ " / " ^ type_of key))
|
||||||
|
|
||||||
|
(** Register get as a primitive override — transpiled code calls (get d k). *)
|
||||||
|
let () =
|
||||||
|
Sx_primitives.register "get" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [c; k] -> get_val c k
|
||||||
|
| _ -> raise (Eval_error "get: 2 args"))
|
||||||
|
|
||||||
|
|
||||||
|
(* ====================================================================== *)
|
||||||
|
(* Primitive aliases — top-level functions called by transpiled code *)
|
||||||
|
(* ====================================================================== *)
|
||||||
|
|
||||||
|
(** The transpiled evaluator calls primitives directly by their mangled
|
||||||
|
OCaml name. These aliases delegate to the primitives table so the
|
||||||
|
transpiled code compiles without needing [prim_call] everywhere. *)
|
||||||
|
|
||||||
|
let _prim name = match Hashtbl.find_opt Sx_primitives.primitives name with
|
||||||
|
| Some f -> f | None -> (fun _ -> raise (Eval_error ("Missing prim: " ^ name)))
|
||||||
|
|
||||||
|
(* Collection ops *)
|
||||||
|
let first args = _prim "first" [args]
|
||||||
|
let rest args = _prim "rest" [args]
|
||||||
|
let last args = _prim "last" [args]
|
||||||
|
let nth coll i = _prim "nth" [coll; i]
|
||||||
|
let cons x l = _prim "cons" [x; l]
|
||||||
|
let append a b = _prim "append" [a; b]
|
||||||
|
let reverse l = _prim "reverse" [l]
|
||||||
|
let flatten l = _prim "flatten" [l]
|
||||||
|
let concat a b = _prim "concat" [a; b]
|
||||||
|
let slice a b = _prim "slice" [a; b]
|
||||||
|
let len a = _prim "len" [a]
|
||||||
|
let get a b = get_val a b
|
||||||
|
let sort' a = _prim "sort" [a]
|
||||||
|
let range' a = _prim "range" [a]
|
||||||
|
let unique a = _prim "unique" [a]
|
||||||
|
let zip a b = _prim "zip" [a; b]
|
||||||
|
let zip_pairs a = _prim "zip-pairs" [a]
|
||||||
|
let take a b = _prim "take" [a; b]
|
||||||
|
let drop a b = _prim "drop" [a; b]
|
||||||
|
let chunk_every a b = _prim "chunk-every" [a; b]
|
||||||
|
|
||||||
|
(* Predicates *)
|
||||||
|
let empty_p a = _prim "empty?" [a]
|
||||||
|
let nil_p a = _prim "nil?" [a]
|
||||||
|
let number_p a = _prim "number?" [a]
|
||||||
|
let string_p a = _prim "string?" [a]
|
||||||
|
let boolean_p a = _prim "boolean?" [a]
|
||||||
|
let list_p a = _prim "list?" [a]
|
||||||
|
let dict_p a = _prim "dict?" [a]
|
||||||
|
let symbol_p a = _prim "symbol?" [a]
|
||||||
|
let keyword_p a = _prim "keyword?" [a]
|
||||||
|
let contains_p a b = _prim "contains?" [a; b]
|
||||||
|
let has_key_p a b = _prim "has-key?" [a; b]
|
||||||
|
let starts_with_p a b = _prim "starts-with?" [a; b]
|
||||||
|
let ends_with_p a b = _prim "ends-with?" [a; b]
|
||||||
|
let string_contains_p a b = _prim "string-contains?" [a; b]
|
||||||
|
let odd_p a = _prim "odd?" [a]
|
||||||
|
let even_p a = _prim "even?" [a]
|
||||||
|
let zero_p a = _prim "zero?" [a]
|
||||||
|
|
||||||
|
(* String ops *)
|
||||||
|
let str' args = String (sx_str args)
|
||||||
|
let upper a = _prim "upper" [a]
|
||||||
|
let upcase a = _prim "upcase" [a]
|
||||||
|
let lower a = _prim "lower" [a]
|
||||||
|
let downcase a = _prim "downcase" [a]
|
||||||
|
let trim a = _prim "trim" [a]
|
||||||
|
let split a b = _prim "split" [a; b]
|
||||||
|
let join a b = _prim "join" [a; b]
|
||||||
|
let replace a b c = _prim "replace" [a; b; c]
|
||||||
|
let index_of a b = _prim "index-of" [a; b]
|
||||||
|
let substring a b c = _prim "substring" [a; b; c]
|
||||||
|
let string_length a = _prim "string-length" [a]
|
||||||
|
let char_from_code a = _prim "char-from-code" [a]
|
||||||
|
|
||||||
|
(* Dict ops *)
|
||||||
|
let assoc d k v = _prim "assoc" [d; k; v]
|
||||||
|
let dissoc d k = _prim "dissoc" [d; k]
|
||||||
|
let merge' a b = _prim "merge" [a; b]
|
||||||
|
let keys a = _prim "keys" [a]
|
||||||
|
let vals a = _prim "vals" [a]
|
||||||
|
let dict_set a b c = _prim "dict-set!" [a; b; c]
|
||||||
|
let dict_get a b = _prim "dict-get" [a; b]
|
||||||
|
let dict_has_p a b = _prim "dict-has?" [a; b]
|
||||||
|
let dict_delete a b = _prim "dict-delete!" [a; b]
|
||||||
|
|
||||||
|
(* Math *)
|
||||||
|
let abs' a = _prim "abs" [a]
|
||||||
|
let sqrt' a = _prim "sqrt" [a]
|
||||||
|
let pow' a b = _prim "pow" [a; b]
|
||||||
|
let floor' a = _prim "floor" [a]
|
||||||
|
let ceil' a = _prim "ceil" [a]
|
||||||
|
let round' a = _prim "round" [a]
|
||||||
|
let min' a b = _prim "min" [a; b]
|
||||||
|
let max' a b = _prim "max" [a; b]
|
||||||
|
let clamp a b c = _prim "clamp" [a; b; c]
|
||||||
|
let parse_int a = _prim "parse-int" [a]
|
||||||
|
let parse_float a = _prim "parse-float" [a]
|
||||||
|
|
||||||
|
(* Misc *)
|
||||||
|
let error msg = raise (Eval_error (value_to_str msg))
|
||||||
|
|
||||||
|
(* inspect wrapper — returns String value instead of OCaml string *)
|
||||||
|
let inspect v = String (Sx_types.inspect v)
|
||||||
|
let apply' f args = sx_apply f args
|
||||||
|
let identical_p a b = _prim "identical?" [a; b]
|
||||||
|
let _is_spread_prim a = _prim "spread?" [a]
|
||||||
|
let spread_attrs a = _prim "spread-attrs" [a]
|
||||||
|
let make_spread a = _prim "make-spread" [a]
|
||||||
|
|
||||||
|
(* Scope primitives — delegate to sx_ref.py's shared scope stacks *)
|
||||||
|
let sx_collect a b = prim_call "collect!" [a; b]
|
||||||
|
let sx_collected a = prim_call "collected" [a]
|
||||||
|
let sx_clear_collected a = prim_call "clear-collected!" [a]
|
||||||
|
let sx_emit a b = prim_call "emit!" [a; b]
|
||||||
|
let sx_emitted a = prim_call "emitted" [a]
|
||||||
|
let sx_context a b = prim_call "context" [a; b]
|
||||||
|
|
||||||
|
(* Trampoline — evaluate thunks iteratively *)
|
||||||
|
let trampoline v = v (* CEK machine doesn't use tree-walk thunks *)
|
||||||
|
|
||||||
|
(* Value-returning type predicates — the transpiled code passes these through
|
||||||
|
sx_truthy, so they need to return Bool, not OCaml bool. *)
|
||||||
|
(* type_of returns value, not string *)
|
||||||
|
let type_of v = String (Sx_types.type_of v)
|
||||||
|
|
||||||
|
(* Env operations — accept Env-wrapped values and value keys.
|
||||||
|
The transpiled CEK machine stores envs in dicts as Env values. *)
|
||||||
|
let unwrap_env = function
|
||||||
|
| Env e -> e
|
||||||
|
| _ -> raise (Eval_error "Expected env")
|
||||||
|
|
||||||
|
let env_has e name = Bool (Sx_types.env_has (unwrap_env e) (value_to_str name))
|
||||||
|
let env_get e name = Sx_types.env_get (unwrap_env e) (value_to_str name)
|
||||||
|
let env_bind e name v = Sx_types.env_bind (unwrap_env e) (value_to_str name) v
|
||||||
|
let env_set e name v = Sx_types.env_set (unwrap_env e) (value_to_str name) v
|
||||||
|
|
||||||
|
let make_env () = Env (Sx_types.make_env ())
|
||||||
|
let env_extend e = Env (Sx_types.env_extend (unwrap_env e))
|
||||||
|
let env_merge a b = Env (Sx_types.env_merge (unwrap_env a) (unwrap_env b))
|
||||||
|
|
||||||
|
(* set_lambda_name wrapper — accepts value, extracts string *)
|
||||||
|
let set_lambda_name l n = Sx_types.set_lambda_name l (value_to_str n)
|
||||||
|
|
||||||
|
let is_nil v = Bool (Sx_types.is_nil v)
|
||||||
|
let is_thunk v = Bool (Sx_types.is_thunk v)
|
||||||
|
let is_lambda v = Bool (Sx_types.is_lambda v)
|
||||||
|
let is_component v = Bool (Sx_types.is_component v)
|
||||||
|
let is_island v = Bool (Sx_types.is_island v)
|
||||||
|
let is_macro v = Bool (Sx_types.is_macro v)
|
||||||
|
let is_signal v = Bool (Sx_types.is_signal v)
|
||||||
|
let is_callable v = Bool (Sx_types.is_callable v)
|
||||||
|
let is_identical a b = Bool (a == b)
|
||||||
|
let is_primitive name = Bool (Sx_primitives.is_primitive (value_to_str name))
|
||||||
|
let get_primitive name = Sx_primitives.get_primitive (value_to_str name)
|
||||||
|
let is_spread v = match v with Spread _ -> Bool true | _ -> Bool false
|
||||||
|
|
||||||
|
(* Stubs for functions defined in sx_ref.ml — resolved at link time *)
|
||||||
|
(* These are forward-declared here; sx_ref.ml defines the actual implementations *)
|
||||||
|
|
||||||
|
(* strip-prefix *)
|
||||||
|
(* Stubs for evaluator functions — defined in sx_ref.ml but
|
||||||
|
sometimes referenced before their definition via forward calls.
|
||||||
|
These get overridden by the actual transpiled definitions. *)
|
||||||
|
|
||||||
|
let map_indexed fn coll =
|
||||||
|
List (List.mapi (fun i x -> sx_call fn [Number (float_of_int i); x]) (sx_to_list coll))
|
||||||
|
|
||||||
|
let map_dict fn d =
|
||||||
|
match d with
|
||||||
|
| Dict tbl ->
|
||||||
|
let result = Hashtbl.create (Hashtbl.length tbl) in
|
||||||
|
Hashtbl.iter (fun k v -> Hashtbl.replace result k (sx_call fn [String k; v])) tbl;
|
||||||
|
Dict result
|
||||||
|
| _ -> raise (Eval_error "map-dict: expected dict")
|
||||||
|
|
||||||
|
let for_each fn coll =
|
||||||
|
List.iter (fun x -> ignore (sx_call fn [x])) (sx_to_list coll);
|
||||||
|
Nil
|
||||||
|
|
||||||
|
let for_each_indexed fn coll =
|
||||||
|
List.iteri (fun i x -> ignore (sx_call fn [Number (float_of_int i); x])) (sx_to_list coll);
|
||||||
|
Nil
|
||||||
|
|
||||||
|
(* Continuation support *)
|
||||||
|
let continuation_p v = match v with Continuation (_, _) -> Bool true | _ -> Bool false
|
||||||
|
|
||||||
|
let make_cek_continuation captured rest_kont =
|
||||||
|
let data = Hashtbl.create 2 in
|
||||||
|
Hashtbl.replace data "captured" captured;
|
||||||
|
Hashtbl.replace data "rest-kont" rest_kont;
|
||||||
|
Continuation ((fun v -> v), Some data)
|
||||||
|
|
||||||
|
let continuation_data v = match v with
|
||||||
|
| Continuation (_, Some d) -> Dict d
|
||||||
|
| Continuation (_, None) -> Dict (Hashtbl.create 0)
|
||||||
|
| _ -> raise (Eval_error "not a continuation")
|
||||||
|
|
||||||
|
(* Dynamic wind — simplified for OCaml (no async) *)
|
||||||
|
let dynamic_wind_call before body after _env =
|
||||||
|
ignore (sx_call before []);
|
||||||
|
let result = sx_call body [] in
|
||||||
|
ignore (sx_call after []);
|
||||||
|
result
|
||||||
|
|
||||||
|
(* Scope stack stubs — delegated to primitives when available *)
|
||||||
|
let scope_push name value = prim_call "collect!" [name; value]
|
||||||
|
let scope_pop _name = Nil
|
||||||
|
let provide_push name value = ignore name; ignore value; Nil
|
||||||
|
let provide_pop _name = Nil
|
||||||
|
|
||||||
|
(* Render mode stubs *)
|
||||||
|
let render_active_p () = Bool false
|
||||||
|
let render_expr _expr _env = Nil
|
||||||
|
let is_render_expr _expr = Bool false
|
||||||
|
|
||||||
|
(* Signal accessors *)
|
||||||
|
let signal_value s = match s with Signal sig' -> sig'.s_value | _ -> raise (Eval_error "not a signal")
|
||||||
|
let signal_set_value s v = match s with Signal sig' -> sig'.s_value <- v; v | _ -> raise (Eval_error "not a signal")
|
||||||
|
let signal_subscribers s = match s with Signal sig' -> List (List.map (fun _ -> Nil) sig'.s_subscribers) | _ -> List []
|
||||||
|
let signal_add_sub_b _s _f = Nil
|
||||||
|
let signal_remove_sub_b _s _f = Nil
|
||||||
|
let signal_deps _s = List []
|
||||||
|
let signal_set_deps _s _d = Nil
|
||||||
|
let notify_subscribers _s = Nil
|
||||||
|
let flush_subscribers _s = Nil
|
||||||
|
let dispose_computed _s = Nil
|
||||||
|
|
||||||
|
(* Island scope stubs — accept OCaml functions from transpiled code *)
|
||||||
|
let with_island_scope _register_fn body_fn = body_fn ()
|
||||||
|
let register_in_scope _dispose_fn = Nil
|
||||||
|
|
||||||
|
(* Component type annotation stub *)
|
||||||
|
let component_set_param_types_b _comp _types = Nil
|
||||||
|
|
||||||
|
(* Parse keyword args from a call — this is defined in evaluator.sx,
|
||||||
|
the transpiled version will override this stub. *)
|
||||||
|
(* Forward-reference stubs for evaluator functions used before definition *)
|
||||||
|
let parse_comp_params _params = List [List []; Nil; Bool false]
|
||||||
|
let parse_macro_params _params = List [List []; Nil]
|
||||||
|
|
||||||
|
let parse_keyword_args _raw_args _env =
|
||||||
|
(* Stub — the real implementation is transpiled from evaluator.sx *)
|
||||||
|
List [Dict (Hashtbl.create 0); List []]
|
||||||
|
|
||||||
|
(* Make handler/query/action/page def stubs *)
|
||||||
|
let make_handler_def name params body _env = Dict (let d = Hashtbl.create 4 in Hashtbl.replace d "type" (String "handler"); Hashtbl.replace d "name" name; Hashtbl.replace d "params" params; Hashtbl.replace d "body" body; d)
|
||||||
|
let make_query_def name params body _env = make_handler_def name params body _env
|
||||||
|
let make_action_def name params body _env = make_handler_def name params body _env
|
||||||
|
let make_page_def name _opts = Dict (let d = Hashtbl.create 4 in Hashtbl.replace d "type" (String "page"); Hashtbl.replace d "name" name; d)
|
||||||
|
|
||||||
|
(* sf-def* stubs — platform-specific def-forms, not in the SX spec *)
|
||||||
|
let sf_defhandler args env =
|
||||||
|
let name = first args in let rest_args = rest args in
|
||||||
|
make_handler_def name (first rest_args) (nth rest_args (Number 1.0)) env
|
||||||
|
let sf_defquery args env = sf_defhandler args env
|
||||||
|
let sf_defaction args env = sf_defhandler args env
|
||||||
|
let sf_defpage args _env =
|
||||||
|
let name = first args in make_page_def name (rest args)
|
||||||
|
|
||||||
|
let strip_prefix s prefix =
|
||||||
|
match s, prefix with
|
||||||
|
| String s, String p ->
|
||||||
|
let pl = String.length p in
|
||||||
|
if String.length s >= pl && String.sub s 0 pl = p
|
||||||
|
then String (String.sub s pl (String.length s - pl))
|
||||||
|
else String s
|
||||||
|
| _ -> s
|
||||||
370
hosts/ocaml/lib/sx_types.ml
Normal file
370
hosts/ocaml/lib/sx_types.ml
Normal file
@@ -0,0 +1,370 @@
|
|||||||
|
(** Core types for the SX language.
|
||||||
|
|
||||||
|
The [value] sum type represents every possible SX runtime value.
|
||||||
|
OCaml's algebraic types make the CEK machine's frame dispatch a
|
||||||
|
pattern match — exactly what the spec describes. *)
|
||||||
|
|
||||||
|
(** {1 Environment} *)
|
||||||
|
|
||||||
|
(** Lexical scope chain. Each frame holds a mutable binding table and
|
||||||
|
an optional parent link for scope-chain lookup. *)
|
||||||
|
type env = {
|
||||||
|
bindings : (string, value) Hashtbl.t;
|
||||||
|
parent : env option;
|
||||||
|
}
|
||||||
|
|
||||||
|
(** {1 Values} *)
|
||||||
|
|
||||||
|
and value =
|
||||||
|
| Nil
|
||||||
|
| Bool of bool
|
||||||
|
| Number of float
|
||||||
|
| String of string
|
||||||
|
| Symbol of string
|
||||||
|
| Keyword of string
|
||||||
|
| List of value list
|
||||||
|
| Dict of dict
|
||||||
|
| Lambda of lambda
|
||||||
|
| Component of component
|
||||||
|
| Island of island
|
||||||
|
| Macro of macro
|
||||||
|
| Thunk of value * env
|
||||||
|
| Continuation of (value -> value) * dict option
|
||||||
|
| NativeFn of string * (value list -> value)
|
||||||
|
| Signal of signal
|
||||||
|
| RawHTML of string
|
||||||
|
| Spread of (string * value) list
|
||||||
|
| SxExpr of string (** Opaque SX wire-format string — aser output. *)
|
||||||
|
| Env of env (** First-class environment — used by CEK machine state dicts. *)
|
||||||
|
|
||||||
|
(** Mutable string-keyed table (SX dicts support [dict-set!]). *)
|
||||||
|
and dict = (string, value) Hashtbl.t
|
||||||
|
|
||||||
|
and lambda = {
|
||||||
|
l_params : string list;
|
||||||
|
l_body : value;
|
||||||
|
l_closure : env;
|
||||||
|
mutable l_name : string option;
|
||||||
|
}
|
||||||
|
|
||||||
|
and component = {
|
||||||
|
c_name : string;
|
||||||
|
c_params : string list;
|
||||||
|
c_has_children : bool;
|
||||||
|
c_body : value;
|
||||||
|
c_closure : env;
|
||||||
|
c_affinity : string; (** "auto" | "client" | "server" *)
|
||||||
|
}
|
||||||
|
|
||||||
|
and island = {
|
||||||
|
i_name : string;
|
||||||
|
i_params : string list;
|
||||||
|
i_has_children : bool;
|
||||||
|
i_body : value;
|
||||||
|
i_closure : env;
|
||||||
|
}
|
||||||
|
|
||||||
|
and macro = {
|
||||||
|
m_params : string list;
|
||||||
|
m_rest_param : string option;
|
||||||
|
m_body : value;
|
||||||
|
m_closure : env;
|
||||||
|
m_name : string option;
|
||||||
|
}
|
||||||
|
|
||||||
|
and signal = {
|
||||||
|
mutable s_value : value;
|
||||||
|
mutable s_subscribers : (unit -> unit) list;
|
||||||
|
mutable s_deps : signal list;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
(** {1 Errors} *)
|
||||||
|
|
||||||
|
exception Eval_error of string
|
||||||
|
exception Parse_error of string
|
||||||
|
|
||||||
|
|
||||||
|
(** {1 Environment operations} *)
|
||||||
|
|
||||||
|
let make_env () =
|
||||||
|
{ bindings = Hashtbl.create 16; parent = None }
|
||||||
|
|
||||||
|
let env_extend parent =
|
||||||
|
{ bindings = Hashtbl.create 16; parent = Some parent }
|
||||||
|
|
||||||
|
let env_bind env name v =
|
||||||
|
Hashtbl.replace env.bindings name v; Nil
|
||||||
|
|
||||||
|
let rec env_has env name =
|
||||||
|
Hashtbl.mem env.bindings name ||
|
||||||
|
match env.parent with Some p -> env_has p name | None -> false
|
||||||
|
|
||||||
|
let rec env_get env name =
|
||||||
|
match Hashtbl.find_opt env.bindings name with
|
||||||
|
| Some v -> v
|
||||||
|
| None ->
|
||||||
|
match env.parent with
|
||||||
|
| Some p -> env_get p name
|
||||||
|
| None -> raise (Eval_error ("Undefined symbol: " ^ name))
|
||||||
|
|
||||||
|
let rec env_set env name v =
|
||||||
|
if Hashtbl.mem env.bindings name then
|
||||||
|
(Hashtbl.replace env.bindings name v; Nil)
|
||||||
|
else
|
||||||
|
match env.parent with
|
||||||
|
| Some p -> env_set p name v
|
||||||
|
| None -> Hashtbl.replace env.bindings name v; Nil
|
||||||
|
|
||||||
|
let env_merge base overlay =
|
||||||
|
let e = { bindings = Hashtbl.copy base.bindings; parent = base.parent } in
|
||||||
|
Hashtbl.iter (fun k v -> Hashtbl.replace e.bindings k v) overlay.bindings;
|
||||||
|
e
|
||||||
|
|
||||||
|
|
||||||
|
(** {1 Value extraction helpers} *)
|
||||||
|
|
||||||
|
let value_to_string = function
|
||||||
|
| String s -> s | Symbol s -> s | Keyword k -> k
|
||||||
|
| Number n -> if Float.is_integer n then string_of_int (int_of_float n) else Printf.sprintf "%g" n
|
||||||
|
| Bool true -> "true" | Bool false -> "false"
|
||||||
|
| Nil -> "" | _ -> "<value>"
|
||||||
|
|
||||||
|
let value_to_string_list = function
|
||||||
|
| List items -> List.map value_to_string items
|
||||||
|
| _ -> []
|
||||||
|
|
||||||
|
let value_to_bool = function
|
||||||
|
| Bool b -> b | Nil -> false | _ -> true
|
||||||
|
|
||||||
|
let value_to_string_opt = function
|
||||||
|
| String s -> Some s | Symbol s -> Some s | Nil -> None | _ -> None
|
||||||
|
|
||||||
|
|
||||||
|
(** {1 Constructors — accept [value] args from transpiled code} *)
|
||||||
|
|
||||||
|
let unwrap_env_val = function
|
||||||
|
| Env e -> e
|
||||||
|
| _ -> raise (Eval_error "make_lambda: expected env for closure")
|
||||||
|
|
||||||
|
let make_lambda params body closure =
|
||||||
|
let ps = match params with
|
||||||
|
| List items -> List.map value_to_string items
|
||||||
|
| _ -> value_to_string_list params
|
||||||
|
in
|
||||||
|
Lambda { l_params = ps; l_body = body; l_closure = unwrap_env_val closure; l_name = None }
|
||||||
|
|
||||||
|
let make_component name params has_children body closure affinity =
|
||||||
|
let n = value_to_string name in
|
||||||
|
let ps = value_to_string_list params in
|
||||||
|
let hc = value_to_bool has_children in
|
||||||
|
let aff = match affinity with String s -> s | _ -> "auto" in
|
||||||
|
Component {
|
||||||
|
c_name = n; c_params = ps; c_has_children = hc;
|
||||||
|
c_body = body; c_closure = unwrap_env_val closure; c_affinity = aff;
|
||||||
|
}
|
||||||
|
|
||||||
|
let make_island name params has_children body closure =
|
||||||
|
let n = value_to_string name in
|
||||||
|
let ps = value_to_string_list params in
|
||||||
|
let hc = value_to_bool has_children in
|
||||||
|
Island {
|
||||||
|
i_name = n; i_params = ps; i_has_children = hc;
|
||||||
|
i_body = body; i_closure = unwrap_env_val closure;
|
||||||
|
}
|
||||||
|
|
||||||
|
let make_macro params rest_param body closure name =
|
||||||
|
let ps = value_to_string_list params in
|
||||||
|
let rp = value_to_string_opt rest_param in
|
||||||
|
let n = value_to_string_opt name in
|
||||||
|
Macro {
|
||||||
|
m_params = ps; m_rest_param = rp;
|
||||||
|
m_body = body; m_closure = unwrap_env_val closure; m_name = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
let make_thunk expr env = Thunk (expr, unwrap_env_val env)
|
||||||
|
|
||||||
|
let make_symbol name = Symbol (value_to_string name)
|
||||||
|
let make_keyword name = Keyword (value_to_string name)
|
||||||
|
|
||||||
|
|
||||||
|
(** {1 Type inspection} *)
|
||||||
|
|
||||||
|
let type_of = function
|
||||||
|
| Nil -> "nil"
|
||||||
|
| Bool _ -> "boolean"
|
||||||
|
| Number _ -> "number"
|
||||||
|
| String _ -> "string"
|
||||||
|
| Symbol _ -> "symbol"
|
||||||
|
| Keyword _ -> "keyword"
|
||||||
|
| List _ -> "list"
|
||||||
|
| Dict _ -> "dict"
|
||||||
|
| Lambda _ -> "lambda"
|
||||||
|
| Component _ -> "component"
|
||||||
|
| Island _ -> "island"
|
||||||
|
| Macro _ -> "macro"
|
||||||
|
| Thunk _ -> "thunk"
|
||||||
|
| Continuation (_, _) -> "continuation"
|
||||||
|
| NativeFn _ -> "function"
|
||||||
|
| Signal _ -> "signal"
|
||||||
|
| RawHTML _ -> "raw-html"
|
||||||
|
| Spread _ -> "spread"
|
||||||
|
| SxExpr _ -> "sx-expr"
|
||||||
|
| Env _ -> "env"
|
||||||
|
|
||||||
|
let is_nil = function Nil -> true | _ -> false
|
||||||
|
let is_lambda = function Lambda _ -> true | _ -> false
|
||||||
|
let is_component = function Component _ -> true | _ -> false
|
||||||
|
let is_island = function Island _ -> true | _ -> false
|
||||||
|
let is_macro = function Macro _ -> true | _ -> false
|
||||||
|
let is_thunk = function Thunk _ -> true | _ -> false
|
||||||
|
let is_signal = function Signal _ -> true | _ -> false
|
||||||
|
|
||||||
|
let is_callable = function
|
||||||
|
| Lambda _ | NativeFn _ | Continuation (_, _) -> true
|
||||||
|
| _ -> false
|
||||||
|
|
||||||
|
|
||||||
|
(** {1 Truthiness} *)
|
||||||
|
|
||||||
|
(** SX truthiness: everything is truthy except [Nil] and [Bool false]. *)
|
||||||
|
let sx_truthy = function
|
||||||
|
| Nil | Bool false -> false
|
||||||
|
| _ -> true
|
||||||
|
|
||||||
|
|
||||||
|
(** {1 Accessors} *)
|
||||||
|
|
||||||
|
let symbol_name = function
|
||||||
|
| Symbol s -> String s
|
||||||
|
| v -> raise (Eval_error ("Expected symbol, got " ^ type_of v))
|
||||||
|
|
||||||
|
let keyword_name = function
|
||||||
|
| Keyword k -> String k
|
||||||
|
| v -> raise (Eval_error ("Expected keyword, got " ^ type_of v))
|
||||||
|
|
||||||
|
let lambda_params = function
|
||||||
|
| Lambda l -> List (List.map (fun s -> String s) l.l_params)
|
||||||
|
| v -> raise (Eval_error ("Expected lambda, got " ^ type_of v))
|
||||||
|
|
||||||
|
let lambda_body = function
|
||||||
|
| Lambda l -> l.l_body
|
||||||
|
| v -> raise (Eval_error ("Expected lambda, got " ^ type_of v))
|
||||||
|
|
||||||
|
let lambda_closure = function
|
||||||
|
| Lambda l -> Env l.l_closure
|
||||||
|
| v -> raise (Eval_error ("Expected lambda, got " ^ type_of v))
|
||||||
|
|
||||||
|
let lambda_name = function
|
||||||
|
| Lambda l -> (match l.l_name with Some n -> String n | None -> Nil)
|
||||||
|
| v -> raise (Eval_error ("Expected lambda, got " ^ type_of v))
|
||||||
|
|
||||||
|
let set_lambda_name l n = match l with
|
||||||
|
| Lambda l -> l.l_name <- Some n; Nil
|
||||||
|
| _ -> raise (Eval_error "set-lambda-name!: not a lambda")
|
||||||
|
|
||||||
|
let component_name = function
|
||||||
|
| Component c -> String c.c_name
|
||||||
|
| v -> raise (Eval_error ("Expected component, got " ^ type_of v))
|
||||||
|
|
||||||
|
let component_params = function
|
||||||
|
| Component c -> List (List.map (fun s -> String s) c.c_params)
|
||||||
|
| v -> raise (Eval_error ("Expected component, got " ^ type_of v))
|
||||||
|
|
||||||
|
let component_body = function
|
||||||
|
| Component c -> c.c_body
|
||||||
|
| v -> raise (Eval_error ("Expected component, got " ^ type_of v))
|
||||||
|
|
||||||
|
let component_closure = function
|
||||||
|
| Component c -> Env c.c_closure
|
||||||
|
| v -> raise (Eval_error ("Expected component, got " ^ type_of v))
|
||||||
|
|
||||||
|
let component_has_children = function
|
||||||
|
| Component c -> Bool c.c_has_children
|
||||||
|
| v -> raise (Eval_error ("Expected component, got " ^ type_of v))
|
||||||
|
|
||||||
|
let component_affinity = function
|
||||||
|
| Component c -> String c.c_affinity
|
||||||
|
| _ -> String "auto"
|
||||||
|
|
||||||
|
let macro_params = function
|
||||||
|
| Macro m -> List (List.map (fun s -> String s) m.m_params)
|
||||||
|
| v -> raise (Eval_error ("Expected macro, got " ^ type_of v))
|
||||||
|
|
||||||
|
let macro_rest_param = function
|
||||||
|
| Macro m -> (match m.m_rest_param with Some s -> String s | None -> Nil)
|
||||||
|
| v -> raise (Eval_error ("Expected macro, got " ^ type_of v))
|
||||||
|
|
||||||
|
let macro_body = function
|
||||||
|
| Macro m -> m.m_body
|
||||||
|
| v -> raise (Eval_error ("Expected macro, got " ^ type_of v))
|
||||||
|
|
||||||
|
let macro_closure = function
|
||||||
|
| Macro m -> Env m.m_closure
|
||||||
|
| v -> raise (Eval_error ("Expected macro, got " ^ type_of v))
|
||||||
|
|
||||||
|
let thunk_expr = function
|
||||||
|
| Thunk (e, _) -> e
|
||||||
|
| v -> raise (Eval_error ("Expected thunk, got " ^ type_of v))
|
||||||
|
|
||||||
|
let thunk_env = function
|
||||||
|
| Thunk (_, e) -> Env e
|
||||||
|
| v -> raise (Eval_error ("Expected thunk, got " ^ type_of v))
|
||||||
|
|
||||||
|
|
||||||
|
(** {1 Dict operations} *)
|
||||||
|
|
||||||
|
let make_dict () : dict = Hashtbl.create 8
|
||||||
|
|
||||||
|
let dict_get (d : dict) key =
|
||||||
|
match Hashtbl.find_opt d key with Some v -> v | None -> Nil
|
||||||
|
|
||||||
|
let dict_has (d : dict) key = Hashtbl.mem d key
|
||||||
|
|
||||||
|
let dict_set (d : dict) key v = Hashtbl.replace d key v
|
||||||
|
|
||||||
|
let dict_delete (d : dict) key = Hashtbl.remove d key
|
||||||
|
|
||||||
|
let dict_keys (d : dict) =
|
||||||
|
Hashtbl.fold (fun k _ acc -> String k :: acc) d []
|
||||||
|
|
||||||
|
let dict_vals (d : dict) =
|
||||||
|
Hashtbl.fold (fun _ v acc -> v :: acc) d []
|
||||||
|
|
||||||
|
|
||||||
|
(** {1 Value display} *)
|
||||||
|
|
||||||
|
let rec inspect = function
|
||||||
|
| Nil -> "nil"
|
||||||
|
| Bool true -> "true"
|
||||||
|
| Bool false -> "false"
|
||||||
|
| Number n ->
|
||||||
|
if Float.is_integer n then Printf.sprintf "%d" (int_of_float n)
|
||||||
|
else Printf.sprintf "%g" n
|
||||||
|
| String s -> Printf.sprintf "%S" s
|
||||||
|
| Symbol s -> s
|
||||||
|
| Keyword k -> ":" ^ k
|
||||||
|
| List items ->
|
||||||
|
"(" ^ String.concat " " (List.map inspect items) ^ ")"
|
||||||
|
| Dict d ->
|
||||||
|
let pairs = Hashtbl.fold (fun k v acc ->
|
||||||
|
(Printf.sprintf ":%s %s" k (inspect v)) :: acc) d [] in
|
||||||
|
"{" ^ String.concat " " pairs ^ "}"
|
||||||
|
| Lambda l ->
|
||||||
|
let tag = match l.l_name with Some n -> n | None -> "lambda" in
|
||||||
|
Printf.sprintf "<%s(%s)>" tag (String.concat ", " l.l_params)
|
||||||
|
| Component c ->
|
||||||
|
Printf.sprintf "<Component ~%s(%s)>" c.c_name (String.concat ", " c.c_params)
|
||||||
|
| Island i ->
|
||||||
|
Printf.sprintf "<Island ~%s(%s)>" i.i_name (String.concat ", " i.i_params)
|
||||||
|
| Macro m ->
|
||||||
|
let tag = match m.m_name with Some n -> n | None -> "macro" in
|
||||||
|
Printf.sprintf "<%s(%s)>" tag (String.concat ", " m.m_params)
|
||||||
|
| Thunk _ -> "<thunk>"
|
||||||
|
| Continuation (_, _) -> "<continuation>"
|
||||||
|
| NativeFn (name, _) -> Printf.sprintf "<native:%s>" name
|
||||||
|
| Signal _ -> "<signal>"
|
||||||
|
| RawHTML s -> Printf.sprintf "<raw-html:%d chars>" (String.length s)
|
||||||
|
| Spread _ -> "<spread>"
|
||||||
|
| SxExpr s -> Printf.sprintf "<sx-expr:%d chars>" (String.length s)
|
||||||
|
| Env _ -> "<env>"
|
||||||
1212
hosts/ocaml/transpiler.sx
Normal file
1212
hosts/ocaml/transpiler.sx
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user