Compare commits

..

27 Commits

Author SHA1 Message Date
973085e15f plans: tick conformance.sh + Phase 3 complete
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 12s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 19:26:56 +00:00
9f71706bc8 haskell: conformance.sh runner + scoreboard.json + scoreboard.md (16/16, 5/5)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 19:26:26 +00:00
161fa613f2 plans: tick calculator.hs + 5/5 classic programs target
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 18:57:59 +00:00
ba63cdf8c4 haskell: classic program calculator.hs + nested constructor patterns (+5 tests, 402/402)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 18:57:44 +00:00
2b117288f6 plans: tick nqueens.hs, progress log 2026-04-25
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
2026-04-25 18:40:56 +00:00
8a9168c8d5 haskell: n-queens via list comprehension + where (+2 tests, 397/397)
- fix hk-eval-let: multi-clause where/let now uses hk-bind-decls!
  grouping (enables go 0 / go k pattern)
- add concatMap/concat/abs/negate to Prelude (list comprehension support)
- cache init env in hk-env0 (eval-expr-source 5x faster)
2026-04-25 18:40:27 +00:00
9facbb4836 plans: tick quicksort.hs, progress log 2026-04-25
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
2026-04-25 18:06:58 +00:00
a12dcef327 haskell: naive quicksort classic program (+5 tests, 395/395) 2026-04-25 18:06:41 +00:00
d33c520318 plans: tick sieve.hs, progress log 2026-04-25
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
2026-04-25 18:00:02 +00:00
9be65d7d60 haskell: lazy sieve of Eratosthenes (+mod/div/rem/quot, +2 tests, 390/390) 2026-04-25 17:59:39 +00:00
4ed7ffe9dd haskell: classic program fib.hs + source-order top-level binding (+2 tests, 388/388)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
2026-04-25 08:53:47 +00:00
cd489b19be haskell: do-notation desugar + stub IO monad (return/>>=/>>) (+14 tests, 382/382)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
2026-04-25 00:59:42 +00:00
04a25d17d0 haskell: seq + deepseq via lazy-builtin flag (+9 tests, 368/368)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
2026-04-25 00:28:19 +00:00
cc5315a5e6 haskell: lazy : + ranges + Prelude (repeat/iterate/fibs/take, +25 tests, 359/359)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
2026-04-24 23:58:21 +00:00
0e53e88b02 haskell: thunks + force, app args become lazy (+6 tests, 333/333)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
2026-04-24 23:22:21 +00:00
fba92c2b69 haskell: strict evaluator + 38 eval tests, Phase 2 complete (329/329)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
2026-04-24 22:49:12 +00:00
1aa06237f1 haskell: value-level pattern matcher (+31 tests, 281/281)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
2026-04-24 22:15:13 +00:00
e9c8f803b5 haskell: runtime constructor registry (+24 tests, 250/250)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
2026-04-24 21:45:51 +00:00
ef81fffb6f haskell: desugar guards/where/list-comp → core AST (+15 tests, 226/226)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
2026-04-24 21:16:53 +00:00
cab7ca883f haskell: operator sections + list comprehensions, Phase 1 parser complete (+22 tests, 211/211)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
2026-04-24 20:47:51 +00:00
bf0d72fd2f haskell: module header + imports (+16 tests, 189/189)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
2026-04-24 20:08:30 +00:00
defbe0a612 haskell: guards + where clauses (+11 tests, 173/173)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
2026-04-24 19:37:52 +00:00
869b0b552d haskell: top-level decls (fn-clause, type-sig, data, type, newtype, fixity) + type parser (+24 tests, 162/162)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
2026-04-24 19:06:38 +00:00
58dbbc5d8b haskell: full patterns — as/lazy/negative/infix + lambda & let pat LHS (+18 tests, 138/138)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
2026-04-24 18:34:47 +00:00
36234f0132 haskell: case/do + minimal patterns (+19 tests, 119/119)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
2026-04-24 18:00:58 +00:00
6ccef45ce4 haskell: expression parser + precedence climbing (+42 tests, 100/100)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
2026-04-24 17:31:38 +00:00
c07ff90f6b haskell: layout rule per §10.3 (+15 tests, 58/58)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
2026-04-24 17:05:35 +00:00
198 changed files with 9802 additions and 39028 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -293,8 +293,6 @@ env["pop-suite"] = function() {
return null;
};
env["test-allowed?"] = function(name) { return true; };
// Load test framework
const projectDir = path.join(__dirname, "..", "..");
const specTests = path.join(projectDir, "spec", "tests");
@@ -343,20 +341,6 @@ if (fs.existsSync(swapPath)) {
}
}
// Load spec library files (define-library modules imported by tests)
for (const libFile of ["stdlib.sx", "signals.sx", "coroutines.sx"]) {
const libPath = path.join(projectDir, "spec", libFile);
if (fs.existsSync(libPath)) {
const libSrc = fs.readFileSync(libPath, "utf8");
const libExprs = Sx.parse(libSrc);
for (const expr of libExprs) {
try { Sx.eval(expr, env); } catch (e) {
console.error(`Error loading spec/${libFile}: ${e.message}`);
}
}
}
}
// Load tw system (needed by spec/tests/test-tw.sx)
const twDir = path.join(projectDir, "shared", "sx", "templates");
for (const twFile of ["tw-type.sx", "tw-layout.sx", "tw.sx"]) {

File diff suppressed because one or more lines are too long

View File

@@ -37,10 +37,7 @@ let rec deep_equal a b =
match a, b with
| Nil, Nil -> true
| Bool a, Bool b -> a = b
| Integer a, Integer b -> a = b
| Number a, Number b -> a = b
| Integer a, Number b -> float_of_int a = b
| Number a, Integer b -> a = float_of_int b
| String a, String b -> a = b
| Symbol a, Symbol b -> a = b
| Keyword a, Keyword b -> a = b
@@ -229,7 +226,7 @@ let make_test_env () =
| [String s] ->
let parsed = Sx_parser.parse_all s in
(match parsed with
| [List (Symbol "sxbc" :: (Number _ | Integer _) :: payload :: _)] -> payload
| [List (Symbol "sxbc" :: Number _ :: payload :: _)] -> payload
| _ -> raise (Eval_error "bytecode-deserialize: invalid sxbc format"))
| _ -> raise (Eval_error "bytecode-deserialize: expected string"));
@@ -243,7 +240,7 @@ let make_test_env () =
| [String s] ->
let parsed = Sx_parser.parse_all s in
(match parsed with
| [List (Symbol "cek-state" :: (Number _ | Integer _) :: payload :: _)] -> payload
| [List (Symbol "cek-state" :: Number _ :: payload :: _)] -> payload
| _ -> raise (Eval_error "cek-deserialize: invalid cek-state format"))
| _ -> raise (Eval_error "cek-deserialize: expected string"));
@@ -323,10 +320,7 @@ let make_test_env () =
bind "identical?" (fun args ->
match args with
| [a; b] -> Bool (match a, b with
| Integer x, Integer y -> x = y
| Number x, Number y -> x = y
| Integer x, Number y -> float_of_int x = y
| Number x, Integer y -> x = float_of_int y
| String x, String y -> x = y
| Bool x, Bool y -> x = y
| Nil, Nil -> true
@@ -372,15 +366,11 @@ let make_test_env () =
bind "append!" (fun args ->
match args with
| [ListRef r; v; (Number n)] when int_of_float n = 0 ->
| [ListRef r; v; Number n] when int_of_float n = 0 ->
r := v :: !r; ListRef r (* prepend *)
| [ListRef r; v; (Integer 0)] ->
r := v :: !r; ListRef r (* prepend Integer index *)
| [ListRef r; v] -> r := !r @ [v]; ListRef r (* append in place *)
| [List items; v; (Number n)] when int_of_float n = 0 ->
| [List items; v; Number n] when int_of_float n = 0 ->
List (v :: items) (* immutable prepend *)
| [List items; v; (Integer 0)] ->
List (v :: items) (* immutable prepend Integer index *)
| [List items; v] -> List (items @ [v]) (* immutable fallback *)
| _ -> raise (Eval_error "append!: expected list and value"));
@@ -556,10 +546,7 @@ let make_test_env () =
bind "batch-begin!" (fun _args -> Sx_ref.batch_begin_b ());
bind "batch-end!" (fun _args -> Sx_ref.batch_end_b ());
bind "now-ms" (fun _args -> Number 1000.0);
bind "random-int" (fun args -> match args with
| [Number lo; _] -> Number lo
| [Integer lo; _] -> Integer lo
| _ -> Integer 0);
bind "random-int" (fun args -> match args with [Number lo; _] -> Number lo | _ -> Number 0.0);
bind "try-rerender-page" (fun _args -> Nil);
bind "collect!" (fun args ->
match args with
@@ -1120,47 +1107,6 @@ let make_test_env () =
| _ :: _ -> String "confirmed"
| _ -> Nil);
bind "values" (fun args ->
match args with
| [v] -> v
| vs ->
let d = Hashtbl.create 2 in
Hashtbl.replace d "_values" (Bool true);
Hashtbl.replace d "_list" (List vs);
Dict d);
bind "call-with-values" (fun args ->
match args with
| [producer; consumer] ->
let result = Sx_ref.cek_call producer (List []) in
let spread = (match result with
| Dict d when (match Hashtbl.find_opt d "_values" with Some (Bool true) -> true | _ -> false) ->
(match Hashtbl.find_opt d "_list" with Some (List l) -> l | _ -> [result])
| _ -> [result])
in
Sx_ref.cek_call consumer (List spread)
| _ -> raise (Eval_error "call-with-values: expected 2 args"));
bind "promise?" (fun args ->
match args with
| [v] -> Bool (Sx_ref.is_promise v)
| _ -> Bool false);
bind "make-promise" (fun args ->
match args with
| [v] ->
let d = Hashtbl.create 4 in
Hashtbl.replace d "_promise" (Bool true);
Hashtbl.replace d "forced" (Bool true);
Hashtbl.replace d "value" v;
Dict d
| _ -> Nil);
bind "force" (fun args ->
match args with
| [p] -> Sx_ref.force_promise p
| _ -> Nil);
env
(* ====================================================================== *)
@@ -1196,20 +1142,18 @@ let run_foundation_tests () =
in
Printf.printf "Suite: parser\n";
assert_eq "number" (Integer 42) (List.hd (parse_all "42"));
assert_eq "number" (Number 42.0) (List.hd (parse_all "42"));
assert_eq "string" (String "hello") (List.hd (parse_all "\"hello\""));
assert_eq "bool true" (Bool true) (List.hd (parse_all "true"));
assert_eq "nil" Nil (List.hd (parse_all "nil"));
assert_eq "keyword" (Keyword "class") (List.hd (parse_all ":class"));
assert_eq "symbol" (Symbol "foo") (List.hd (parse_all "foo"));
assert_eq "list" (List [Symbol "+"; Integer 1; Integer 2]) (List.hd (parse_all "(+ 1 2)"));
assert_eq "list" (List [Symbol "+"; Number 1.0; Number 2.0]) (List.hd (parse_all "(+ 1 2)"));
(match List.hd (parse_all "(div :class \"card\" (p \"hi\"))") 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" (Sx_types.inspect v));
(match List.hd (parse_all "'(1 2 3)") with
| List [Symbol "quote"; List [Integer 1; Integer 2; Integer 3]] ->
incr pass_count; Printf.printf " PASS: quote sugar\n"
| 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" (Sx_types.inspect v));
@@ -1217,7 +1161,7 @@ let run_foundation_tests () =
| 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" (Sx_types.inspect v));
assert_eq "comment" (Integer 42) (List.hd (parse_all ";; comment\n42"));
assert_eq "comment" (Number 42.0) (List.hd (parse_all ";; comment\n42"));
assert_eq "string escape" (String "hello\nworld") (List.hd (parse_all "\"hello\\nworld\""));
assert_eq "multiple exprs" (Number 2.0) (Number (float_of_int (List.length (parse_all "(1 2 3) (4 5)"))));
@@ -2034,10 +1978,6 @@ let run_spec_tests env test_files =
(match Hashtbl.find_opt d "children" with
| Some (List l) when i >= 0 && i < List.length l -> List.nth l i
| _ -> (match Hashtbl.find_opt d (string_of_int i) with Some v -> v | None -> Nil))
| [Dict d; Integer n] ->
(match Hashtbl.find_opt d "children" with
| Some (List l) when n >= 0 && n < List.length l -> List.nth l n
| _ -> (match Hashtbl.find_opt d (string_of_int n) with Some v -> v | None -> Nil))
| _ -> Nil);
(* Stringify a value for DOM string properties *)
@@ -2112,8 +2052,8 @@ let run_spec_tests env test_files =
Hashtbl.replace d "childNodes" (List [])
| _ -> ());
stored
| [ListRef r; idx_v; value] when (match idx_v with Number _ | Integer _ -> true | _ -> false) ->
let idx = match idx_v with Number n -> int_of_float n | Integer n -> n | _ -> 0 in
| [ListRef r; Number n; value] ->
let idx = int_of_float n in
let lst = !r in
if idx >= 0 && idx < List.length lst then
r := List.mapi (fun i v -> if i = idx then value else v) lst
@@ -2250,7 +2190,7 @@ let run_spec_tests env test_files =
| [String name; value] ->
let attrs = match Hashtbl.find_opt d "attributes" with Some (Dict a) -> a | _ ->
let a = Hashtbl.create 4 in Hashtbl.replace d "attributes" (Dict a); a in
let sv = match value with String s -> s | Integer n -> string_of_int n | Number n ->
let sv = match value with String s -> s | Number n ->
let i = int_of_float n in if float_of_int i = n then string_of_int i
else string_of_float n | _ -> Sx_types.inspect value in
Hashtbl.replace attrs name (String sv);
@@ -2692,7 +2632,6 @@ let run_spec_tests env test_files =
let rec json_of_value = function
| Nil -> `Null
| Bool b -> `Bool b
| Integer n -> `Int n
| Number n ->
if Float.is_integer n && Float.abs n < 1e16
then `Int (int_of_float n) else `Float n
@@ -2708,8 +2647,8 @@ let run_spec_tests env test_files =
let rec value_of_json = function
| `Null -> Nil
| `Bool b -> Bool b
| `Int i -> Integer i
| `Intlit s -> (try Integer (int_of_string s) with _ -> try Number (float_of_string s) with _ -> String s)
| `Int i -> Number (float_of_int i)
| `Intlit s -> (try Number (float_of_string s) with _ -> String s)
| `Float f -> Number f
| `String s -> String s
| `List xs -> List (List.map value_of_json xs)
@@ -2872,7 +2811,6 @@ let run_spec_tests env test_files =
match sx_vm_execute with
| Some fn -> Sx_ref.cek_call fn (List args)
| None -> Nil)));
load_module "stdlib.sx" spec_dir; (* pure SX stdlib: format etc. *)
load_module "signals.sx" spec_dir; (* core reactive primitives *)
load_module "signals.sx" web_dir; (* web extensions *)
load_module "freeze.sx" lib_dir;

View File

@@ -296,10 +296,6 @@ let read_blob () =
(* consume trailing newline *)
(try ignore (input_line stdin) with End_of_file -> ());
data
| [List [Symbol "blob"; Integer n]] ->
let data = read_exact_bytes n in
(try ignore (input_line stdin) with End_of_file -> ());
data
| _ -> raise (Eval_error ("read_blob: expected (blob N), got: " ^ line))
(** Batch IO mode — collect requests during aser-slot, resolve after. *)
@@ -361,11 +357,6 @@ let rec read_io_response () =
| [List (Symbol "io-response" :: Number n :: values)]
when int_of_float n = !current_epoch ->
(match values with [v] -> v | _ -> List values)
| [List [Symbol "io-response"; Integer n; value]]
when n = !current_epoch -> value
| [List (Symbol "io-response" :: Integer n :: values)]
when n = !current_epoch ->
(match values with [v] -> v | _ -> List values)
(* Legacy untagged: (io-response value) — accept for backwards compat *)
| [List [Symbol "io-response"; value]] -> value
| [List (Symbol "io-response" :: values)] ->
@@ -405,12 +396,6 @@ let read_batched_io_response () =
when int_of_float n = !current_epoch -> s
| [List [Symbol "io-response"; Number n; v]]
when int_of_float n = !current_epoch -> serialize_value v
| [List [Symbol "io-response"; Integer n; String s]]
when n = !current_epoch -> s
| [List [Symbol "io-response"; Integer n; SxExpr s]]
when n = !current_epoch -> s
| [List [Symbol "io-response"; Integer n; v]]
when n = !current_epoch -> serialize_value v
(* Legacy untagged *)
| [List [Symbol "io-response"; String s]]
| [List [Symbol "io-response"; SxExpr s]] -> s
@@ -974,7 +959,6 @@ let setup_io_bridges env =
bind "sleep" (fun args -> io_request "sleep" args);
bind "set-response-status" (fun args -> match args with
| [Number n] -> _pending_response_status := int_of_float n; Nil
| [Integer n] -> _pending_response_status := n; Nil
| _ -> Nil);
bind "set-response-header" (fun args -> io_request "set-response-header" args)
@@ -1377,7 +1361,6 @@ let rec dispatch env cmd =
| Bool true -> "true"
| Bool false -> "false"
| Number n -> Sx_types.format_number n
| Integer n -> string_of_int n
| String s -> "\"" ^ escape_sx_string s ^ "\""
| Symbol s -> s
| Keyword k -> ":" ^ k
@@ -1391,10 +1374,6 @@ let rec dispatch env cmd =
| Island i -> "~" ^ i.i_name
| SxExpr s -> s
| RawHTML s -> "\"" ^ escape_sx_string s ^ "\""
| Char n -> Sx_types.inspect (Char n)
| Eof -> Sx_types.inspect Eof
| Port _ -> Sx_types.inspect result
| Rational (n, d) -> Printf.sprintf "%d/%d" n d
| _ -> "nil"
in
send_ok_raw (raw_serialize result)
@@ -4471,8 +4450,6 @@ let site_mode () =
match exprs with
| [List [Symbol "epoch"; Number n]] ->
current_epoch := int_of_float n
| [List [Symbol "epoch"; Integer n]] ->
current_epoch := n
(* render-page: full SSR pipeline — URL → complete HTML *)
| [List [Symbol "render-page"; String path]] ->
(try match http_render_page env path [] with
@@ -4530,8 +4507,6 @@ let () =
(* Epoch marker: (epoch N) — set current epoch, read next command *)
| [List [Symbol "epoch"; Number n]] ->
current_epoch := int_of_float n
| [List [Symbol "epoch"; Integer n]] ->
current_epoch := n
| [cmd] -> dispatch env cmd
| _ -> send_error ("Expected single command, got " ^ string_of_int (List.length exprs))
end

View File

@@ -47,9 +47,7 @@ open Sx_runtime
let trampoline_fn : (value -> value) ref = ref (fun v -> v)
let trampoline v = !trampoline_fn v
(* Step limit for timeout detection — set to 0 to disable *)
let step_limit : int ref = ref 0
let step_count : int ref = ref 0
(* === Mutable globals — backing refs for transpiler's !_ref / _ref := === *)
let _strict_ref = ref (Bool false)
@@ -128,90 +126,6 @@ let enhance_error_with_trace msg =
_last_error_kont_ref := Nil;
msg ^ (format_comp_trace trace)
(* Hand-written sf_define_type — skipped from transpile because the spec uses
&rest params and empty-dict literals that the transpiler can't emit cleanly.
Implements: (define-type Name (Ctor1 f1 f2) (Ctor2 f3) ...)
Creates constructor fns, Name?/Ctor? predicates, Ctor-field accessors,
and records ctors in *adt-registry*. *)
let sf_define_type args env_val =
let items = (match args with List l -> l | _ -> []) in
let type_sym = List.nth items 0 in
let type_name = value_to_string type_sym in
let ctor_specs = List.tl items in
let env_has_v k = sx_truthy (env_has env_val (String k)) in
let env_bind_v k v = ignore (env_bind env_val (String k) v) in
let env_get_v k = env_get env_val (String k) in
if not (env_has_v "*adt-registry*") then
env_bind_v "*adt-registry*" (Dict (Hashtbl.create 8));
let registry = env_get_v "*adt-registry*" in
let ctor_names = List.map (fun spec ->
(match spec with List (sym :: _) -> String (value_to_string sym) | _ -> Nil)
) ctor_specs in
(match registry with Dict d -> Hashtbl.replace d type_name (List ctor_names) | _ -> ());
env_bind_v (type_name ^ "?")
(NativeFn (type_name ^ "?", fun pargs ->
(match pargs with
| [v] ->
(match v with
| Dict d -> Bool (Hashtbl.mem d "_adt" &&
(match Hashtbl.find_opt d "_type" with Some (String t) -> t = type_name | _ -> false))
| _ -> Bool false)
| _ -> Bool false)));
List.iter (fun spec ->
(match spec with
| List (sym :: fields) ->
let cn = value_to_string sym in
let field_names = List.map value_to_string fields in
let arity = List.length fields in
env_bind_v cn
(NativeFn (cn, fun ctor_args ->
if List.length ctor_args <> arity then
raise (Eval_error (Printf.sprintf "%s: expected %d args, got %d"
cn arity (List.length ctor_args)))
else begin
let d = Hashtbl.create 4 in
Hashtbl.replace d "_adt" (Bool true);
Hashtbl.replace d "_type" (String type_name);
Hashtbl.replace d "_ctor" (String cn);
Hashtbl.replace d "_fields" (List ctor_args);
Dict d
end));
env_bind_v (cn ^ "?")
(NativeFn (cn ^ "?", fun pargs ->
(match pargs with
| [v] ->
(match v with
| Dict d -> Bool (Hashtbl.mem d "_adt" &&
(match Hashtbl.find_opt d "_ctor" with Some (String c) -> c = cn | _ -> false))
| _ -> Bool false)
| _ -> Bool false)));
List.iteri (fun idx fname ->
env_bind_v (cn ^ "-" ^ fname)
(NativeFn (cn ^ "-" ^ fname, fun pargs ->
(match pargs with
| [v] ->
(match v with
| Dict d ->
(match Hashtbl.find_opt d "_fields" with
| Some (List fs) ->
if idx < List.length fs then List.nth fs idx
else raise (Eval_error (cn ^ "-" ^ fname ^ ": index out of bounds"))
| _ -> raise (Eval_error (cn ^ "-" ^ fname ^ ": not an ADT")))
| _ -> raise (Eval_error (cn ^ "-" ^ fname ^ ": not a dict")))
| _ -> raise (Eval_error (cn ^ "-" ^ fname ^ ": expected 1 arg")))))
) field_names
| _ -> ())
) ctor_specs;
Nil
(* Register define-type via custom_special_forms so the CEK dispatch finds it.
The top-level (register-special-form! ...) in spec/evaluator.sx is not a
define and therefore is not transpiled; we wire it up here instead. *)
let () = ignore (register_special_form (String "define-type")
(NativeFn ("define-type", fun call_args ->
match call_args with
| [args; env] -> sf_define_type args env
| _ -> Nil)))
"""
@@ -257,10 +171,7 @@ def compile_spec_to_ml(spec_dir: str | None = None) -> str:
"debug-log", "debug_log", "range", "chunk-every", "zip-pairs",
"string-contains?", "starts-with?", "ends-with?",
"string-replace", "trim", "split", "index-of",
"pad-left", "pad-right", "char-at", "substring",
# sf-define-type uses &rest + empty-dict literals that the transpiler
# can't emit as valid OCaml; hand-written implementation in FIXUPS.
"sf-define-type"}
"pad-left", "pad-right", "char-at", "substring"}
defines = [(n, e) for n, e in defines if n not in skip]
# Deduplicate — keep last definition for each name (CEK overrides tree-walk)

View File

@@ -89,38 +89,10 @@ let read_symbol s =
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 gcd a b =
let rec g a b = if b = 0 then a else g b (a mod b) in g (abs a) (abs b)
let make_rat n d =
if d = 0 then raise (Parse_error "rational: division by zero");
let sign = if d < 0 then -1 else 1 in
let g = gcd (abs n) (abs d) in
let rn = sign * n / g and rd = sign * d / g in
if rd = 1 then Integer rn else Rational (rn, rd)
let try_number str =
(* Integers (no '.' or 'e'/'E') → exact Integer; rationals N/D; floats → inexact Number *)
let has_dec = String.contains str '.' in
let has_exp = String.contains str 'e' || String.contains str 'E' in
if has_dec || has_exp then
match float_of_string_opt str with
| Some n -> Some (Number n)
| None -> None
else
match String.split_on_char '/' str with
| [num_s; den_s] when num_s <> "" && den_s <> "" ->
(match int_of_string_opt num_s, int_of_string_opt den_s with
| Some n, Some d -> (try Some (make_rat n d) with _ -> None)
| _ -> None)
| _ ->
match int_of_string_opt str with
| Some n -> Some (Integer n)
| None ->
(* handles "nan", "inf", "-inf" *)
match float_of_string_opt str with
| Some n -> Some (Number n)
| None -> None
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;
@@ -136,34 +108,6 @@ let rec read_value s : value =
| '"' -> 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] = '\\' ->
(* Character literal: #\a, #\space, #\newline, etc. *)
advance s; advance s;
if at_end s then raise (Parse_error "Unexpected end of input after #\\");
let char_start = s.pos in
(* Read a name if starts with ident char, else single char *)
if is_ident_start s.src.[s.pos] then begin
while s.pos < s.len && is_ident_char s.src.[s.pos] do advance s done;
let name = String.sub s.src char_start (s.pos - char_start) in
let cp = match name with
| "space" -> 32 | "newline" -> 10 | "tab" -> 9
| "return" -> 13 | "nul" -> 0 | "null" -> 0
| "escape" -> 27 | "delete" -> 127 | "backspace" -> 8
| "altmode" -> 27 | "rubout" -> 127
| _ -> Char.code name.[0] (* single letter like #\a *)
in Char cp
end else begin
let c = s.src.[s.pos] in
advance s;
Char (Char.code c)
end
| '#' when s.pos + 1 < s.len &&
(s.src.[s.pos + 1] = 't' || s.src.[s.pos + 1] = 'f') &&
(s.pos + 2 >= s.len || not (is_ident_char s.src.[s.pos + 2])) ->
(* #t / #f — boolean literals (R7RS shorthand) *)
let b = s.src.[s.pos + 1] = 't' in
advance s; advance s;
Bool b
| '#' when s.pos + 1 < s.len && s.src.[s.pos + 1] = ';' ->
(* Datum comment: #; discards next expression *)
advance s; advance s;

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -46,7 +46,7 @@ let sx_call f args =
!Sx_types._cek_eval_lambda_ref f args
| Continuation (k, _) ->
k (match args with x :: _ -> x | [] -> Nil)
| CallccContinuation (_, _) ->
| CallccContinuation _ ->
raise (Eval_error "callcc continuations must be invoked through the CEK machine")
| _ ->
let nargs = List.length args in
@@ -156,9 +156,6 @@ let get_val container key =
| "extra" -> f.cf_extra | "extra2" -> f.cf_extra2
| "subscribers" -> f.cf_results
| "prev-tracking" -> f.cf_extra
| "after-thunk" -> f.cf_f (* wind-after frame *)
| "winders-len" -> f.cf_extra (* wind-after frame *)
| "body-result" -> f.cf_name (* wind-return frame *)
| _ -> Nil)
| VmFrame f, String k ->
(match k with
@@ -211,8 +208,6 @@ let get_val container key =
| Dict d, Keyword k -> dict_get d k
| (List l | ListRef { contents = l }), Number n ->
(try List.nth l (int_of_float n) with _ -> Nil)
| (List l | ListRef { contents = l }), Integer n ->
(try List.nth l n with _ -> Nil)
| Nil, _ -> Nil (* nil.anything → nil *)
| _, _ -> Nil (* type mismatch → nil (matches JS/Python behavior) *)
@@ -386,20 +381,15 @@ let continuation_data v = match v with
| _ -> raise (Eval_error "not a continuation")
(* Callcc (undelimited) continuation support *)
let callcc_continuation_p v = match v with CallccContinuation (_, _) -> Bool true | _ -> Bool false
let callcc_continuation_p v = match v with CallccContinuation _ -> Bool true | _ -> Bool false
let make_callcc_continuation captured winders_len =
let n = match winders_len with Number f -> int_of_float f | Integer n -> n | _ -> 0 in
CallccContinuation (sx_to_list captured, n)
let make_callcc_continuation captured =
CallccContinuation (sx_to_list captured)
let callcc_continuation_data v = match v with
| CallccContinuation (frames, _) -> List frames
| CallccContinuation frames -> List frames
| _ -> raise (Eval_error "not a callcc continuation")
let callcc_continuation_winders_len v = match v with
| CallccContinuation (_, n) -> Number (float_of_int n)
| _ -> Number 0.0
(* Dynamic wind — simplified for OCaml (no async) *)
let host_error msg =
raise (Eval_error (value_to_str msg))

View File

@@ -43,10 +43,9 @@ type env = {
and value =
| Nil
| Bool of bool
| Integer of int (** Exact integer — distinct from inexact float. *)
| Number of float (** Inexact float. *)
| String of string
| Bool of bool
| Number of float
| String of string
| Symbol of string
| Keyword of string
| List of value list
@@ -57,7 +56,7 @@ and value =
| Macro of macro
| Thunk of value * env
| Continuation of (value -> value) * dict option
| CallccContinuation of value list * int (** Undelimited continuation — captured kont frames + winders depth at capture *)
| CallccContinuation of value list (** Undelimited continuation — captured kont frames *)
| NativeFn of string * (value list -> value)
| Signal of signal
| RawHTML of string
@@ -73,25 +72,6 @@ and value =
| Record of record (** R7RS record — opaque, generative, field-indexed. *)
| Parameter of parameter (** R7RS parameter — dynamic binding via kont-stack provide frames. *)
| Vector of value array (** R7RS vector — mutable fixed-size array. *)
| StringBuffer of Buffer.t (** Mutable string buffer — O(1) amortized append. *)
| HashTable of (value, value) Hashtbl.t (** Mutable hash table with arbitrary keys. *)
| Char of int (** Unicode codepoint — R7RS char type. *)
| Eof (** EOF sentinel — returned by read-char etc. at end of input. *)
| Port of sx_port (** String port — input (string cursor) or output (buffer). *)
| Rational of int * int (** Exact rational: numerator, denominator (reduced, denom>0). *)
| SxSet of (string, value) Hashtbl.t (** Mutable set keyed by inspect(value). *)
| SxRegexp of string * string * Re.re (** Regexp: source, flags, compiled. *)
| SxBytevector of bytes (** Mutable bytevector — R7RS bytevector type. *)
(** String input port: source string + mutable cursor position. *)
and sx_port_kind =
| PortInput of string * int ref
| PortOutput of Buffer.t
and sx_port = {
mutable sp_closed : bool;
sp_kind : sx_port_kind;
}
(** CEK machine state — record instead of Dict for performance.
5 fields × 55K steps/sec = 275K Hashtbl allocations/sec eliminated. *)
@@ -412,7 +392,6 @@ let format_number n =
let value_to_string = function
| String s -> s | Symbol s -> s | Keyword k -> k
| Integer n -> string_of_int n
| Number n -> format_number n
| Bool true -> "true" | Bool false -> "false"
| Nil -> "" | _ -> "<value>"
@@ -482,7 +461,6 @@ let make_keyword name = Keyword (value_to_string name)
let type_of = function
| Nil -> "nil"
| Bool _ -> "boolean"
| Integer _ -> "number"
| Number _ -> "number"
| String _ -> "string"
| Symbol _ -> "symbol"
@@ -495,7 +473,7 @@ let type_of = function
| Macro _ -> "macro"
| Thunk _ -> "thunk"
| Continuation (_, _) -> "continuation"
| CallccContinuation (_, _) -> "continuation"
| CallccContinuation _ -> "continuation"
| NativeFn _ -> "function"
| Signal _ -> "signal"
| RawHTML _ -> "raw-html"
@@ -510,16 +488,6 @@ let type_of = function
| Record r -> r.r_type.rt_name
| Parameter _ -> "parameter"
| Vector _ -> "vector"
| StringBuffer _ -> "string-buffer"
| HashTable _ -> "hash-table"
| Char _ -> "char"
| Eof -> "eof-object"
| Port { sp_kind = PortInput _; _ } -> "input-port"
| Port { sp_kind = PortOutput _; _ } -> "output-port"
| Rational _ -> "rational"
| SxSet _ -> "set"
| SxRegexp _ -> "regexp"
| SxBytevector _ -> "bytevector"
let is_nil = function Nil -> true | _ -> false
let is_lambda = function Lambda _ -> true | _ -> false
@@ -535,7 +503,7 @@ let is_signal = function
let is_record = function Record _ -> true | _ -> false
let is_callable = function
| Lambda _ | NativeFn _ | Continuation (_, _) | CallccContinuation (_, _) | VmClosure _ -> true
| Lambda _ | NativeFn _ | Continuation (_, _) | CallccContinuation _ | VmClosure _ -> true
| _ -> false
@@ -648,7 +616,6 @@ let thunk_env = function
(** {1 Record operations} *)
let val_to_int = function
| Integer n -> n
| Number n -> int_of_float n
| v -> raise (Eval_error ("Expected number, got " ^ type_of v))
@@ -810,7 +777,6 @@ let rec inspect = function
| Nil -> "nil"
| Bool true -> "true"
| Bool false -> "false"
| Integer n -> string_of_int n
| Number n -> format_number n
| String s ->
let buf = Buffer.create (String.length s + 2) in
@@ -844,7 +810,7 @@ let rec inspect = function
Printf.sprintf "<%s(%s)>" tag (String.concat ", " m.m_params)
| Thunk _ -> "<thunk>"
| Continuation (_, _) -> "<continuation>"
| CallccContinuation (_, _) -> "<callcc-continuation>"
| CallccContinuation _ -> "<callcc-continuation>"
| NativeFn (name, _) -> Printf.sprintf "<native:%s>" name
| Signal _ -> "<signal>"
| RawHTML s -> Printf.sprintf "\"<raw-html:%d>\"" (String.length s)
@@ -865,23 +831,3 @@ let rec inspect = function
Printf.sprintf "#(%s)" (String.concat " " elts)
| VmFrame f -> Printf.sprintf "<vm-frame:ip=%d base=%d>" f.vf_ip f.vf_base
| VmMachine m -> Printf.sprintf "<vm-machine:sp=%d frames=%d>" m.vm_sp (List.length m.vm_frames)
| StringBuffer buf -> Printf.sprintf "<string-buffer:%d>" (Buffer.length buf)
| HashTable ht -> Printf.sprintf "<hash-table:%d>" (Hashtbl.length ht)
| Char n ->
let name = match n with
| 32 -> "space" | 10 -> "newline" | 9 -> "tab"
| 13 -> "return" | 0 -> "nul" | 27 -> "escape"
| 127 -> "delete" | 8 -> "backspace"
| _ -> let buf = Buffer.create 1 in
Buffer.add_utf_8_uchar buf (Uchar.of_int n);
Buffer.contents buf
in "#\\" ^ name
| Eof -> "#!eof"
| Port { sp_kind = PortInput (_, pos); sp_closed } ->
Printf.sprintf "<input-port:pos=%d%s>" !pos (if sp_closed then ":closed" else "")
| Port { sp_kind = PortOutput buf; sp_closed } ->
Printf.sprintf "<output-port:len=%d%s>" (Buffer.length buf) (if sp_closed then ":closed" else "")
| Rational (n, d) -> Printf.sprintf "%d/%d" n d
| SxSet ht -> Printf.sprintf "<set:%d>" (Hashtbl.length ht)
| SxRegexp (src, flags, _) -> Printf.sprintf "#/%s/%s" src flags
| SxBytevector b -> Printf.sprintf "#u8(%s)" (String.concat " " (List.init (Bytes.length b) (fun i -> string_of_int (Char.code (Bytes.get b i)))))

View File

@@ -185,8 +185,7 @@ let code_from_value v =
| Some _ as r -> r | None -> Hashtbl.find_opt d k2 in
let bc_list = match find2 "bytecode" "vc-bytecode" with
| Some (List l | ListRef { contents = l }) ->
Array.of_list (List.map (fun x -> match x with
| Integer n -> n | Number n -> int_of_float n | _ -> 0) l)
Array.of_list (List.map (fun x -> match x with Number n -> int_of_float n | _ -> 0) l)
| _ -> [||]
in
let entries = match find2 "constants" "vc-constants" with
@@ -199,10 +198,10 @@ let code_from_value v =
| _ -> entry
) entries in
let arity = match find2 "arity" "vc-arity" with
| Some (Integer n) -> n | Some (Number n) -> int_of_float n | _ -> 0
| Some (Number n) -> int_of_float n | _ -> 0
in
let rest_arity = match find2 "rest-arity" "vc-rest-arity" with
| Some (Integer n) -> n | Some (Number n) -> int_of_float n | _ -> -1
| Some (Number n) -> int_of_float n | _ -> -1
in
(* Compute locals from bytecode: scan for highest LOCAL_GET/LOCAL_SET slot.
The compiler's arity may undercount when nested lets add many locals. *)
@@ -750,7 +749,10 @@ and run vm =
| _ -> (Hashtbl.find Sx_primitives.primitives "/") [a; b])
| 164 (* OP_EQ *) ->
let b = pop vm and a = pop vm in
push vm ((Hashtbl.find Sx_primitives.primitives "=") [a; b])
let rec norm = function
| ListRef { contents = l } -> List (List.map norm l)
| List l -> List (List.map norm l) | v -> v in
push vm (Bool (norm a = norm b))
| 165 (* OP_LT *) ->
let b = pop vm and a = pop vm in
push vm (match a, b with
@@ -769,10 +771,10 @@ and run vm =
| 168 (* OP_LEN *) ->
let v = pop vm in
push vm (match v with
| List l | ListRef { contents = l } -> Integer (List.length l)
| String s -> Integer (String.length s)
| Dict d -> Integer (Hashtbl.length d)
| Nil -> Integer 0
| List l | ListRef { contents = 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))
| Nil -> Number 0.0
| _ -> (Hashtbl.find Sx_primitives.primitives "len") [v])
| 169 (* OP_FIRST *) ->
let v = pop vm in

View File

@@ -256,7 +256,6 @@
"callcc-continuation?"
"callcc-continuation-data"
"make-callcc-continuation"
"callcc-continuation-winders-len"
"dynamic-wind-call"
"strip-prefix"
"component-set-param-types!"
@@ -296,8 +295,7 @@
"*bind-tracking*"
"*provide-batch-depth*"
"*provide-batch-queue*"
"*provide-subscribers*"
"*winders*"))
"*provide-subscribers*"))
(define
ml-is-mutable-global?
@@ -535,13 +533,13 @@
"; cf_env = "
(ef "env")
"; cf_name = "
(if (= frame-type "if") (ef "else") (cond (some (fn (k) (= k "body-result")) items) (ef "body-result") :else (ef "name")))
(if (= frame-type "if") (ef "else") (ef "name"))
"; cf_body = "
(if (= frame-type "if") (ef "then") (ef "body"))
"; cf_remaining = "
(ef "remaining")
"; cf_f = "
(cond (some (fn (k) (= k "after-thunk")) items) (ef "after-thunk") (some (fn (k) (= k "f")) items) (ef "f") :else "Nil")
(ef "f")
"; cf_args = "
(cond
(some (fn (k) (= k "evaled")) items)
@@ -584,8 +582,6 @@
(ef "prev-tracking")
(some (fn (k) (= k "extra")) items)
(ef "extra")
(some (fn (k) (= k "winders-len")) items)
(ef "winders-len")
:else "Nil")
"; cf_extra2 = "
(cond

View File

@@ -1,289 +0,0 @@
;; lib/apl/runtime.sx — APL primitives on SX
;;
;; APL vectors are represented as SX lists (functional, immutable results).
;; Operations are rank-polymorphic: scalar/vector arguments both accepted.
;; Index origin: 1 (traditional APL).
;;
;; Primitives used:
;; map (multi-arg, Phase 1)
;; bitwise-and/or/xor/not/arithmetic-shift (Phase 7)
;; make-set/set-member?/set-add!/set->list (Phase 18)
;; ---------------------------------------------------------------------------
;; 1. Core vector constructors
;; ---------------------------------------------------------------------------
;; N — iota: generate integer vector 1, 2, ..., N
(define
(apl-iota n)
(letrec
((go (fn (i acc) (if (< i 1) acc (go (- i 1) (cons i acc))))))
(go n (list))))
;; A — shape (length of a vector)
(define (apl-rho v) (if (list? v) (len v) 1))
;; A[I] — 1-indexed access
(define (apl-at v i) (nth v (- i 1)))
;; Scalar predicate
(define (apl-scalar? v) (not (list? v)))
;; ---------------------------------------------------------------------------
;; 2. Rank-polymorphic helpers
;; dyadic: scalar/vector × scalar/vector → scalar/vector
;; monadic: scalar/vector → scalar/vector
;; ---------------------------------------------------------------------------
(define
(apl-dyadic op a b)
(cond
((and (list? a) (list? b)) (map op a b))
((list? a) (map (fn (x) (op x b)) a))
((list? b) (map (fn (y) (op a y)) b))
(else (op a b))))
(define (apl-monadic op a) (if (list? a) (map op a) (op a)))
;; ---------------------------------------------------------------------------
;; 3. Arithmetic (element-wise, rank-polymorphic)
;; ---------------------------------------------------------------------------
(define (apl-add a b) (apl-dyadic + a b))
(define (apl-sub a b) (apl-dyadic - a b))
(define (apl-mul a b) (apl-dyadic * a b))
(define (apl-div a b) (apl-dyadic / a b))
(define (apl-mod a b) (apl-dyadic modulo a b))
(define (apl-pow a b) (apl-dyadic pow a b))
(define (apl-max a b) (apl-dyadic (fn (x y) (if (> x y) x y)) a b))
(define (apl-min a b) (apl-dyadic (fn (x y) (if (< x y) x y)) a b))
(define (apl-neg a) (apl-monadic (fn (x) (- 0 x)) a))
(define (apl-abs a) (apl-monadic abs a))
(define (apl-floor a) (apl-monadic floor a))
(define (apl-ceil a) (apl-monadic ceil a))
(define (apl-sqrt a) (apl-monadic sqrt a))
(define (apl-exp a) (apl-monadic exp a))
(define (apl-log a) (apl-monadic log a))
;; ---------------------------------------------------------------------------
;; 4. Comparison (element-wise, returns 0/1 booleans)
;; ---------------------------------------------------------------------------
(define (apl-bool v) (if v 1 0))
(define (apl-eq a b) (apl-dyadic (fn (x y) (apl-bool (= x y))) a b))
(define
(apl-neq a b)
(apl-dyadic (fn (x y) (apl-bool (not (= x y)))) a b))
(define (apl-lt a b) (apl-dyadic (fn (x y) (apl-bool (< x y))) a b))
(define (apl-le a b) (apl-dyadic (fn (x y) (apl-bool (<= x y))) a b))
(define (apl-gt a b) (apl-dyadic (fn (x y) (apl-bool (> x y))) a b))
(define (apl-ge a b) (apl-dyadic (fn (x y) (apl-bool (>= x y))) a b))
;; Boolean logic (0/1 vectors)
(define
(apl-and a b)
(apl-dyadic
(fn
(x y)
(if
(and (not (= x 0)) (not (= y 0)))
1
0))
a
b))
(define
(apl-or a b)
(apl-dyadic
(fn
(x y)
(if
(or (not (= x 0)) (not (= y 0)))
1
0))
a
b))
(define
(apl-not a)
(apl-monadic (fn (x) (if (= x 0) 1 0)) a))
;; ---------------------------------------------------------------------------
;; 5. Bitwise operations (element-wise)
;; ---------------------------------------------------------------------------
(define (apl-bitand a b) (apl-dyadic bitwise-and a b))
(define (apl-bitor a b) (apl-dyadic bitwise-or a b))
(define (apl-bitxor a b) (apl-dyadic bitwise-xor a b))
(define (apl-bitnot a) (apl-monadic bitwise-not a))
(define
(apl-lshift a b)
(apl-dyadic (fn (x n) (arithmetic-shift x n)) a b))
(define
(apl-rshift a b)
(apl-dyadic (fn (x n) (arithmetic-shift x (- 0 n))) a b))
;; ---------------------------------------------------------------------------
;; 6. Reduction (fold) and scan
;; ---------------------------------------------------------------------------
(define (apl-reduce-add v) (reduce + 0 v))
(define (apl-reduce-mul v) (reduce * 1 v))
(define
(apl-reduce-max v)
(reduce (fn (acc x) (if (> acc x) acc x)) (first v) (rest v)))
(define
(apl-reduce-min v)
(reduce (fn (acc x) (if (< acc x) acc x)) (first v) (rest v)))
(define
(apl-reduce-and v)
(reduce
(fn
(acc x)
(if
(and (not (= acc 0)) (not (= x 0)))
1
0))
1
v))
(define
(apl-reduce-or v)
(reduce
(fn
(acc x)
(if
(or (not (= acc 0)) (not (= x 0)))
1
0))
0
v))
;; Scan: prefix reduction (yields a vector of running totals)
(define
(apl-scan op v)
(if
(= (len v) 0)
(list)
(letrec
((go (fn (xs acc result) (if (= (len xs) 0) (reverse result) (let ((next (op acc (first xs)))) (go (rest xs) next (cons next result)))))))
(go (rest v) (first v) (list (first v))))))
(define (apl-scan-add v) (apl-scan + v))
(define (apl-scan-mul v) (apl-scan * v))
;; ---------------------------------------------------------------------------
;; 7. Vector manipulation
;; ---------------------------------------------------------------------------
;; ⌽A — reverse
(define (apl-reverse v) (reverse v))
;; A,B — catenate
(define
(apl-cat a b)
(cond
((and (list? a) (list? b)) (append a b))
((list? a) (append a (list b)))
((list? b) (cons a b))
(else (list a b))))
;; ↑N A — take first N elements (negative: take last N)
(define
(apl-take n v)
(if
(>= n 0)
(letrec
((go (fn (xs i) (if (or (= i 0) (= (len xs) 0)) (list) (cons (first xs) (go (rest xs) (- i 1)))))))
(go v n))
(apl-reverse (apl-take (- 0 n) (apl-reverse v)))))
;; ↓N A — drop first N elements
(define
(apl-drop n v)
(if
(>= n 0)
(letrec
((go (fn (xs i) (if (or (= i 0) (= (len xs) 0)) xs (go (rest xs) (- i 1))))))
(go v n))
(apl-reverse (apl-drop (- 0 n) (apl-reverse v)))))
;; Rotate left by n positions
(define
(apl-rotate n v)
(let ((m (modulo n (len v)))) (append (apl-drop m v) (apl-take m v))))
;; Compression: A/B — select elements of B where A is 1
(define
(apl-compress mask v)
(if
(= (len mask) 0)
(list)
(let
((rest-result (apl-compress (rest mask) (rest v))))
(if
(not (= (first mask) 0))
(cons (first v) rest-result)
rest-result))))
;; Indexing: A[B] — select elements at indices B (1-indexed)
(define (apl-index v indices) (map (fn (i) (apl-at v i)) indices))
;; Grade up: indices that would sort the vector ascending
(define
(apl-grade-up v)
(let
((indexed (map (fn (x i) (list x i)) v (apl-iota (len v)))))
(map (fn (p) (nth p 1)) (sort indexed))))
;; ---------------------------------------------------------------------------
;; 8. Set operations (∊ ∩ ~)
;; ---------------------------------------------------------------------------
;; Membership ∊: for each element in A, is it in B? → 0/1 vector
(define
(apl-member a b)
(let
((bset (let ((s (make-set))) (for-each (fn (x) (set-add! s x)) b) s)))
(if
(list? a)
(map (fn (x) (apl-bool (set-member? bset x))) a)
(apl-bool (set-member? bset a)))))
;; Nub A — unique elements, preserving order
(define
(apl-nub v)
(let
((seen (make-set)))
(letrec
((go (fn (xs acc) (if (= (len xs) 0) (reverse acc) (if (set-member? seen (first xs)) (go (rest xs) acc) (begin (set-add! seen (first xs)) (go (rest xs) (cons (first xs) acc))))))))
(go v (list)))))
;; Union AB — nub of concatenation
(define (apl-union a b) (apl-nub (apl-cat a b)))
;; Intersection A∩B
(define
(apl-intersect a b)
(let
((bset (let ((s (make-set))) (for-each (fn (x) (set-add! s x)) b) s)))
(filter (fn (x) (set-member? bset x)) a)))
;; Without A~B
(define
(apl-without a b)
(let
((bset (let ((s (make-set))) (for-each (fn (x) (set-add! s x)) b) s)))
(filter (fn (x) (not (set-member? bset x))) a)))
;; ---------------------------------------------------------------------------
;; 9. Format (⍕) — APL-style display
;; ---------------------------------------------------------------------------
(define
(apl-format v)
(if
(list? v)
(letrec
((go (fn (xs acc) (if (= (len xs) 0) acc (go (rest xs) (str acc (if (= acc "") "" " ") (str (first xs))))))))
(go v ""))
(str v)))

View File

@@ -1,51 +0,0 @@
#!/usr/bin/env bash
# lib/apl/test.sh — smoke-test the APL runtime layer.
set -uo pipefail
cd "$(git rev-parse --show-toplevel)"
SX_SERVER="${SX_SERVER:-hosts/ocaml/_build/default/bin/sx_server.exe}"
if [ ! -x "$SX_SERVER" ]; then
SX_SERVER="/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe"
fi
if [ ! -x "$SX_SERVER" ]; then
echo "ERROR: sx_server.exe not found."
exit 1
fi
TMPFILE=$(mktemp); trap "rm -f $TMPFILE" EXIT
cat > "$TMPFILE" << 'EPOCHS'
(epoch 1)
(load "spec/stdlib.sx")
(load "lib/apl/runtime.sx")
(epoch 2)
(load "lib/apl/tests/runtime.sx")
(epoch 3)
(eval "(list apl-test-pass apl-test-fail)")
EPOCHS
OUTPUT=$(timeout 60 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
LINE=$(echo "$OUTPUT" | awk '/^\(ok-len 3 / {getline; print; exit}')
if [ -z "$LINE" ]; then
LINE=$(echo "$OUTPUT" | grep -E '^\(ok 3 \([0-9]+ [0-9]+\)\)' | tail -1 \
| sed -E 's/^\(ok 3 //; s/\)$//')
fi
if [ -z "$LINE" ]; then
echo "ERROR: could not extract summary"
echo "$OUTPUT" | tail -10
exit 1
fi
P=$(echo "$LINE" | sed -E 's/^\(([0-9]+) ([0-9]+)\).*/\1/')
F=$(echo "$LINE" | sed -E 's/^\(([0-9]+) ([0-9]+)\).*/\2/')
TOTAL=$((P + F))
if [ "$F" -eq 0 ]; then
echo "ok $P/$TOTAL lib/apl tests passed"
else
echo "FAIL $P/$TOTAL passed, $F failed"
fi
[ "$F" -eq 0 ]

View File

@@ -1,327 +0,0 @@
;; lib/apl/tests/runtime.sx — Tests for lib/apl/runtime.sx
;; --- Test framework ---
(define apl-test-pass 0)
(define apl-test-fail 0)
(define apl-test-fails (list))
(define
(apl-test name got expected)
(if
(= got expected)
(set! apl-test-pass (+ apl-test-pass 1))
(begin
(set! apl-test-fail (+ apl-test-fail 1))
(set! apl-test-fails (append apl-test-fails (list {:got got :expected expected :name name}))))))
;; ---------------------------------------------------------------------------
;; 1. Core vector constructors
;; ---------------------------------------------------------------------------
(apl-test
"iota 5"
(apl-iota 5)
(list 1 2 3 4 5))
(apl-test "iota 1" (apl-iota 1) (list 1))
(apl-test "iota 0" (apl-iota 0) (list))
(apl-test
"rho list"
(apl-rho (list 1 2 3))
3)
(apl-test "rho scalar" (apl-rho 42) 1)
(apl-test
"at 1"
(apl-at (list 10 20 30) 1)
10)
(apl-test
"at 3"
(apl-at (list 10 20 30) 3)
30)
;; ---------------------------------------------------------------------------
;; 2. Arithmetic — element-wise and rank-polymorphic
;; ---------------------------------------------------------------------------
(apl-test
"add v+v"
(apl-add
(list 1 2 3)
(list 10 20 30))
(list 11 22 33))
(apl-test
"add s+v"
(apl-add 10 (list 1 2 3))
(list 11 12 13))
(apl-test
"add v+s"
(apl-add (list 1 2 3) 100)
(list 101 102 103))
(apl-test "add s+s" (apl-add 3 4) 7)
(apl-test
"sub v-v"
(apl-sub
(list 5 4 3)
(list 1 2 3))
(list 4 2 0))
(apl-test
"mul v*s"
(apl-mul (list 1 2 3) 3)
(list 3 6 9))
(apl-test
"neg -v"
(apl-neg (list 1 -2 3))
(list -1 2 -3))
(apl-test
"abs v"
(apl-abs (list -1 2 -3))
(list 1 2 3))
(apl-test
"floor v"
(apl-floor (list 1.7 2.2 3.9))
(list 1 2 3))
(apl-test
"ceil v"
(apl-ceil (list 1.1 2.5 3))
(list 2 3 3))
(apl-test
"max v v"
(apl-max
(list 1 5 3)
(list 4 2 6))
(list 4 5 6))
(apl-test
"min v v"
(apl-min
(list 1 5 3)
(list 4 2 6))
(list 1 2 3))
;; ---------------------------------------------------------------------------
;; 3. Comparison (returns 0/1)
;; ---------------------------------------------------------------------------
(apl-test "eq 3 3" (apl-eq 3 3) 1)
(apl-test "eq 3 4" (apl-eq 3 4) 0)
(apl-test
"gt v>s"
(apl-gt (list 1 5 3 7) 4)
(list 0 1 0 1))
(apl-test
"lt v<v"
(apl-lt
(list 1 2 3)
(list 3 2 1))
(list 1 0 0))
(apl-test
"le v<=s"
(apl-le (list 3 4 5) 4)
(list 1 1 0))
(apl-test
"ge v>=s"
(apl-ge (list 3 4 5) 4)
(list 0 1 1))
(apl-test
"neq v!=s"
(apl-neq (list 1 2 3) 2)
(list 1 0 1))
;; ---------------------------------------------------------------------------
;; 4. Boolean logic (0/1 values)
;; ---------------------------------------------------------------------------
(apl-test "and 1 1" (apl-and 1 1) 1)
(apl-test "and 1 0" (apl-and 1 0) 0)
(apl-test "or 0 1" (apl-or 0 1) 1)
(apl-test "or 0 0" (apl-or 0 0) 0)
(apl-test "not 0" (apl-not 0) 1)
(apl-test "not 1" (apl-not 1) 0)
(apl-test
"not vec"
(apl-not (list 1 0 1 0))
(list 0 1 0 1))
;; ---------------------------------------------------------------------------
;; 5. Bitwise operations
;; ---------------------------------------------------------------------------
(apl-test "bitand s" (apl-bitand 5 3) 1)
(apl-test "bitor s" (apl-bitor 5 3) 7)
(apl-test "bitxor s" (apl-bitxor 5 3) 6)
(apl-test "bitnot 0" (apl-bitnot 0) -1)
(apl-test "lshift 1 4" (apl-lshift 1 4) 16)
(apl-test "rshift 16 2" (apl-rshift 16 2) 4)
(apl-test
"bitand vec"
(apl-bitand (list 5 6) (list 3 7))
(list 1 6))
(apl-test
"bitor vec"
(apl-bitor (list 5 6) (list 3 7))
(list 7 7))
;; ---------------------------------------------------------------------------
;; 6. Reduction and scan
;; ---------------------------------------------------------------------------
(apl-test
"reduce-add"
(apl-reduce-add
(list 1 2 3 4 5))
15)
(apl-test
"reduce-mul"
(apl-reduce-mul (list 1 2 3 4))
24)
(apl-test
"reduce-max"
(apl-reduce-max
(list 3 1 4 1 5))
5)
(apl-test
"reduce-min"
(apl-reduce-min
(list 3 1 4 1 5))
1)
(apl-test
"reduce-and"
(apl-reduce-and (list 1 1 1))
1)
(apl-test
"reduce-and0"
(apl-reduce-and (list 1 0 1))
0)
(apl-test
"reduce-or"
(apl-reduce-or (list 0 1 0))
1)
(apl-test
"scan-add"
(apl-scan-add (list 1 2 3 4))
(list 1 3 6 10))
(apl-test
"scan-mul"
(apl-scan-mul (list 1 2 3 4))
(list 1 2 6 24))
;; ---------------------------------------------------------------------------
;; 7. Vector manipulation
;; ---------------------------------------------------------------------------
(apl-test
"reverse"
(apl-reverse (list 1 2 3 4))
(list 4 3 2 1))
(apl-test
"cat v v"
(apl-cat (list 1 2) (list 3 4))
(list 1 2 3 4))
(apl-test
"cat v s"
(apl-cat (list 1 2) 3)
(list 1 2 3))
(apl-test
"cat s v"
(apl-cat 1 (list 2 3))
(list 1 2 3))
(apl-test
"cat s s"
(apl-cat 1 2)
(list 1 2))
(apl-test
"take 3"
(apl-take
3
(list 10 20 30 40 50))
(list 10 20 30))
(apl-test
"take 0"
(apl-take 0 (list 1 2 3))
(list))
(apl-test
"take neg"
(apl-take -2 (list 10 20 30))
(list 20 30))
(apl-test
"drop 2"
(apl-drop 2 (list 10 20 30 40))
(list 30 40))
(apl-test
"drop neg"
(apl-drop -1 (list 10 20 30))
(list 10 20))
(apl-test
"rotate 2"
(apl-rotate
2
(list 1 2 3 4 5))
(list 3 4 5 1 2))
(apl-test
"compress"
(apl-compress
(list 1 0 1 0)
(list 10 20 30 40))
(list 10 30))
(apl-test
"index"
(apl-index
(list 10 20 30 40)
(list 2 4))
(list 20 40))
;; ---------------------------------------------------------------------------
;; 8. Set operations
;; ---------------------------------------------------------------------------
(apl-test
"member yes"
(apl-member
(list 1 2 5)
(list 2 4 6))
(list 0 1 0))
(apl-test
"member s"
(apl-member 2 (list 1 2 3))
1)
(apl-test
"member no"
(apl-member 9 (list 1 2 3))
0)
(apl-test
"nub"
(apl-nub (list 1 2 1 3 2))
(list 1 2 3))
(apl-test
"union"
(apl-union
(list 1 2 3)
(list 2 3 4))
(list 1 2 3 4))
(apl-test
"intersect"
(apl-intersect
(list 1 2 3 4)
(list 2 4 6))
(list 2 4))
(apl-test
"without"
(apl-without
(list 1 2 3 4)
(list 2 4))
(list 1 3))
;; ---------------------------------------------------------------------------
;; 9. Format
;; ---------------------------------------------------------------------------
(apl-test
"format vec"
(apl-format (list 1 2 3))
"1 2 3")
(apl-test "format scalar" (apl-format 42) "42")
(apl-test "format empty" (apl-format (list)) "")
;; ---------------------------------------------------------------------------
;; Summary
;; ---------------------------------------------------------------------------
(list apl-test-pass apl-test-fail)

View File

@@ -1,306 +0,0 @@
;; lib/common-lisp/runtime.sx — CL built-ins using SX spec primitives
;;
;; Provides CL-specific wrappers and helpers. Deliberately thin: wherever
;; an SX spec primitive already does the job, we alias it rather than
;; reinventing it.
;;
;; Primitives used from spec:
;; char/char->integer/integer->char/char-upcase/char-downcase
;; format (Phase 21 — must be loaded before this file)
;; gensym (Phase 12)
;; rational/rational? (Phase 16)
;; make-set/set-member?/set-union/etc (Phase 18)
;; open-input-string/read-char/etc (Phase 14)
;; modulo/remainder/quotient/gcd/lcm/expt (Phase 2 / Phase 15)
;; number->string with radix (Phase 15)
;; ---------------------------------------------------------------------------
;; 1. Type predicates
;; ---------------------------------------------------------------------------
(define (cl-null? x) (= x nil))
(define (cl-consp? x) (and (list? x) (not (cl-empty? x))))
(define (cl-listp? x) (or (cl-empty? x) (list? x)))
(define (cl-atom? x) (not (cl-consp? x)))
(define
(cl-numberp? x)
(let ((t (type-of x))) (or (= t "number") (= t "rational"))))
(define cl-integerp? integer?)
(define cl-floatp? float?)
(define cl-rationalp? rational?)
(define (cl-realp? x) (or (integer? x) (float? x) (rational? x)))
(define cl-characterp? char?)
(define cl-stringp? (fn (x) (= (type-of x) "string")))
(define cl-symbolp? (fn (x) (= (type-of x) "symbol")))
(define cl-keywordp? (fn (x) (= (type-of x) "keyword")))
(define
(cl-functionp? x)
(let
((t (type-of x)))
(or
(= t "function")
(= t "lambda")
(= t "native-fn")
(= t "component"))))
(define cl-vectorp? vector?)
(define cl-arrayp? vector?)
;; sx_server: (rest (list x)) returns () not nil — cl-empty? handles both
(define
(cl-empty? x)
(or (nil? x) (and (list? x) (= (len x) 0))))
;; ---------------------------------------------------------------------------
;; 2. Arithmetic — thin aliases to spec primitives
;; ---------------------------------------------------------------------------
(define cl-mod modulo)
(define cl-rem remainder)
(define cl-gcd gcd)
(define cl-lcm lcm)
(define cl-expt expt)
(define cl-floor floor)
(define cl-ceiling ceil)
(define cl-truncate truncate)
(define cl-round round)
(define cl-abs (fn (x) (if (< x 0) (- 0 x) x)))
(define cl-min (fn (a b) (if (< a b) a b)))
(define cl-max (fn (a b) (if (> a b) a b)))
(define cl-quotient quotient)
(define
(cl-signum x)
(cond
((> x 0) 1)
((< x 0) -1)
(else 0)))
(define (cl-evenp? n) (= (modulo n 2) 0))
(define (cl-oddp? n) (= (modulo n 2) 1))
(define (cl-zerop? n) (= n 0))
(define (cl-plusp? n) (> n 0))
(define (cl-minusp? n) (< n 0))
;; ---------------------------------------------------------------------------
;; 3. Character functions — alias spec char primitives + CL name mapping
;; ---------------------------------------------------------------------------
(define cl-char->integer char->integer)
(define cl-integer->char integer->char)
(define cl-char-upcase char-upcase)
(define cl-char-downcase char-downcase)
(define cl-char-code char->integer)
(define cl-code-char integer->char)
(define cl-char=? char=?)
(define cl-char<? char<?)
(define cl-char>? char>?)
(define cl-char<=? char<=?)
(define cl-char>=? char>=?)
(define cl-char-ci=? char-ci=?)
(define cl-char-ci<? char-ci<?)
(define cl-char-ci>? char-ci>?)
;; Inline predicates — char-alphabetic?/char-numeric? unreliable in sx_server
(define
(cl-alpha-char-p c)
(let
((n (char->integer c)))
(or
(and (>= n 65) (<= n 90))
(and (>= n 97) (<= n 122)))))
(define
(cl-digit-char-p c)
(let ((n (char->integer c))) (and (>= n 48) (<= n 57))))
(define
(cl-alphanumericp c)
(let
((n (char->integer c)))
(or
(and (>= n 48) (<= n 57))
(and (>= n 65) (<= n 90))
(and (>= n 97) (<= n 122)))))
(define
(cl-upper-case-p c)
(let ((n (char->integer c))) (and (>= n 65) (<= n 90))))
(define
(cl-lower-case-p c)
(let ((n (char->integer c))) (and (>= n 97) (<= n 122))))
;; Named character constants
(define cl-char-space (integer->char 32))
(define cl-char-newline (integer->char 10))
(define cl-char-tab (integer->char 9))
(define cl-char-backspace (integer->char 8))
(define cl-char-return (integer->char 13))
(define cl-char-null (integer->char 0))
(define cl-char-escape (integer->char 27))
(define cl-char-delete (integer->char 127))
;; ---------------------------------------------------------------------------
;; 4. String + IO — use spec format and ports
;; ---------------------------------------------------------------------------
;; CL format: (cl-format nil "~a ~a" x y) — nil destination means return string
(define
(cl-format dest template &rest args)
(let ((s (apply format (cons template args)))) (if (= dest nil) s s)))
(define cl-write-to-string write-to-string)
(define cl-princ-to-string display-to-string)
;; CL read-from-string: parse value from a string using SX port
(define
(cl-read-from-string s)
(let ((p (open-input-string s))) (read p)))
;; String stream (output)
(define cl-make-string-output-stream open-output-string)
(define cl-get-output-stream-string get-output-string)
;; String stream (input)
(define cl-make-string-input-stream open-input-string)
;; ---------------------------------------------------------------------------
;; 5. Gensym
;; ---------------------------------------------------------------------------
(define cl-gensym gensym)
(define cl-gentemp gensym)
;; ---------------------------------------------------------------------------
;; 6. Multiple values (CL: values / nth-value)
;; ---------------------------------------------------------------------------
(define (cl-values &rest args) {:_values true :_list args})
(define
(cl-call-with-values producer consumer)
(let
((mv (producer)))
(if
(and (dict? mv) (get mv :_values))
(apply consumer (get mv :_list))
(consumer mv))))
(define
(cl-nth-value n mv)
(cond
((and (dict? mv) (get mv :_values))
(let
((lst (get mv :_list)))
(if (>= n (len lst)) nil (nth lst n))))
((= n 0) mv)
(else nil)))
;; ---------------------------------------------------------------------------
;; 7. Sets (CL: adjoin / member / union / intersection / set-difference)
;; ---------------------------------------------------------------------------
(define cl-make-set make-set)
(define cl-set? set?)
(define cl-set-add set-add!)
(define cl-set-memberp set-member?)
(define cl-set-remove set-remove!)
(define cl-set-union set-union)
(define cl-set-intersect set-intersection)
(define cl-set-difference set-difference)
(define cl-list->set list->set)
(define cl-set->list set->list)
;; CL: (member item list) — returns tail starting at item, or nil
(define
(cl-member item lst)
(cond
((cl-empty? lst) nil)
((equal? item (first lst)) lst)
(else (cl-member item (rest lst)))))
;; CL: (adjoin item list) — cons only if not already present
(define (cl-adjoin item lst) (if (cl-member item lst) lst (cons item lst)))
;; ---------------------------------------------------------------------------
;; 8. Radix formatting (CL: (write-to-string n :base radix))
;; ---------------------------------------------------------------------------
(define (cl-integer-to-string n radix) (number->string n radix))
(define (cl-string-to-integer s radix) (string->number s radix))
;; CL ~R directive helpers
(define (cl-format-binary n) (number->string n 2))
(define (cl-format-octal n) (number->string n 8))
(define (cl-format-hex n) (number->string n 16))
(define (cl-format-decimal n) (number->string n 10))
;; ---------------------------------------------------------------------------
;; 9. List utilities — cl-empty? guards against () from rest
;; ---------------------------------------------------------------------------
(define
(cl-last lst)
(cond
((cl-empty? lst) nil)
((cl-empty? (rest lst)) lst)
(else (cl-last (rest lst)))))
(define
(cl-butlast lst)
(if
(or (cl-empty? lst) (cl-empty? (rest lst)))
nil
(cons (first lst) (cl-butlast (rest lst)))))
(define
(cl-nthcdr n lst)
(if (= n 0) lst (cl-nthcdr (- n 1) (rest lst))))
(define (cl-nth n lst) (first (cl-nthcdr n lst)))
(define (cl-list-length lst) (len lst))
(define
(cl-copy-list lst)
(if (cl-empty? lst) nil (cons (first lst) (cl-copy-list (rest lst)))))
(define
(cl-flatten lst)
(cond
((cl-empty? lst) nil)
((list? (first lst))
(append (cl-flatten (first lst)) (cl-flatten (rest lst))))
(else (cons (first lst) (cl-flatten (rest lst))))))
;; CL: (assoc key alist) — returns matching pair or nil
(define
(cl-assoc key alist)
(cond
((cl-empty? alist) nil)
((equal? key (first (first alist))) (first alist))
(else (cl-assoc key (rest alist)))))
;; CL: (rassoc val alist) — reverse assoc (match on second element)
(define
(cl-rassoc val alist)
(cond
((cl-empty? alist) nil)
((equal? val (first (rest (first alist)))) (first alist))
(else (cl-rassoc val (rest alist)))))
;; CL: (getf plist key) — property list lookup
(define
(cl-getf plist key)
(cond
((or (cl-empty? plist) (cl-empty? (rest plist))) nil)
((equal? (first plist) key) (first (rest plist)))
(else (cl-getf (rest (rest plist)) key))))

View File

@@ -1,302 +0,0 @@
#!/usr/bin/env bash
# lib/common-lisp/test.sh — quick smoke-test the CL runtime layer.
# Uses sx_server.exe epoch protocol (same as lib/lua/test.sh).
#
# Usage:
# bash lib/common-lisp/test.sh
# bash lib/common-lisp/test.sh -v
set -uo pipefail
cd "$(git rev-parse --show-toplevel)"
SX_SERVER="${SX_SERVER:-hosts/ocaml/_build/default/bin/sx_server.exe}"
if [ ! -x "$SX_SERVER" ]; then
SX_SERVER="/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe"
fi
if [ ! -x "$SX_SERVER" ]; then
echo "ERROR: sx_server.exe not found. Run: cd hosts/ocaml && dune build"
exit 1
fi
VERBOSE="${1:-}"
PASS=0; FAIL=0; ERRORS=""
TMPFILE=$(mktemp); trap "rm -f $TMPFILE" EXIT
cat > "$TMPFILE" << 'EPOCHS'
(epoch 1)
(load "spec/stdlib.sx")
(load "lib/common-lisp/runtime.sx")
;; --- Type predicates ---
(epoch 10)
(eval "(cl-null? nil)")
(epoch 11)
(eval "(cl-null? false)")
(epoch 12)
(eval "(cl-consp? (list 1 2))")
(epoch 13)
(eval "(cl-consp? nil)")
(epoch 14)
(eval "(cl-listp? nil)")
(epoch 15)
(eval "(cl-listp? (list 1))")
(epoch 16)
(eval "(cl-atom? nil)")
(epoch 17)
(eval "(cl-atom? (list 1))")
(epoch 18)
(eval "(cl-integerp? 42)")
(epoch 19)
(eval "(cl-floatp? 3.14)")
(epoch 20)
(eval "(cl-characterp? (integer->char 65))")
(epoch 21)
(eval "(cl-stringp? \"hello\")")
;; --- Arithmetic ---
(epoch 30)
(eval "(cl-mod 10 3)")
(epoch 31)
(eval "(cl-rem 10 3)")
(epoch 32)
(eval "(cl-quotient 10 3)")
(epoch 33)
(eval "(cl-gcd 12 8)")
(epoch 34)
(eval "(cl-lcm 4 6)")
(epoch 35)
(eval "(cl-abs -5)")
(epoch 36)
(eval "(cl-abs 5)")
(epoch 37)
(eval "(cl-min 2 7)")
(epoch 38)
(eval "(cl-max 2 7)")
(epoch 39)
(eval "(cl-evenp? 4)")
(epoch 40)
(eval "(cl-evenp? 3)")
(epoch 41)
(eval "(cl-oddp? 7)")
(epoch 42)
(eval "(cl-zerop? 0)")
(epoch 43)
(eval "(cl-plusp? 1)")
(epoch 44)
(eval "(cl-minusp? -1)")
(epoch 45)
(eval "(cl-signum 42)")
(epoch 46)
(eval "(cl-signum -7)")
(epoch 47)
(eval "(cl-signum 0)")
;; --- Characters ---
(epoch 50)
(eval "(cl-char-code (integer->char 65))")
(epoch 51)
(eval "(char? (cl-code-char 65))")
(epoch 52)
(eval "(cl-char=? (integer->char 65) (integer->char 65))")
(epoch 53)
(eval "(cl-char<? (integer->char 65) (integer->char 90))")
(epoch 54)
(eval "(cl-char-code cl-char-space)")
(epoch 55)
(eval "(cl-char-code cl-char-newline)")
(epoch 56)
(eval "(cl-alpha-char-p (integer->char 65))")
(epoch 57)
(eval "(cl-digit-char-p (integer->char 48))")
;; --- Format ---
(epoch 60)
(eval "(cl-format nil \"hello\")")
(epoch 61)
(eval "(cl-format nil \"~a\" \"world\")")
(epoch 62)
(eval "(cl-format nil \"~d\" 42)")
(epoch 63)
(eval "(cl-format nil \"~x\" 255)")
(epoch 64)
(eval "(cl-format nil \"x=~d y=~d\" 3 4)")
;; --- Gensym ---
(epoch 70)
(eval "(= (type-of (cl-gensym)) \"symbol\")")
(epoch 71)
(eval "(not (= (cl-gensym) (cl-gensym)))")
;; --- Sets ---
(epoch 80)
(eval "(cl-set? (cl-make-set))")
(epoch 81)
(eval "(let ((s (cl-make-set))) (do (cl-set-add s 1) (cl-set-memberp s 1)))")
(epoch 82)
(eval "(cl-set-memberp (cl-make-set) 42)")
(epoch 83)
(eval "(cl-set-memberp (cl-list->set (list 1 2 3)) 2)")
;; --- Lists ---
(epoch 90)
(eval "(cl-nth 0 (list 1 2 3))")
(epoch 91)
(eval "(cl-nth 2 (list 1 2 3))")
(epoch 92)
(eval "(cl-last (list 1 2 3))")
(epoch 93)
(eval "(cl-butlast (list 1 2 3))")
(epoch 94)
(eval "(cl-nthcdr 1 (list 1 2 3))")
(epoch 95)
(eval "(cl-assoc \"b\" (list (list \"a\" 1) (list \"b\" 2)))")
(epoch 96)
(eval "(cl-assoc \"z\" (list (list \"a\" 1)))")
(epoch 97)
(eval "(cl-getf (list \"x\" 42 \"y\" 99) \"x\")")
(epoch 98)
(eval "(cl-adjoin 0 (list 1 2))")
(epoch 99)
(eval "(cl-adjoin 1 (list 1 2))")
(epoch 100)
(eval "(cl-member 2 (list 1 2 3))")
(epoch 101)
(eval "(cl-member 9 (list 1 2 3))")
(epoch 102)
(eval "(cl-flatten (list 1 (list 2 3) 4))")
;; --- Radix ---
(epoch 110)
(eval "(cl-format-binary 10)")
(epoch 111)
(eval "(cl-format-octal 15)")
(epoch 112)
(eval "(cl-format-hex 255)")
(epoch 113)
(eval "(cl-format-decimal 42)")
(epoch 114)
(eval "(cl-integer-to-string 31 16)")
(epoch 115)
(eval "(cl-string-to-integer \"1f\" 16)")
EPOCHS
OUTPUT=$(timeout 30 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
check() {
local epoch="$1" desc="$2" expected="$3"
local actual
# ok-len format: value appears on the line AFTER "(ok-len N length)"
actual=$(echo "$OUTPUT" | grep -A1 "^(ok-len $epoch " | tail -1 || true)
# strip any leading "(ok-len ...)" if grep -A1 returned it instead
if echo "$actual" | grep -q "^(ok-len"; then actual=""; fi
if [ -z "$actual" ]; then
actual=$(echo "$OUTPUT" | grep "^(ok $epoch " | head -1 || true)
fi
if [ -z "$actual" ]; then
actual=$(echo "$OUTPUT" | grep "^(error $epoch " | head -1 || true)
fi
[ -z "$actual" ] && actual="<no output for epoch $epoch>"
if echo "$actual" | grep -qF -- "$expected"; then
PASS=$((PASS+1))
[ "$VERBOSE" = "-v" ] && echo " ok $desc"
else
FAIL=$((FAIL+1))
ERRORS+=" FAIL [$desc] (epoch $epoch) expected: $expected | actual: $actual
"
fi
}
# Type predicates
check 10 "cl-null? nil" "true"
check 11 "cl-null? false" "false"
check 12 "cl-consp? pair" "true"
check 13 "cl-consp? nil" "false"
check 14 "cl-listp? nil" "true"
check 15 "cl-listp? list" "true"
check 16 "cl-atom? nil" "true"
check 17 "cl-atom? pair" "false"
check 18 "cl-integerp?" "true"
check 19 "cl-floatp?" "true"
check 20 "cl-characterp?" "true"
check 21 "cl-stringp?" "true"
# Arithmetic
check 30 "cl-mod 10 3" "1"
check 31 "cl-rem 10 3" "1"
check 32 "cl-quotient 10 3" "3"
check 33 "cl-gcd 12 8" "4"
check 34 "cl-lcm 4 6" "12"
check 35 "cl-abs -5" "5"
check 36 "cl-abs 5" "5"
check 37 "cl-min 2 7" "2"
check 38 "cl-max 2 7" "7"
check 39 "cl-evenp? 4" "true"
check 40 "cl-evenp? 3" "false"
check 41 "cl-oddp? 7" "true"
check 42 "cl-zerop? 0" "true"
check 43 "cl-plusp? 1" "true"
check 44 "cl-minusp? -1" "true"
check 45 "cl-signum pos" "1"
check 46 "cl-signum neg" "-1"
check 47 "cl-signum zero" "0"
# Characters
check 50 "cl-char-code" "65"
check 51 "code-char returns char" "true"
check 52 "cl-char=?" "true"
check 53 "cl-char<?" "true"
check 54 "cl-char-space code" "32"
check 55 "cl-char-newline code" "10"
check 56 "cl-alpha-char-p A" "true"
check 57 "cl-digit-char-p 0" "true"
# Format
check 60 "cl-format plain" '"hello"'
check 61 "cl-format ~a" '"world"'
check 62 "cl-format ~d" '"42"'
check 63 "cl-format ~x" '"ff"'
check 64 "cl-format multi" '"x=3 y=4"'
# Gensym
check 70 "gensym returns symbol" "true"
check 71 "gensyms are unique" "true"
# Sets
check 80 "make-set is set?" "true"
check 81 "set-add + member" "true"
check 82 "member in empty" "false"
check 83 "list->set member" "true"
# Lists
check 90 "cl-nth 0" "1"
check 91 "cl-nth 2" "3"
check 92 "cl-last" "(3)"
check 93 "cl-butlast" "(1 2)"
check 94 "cl-nthcdr 1" "(2 3)"
check 95 "cl-assoc hit" '("b" 2)'
check 96 "cl-assoc miss" "nil"
check 97 "cl-getf hit" "42"
check 98 "cl-adjoin new" "(0 1 2)"
check 99 "cl-adjoin dup" "(1 2)"
check 100 "cl-member hit" "(2 3)"
check 101 "cl-member miss" "nil"
check 102 "cl-flatten" "(1 2 3 4)"
# Radix
check 110 "cl-format-binary 10" '"1010"'
check 111 "cl-format-octal 15" '"17"'
check 112 "cl-format-hex 255" '"ff"'
check 113 "cl-format-decimal 42" '"42"'
check 114 "n->s base 16" '"1f"'
check 115 "s->n base 16" "31"
TOTAL=$((PASS+FAIL))
if [ $FAIL -eq 0 ]; then
echo "ok $PASS/$TOTAL lib/common-lisp tests passed"
else
echo "FAIL $PASS/$TOTAL passed, $FAIL failed:"
echo "$ERRORS"
fi
[ $FAIL -eq 0 ]

View File

@@ -1,86 +0,0 @@
#!/usr/bin/env bash
# Erlang-on-SX ring benchmark.
#
# Spawns N processes in a ring, passes a token N hops (one full round),
# and reports wall-clock time + throughput. Aspirational target from
# the plan is 1M processes; current sync-scheduler architecture caps out
# orders of magnitude lower — this script measures honestly across a
# range of N so the result/scaling is recorded.
#
# Usage:
# bash lib/erlang/bench_ring.sh # default ladder
# bash lib/erlang/bench_ring.sh 100 1000 5000 # custom Ns
set -uo pipefail
cd "$(git rev-parse --show-toplevel)"
SX_SERVER="${SX_SERVER:-hosts/ocaml/_build/default/bin/sx_server.exe}"
if [ ! -x "$SX_SERVER" ]; then
SX_SERVER="/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe"
fi
if [ ! -x "$SX_SERVER" ]; then
echo "ERROR: sx_server.exe not found." >&2
exit 1
fi
if [ "$#" -gt 0 ]; then
NS=("$@")
else
NS=(10 100 500 1000)
fi
TMPFILE=$(mktemp)
trap "rm -f $TMPFILE" EXIT
# One-line Erlang program. Replaces __N__ with the size for each run.
PROGRAM='Me = self(), N = __N__, Spawner = fun () -> receive {setup, Next} -> Loop = fun () -> receive {token, 0, Parent} -> Parent ! done; {token, K, Parent} -> Next ! {token, K-1, Parent}, Loop() end end, Loop() end end, BuildRing = fun (K, Acc) -> if K =:= 0 -> Acc; true -> BuildRing(K-1, [spawn(Spawner) | Acc]) end end, Pids = BuildRing(N, []), Wire = fun (Ps) -> case Ps of [P, Q | _] -> P ! {setup, Q}, Wire(tl(Ps)); [Last] -> Last ! {setup, hd(Pids)} end end, Wire(Pids), hd(Pids) ! {token, N, Me}, receive done -> done end'
run_n() {
local n="$1"
local prog="${PROGRAM//__N__/$n}"
cat > "$TMPFILE" <<EPOCHS
(epoch 1)
(load "lib/erlang/tokenizer.sx")
(load "lib/erlang/parser.sx")
(load "lib/erlang/parser-core.sx")
(load "lib/erlang/parser-expr.sx")
(load "lib/erlang/parser-module.sx")
(load "lib/erlang/transpile.sx")
(load "lib/erlang/runtime.sx")
(epoch 2)
(eval "(erlang-eval-ast \"${prog//\"/\\\"}\")")
EPOCHS
local start_s start_ns end_s end_ns elapsed_ms
start_s=$(date +%s)
start_ns=$(date +%N)
out=$(timeout 300 "$SX_SERVER" < "$TMPFILE" 2>&1)
end_s=$(date +%s)
end_ns=$(date +%N)
local ok="false"
if echo "$out" | grep -q ':name "done"'; then ok="true"; fi
# ms = (end_s - start_s)*1000 + (end_ns - start_ns)/1e6
elapsed_ms=$(awk -v s1="$start_s" -v n1="$start_ns" -v s2="$end_s" -v n2="$end_ns" \
'BEGIN { printf "%d", (s2 - s1) * 1000 + (n2 - n1) / 1000000 }')
if [ "$ok" = "true" ]; then
local hops_per_s
hops_per_s=$(awk -v n="$n" -v ms="$elapsed_ms" \
'BEGIN { if (ms == 0) ms = 1; printf "%.0f", n * 1000 / ms }')
printf " N=%-8s hops=%-8s %sms (%s hops/s)\n" "$n" "$n" "$elapsed_ms" "$hops_per_s"
else
printf " N=%-8s FAILED %sms\n" "$n" "$elapsed_ms"
fi
}
echo "Ring benchmark — sx_server.exe (synchronous scheduler)"
echo
for n in "${NS[@]}"; do
run_n "$n"
done
echo
echo "Note: 1M-process target from the plan is aspirational; the synchronous"
echo "scheduler with shift-based suspension and dict-based env copies is not"
echo "engineered for that scale. Numbers above are honest baselines."

View File

@@ -1,35 +0,0 @@
# Ring Benchmark Results
Generated by `lib/erlang/bench_ring.sh` against `sx_server.exe` on the
synchronous Erlang-on-SX scheduler.
| N (processes) | Hops | Wall-clock | Throughput |
|---|---|---|---|
| 10 | 10 | 907ms | 11 hops/s |
| 50 | 50 | 2107ms | 24 hops/s |
| 100 | 100 | 3827ms | 26 hops/s |
| 500 | 500 | 17004ms | 29 hops/s |
| 1000 | 1000 | 29832ms | 34 hops/s |
(Each `Nm` row spawns N processes connected in a ring and passes a
single token N hops total — i.e. the token completes one full lap.)
## Status of the 1M-process target
Phase 3's stretch goal in `plans/erlang-on-sx.md` is a million-process
ring benchmark. **That target is not met** in the current synchronous
scheduler; extrapolating from the table above, 1M hops would take
~30 000 s. Correctness is fine — the program runs at every measured
size — but throughput is bound by per-hop overhead.
Per-hop cost is dominated by:
- `er-env-copy` per fun clause attempt (whole-dict copy each time)
- `call/cc` capture + `raise`/`guard` unwind on every `receive`
- `er-q-delete-at!` rebuilds the mailbox backing list on every match
- `dict-set!`/`dict-has?` lookups in the global processes table
To reach 1M-process throughput in this architecture would need at
least: persistent (path-copying) envs, an inline scheduler that
doesn't call/cc on the common path (msg-already-in-mailbox), and a
linked-list mailbox. None of those are in scope for the Phase 3
checkbox — captured here as the floor we're starting from.

View File

@@ -1,153 +0,0 @@
#!/usr/bin/env bash
# Erlang-on-SX conformance runner.
#
# Loads every erlang test suite via the epoch protocol, collects
# pass/fail counts, and writes lib/erlang/scoreboard.json + .md.
#
# Usage:
# bash lib/erlang/conformance.sh # run all suites
# bash lib/erlang/conformance.sh -v # verbose per-suite
set -uo pipefail
cd "$(git rev-parse --show-toplevel)"
SX_SERVER="${SX_SERVER:-hosts/ocaml/_build/default/bin/sx_server.exe}"
if [ ! -x "$SX_SERVER" ]; then
SX_SERVER="/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe"
fi
if [ ! -x "$SX_SERVER" ]; then
echo "ERROR: sx_server.exe not found." >&2
exit 1
fi
VERBOSE="${1:-}"
TMPFILE=$(mktemp)
OUTFILE=$(mktemp)
trap "rm -f $TMPFILE $OUTFILE" EXIT
# Each suite: name | counter pass | counter total
SUITES=(
"tokenize|er-test-pass|er-test-count"
"parse|er-parse-test-pass|er-parse-test-count"
"eval|er-eval-test-pass|er-eval-test-count"
"runtime|er-rt-test-pass|er-rt-test-count"
"ring|er-ring-test-pass|er-ring-test-count"
"ping-pong|er-pp-test-pass|er-pp-test-count"
"bank|er-bank-test-pass|er-bank-test-count"
"echo|er-echo-test-pass|er-echo-test-count"
"fib|er-fib-test-pass|er-fib-test-count"
)
cat > "$TMPFILE" << 'EPOCHS'
(epoch 1)
(load "lib/erlang/tokenizer.sx")
(load "lib/erlang/parser.sx")
(load "lib/erlang/parser-core.sx")
(load "lib/erlang/parser-expr.sx")
(load "lib/erlang/parser-module.sx")
(load "lib/erlang/transpile.sx")
(load "lib/erlang/runtime.sx")
(load "lib/erlang/tests/tokenize.sx")
(load "lib/erlang/tests/parse.sx")
(load "lib/erlang/tests/eval.sx")
(load "lib/erlang/tests/runtime.sx")
(load "lib/erlang/tests/programs/ring.sx")
(load "lib/erlang/tests/programs/ping_pong.sx")
(load "lib/erlang/tests/programs/bank.sx")
(load "lib/erlang/tests/programs/echo.sx")
(load "lib/erlang/tests/programs/fib_server.sx")
(epoch 100)
(eval "(list er-test-pass er-test-count)")
(epoch 101)
(eval "(list er-parse-test-pass er-parse-test-count)")
(epoch 102)
(eval "(list er-eval-test-pass er-eval-test-count)")
(epoch 103)
(eval "(list er-rt-test-pass er-rt-test-count)")
(epoch 104)
(eval "(list er-ring-test-pass er-ring-test-count)")
(epoch 105)
(eval "(list er-pp-test-pass er-pp-test-count)")
(epoch 106)
(eval "(list er-bank-test-pass er-bank-test-count)")
(epoch 107)
(eval "(list er-echo-test-pass er-echo-test-count)")
(epoch 108)
(eval "(list er-fib-test-pass er-fib-test-count)")
EPOCHS
timeout 120 "$SX_SERVER" < "$TMPFILE" > "$OUTFILE" 2>&1
# Parse "(N M)" from the line after each "(ok-len <epoch> ...)" marker.
parse_pair() {
local epoch="$1"
local line
line=$(grep -A1 "^(ok-len $epoch " "$OUTFILE" | tail -1)
echo "$line" | sed -E 's/[()]//g'
}
TOTAL_PASS=0
TOTAL_COUNT=0
JSON_SUITES=""
MD_ROWS=""
idx=0
for entry in "${SUITES[@]}"; do
name="${entry%%|*}"
epoch=$((100 + idx))
pair=$(parse_pair "$epoch")
pass=$(echo "$pair" | awk '{print $1}')
count=$(echo "$pair" | awk '{print $2}')
if [ -z "$pass" ] || [ -z "$count" ]; then
pass=0
count=0
fi
TOTAL_PASS=$((TOTAL_PASS + pass))
TOTAL_COUNT=$((TOTAL_COUNT + count))
status="ok"
marker="✅"
if [ "$pass" != "$count" ]; then
status="fail"
marker="❌"
fi
if [ "$VERBOSE" = "-v" ]; then
printf " %-12s %s/%s\n" "$name" "$pass" "$count"
fi
if [ -n "$JSON_SUITES" ]; then JSON_SUITES+=","; fi
JSON_SUITES+=$'\n '
JSON_SUITES+="{\"name\":\"$name\",\"pass\":$pass,\"total\":$count,\"status\":\"$status\"}"
MD_ROWS+="| $marker | $name | $pass | $count |"$'\n'
idx=$((idx + 1))
done
printf '\nErlang-on-SX conformance: %d / %d\n' "$TOTAL_PASS" "$TOTAL_COUNT"
# scoreboard.json
cat > lib/erlang/scoreboard.json <<JSON
{
"language": "erlang",
"total_pass": $TOTAL_PASS,
"total": $TOTAL_COUNT,
"suites": [$JSON_SUITES
]
}
JSON
# scoreboard.md
cat > lib/erlang/scoreboard.md <<MD
# Erlang-on-SX Scoreboard
**Total: ${TOTAL_PASS} / ${TOTAL_COUNT} tests passing**
| | Suite | Pass | Total |
|---|---|---|---|
$MD_ROWS
Generated by \`lib/erlang/conformance.sh\`.
MD
if [ "$TOTAL_PASS" -eq "$TOTAL_COUNT" ]; then
exit 0
else
exit 1
fi

View File

@@ -237,8 +237,6 @@
(er-parse-fun-expr st)
(er-is? st "keyword" "try")
(er-parse-try st)
(er-is? st "punct" "<<")
(er-parse-binary st)
:else (error
(str
"Erlang parse: unexpected "
@@ -283,56 +281,12 @@
(fn
(st)
(er-expect! st "punct" "[")
(cond
(if
(er-is? st "punct" "]")
(do (er-advance! st) {:type "nil"})
:else (let
((first (er-parse-expr-prec st 0)))
(cond
(er-is? st "punct" "||") (er-parse-list-comp st first)
:else (er-parse-list-tail st (list first)))))))
(define
er-parse-list-comp
(fn
(st head)
(er-advance! st)
(let
((quals (list (er-parse-lc-qualifier st))))
(er-parse-list-comp-tail st head quals))))
(define
er-parse-list-comp-tail
(fn
(st head quals)
(cond
(er-is? st "punct" ",")
(do
(er-advance! st)
(append! quals (er-parse-lc-qualifier st))
(er-parse-list-comp-tail st head quals))
(er-is? st "punct" "]")
(do (er-advance! st) {:head head :qualifiers quals :type "lc"})
:else (error
(str
"Erlang parse: expected ',' or ']' in list comprehension, got '"
(er-cur-value st)
"'")))))
(define
er-parse-lc-qualifier
(fn
(st)
(let
((e (er-parse-expr-prec st 0)))
(cond
(er-is? st "punct" "<-")
(do
(er-advance! st)
(let
((source (er-parse-expr-prec st 0)))
{:kind "gen" :pattern e :source source}))
:else {:kind "filter" :expr e}))))
(let
((elems (list (er-parse-expr-prec st 0))))
(er-parse-list-tail st elems)))))
(define
er-parse-list-tail
@@ -578,63 +532,3 @@
((guards (if (er-is? st "keyword" "when") (do (er-advance! st) (er-parse-guards st)) (list))))
(er-expect! st "punct" "->")
(let ((body (er-parse-body st))) {:pattern pat :body body :class klass :guards guards}))))))
;; ── binary literals / patterns ────────────────────────────────
;; `<< [Seg {, Seg}] >>` where Seg = Value [: Size] [/ Spec]. Size is
;; a literal integer (multiple of 8 supported); Spec is `integer`
;; (default) or `binary` (rest-of-binary tail). Sufficient for the
;; common `<<A:8, B:16, Rest/binary>>` patterns.
(define
er-parse-binary
(fn
(st)
(er-expect! st "punct" "<<")
(cond
(er-is? st "punct" ">>")
(do (er-advance! st) {:segments (list) :type "binary"})
:else (let
((segs (list (er-parse-binary-segment st))))
(er-parse-binary-tail st segs)))))
(define
er-parse-binary-tail
(fn
(st segs)
(cond
(er-is? st "punct" ",")
(do
(er-advance! st)
(append! segs (er-parse-binary-segment st))
(er-parse-binary-tail st segs))
(er-is? st "punct" ">>")
(do (er-advance! st) {:segments segs :type "binary"})
:else (error
(str
"Erlang parse: expected ',' or '>>' in binary, got '"
(er-cur-value st)
"'")))))
(define
er-parse-binary-segment
(fn
(st)
;; Use `er-parse-primary` for the value so a leading `:` falls
;; through to the segment's size suffix instead of being eaten
;; by `er-parse-postfix-loop` as a `Mod:Fun` remote call.
(let
((v (er-parse-primary st)))
(let
((size (cond
(er-is? st "punct" ":")
(do (er-advance! st) (er-parse-primary st))
:else nil))
(spec (cond
(er-is? st "op" "/")
(do
(er-advance! st)
(let
((tok (er-cur st)))
(er-advance! st)
(get tok :value)))
:else "integer")))
{:size size :spec spec :value v}))))

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +0,0 @@
{
"language": "erlang",
"total_pass": 530,
"total": 530,
"suites": [
{"name":"tokenize","pass":62,"total":62,"status":"ok"},
{"name":"parse","pass":52,"total":52,"status":"ok"},
{"name":"eval","pass":346,"total":346,"status":"ok"},
{"name":"runtime","pass":39,"total":39,"status":"ok"},
{"name":"ring","pass":4,"total":4,"status":"ok"},
{"name":"ping-pong","pass":4,"total":4,"status":"ok"},
{"name":"bank","pass":8,"total":8,"status":"ok"},
{"name":"echo","pass":7,"total":7,"status":"ok"},
{"name":"fib","pass":8,"total":8,"status":"ok"}
]
}

View File

@@ -1,18 +0,0 @@
# Erlang-on-SX Scoreboard
**Total: 530 / 530 tests passing**
| | Suite | Pass | Total |
|---|---|---|---|
| ✅ | tokenize | 62 | 62 |
| ✅ | parse | 52 | 52 |
| ✅ | eval | 346 | 346 |
| ✅ | runtime | 39 | 39 |
| ✅ | ring | 4 | 4 |
| ✅ | ping-pong | 4 | 4 |
| ✅ | bank | 8 | 8 |
| ✅ | echo | 7 | 7 |
| ✅ | fib | 8 | 8 |
Generated by `lib/erlang/conformance.sh`.

View File

@@ -1,260 +0,0 @@
#!/usr/bin/env bash
# lib/erlang/test.sh — smoke-test the Erlang runtime layer.
# Uses sx_server.exe epoch protocol.
#
# Usage:
# bash lib/erlang/test.sh
# bash lib/erlang/test.sh -v
set -uo pipefail
cd "$(git rev-parse --show-toplevel)"
SX_SERVER="${SX_SERVER:-hosts/ocaml/_build/default/bin/sx_server.exe}"
if [ ! -x "$SX_SERVER" ]; then
SX_SERVER="/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe"
fi
if [ ! -x "$SX_SERVER" ]; then
echo "ERROR: sx_server.exe not found. Run: cd hosts/ocaml && dune build"
exit 1
fi
VERBOSE="${1:-}"
PASS=0; FAIL=0; ERRORS=""
TMPFILE=$(mktemp); trap "rm -f $TMPFILE" EXIT
cat > "$TMPFILE" << 'EPOCHS'
(epoch 1)
(load "lib/erlang/runtime.sx")
;; --- Numeric tower ---
(epoch 10)
(eval "(er-is-integer? 42)")
(epoch 11)
(eval "(er-is-integer? 3.14)")
(epoch 12)
(eval "(er-is-float? 3.14)")
(epoch 13)
(eval "(er-is-float? 42)")
(epoch 14)
(eval "(er-is-number? 42)")
(epoch 15)
(eval "(er-is-number? 3.14)")
(epoch 16)
(eval "(er-float 5)")
(epoch 17)
(eval "(er-trunc 3.9)")
(epoch 18)
(eval "(er-round 3.5)")
(epoch 19)
(eval "(er-abs -7)")
(epoch 20)
(eval "(er-max 3 7)")
(epoch 21)
(eval "(er-min 3 7)")
;; --- div + rem ---
(epoch 30)
(eval "(er-div 10 3)")
(epoch 31)
(eval "(er-div -10 3)")
(epoch 32)
(eval "(er-rem 10 3)")
(epoch 33)
(eval "(er-rem -10 3)")
(epoch 34)
(eval "(er-gcd 12 8)")
;; --- Bitwise ---
(epoch 40)
(eval "(er-band 12 10)")
(epoch 41)
(eval "(er-bor 12 10)")
(epoch 42)
(eval "(er-bxor 12 10)")
(epoch 43)
(eval "(er-bnot 0)")
(epoch 44)
(eval "(er-bsl 1 4)")
(epoch 45)
(eval "(er-bsr 16 2)")
;; --- Sets ---
(epoch 50)
(eval "(er-sets-is-set? (er-sets-new))")
(epoch 51)
(eval "(let ((s (er-sets-new))) (do (er-sets-add-element s 1) (er-sets-is-element s 1)))")
(epoch 52)
(eval "(er-sets-is-element (er-sets-new) 42)")
(epoch 53)
(eval "(er-sets-is-element (er-sets-from-list (list 1 2 3)) 2)")
(epoch 54)
(eval "(er-sets-size (er-sets-from-list (list 1 2 3)))")
(epoch 55)
(eval "(len (er-sets-to-list (er-sets-from-list (list 1 2 3))))")
;; --- Regexp ---
(epoch 60)
(eval "(not (= (er-re-run \"hello\" \"ll\") nil))")
(epoch 61)
(eval "(= (er-re-run \"hello\" \"xyz\") nil)")
(epoch 62)
(eval "(get (er-re-run \"hello\" \"ll\") :match)")
(epoch 63)
(eval "(er-re-replace \"hello\" \"l\" \"r\")")
(epoch 64)
(eval "(er-re-replace-all \"hello\" \"l\" \"r\")")
(epoch 65)
(eval "(er-re-match-groups (er-re-run \"hello world\" \"(\\w+)\\s+(\\w+)\"))")
(epoch 66)
(eval "(len (er-re-split \"a,b,c\" \",\"))")
;; --- List BIFs ---
(epoch 70)
(eval "(er-hd (list 1 2 3))")
(epoch 71)
(eval "(er-tl (list 1 2 3))")
(epoch 72)
(eval "(er-length (list 1 2 3))")
(epoch 73)
(eval "(er-lists-member 2 (list 1 2 3))")
(epoch 74)
(eval "(er-lists-member 9 (list 1 2 3))")
(epoch 75)
(eval "(er-lists-reverse (list 1 2 3))")
(epoch 76)
(eval "(er-lists-nth 2 (list 10 20 30))")
(epoch 77)
(eval "(er-lists-foldl + 0 (list 1 2 3 4 5))")
(epoch 78)
(eval "(er-lists-seq 1 5)")
(epoch 79)
(eval "(er-lists-flatten (list 1 (list 2 3) (list 4 (list 5))))")
;; --- Type conversions ---
(epoch 80)
(eval "(er-integer-to-list 42)")
(epoch 81)
(eval "(er-list-to-integer \"42\")")
(epoch 82)
(eval "(er-integer-to-list-radix 255 16)")
(epoch 83)
(eval "(er-atom-to-list (make-symbol \"hello\"))")
(epoch 84)
(eval "(= (type-of (er-list-to-atom \"foo\")) \"symbol\")")
;; --- ok/error tuples ---
(epoch 90)
(eval "(er-is-ok? (er-ok 42))")
(epoch 91)
(eval "(er-is-error? (er-error \"reason\"))")
(epoch 92)
(eval "(er-unwrap (er-ok 42))")
(epoch 93)
(eval "(er-is-ok? (er-error \"bad\"))")
EPOCHS
OUTPUT=$(timeout 30 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
check() {
local epoch="$1" desc="$2" expected="$3"
local actual
actual=$(echo "$OUTPUT" | grep -A1 "^(ok-len $epoch " | tail -1 || true)
if echo "$actual" | grep -q "^(ok-len"; then actual=""; fi
if [ -z "$actual" ]; then
actual=$(echo "$OUTPUT" | grep "^(ok $epoch " | head -1 || true)
fi
if [ -z "$actual" ]; then
actual=$(echo "$OUTPUT" | grep "^(error $epoch " | head -1 || true)
fi
[ -z "$actual" ] && actual="<no output for epoch $epoch>"
if echo "$actual" | grep -qF -- "$expected"; then
PASS=$((PASS+1))
[ "$VERBOSE" = "-v" ] && echo " ok $desc"
else
FAIL=$((FAIL+1))
ERRORS+=" FAIL [$desc] (epoch $epoch) expected: $expected | actual: $actual
"
fi
}
# Numeric tower
check 10 "is-integer? 42" "true"
check 11 "is-integer? float" "false"
check 12 "is-float? 3.14" "true"
check 13 "is-float? int" "false"
check 14 "is-number? int" "true"
check 15 "is-number? float" "true"
check 16 "float 5" "5"
check 17 "trunc 3.9" "3"
check 18 "round 3.5" "4"
check 19 "abs -7" "7"
check 20 "max 3 7" "7"
check 21 "min 3 7" "3"
# div + rem
check 30 "div 10 3" "3"
check 31 "div -10 3" "-3"
check 32 "rem 10 3" "1"
check 33 "rem -10 3" "-1"
check 34 "gcd 12 8" "4"
# Bitwise
check 40 "band 12 10" "8"
check 41 "bor 12 10" "14"
check 42 "bxor 12 10" "6"
check 43 "bnot 0" "-1"
check 44 "bsl 1 4" "16"
check 45 "bsr 16 2" "4"
# Sets
check 50 "sets-new is-set?" "true"
check 51 "sets add+member" "true"
check 52 "member empty" "false"
check 53 "from-list member" "true"
check 54 "sets-size" "3"
check 55 "sets-to-list len" "3"
# Regexp
check 60 "re-run match" "true"
check 61 "re-run no match" "true"
check 62 "re-run match text" '"ll"'
check 63 "re-replace first" '"herlo"'
check 64 "re-replace-all" '"herro"'
check 65 "re-match-groups" '"hello"'
check 66 "re-split count" "3"
# List BIFs
check 70 "hd" "1"
check 71 "tl" "(2 3)"
check 72 "length" "3"
check 73 "member hit" "true"
check 74 "member miss" "false"
check 75 "reverse" "(3 2 1)"
check 76 "nth 2" "20"
check 77 "foldl sum" "15"
check 78 "seq 1..5" "(1 2 3 4 5)"
check 79 "flatten" "(1 2 3 4 5)"
# Type conversions
check 80 "integer-to-list" '"42"'
check 81 "list-to-integer" "42"
check 82 "integer-to-list hex" '"ff"'
check 83 "atom-to-list" '"hello"'
check 84 "list-to-atom" "true"
# ok/error
check 90 "ok? ok-tuple" "true"
check 91 "error? error-tuple" "true"
check 92 "unwrap ok" "42"
check 93 "ok? error-tuple" "false"
TOTAL=$((PASS+FAIL))
if [ $FAIL -eq 0 ]; then
echo "ok $PASS/$TOTAL lib/erlang tests passed"
else
echo "FAIL $PASS/$TOTAL passed, $FAIL failed:"
echo "$ERRORS"
fi
[ $FAIL -eq 0 ]

File diff suppressed because it is too large Load Diff

View File

@@ -1,159 +0,0 @@
;; Bank account server — stateful process, balance threaded through
;; recursive loop. Handles {deposit, Amt, From}, {withdraw, Amt, From},
;; {balance, From}, stop. Tests stateful process patterns.
(define er-bank-test-count 0)
(define er-bank-test-pass 0)
(define er-bank-test-fails (list))
(define
er-bank-test
(fn
(name actual expected)
(set! er-bank-test-count (+ er-bank-test-count 1))
(if
(= actual expected)
(set! er-bank-test-pass (+ er-bank-test-pass 1))
(append! er-bank-test-fails {:actual actual :expected expected :name name}))))
(define bank-ev erlang-eval-ast)
;; Server fun shared by all tests — threaded via the program string.
(define
er-bank-server-src
"Server = fun (Balance) ->
receive
{deposit, Amt, From} -> From ! ok, Server(Balance + Amt);
{withdraw, Amt, From} ->
if Amt > Balance -> From ! insufficient, Server(Balance);
true -> From ! ok, Server(Balance - Amt)
end;
{balance, From} -> From ! Balance, Server(Balance);
stop -> ok
end
end")
;; Open account, deposit, check balance.
(er-bank-test
"deposit 100 -> balance 100"
(bank-ev
(str
er-bank-server-src
", Me = self(),
Bank = spawn(fun () -> Server(0) end),
Bank ! {deposit, 100, Me},
receive ok -> ok end,
Bank ! {balance, Me},
receive B -> Bank ! stop, B end"))
100)
;; Multiple deposits accumulate.
(er-bank-test
"deposits accumulate"
(bank-ev
(str
er-bank-server-src
", Me = self(),
Bank = spawn(fun () -> Server(0) end),
Bank ! {deposit, 50, Me}, receive ok -> ok end,
Bank ! {deposit, 25, Me}, receive ok -> ok end,
Bank ! {deposit, 10, Me}, receive ok -> ok end,
Bank ! {balance, Me},
receive B -> Bank ! stop, B end"))
85)
;; Withdraw within balance succeeds; insufficient gets rejected.
(er-bank-test
"withdraw within balance"
(bank-ev
(str
er-bank-server-src
", Me = self(),
Bank = spawn(fun () -> Server(100) end),
Bank ! {withdraw, 30, Me}, receive ok -> ok end,
Bank ! {balance, Me},
receive B -> Bank ! stop, B end"))
70)
(er-bank-test
"withdraw insufficient"
(get
(bank-ev
(str
er-bank-server-src
", Me = self(),
Bank = spawn(fun () -> Server(20) end),
Bank ! {withdraw, 100, Me},
receive R -> Bank ! stop, R end"))
:name)
"insufficient")
;; State preserved across an insufficient withdrawal.
(er-bank-test
"state preserved on rejection"
(bank-ev
(str
er-bank-server-src
", Me = self(),
Bank = spawn(fun () -> Server(50) end),
Bank ! {withdraw, 1000, Me}, receive _ -> ok end,
Bank ! {balance, Me},
receive B -> Bank ! stop, B end"))
50)
;; Mixed deposits and withdrawals.
(er-bank-test
"mixed transactions"
(bank-ev
(str
er-bank-server-src
", Me = self(),
Bank = spawn(fun () -> Server(100) end),
Bank ! {deposit, 50, Me}, receive ok -> ok end,
Bank ! {withdraw, 30, Me}, receive ok -> ok end,
Bank ! {deposit, 10, Me}, receive ok -> ok end,
Bank ! {withdraw, 5, Me}, receive ok -> ok end,
Bank ! {balance, Me},
receive B -> Bank ! stop, B end"))
125)
;; Server.stop terminates the bank cleanly — main can verify by
;; sending stop and then exiting normally.
(er-bank-test
"server stops cleanly"
(get
(bank-ev
(str
er-bank-server-src
", Me = self(),
Bank = spawn(fun () -> Server(0) end),
Bank ! stop,
done"))
:name)
"done")
;; Two clients sharing one bank — interleaved transactions.
(er-bank-test
"two clients share bank"
(bank-ev
(str
er-bank-server-src
", Me = self(),
Bank = spawn(fun () -> Server(0) end),
Client = fun (Amt) ->
spawn(fun () ->
Bank ! {deposit, Amt, self()},
receive ok -> Me ! deposited end
end)
end,
Client(40),
Client(60),
receive deposited -> ok end,
receive deposited -> ok end,
Bank ! {balance, Me},
receive B -> Bank ! stop, B end"))
100)
(define
er-bank-test-summary
(str "bank " er-bank-test-pass "/" er-bank-test-count))

View File

@@ -1,140 +0,0 @@
;; Echo server — minimal classic Erlang server. Receives {From, Msg}
;; and sends Msg back to From, then loops. `stop` ends the server.
(define er-echo-test-count 0)
(define er-echo-test-pass 0)
(define er-echo-test-fails (list))
(define
er-echo-test
(fn
(name actual expected)
(set! er-echo-test-count (+ er-echo-test-count 1))
(if
(= actual expected)
(set! er-echo-test-pass (+ er-echo-test-pass 1))
(append! er-echo-test-fails {:actual actual :expected expected :name name}))))
(define echo-ev erlang-eval-ast)
(define
er-echo-server-src
"EchoSrv = fun () ->
Loop = fun () ->
receive
{From, Msg} -> From ! Msg, Loop();
stop -> ok
end
end,
Loop()
end")
;; Single round-trip with an atom.
(er-echo-test
"atom round-trip"
(get
(echo-ev
(str
er-echo-server-src
", Me = self(),
Echo = spawn(EchoSrv),
Echo ! {Me, hello},
receive R -> Echo ! stop, R end"))
:name)
"hello")
;; Number round-trip.
(er-echo-test
"number round-trip"
(echo-ev
(str
er-echo-server-src
", Me = self(),
Echo = spawn(EchoSrv),
Echo ! {Me, 42},
receive R -> Echo ! stop, R end"))
42)
;; Tuple round-trip — pattern-match the reply to extract V.
(er-echo-test
"tuple round-trip"
(echo-ev
(str
er-echo-server-src
", Me = self(),
Echo = spawn(EchoSrv),
Echo ! {Me, {ok, 7}},
receive {ok, V} -> Echo ! stop, V end"))
7)
;; List round-trip.
(er-echo-test
"list round-trip"
(echo-ev
(str
er-echo-server-src
", Me = self(),
Echo = spawn(EchoSrv),
Echo ! {Me, [1, 2, 3]},
receive [H | _] -> Echo ! stop, H end"))
1)
;; Multiple sequential round-trips.
(er-echo-test
"three round-trips"
(echo-ev
(str
er-echo-server-src
", Me = self(),
Echo = spawn(EchoSrv),
Echo ! {Me, 10}, A = receive Ra -> Ra end,
Echo ! {Me, 20}, B = receive Rb -> Rb end,
Echo ! {Me, 30}, C = receive Rc -> Rc end,
Echo ! stop,
A + B + C"))
60)
;; Two clients sharing one echo server. Each gets its own reply.
(er-echo-test
"two clients"
(get
(echo-ev
(str
er-echo-server-src
", Me = self(),
Echo = spawn(EchoSrv),
Client = fun (Tag) ->
spawn(fun () ->
Echo ! {self(), Tag},
receive R -> Me ! {got, R} end
end)
end,
Client(a),
Client(b),
receive {got, _} -> ok end,
receive {got, _} -> ok end,
Echo ! stop,
finished"))
:name)
"finished")
;; Echo via io trace — verify each message round-trips through.
(er-echo-test
"trace 4 messages"
(do
(er-io-flush!)
(echo-ev
(str
er-echo-server-src
", Me = self(),
Echo = spawn(EchoSrv),
Send = fun (V) -> Echo ! {Me, V}, receive R -> io:format(\"~p \", [R]) end end,
Send(1), Send(2), Send(3), Send(4),
Echo ! stop,
done"))
(er-io-buffer-content))
"1 2 3 4 ")
(define
er-echo-test-summary
(str "echo " er-echo-test-pass "/" er-echo-test-count))

View File

@@ -1,152 +0,0 @@
;; Fib server — long-lived process that computes fibonacci numbers on
;; request. Tests recursive function evaluation inside a server loop.
(define er-fib-test-count 0)
(define er-fib-test-pass 0)
(define er-fib-test-fails (list))
(define
er-fib-test
(fn
(name actual expected)
(set! er-fib-test-count (+ er-fib-test-count 1))
(if
(= actual expected)
(set! er-fib-test-pass (+ er-fib-test-pass 1))
(append! er-fib-test-fails {:actual actual :expected expected :name name}))))
(define fib-ev erlang-eval-ast)
;; Fib + server-loop source. Standalone so each test can chain queries.
(define
er-fib-server-src
"Fib = fun (0) -> 0; (1) -> 1; (N) -> Fib(N-1) + Fib(N-2) end,
FibSrv = fun () ->
Loop = fun () ->
receive
{fib, N, From} -> From ! Fib(N), Loop();
stop -> ok
end
end,
Loop()
end")
;; Base cases.
(er-fib-test
"fib(0)"
(fib-ev
(str
er-fib-server-src
", Me = self(),
Srv = spawn(FibSrv),
Srv ! {fib, 0, Me},
receive R -> Srv ! stop, R end"))
0)
(er-fib-test
"fib(1)"
(fib-ev
(str
er-fib-server-src
", Me = self(),
Srv = spawn(FibSrv),
Srv ! {fib, 1, Me},
receive R -> Srv ! stop, R end"))
1)
;; Larger values.
(er-fib-test
"fib(10) = 55"
(fib-ev
(str
er-fib-server-src
", Me = self(),
Srv = spawn(FibSrv),
Srv ! {fib, 10, Me},
receive R -> Srv ! stop, R end"))
55)
(er-fib-test
"fib(15) = 610"
(fib-ev
(str
er-fib-server-src
", Me = self(),
Srv = spawn(FibSrv),
Srv ! {fib, 15, Me},
receive R -> Srv ! stop, R end"))
610)
;; Multiple sequential queries to one server. Sum to avoid dict-equality.
(er-fib-test
"sequential fib(5..8) sum"
(fib-ev
(str
er-fib-server-src
", Me = self(),
Srv = spawn(FibSrv),
Srv ! {fib, 5, Me}, A = receive Ra -> Ra end,
Srv ! {fib, 6, Me}, B = receive Rb -> Rb end,
Srv ! {fib, 7, Me}, C = receive Rc -> Rc end,
Srv ! {fib, 8, Me}, D = receive Rd -> Rd end,
Srv ! stop,
A + B + C + D"))
47)
;; Verify Fib obeys the recurrence — fib(n) = fib(n-1) + fib(n-2).
(er-fib-test
"fib recurrence at n=12"
(fib-ev
(str
er-fib-server-src
", Me = self(),
Srv = spawn(FibSrv),
Srv ! {fib, 10, Me}, A = receive Ra -> Ra end,
Srv ! {fib, 11, Me}, B = receive Rb -> Rb end,
Srv ! {fib, 12, Me}, C = receive Rc -> Rc end,
Srv ! stop,
C - (A + B)"))
0)
;; Two clients each get their own answer; main sums the results.
(er-fib-test
"two clients sum"
(fib-ev
(str
er-fib-server-src
", Me = self(),
Srv = spawn(FibSrv),
Client = fun (N) ->
spawn(fun () ->
Srv ! {fib, N, self()},
receive R -> Me ! {result, R} end
end)
end,
Client(7),
Client(9),
{result, A} = receive M1 -> M1 end,
{result, B} = receive M2 -> M2 end,
Srv ! stop,
A + B"))
47)
;; Trace queries via io-buffer.
(er-fib-test
"trace fib 0..6"
(do
(er-io-flush!)
(fib-ev
(str
er-fib-server-src
", Me = self(),
Srv = spawn(FibSrv),
Ask = fun (N) -> Srv ! {fib, N, Me}, receive R -> io:format(\"~p \", [R]) end end,
Ask(0), Ask(1), Ask(2), Ask(3), Ask(4), Ask(5), Ask(6),
Srv ! stop,
done"))
(er-io-buffer-content))
"0 1 1 2 3 5 8 ")
(define
er-fib-test-summary
(str "fib " er-fib-test-pass "/" er-fib-test-count))

View File

@@ -1,127 +0,0 @@
;; Ping-pong program — two processes exchange N messages, then signal
;; main via separate `ping_done` / `pong_done` notifications.
(define er-pp-test-count 0)
(define er-pp-test-pass 0)
(define er-pp-test-fails (list))
(define
er-pp-test
(fn
(name actual expected)
(set! er-pp-test-count (+ er-pp-test-count 1))
(if
(= actual expected)
(set! er-pp-test-pass (+ er-pp-test-pass 1))
(append! er-pp-test-fails {:actual actual :expected expected :name name}))))
(define pp-ev erlang-eval-ast)
;; Three rounds of ping-pong, then stop. Main receives ping_done and
;; pong_done in arrival order (Ping finishes first because Pong exits
;; only after receiving stop).
(define
er-pp-program
"Me = self(),
Pong = spawn(fun () ->
Loop = fun () ->
receive
{ping, From} -> From ! pong, Loop();
stop -> Me ! pong_done
end
end,
Loop()
end),
Ping = fun (Target, K) ->
if K =:= 0 -> Target ! stop, Me ! ping_done;
true -> Target ! {ping, self()}, receive pong -> Ping(Target, K - 1) end
end
end,
spawn(fun () -> Ping(Pong, 3) end),
receive ping_done -> ok end,
receive pong_done -> both_done end")
(er-pp-test
"ping-pong 3 rounds"
(get (pp-ev er-pp-program) :name)
"both_done")
;; Count exchanges via io-buffer — each pong trip prints "p".
(er-pp-test
"ping-pong 5 rounds trace"
(do
(er-io-flush!)
(pp-ev
"Me = self(),
Pong = spawn(fun () ->
Loop = fun () ->
receive
{ping, From} -> io:format(\"p\"), From ! pong, Loop();
stop -> Me ! pong_done
end
end,
Loop()
end),
Ping = fun (Target, K) ->
if K =:= 0 -> Target ! stop, Me ! ping_done;
true -> Target ! {ping, self()}, receive pong -> Ping(Target, K - 1) end
end
end,
spawn(fun () -> Ping(Pong, 5) end),
receive ping_done -> ok end,
receive pong_done -> ok end")
(er-io-buffer-content))
"ppppp")
;; Main → Pong directly (no Ping process). Main plays the ping role.
(er-pp-test
"main-as-pinger 4 rounds"
(pp-ev
"Me = self(),
Pong = spawn(fun () ->
Loop = fun () ->
receive
{ping, From} -> From ! pong, Loop();
stop -> ok
end
end,
Loop()
end),
Go = fun (K) ->
if K =:= 0 -> Pong ! stop, K;
true -> Pong ! {ping, Me}, receive pong -> Go(K - 1) end
end
end,
Go(4)")
0)
;; Ensure the processes really interleave — inject an id into each
;; ping and check we get them all back via trace (the order is
;; deterministic under our sync scheduler).
(er-pp-test
"ids round-trip"
(do
(er-io-flush!)
(pp-ev
"Me = self(),
Pong = spawn(fun () ->
Loop = fun () ->
receive
{ping, From, Id} -> From ! {pong, Id}, Loop();
stop -> ok
end
end,
Loop()
end),
Go = fun (K) ->
if K =:= 0 -> Pong ! stop, done;
true -> Pong ! {ping, Me, K}, receive {pong, RId} -> io:format(\"~p \", [RId]), Go(K - 1) end
end
end,
Go(4)")
(er-io-buffer-content))
"4 3 2 1 ")
(define
er-pp-test-summary
(str "ping-pong " er-pp-test-pass "/" er-pp-test-count))

View File

@@ -1,132 +0,0 @@
;; Ring program — N processes in a ring, token passes M times.
;;
;; Each process waits for {setup, Next} so main can tie the knot
;; (can't reference a pid before spawning it). Once wired, main
;; injects the first token; each process forwards decrementing K
;; until it hits 0, at which point it signals `done` to main.
(define er-ring-test-count 0)
(define er-ring-test-pass 0)
(define er-ring-test-fails (list))
(define
er-ring-test
(fn
(name actual expected)
(set! er-ring-test-count (+ er-ring-test-count 1))
(if
(= actual expected)
(set! er-ring-test-pass (+ er-ring-test-pass 1))
(append! er-ring-test-fails {:actual actual :expected expected :name name}))))
(define ring-ev erlang-eval-ast)
(define
er-ring-program-3-6
"Me = self(),
Spawner = fun () ->
receive {setup, Next} ->
Loop = fun () ->
receive
{token, 0, Parent} -> Parent ! done;
{token, K, Parent} -> Next ! {token, K-1, Parent}, Loop()
end
end,
Loop()
end
end,
P1 = spawn(Spawner),
P2 = spawn(Spawner),
P3 = spawn(Spawner),
P1 ! {setup, P2},
P2 ! {setup, P3},
P3 ! {setup, P1},
P1 ! {token, 5, Me},
receive done -> finished end")
(er-ring-test
"ring N=3 M=6"
(get (ring-ev er-ring-program-3-6) :name)
"finished")
;; Two-node ring — token bounces twice between P1 and P2.
(er-ring-test
"ring N=2 M=4"
(get (ring-ev
"Me = self(),
Spawner = fun () ->
receive {setup, Next} ->
Loop = fun () ->
receive
{token, 0, Parent} -> Parent ! done;
{token, K, Parent} -> Next ! {token, K-1, Parent}, Loop()
end
end,
Loop()
end
end,
P1 = spawn(Spawner),
P2 = spawn(Spawner),
P1 ! {setup, P2},
P2 ! {setup, P1},
P1 ! {token, 3, Me},
receive done -> done end") :name)
"done")
;; Single-node "ring" — P sends to itself M times.
(er-ring-test
"ring N=1 M=5"
(get (ring-ev
"Me = self(),
Spawner = fun () ->
receive {setup, Next} ->
Loop = fun () ->
receive
{token, 0, Parent} -> Parent ! finished_loop;
{token, K, Parent} -> Next ! {token, K-1, Parent}, Loop()
end
end,
Loop()
end
end,
P = spawn(Spawner),
P ! {setup, P},
P ! {token, 4, Me},
receive finished_loop -> ok end") :name)
"ok")
;; Confirm the token really went around — count hops via io-buffer.
(er-ring-test
"ring N=3 M=9 hop count"
(do
(er-io-flush!)
(ring-ev
"Me = self(),
Spawner = fun () ->
receive {setup, Next} ->
Loop = fun () ->
receive
{token, 0, Parent} -> Parent ! done;
{token, K, Parent} ->
io:format(\"~p \", [K]),
Next ! {token, K-1, Parent},
Loop()
end
end,
Loop()
end
end,
P1 = spawn(Spawner),
P2 = spawn(Spawner),
P3 = spawn(Spawner),
P1 ! {setup, P2},
P2 ! {setup, P3},
P3 ! {setup, P1},
P1 ! {token, 8, Me},
receive done -> done end")
(er-io-buffer-content))
"8 7 6 5 4 3 2 1 ")
(define
er-ring-test-summary
(str "ring " er-ring-test-pass "/" er-ring-test-count))

View File

@@ -1,139 +0,0 @@
;; Erlang runtime tests — scheduler + process-record primitives.
(define er-rt-test-count 0)
(define er-rt-test-pass 0)
(define er-rt-test-fails (list))
(define
er-rt-test
(fn
(name actual expected)
(set! er-rt-test-count (+ er-rt-test-count 1))
(if
(= actual expected)
(set! er-rt-test-pass (+ er-rt-test-pass 1))
(append! er-rt-test-fails {:actual actual :expected expected :name name}))))
;; ── queue ─────────────────────────────────────────────────────────
(er-rt-test "queue empty len" (er-q-len (er-q-new)) 0)
(er-rt-test "queue empty?" (er-q-empty? (er-q-new)) true)
(define q1 (er-q-new))
(er-q-push! q1 "a")
(er-q-push! q1 "b")
(er-q-push! q1 "c")
(er-rt-test "queue push len" (er-q-len q1) 3)
(er-rt-test "queue empty? after push" (er-q-empty? q1) false)
(er-rt-test "queue peek" (er-q-peek q1) "a")
(er-rt-test "queue pop 1" (er-q-pop! q1) "a")
(er-rt-test "queue pop 2" (er-q-pop! q1) "b")
(er-rt-test "queue len after pops" (er-q-len q1) 1)
(er-rt-test "queue pop 3" (er-q-pop! q1) "c")
(er-rt-test "queue empty again" (er-q-empty? q1) true)
(er-rt-test "queue pop empty" (er-q-pop! q1) nil)
;; Queue FIFO under interleaved push/pop
(define q2 (er-q-new))
(er-q-push! q2 1)
(er-q-push! q2 2)
(er-q-pop! q2)
(er-q-push! q2 3)
(er-rt-test "queue interleave peek" (er-q-peek q2) 2)
(er-rt-test "queue to-list" (er-q-to-list q2) (list 2 3))
;; ── scheduler init ─────────────────────────────────────────────
(er-sched-init!)
(er-rt-test "sched process count 0" (er-sched-process-count) 0)
(er-rt-test "sched runnable count 0" (er-sched-runnable-count) 0)
(er-rt-test "sched current nil" (er-sched-current-pid) nil)
;; ── pid allocation ─────────────────────────────────────────────
(define pa (er-pid-new!))
(define pb (er-pid-new!))
(er-rt-test "pid tag" (get pa :tag) "pid")
(er-rt-test "pid ids distinct" (= (er-pid-id pa) (er-pid-id pb)) false)
(er-rt-test "pid? true" (er-pid? pa) true)
(er-rt-test "pid? false" (er-pid? 42) false)
(er-rt-test
"pid-equal same"
(er-pid-equal? pa (er-mk-pid (er-pid-id pa)))
true)
(er-rt-test "pid-equal diff" (er-pid-equal? pa pb) false)
;; ── process lifecycle ──────────────────────────────────────────
(er-sched-init!)
(define p1 (er-proc-new! {}))
(define p2 (er-proc-new! {}))
(er-rt-test "proc count 2" (er-sched-process-count) 2)
(er-rt-test "runnable count 2" (er-sched-runnable-count) 2)
(er-rt-test
"proc state runnable"
(er-proc-field (get p1 :pid) :state)
"runnable")
(er-rt-test
"proc mailbox empty"
(er-proc-mailbox-size (get p1 :pid))
0)
(er-rt-test
"proc lookup"
(er-pid-equal? (get (er-proc-get (get p1 :pid)) :pid) (get p1 :pid))
true)
(er-rt-test "proc exists" (er-proc-exists? (get p1 :pid)) true)
(er-rt-test
"proc no-such-pid"
(er-proc-exists? (er-mk-pid 9999))
false)
;; runnable queue dequeue order
(er-rt-test
"dequeue first"
(er-pid-equal? (er-sched-next-runnable!) (get p1 :pid))
true)
(er-rt-test
"dequeue second"
(er-pid-equal? (er-sched-next-runnable!) (get p2 :pid))
true)
(er-rt-test "dequeue empty" (er-sched-next-runnable!) nil)
;; current-pid get/set
(er-sched-set-current! (get p1 :pid))
(er-rt-test
"current pid set"
(er-pid-equal? (er-sched-current-pid) (get p1 :pid))
true)
;; ── mailbox push ──────────────────────────────────────────────
(er-proc-mailbox-push! (get p1 :pid) {:tag "atom" :name "ping"})
(er-proc-mailbox-push! (get p1 :pid) 42)
(er-rt-test "mailbox size 2" (er-proc-mailbox-size (get p1 :pid)) 2)
;; ── field update ──────────────────────────────────────────────
(er-proc-set! (get p1 :pid) :state "waiting")
(er-rt-test
"proc state waiting"
(er-proc-field (get p1 :pid) :state)
"waiting")
(er-proc-set! (get p1 :pid) :trap-exit true)
(er-rt-test
"proc trap-exit"
(er-proc-field (get p1 :pid) :trap-exit)
true)
;; ── fresh scheduler ends in clean state ───────────────────────
(er-sched-init!)
(er-rt-test
"sched init resets count"
(er-sched-process-count)
0)
(er-rt-test
"sched init resets queue"
(er-sched-runnable-count)
0)
(er-rt-test
"sched init resets current"
(er-sched-current-pid)
nil)
(define
er-rt-test-summary
(str "runtime " er-rt-test-pass "/" er-rt-test-count))

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +0,0 @@
ANS Forth conformance tests — vendored from
https://github.com/gerryjackson/forth2012-test-suite (master, commit-locked
on first fetch: 2026-04-24).
Files in this directory are pristine copies of upstream — do not edit them.
They are consumed by the conformance runner in `lib/forth/conformance.sh`.
- `tester.fr` — John Hayes' test harness (`T{ ... -> ... }T`). (C) 1995
Johns Hopkins APL, distributable under its notice.
- `core.fr` — Core word set tests (Hayes, ~1000 lines).
- `coreexttest.fth` — Core Extension tests (Gerry Jackson).
Only `core.fr` is expected to run green end-to-end for Phase 3; the others
stay parked until later phases.

File diff suppressed because it is too large Load Diff

View File

@@ -1,775 +0,0 @@
\ To test the ANS Forth Core Extension word set
\ This program was written by Gerry Jackson in 2006, with contributions from
\ others where indicated, and is in the public domain - it can be distributed
\ and/or modified in any way but please retain this notice.
\ This program is distributed in the hope that it will be useful,
\ but WITHOUT ANY WARRANTY; without even the implied warranty of
\ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
\ The tests are not claimed to be comprehensive or correct
\ ------------------------------------------------------------------------------
\ Version 0.15 1 August 2025 Added two tests to VALUE
\ 0.14 21 July 2022 Updated first line of BUFFER: test as recommended
\ in issue 32
\ 0.13 28 October 2015
\ Replace <FALSE> and <TRUE> with FALSE and TRUE to avoid
\ dependence on Core tests
\ Moved SAVE-INPUT and RESTORE-INPUT tests in a file to filetest.fth
\ Use of 2VARIABLE (from optional wordset) replaced with CREATE.
\ Minor lower to upper case conversions.
\ Calls to COMPARE replaced by S= (in utilities.fth) to avoid use
\ of a word from an optional word set.
\ UNUSED tests revised as UNUSED UNUSED = may return FALSE when an
\ implementation has the data stack sharing unused dataspace.
\ Double number input dependency removed from the HOLDS tests.
\ Minor case sensitivities removed in definition names.
\ 0.11 25 April 2015
\ Added tests for PARSE-NAME HOLDS BUFFER:
\ S\" tests added
\ DEFER IS ACTION-OF DEFER! DEFER@ tests added
\ Empty CASE statement test added
\ [COMPILE] tests removed because it is obsolescent in Forth 2012
\ 0.10 1 August 2014
\ Added tests contributed by James Bowman for:
\ <> U> 0<> 0> NIP TUCK ROLL PICK 2>R 2R@ 2R>
\ HEX WITHIN UNUSED AGAIN MARKER
\ Added tests for:
\ .R U.R ERASE PAD REFILL SOURCE-ID
\ Removed ABORT from NeverExecuted to enable Win32
\ to continue after failure of RESTORE-INPUT.
\ Removed max-intx which is no longer used.
\ 0.7 6 June 2012 Extra CASE test added
\ 0.6 1 April 2012 Tests placed in the public domain.
\ SAVE-INPUT & RESTORE-INPUT tests, position
\ of T{ moved so that tests work with ttester.fs
\ CONVERT test deleted - obsolete word removed from Forth 200X
\ IMMEDIATE VALUEs tested
\ RECURSE with :NONAME tested
\ PARSE and .( tested
\ Parsing behaviour of C" added
\ 0.5 14 September 2011 Removed the double [ELSE] from the
\ initial SAVE-INPUT & RESTORE-INPUT test
\ 0.4 30 November 2009 max-int replaced with max-intx to
\ avoid redefinition warnings.
\ 0.3 6 March 2009 { and } replaced with T{ and }T
\ CONVERT test now independent of cell size
\ 0.2 20 April 2007 ANS Forth words changed to upper case
\ Tests qd3 to qd6 by Reinhold Straub
\ 0.1 Oct 2006 First version released
\ -----------------------------------------------------------------------------
\ The tests are based on John Hayes test program for the core word set
\ Words tested in this file are:
\ .( .R 0<> 0> 2>R 2R> 2R@ :NONAME <> ?DO AGAIN C" CASE COMPILE, ENDCASE
\ ENDOF ERASE FALSE HEX MARKER NIP OF PAD PARSE PICK REFILL
\ RESTORE-INPUT ROLL SAVE-INPUT SOURCE-ID TO TRUE TUCK U.R U> UNUSED
\ VALUE WITHIN [COMPILE]
\ Words not tested or partially tested:
\ \ because it has been extensively used already and is, hence, unnecessary
\ REFILL and SOURCE-ID from the user input device which are not possible
\ when testing from a file such as this one
\ UNUSED (partially tested) as the value returned is system dependent
\ Obsolescent words #TIB CONVERT EXPECT QUERY SPAN TIB as they have been
\ removed from the Forth 2012 standard
\ Results from words that output to the user output device have to visually
\ checked for correctness. These are .R U.R .(
\ -----------------------------------------------------------------------------
\ Assumptions & dependencies:
\ - tester.fr (or ttester.fs), errorreport.fth and utilities.fth have been
\ included prior to this file
\ - the Core word set available
\ -----------------------------------------------------------------------------
TESTING Core Extension words
DECIMAL
TESTING TRUE FALSE
T{ TRUE -> 0 INVERT }T
T{ FALSE -> 0 }T
\ -----------------------------------------------------------------------------
TESTING <> U> (contributed by James Bowman)
T{ 0 0 <> -> FALSE }T
T{ 1 1 <> -> FALSE }T
T{ -1 -1 <> -> FALSE }T
T{ 1 0 <> -> TRUE }T
T{ -1 0 <> -> TRUE }T
T{ 0 1 <> -> TRUE }T
T{ 0 -1 <> -> TRUE }T
T{ 0 1 U> -> FALSE }T
T{ 1 2 U> -> FALSE }T
T{ 0 MID-UINT U> -> FALSE }T
T{ 0 MAX-UINT U> -> FALSE }T
T{ MID-UINT MAX-UINT U> -> FALSE }T
T{ 0 0 U> -> FALSE }T
T{ 1 1 U> -> FALSE }T
T{ 1 0 U> -> TRUE }T
T{ 2 1 U> -> TRUE }T
T{ MID-UINT 0 U> -> TRUE }T
T{ MAX-UINT 0 U> -> TRUE }T
T{ MAX-UINT MID-UINT U> -> TRUE }T
\ -----------------------------------------------------------------------------
TESTING 0<> 0> (contributed by James Bowman)
T{ 0 0<> -> FALSE }T
T{ 1 0<> -> TRUE }T
T{ 2 0<> -> TRUE }T
T{ -1 0<> -> TRUE }T
T{ MAX-UINT 0<> -> TRUE }T
T{ MIN-INT 0<> -> TRUE }T
T{ MAX-INT 0<> -> TRUE }T
T{ 0 0> -> FALSE }T
T{ -1 0> -> FALSE }T
T{ MIN-INT 0> -> FALSE }T
T{ 1 0> -> TRUE }T
T{ MAX-INT 0> -> TRUE }T
\ -----------------------------------------------------------------------------
TESTING NIP TUCK ROLL PICK (contributed by James Bowman)
T{ 1 2 NIP -> 2 }T
T{ 1 2 3 NIP -> 1 3 }T
T{ 1 2 TUCK -> 2 1 2 }T
T{ 1 2 3 TUCK -> 1 3 2 3 }T
T{ : RO5 100 200 300 400 500 ; -> }T
T{ RO5 3 ROLL -> 100 300 400 500 200 }T
T{ RO5 2 ROLL -> RO5 ROT }T
T{ RO5 1 ROLL -> RO5 SWAP }T
T{ RO5 0 ROLL -> RO5 }T
T{ RO5 2 PICK -> 100 200 300 400 500 300 }T
T{ RO5 1 PICK -> RO5 OVER }T
T{ RO5 0 PICK -> RO5 DUP }T
\ -----------------------------------------------------------------------------
TESTING 2>R 2R@ 2R> (contributed by James Bowman)
T{ : RR0 2>R 100 R> R> ; -> }T
T{ 300 400 RR0 -> 100 400 300 }T
T{ 200 300 400 RR0 -> 200 100 400 300 }T
T{ : RR1 2>R 100 2R@ R> R> ; -> }T
T{ 300 400 RR1 -> 100 300 400 400 300 }T
T{ 200 300 400 RR1 -> 200 100 300 400 400 300 }T
T{ : RR2 2>R 100 2R> ; -> }T
T{ 300 400 RR2 -> 100 300 400 }T
T{ 200 300 400 RR2 -> 200 100 300 400 }T
\ -----------------------------------------------------------------------------
TESTING HEX (contributed by James Bowman)
T{ BASE @ HEX BASE @ DECIMAL BASE @ - SWAP BASE ! -> 6 }T
\ -----------------------------------------------------------------------------
TESTING WITHIN (contributed by James Bowman)
T{ 0 0 0 WITHIN -> FALSE }T
T{ 0 0 MID-UINT WITHIN -> TRUE }T
T{ 0 0 MID-UINT+1 WITHIN -> TRUE }T
T{ 0 0 MAX-UINT WITHIN -> TRUE }T
T{ 0 MID-UINT 0 WITHIN -> FALSE }T
T{ 0 MID-UINT MID-UINT WITHIN -> FALSE }T
T{ 0 MID-UINT MID-UINT+1 WITHIN -> FALSE }T
T{ 0 MID-UINT MAX-UINT WITHIN -> FALSE }T
T{ 0 MID-UINT+1 0 WITHIN -> FALSE }T
T{ 0 MID-UINT+1 MID-UINT WITHIN -> TRUE }T
T{ 0 MID-UINT+1 MID-UINT+1 WITHIN -> FALSE }T
T{ 0 MID-UINT+1 MAX-UINT WITHIN -> FALSE }T
T{ 0 MAX-UINT 0 WITHIN -> FALSE }T
T{ 0 MAX-UINT MID-UINT WITHIN -> TRUE }T
T{ 0 MAX-UINT MID-UINT+1 WITHIN -> TRUE }T
T{ 0 MAX-UINT MAX-UINT WITHIN -> FALSE }T
T{ MID-UINT 0 0 WITHIN -> FALSE }T
T{ MID-UINT 0 MID-UINT WITHIN -> FALSE }T
T{ MID-UINT 0 MID-UINT+1 WITHIN -> TRUE }T
T{ MID-UINT 0 MAX-UINT WITHIN -> TRUE }T
T{ MID-UINT MID-UINT 0 WITHIN -> TRUE }T
T{ MID-UINT MID-UINT MID-UINT WITHIN -> FALSE }T
T{ MID-UINT MID-UINT MID-UINT+1 WITHIN -> TRUE }T
T{ MID-UINT MID-UINT MAX-UINT WITHIN -> TRUE }T
T{ MID-UINT MID-UINT+1 0 WITHIN -> FALSE }T
T{ MID-UINT MID-UINT+1 MID-UINT WITHIN -> FALSE }T
T{ MID-UINT MID-UINT+1 MID-UINT+1 WITHIN -> FALSE }T
T{ MID-UINT MID-UINT+1 MAX-UINT WITHIN -> FALSE }T
T{ MID-UINT MAX-UINT 0 WITHIN -> FALSE }T
T{ MID-UINT MAX-UINT MID-UINT WITHIN -> FALSE }T
T{ MID-UINT MAX-UINT MID-UINT+1 WITHIN -> TRUE }T
T{ MID-UINT MAX-UINT MAX-UINT WITHIN -> FALSE }T
T{ MID-UINT+1 0 0 WITHIN -> FALSE }T
T{ MID-UINT+1 0 MID-UINT WITHIN -> FALSE }T
T{ MID-UINT+1 0 MID-UINT+1 WITHIN -> FALSE }T
T{ MID-UINT+1 0 MAX-UINT WITHIN -> TRUE }T
T{ MID-UINT+1 MID-UINT 0 WITHIN -> TRUE }T
T{ MID-UINT+1 MID-UINT MID-UINT WITHIN -> FALSE }T
T{ MID-UINT+1 MID-UINT MID-UINT+1 WITHIN -> FALSE }T
T{ MID-UINT+1 MID-UINT MAX-UINT WITHIN -> TRUE }T
T{ MID-UINT+1 MID-UINT+1 0 WITHIN -> TRUE }T
T{ MID-UINT+1 MID-UINT+1 MID-UINT WITHIN -> TRUE }T
T{ MID-UINT+1 MID-UINT+1 MID-UINT+1 WITHIN -> FALSE }T
T{ MID-UINT+1 MID-UINT+1 MAX-UINT WITHIN -> TRUE }T
T{ MID-UINT+1 MAX-UINT 0 WITHIN -> FALSE }T
T{ MID-UINT+1 MAX-UINT MID-UINT WITHIN -> FALSE }T
T{ MID-UINT+1 MAX-UINT MID-UINT+1 WITHIN -> FALSE }T
T{ MID-UINT+1 MAX-UINT MAX-UINT WITHIN -> FALSE }T
T{ MAX-UINT 0 0 WITHIN -> FALSE }T
T{ MAX-UINT 0 MID-UINT WITHIN -> FALSE }T
T{ MAX-UINT 0 MID-UINT+1 WITHIN -> FALSE }T
T{ MAX-UINT 0 MAX-UINT WITHIN -> FALSE }T
T{ MAX-UINT MID-UINT 0 WITHIN -> TRUE }T
T{ MAX-UINT MID-UINT MID-UINT WITHIN -> FALSE }T
T{ MAX-UINT MID-UINT MID-UINT+1 WITHIN -> FALSE }T
T{ MAX-UINT MID-UINT MAX-UINT WITHIN -> FALSE }T
T{ MAX-UINT MID-UINT+1 0 WITHIN -> TRUE }T
T{ MAX-UINT MID-UINT+1 MID-UINT WITHIN -> TRUE }T
T{ MAX-UINT MID-UINT+1 MID-UINT+1 WITHIN -> FALSE }T
T{ MAX-UINT MID-UINT+1 MAX-UINT WITHIN -> FALSE }T
T{ MAX-UINT MAX-UINT 0 WITHIN -> TRUE }T
T{ MAX-UINT MAX-UINT MID-UINT WITHIN -> TRUE }T
T{ MAX-UINT MAX-UINT MID-UINT+1 WITHIN -> TRUE }T
T{ MAX-UINT MAX-UINT MAX-UINT WITHIN -> FALSE }T
T{ MIN-INT MIN-INT MIN-INT WITHIN -> FALSE }T
T{ MIN-INT MIN-INT 0 WITHIN -> TRUE }T
T{ MIN-INT MIN-INT 1 WITHIN -> TRUE }T
T{ MIN-INT MIN-INT MAX-INT WITHIN -> TRUE }T
T{ MIN-INT 0 MIN-INT WITHIN -> FALSE }T
T{ MIN-INT 0 0 WITHIN -> FALSE }T
T{ MIN-INT 0 1 WITHIN -> FALSE }T
T{ MIN-INT 0 MAX-INT WITHIN -> FALSE }T
T{ MIN-INT 1 MIN-INT WITHIN -> FALSE }T
T{ MIN-INT 1 0 WITHIN -> TRUE }T
T{ MIN-INT 1 1 WITHIN -> FALSE }T
T{ MIN-INT 1 MAX-INT WITHIN -> FALSE }T
T{ MIN-INT MAX-INT MIN-INT WITHIN -> FALSE }T
T{ MIN-INT MAX-INT 0 WITHIN -> TRUE }T
T{ MIN-INT MAX-INT 1 WITHIN -> TRUE }T
T{ MIN-INT MAX-INT MAX-INT WITHIN -> FALSE }T
T{ 0 MIN-INT MIN-INT WITHIN -> FALSE }T
T{ 0 MIN-INT 0 WITHIN -> FALSE }T
T{ 0 MIN-INT 1 WITHIN -> TRUE }T
T{ 0 MIN-INT MAX-INT WITHIN -> TRUE }T
T{ 0 0 MIN-INT WITHIN -> TRUE }T
T{ 0 0 0 WITHIN -> FALSE }T
T{ 0 0 1 WITHIN -> TRUE }T
T{ 0 0 MAX-INT WITHIN -> TRUE }T
T{ 0 1 MIN-INT WITHIN -> FALSE }T
T{ 0 1 0 WITHIN -> FALSE }T
T{ 0 1 1 WITHIN -> FALSE }T
T{ 0 1 MAX-INT WITHIN -> FALSE }T
T{ 0 MAX-INT MIN-INT WITHIN -> FALSE }T
T{ 0 MAX-INT 0 WITHIN -> FALSE }T
T{ 0 MAX-INT 1 WITHIN -> TRUE }T
T{ 0 MAX-INT MAX-INT WITHIN -> FALSE }T
T{ 1 MIN-INT MIN-INT WITHIN -> FALSE }T
T{ 1 MIN-INT 0 WITHIN -> FALSE }T
T{ 1 MIN-INT 1 WITHIN -> FALSE }T
T{ 1 MIN-INT MAX-INT WITHIN -> TRUE }T
T{ 1 0 MIN-INT WITHIN -> TRUE }T
T{ 1 0 0 WITHIN -> FALSE }T
T{ 1 0 1 WITHIN -> FALSE }T
T{ 1 0 MAX-INT WITHIN -> TRUE }T
T{ 1 1 MIN-INT WITHIN -> TRUE }T
T{ 1 1 0 WITHIN -> TRUE }T
T{ 1 1 1 WITHIN -> FALSE }T
T{ 1 1 MAX-INT WITHIN -> TRUE }T
T{ 1 MAX-INT MIN-INT WITHIN -> FALSE }T
T{ 1 MAX-INT 0 WITHIN -> FALSE }T
T{ 1 MAX-INT 1 WITHIN -> FALSE }T
T{ 1 MAX-INT MAX-INT WITHIN -> FALSE }T
T{ MAX-INT MIN-INT MIN-INT WITHIN -> FALSE }T
T{ MAX-INT MIN-INT 0 WITHIN -> FALSE }T
T{ MAX-INT MIN-INT 1 WITHIN -> FALSE }T
T{ MAX-INT MIN-INT MAX-INT WITHIN -> FALSE }T
T{ MAX-INT 0 MIN-INT WITHIN -> TRUE }T
T{ MAX-INT 0 0 WITHIN -> FALSE }T
T{ MAX-INT 0 1 WITHIN -> FALSE }T
T{ MAX-INT 0 MAX-INT WITHIN -> FALSE }T
T{ MAX-INT 1 MIN-INT WITHIN -> TRUE }T
T{ MAX-INT 1 0 WITHIN -> TRUE }T
T{ MAX-INT 1 1 WITHIN -> FALSE }T
T{ MAX-INT 1 MAX-INT WITHIN -> FALSE }T
T{ MAX-INT MAX-INT MIN-INT WITHIN -> TRUE }T
T{ MAX-INT MAX-INT 0 WITHIN -> TRUE }T
T{ MAX-INT MAX-INT 1 WITHIN -> TRUE }T
T{ MAX-INT MAX-INT MAX-INT WITHIN -> FALSE }T
\ -----------------------------------------------------------------------------
TESTING UNUSED (contributed by James Bowman & Peter Knaggs)
VARIABLE UNUSED0
T{ UNUSED DROP -> }T
T{ ALIGN UNUSED UNUSED0 ! 0 , UNUSED CELL+ UNUSED0 @ = -> TRUE }T
T{ UNUSED UNUSED0 ! 0 C, UNUSED CHAR+ UNUSED0 @ =
-> TRUE }T \ aligned -> unaligned
T{ UNUSED UNUSED0 ! 0 C, UNUSED CHAR+ UNUSED0 @ = -> TRUE }T \ unaligned -> ?
\ -----------------------------------------------------------------------------
TESTING AGAIN (contributed by James Bowman)
T{ : AG0 701 BEGIN DUP 7 MOD 0= IF EXIT THEN 1+ AGAIN ; -> }T
T{ AG0 -> 707 }T
\ -----------------------------------------------------------------------------
TESTING MARKER (contributed by James Bowman)
T{ : MA? BL WORD FIND NIP 0<> ; -> }T
T{ MARKER MA0 -> }T
T{ : MA1 111 ; -> }T
T{ MARKER MA2 -> }T
T{ : MA1 222 ; -> }T
T{ MA? MA0 MA? MA1 MA? MA2 -> TRUE TRUE TRUE }T
T{ MA1 MA2 MA1 -> 222 111 }T
T{ MA? MA0 MA? MA1 MA? MA2 -> TRUE TRUE FALSE }T
T{ MA0 -> }T
T{ MA? MA0 MA? MA1 MA? MA2 -> FALSE FALSE FALSE }T
\ -----------------------------------------------------------------------------
TESTING ?DO
: QD ?DO I LOOP ;
T{ 789 789 QD -> }T
T{ -9876 -9876 QD -> }T
T{ 5 0 QD -> 0 1 2 3 4 }T
: QD1 ?DO I 10 +LOOP ;
T{ 50 1 QD1 -> 1 11 21 31 41 }T
T{ 50 0 QD1 -> 0 10 20 30 40 }T
: QD2 ?DO I 3 > IF LEAVE ELSE I THEN LOOP ;
T{ 5 -1 QD2 -> -1 0 1 2 3 }T
: QD3 ?DO I 1 +LOOP ;
T{ 4 4 QD3 -> }T
T{ 4 1 QD3 -> 1 2 3 }T
T{ 2 -1 QD3 -> -1 0 1 }T
: QD4 ?DO I -1 +LOOP ;
T{ 4 4 QD4 -> }T
T{ 1 4 QD4 -> 4 3 2 1 }T
T{ -1 2 QD4 -> 2 1 0 -1 }T
: QD5 ?DO I -10 +LOOP ;
T{ 1 50 QD5 -> 50 40 30 20 10 }T
T{ 0 50 QD5 -> 50 40 30 20 10 0 }T
T{ -25 10 QD5 -> 10 0 -10 -20 }T
VARIABLE ITERS
VARIABLE INCRMNT
: QD6 ( limit start increment -- )
INCRMNT !
0 ITERS !
?DO
1 ITERS +!
I
ITERS @ 6 = IF LEAVE THEN
INCRMNT @
+LOOP ITERS @
;
T{ 4 4 -1 QD6 -> 0 }T
T{ 1 4 -1 QD6 -> 4 3 2 1 4 }T
T{ 4 1 -1 QD6 -> 1 0 -1 -2 -3 -4 6 }T
T{ 4 1 0 QD6 -> 1 1 1 1 1 1 6 }T
T{ 0 0 0 QD6 -> 0 }T
T{ 1 4 0 QD6 -> 4 4 4 4 4 4 6 }T
T{ 1 4 1 QD6 -> 4 5 6 7 8 9 6 }T
T{ 4 1 1 QD6 -> 1 2 3 3 }T
T{ 4 4 1 QD6 -> 0 }T
T{ 2 -1 -1 QD6 -> -1 -2 -3 -4 -5 -6 6 }T
T{ -1 2 -1 QD6 -> 2 1 0 -1 4 }T
T{ 2 -1 0 QD6 -> -1 -1 -1 -1 -1 -1 6 }T
T{ -1 2 0 QD6 -> 2 2 2 2 2 2 6 }T
T{ -1 2 1 QD6 -> 2 3 4 5 6 7 6 }T
T{ 2 -1 1 QD6 -> -1 0 1 3 }T
\ -----------------------------------------------------------------------------
TESTING BUFFER:
T{ 2 CELLS BUFFER: BUF:TEST -> }T
T{ BUF:TEST DUP ALIGNED = -> TRUE }T
T{ 111 BUF:TEST ! 222 BUF:TEST CELL+ ! -> }T
T{ BUF:TEST @ BUF:TEST CELL+ @ -> 111 222 }T
\ -----------------------------------------------------------------------------
TESTING VALUE TO
T{ 111 VALUE VAL1 -999 VALUE VAL2 -> }T
T{ VAL1 -> 111 }T
T{ VAL2 -> -999 }T
T{ 222 TO VAL1 -> }T
T{ VAL1 -> 222 }T
T{ : VD1 VAL1 ; -> }T
T{ VD1 -> 222 }T
T{ : VD2 TO VAL2 ; -> }T
T{ VAL2 -> -999 }T
T{ -333 VD2 -> }T
T{ VAL2 -> -333 }T
T{ VAL1 -> 222 }T
T{ 444 TO VAL1 -> }T
T{ VD1 -> 444 }T
T{ 123 VALUE VAL3 IMMEDIATE VAL3 -> 123 }T
T{ : VD3 VAL3 LITERAL ; VD3 -> 123 }T
\ -----------------------------------------------------------------------------
TESTING CASE OF ENDOF ENDCASE
: CS1 CASE 1 OF 111 ENDOF
2 OF 222 ENDOF
3 OF 333 ENDOF
>R 999 R>
ENDCASE
;
T{ 1 CS1 -> 111 }T
T{ 2 CS1 -> 222 }T
T{ 3 CS1 -> 333 }T
T{ 4 CS1 -> 999 }T
\ Nested CASE's
: CS2 >R CASE -1 OF CASE R@ 1 OF 100 ENDOF
2 OF 200 ENDOF
>R -300 R>
ENDCASE
ENDOF
-2 OF CASE R@ 1 OF -99 ENDOF
>R -199 R>
ENDCASE
ENDOF
>R 299 R>
ENDCASE R> DROP
;
T{ -1 1 CS2 -> 100 }T
T{ -1 2 CS2 -> 200 }T
T{ -1 3 CS2 -> -300 }T
T{ -2 1 CS2 -> -99 }T
T{ -2 2 CS2 -> -199 }T
T{ 0 2 CS2 -> 299 }T
\ Boolean short circuiting using CASE
: CS3 ( N1 -- N2 )
CASE 1- FALSE OF 11 ENDOF
1- FALSE OF 22 ENDOF
1- FALSE OF 33 ENDOF
44 SWAP
ENDCASE
;
T{ 1 CS3 -> 11 }T
T{ 2 CS3 -> 22 }T
T{ 3 CS3 -> 33 }T
T{ 9 CS3 -> 44 }T
\ Empty CASE statements with/without default
T{ : CS4 CASE ENDCASE ; 1 CS4 -> }T
T{ : CS5 CASE 2 SWAP ENDCASE ; 1 CS5 -> 2 }T
T{ : CS6 CASE 1 OF ENDOF 2 ENDCASE ; 1 CS6 -> }T
T{ : CS7 CASE 3 OF ENDOF 2 ENDCASE ; 1 CS7 -> 1 }T
\ -----------------------------------------------------------------------------
TESTING :NONAME RECURSE
VARIABLE NN1
VARIABLE NN2
:NONAME 1234 ; NN1 !
:NONAME 9876 ; NN2 !
T{ NN1 @ EXECUTE -> 1234 }T
T{ NN2 @ EXECUTE -> 9876 }T
T{ :NONAME ( n -- 0,1,..n ) DUP IF DUP >R 1- RECURSE R> THEN ;
CONSTANT RN1 -> }T
T{ 0 RN1 EXECUTE -> 0 }T
T{ 4 RN1 EXECUTE -> 0 1 2 3 4 }T
:NONAME ( n -- n1 ) \ Multiple RECURSEs in one definition
1- DUP
CASE 0 OF EXIT ENDOF
1 OF 11 SWAP RECURSE ENDOF
2 OF 22 SWAP RECURSE ENDOF
3 OF 33 SWAP RECURSE ENDOF
DROP ABS RECURSE EXIT
ENDCASE
; CONSTANT RN2
T{ 1 RN2 EXECUTE -> 0 }T
T{ 2 RN2 EXECUTE -> 11 0 }T
T{ 4 RN2 EXECUTE -> 33 22 11 0 }T
T{ 25 RN2 EXECUTE -> 33 22 11 0 }T
\ -----------------------------------------------------------------------------
TESTING C"
T{ : CQ1 C" 123" ; -> }T
T{ CQ1 COUNT EVALUATE -> 123 }T
T{ : CQ2 C" " ; -> }T
T{ CQ2 COUNT EVALUATE -> }T
T{ : CQ3 C" 2345"COUNT EVALUATE ; CQ3 -> 2345 }T
\ -----------------------------------------------------------------------------
TESTING COMPILE,
:NONAME DUP + ; CONSTANT DUP+
T{ : Q DUP+ COMPILE, ; -> }T
T{ : AS1 [ Q ] ; -> }T
T{ 123 AS1 -> 246 }T
\ -----------------------------------------------------------------------------
\ Cannot automatically test SAVE-INPUT and RESTORE-INPUT from a console source
TESTING SAVE-INPUT and RESTORE-INPUT with a string source
VARIABLE SI_INC 0 SI_INC !
: SI1
SI_INC @ >IN +!
15 SI_INC !
;
: S$ S" SAVE-INPUT SI1 RESTORE-INPUT 12345" ;
T{ S$ EVALUATE SI_INC @ -> 0 2345 15 }T
\ -----------------------------------------------------------------------------
TESTING .(
CR CR .( Output from .()
T{ CR .( You should see -9876: ) -9876 . -> }T
T{ CR .( and again: ).( -9876)CR -> }T
CR CR .( On the next 2 lines you should see First then Second messages:)
T{ : DOTP CR ." Second message via ." [CHAR] " EMIT \ Check .( is immediate
[ CR ] .( First message via .( ) ; DOTP -> }T
CR CR
T{ : IMM? BL WORD FIND NIP ; IMM? .( -> 1 }T
\ -----------------------------------------------------------------------------
TESTING .R and U.R - has to handle different cell sizes
\ Create some large integers just below/above MAX and Min INTs
MAX-INT 73 79 */ CONSTANT LI1
MIN-INT 71 73 */ CONSTANT LI2
LI1 0 <# #S #> NIP CONSTANT LENLI1
: (.R&U.R) ( u1 u2 -- ) \ u1 <= string length, u2 is required indentation
TUCK + >R
LI1 OVER SPACES . CR R@ LI1 SWAP .R CR
LI2 OVER SPACES . CR R@ 1+ LI2 SWAP .R CR
LI1 OVER SPACES U. CR R@ LI1 SWAP U.R CR
LI2 SWAP SPACES U. CR R> LI2 SWAP U.R CR
;
: .R&U.R ( -- )
CR ." You should see lines duplicated:" CR
." indented by 0 spaces" CR 0 0 (.R&U.R) CR
." indented by 0 spaces" CR LENLI1 0 (.R&U.R) CR \ Just fits required width
." indented by 5 spaces" CR LENLI1 5 (.R&U.R) CR
;
CR CR .( Output from .R and U.R)
T{ .R&U.R -> }T
\ -----------------------------------------------------------------------------
TESTING PAD ERASE
\ Must handle different size characters i.e. 1 CHARS >= 1
84 CONSTANT CHARS/PAD \ Minimum size of PAD in chars
CHARS/PAD CHARS CONSTANT AUS/PAD
: CHECKPAD ( caddr u ch -- f ) \ f = TRUE if u chars = ch
SWAP 0
?DO
OVER I CHARS + C@ OVER <>
IF 2DROP UNLOOP FALSE EXIT THEN
LOOP
2DROP TRUE
;
T{ PAD DROP -> }T
T{ 0 INVERT PAD C! -> }T
T{ PAD C@ CONSTANT MAXCHAR -> }T
T{ PAD CHARS/PAD 2DUP MAXCHAR FILL MAXCHAR CHECKPAD -> TRUE }T
T{ PAD CHARS/PAD 2DUP CHARS ERASE 0 CHECKPAD -> TRUE }T
T{ PAD CHARS/PAD 2DUP MAXCHAR FILL PAD 0 ERASE MAXCHAR CHECKPAD -> TRUE }T
T{ PAD 43 CHARS + 9 CHARS ERASE -> }T
T{ PAD 43 MAXCHAR CHECKPAD -> TRUE }T
T{ PAD 43 CHARS + 9 0 CHECKPAD -> TRUE }T
T{ PAD 52 CHARS + CHARS/PAD 52 - MAXCHAR CHECKPAD -> TRUE }T
\ Check that use of WORD and pictured numeric output do not corrupt PAD
\ Minimum size of buffers for these are 33 chars and (2*n)+2 chars respectively
\ where n is number of bits per cell
PAD CHARS/PAD ERASE
2 BASE !
MAX-UINT MAX-UINT <# #S CHAR 1 DUP HOLD HOLD #> 2DROP
DECIMAL
BL WORD 12345678123456781234567812345678 DROP
T{ PAD CHARS/PAD 0 CHECKPAD -> TRUE }T
\ -----------------------------------------------------------------------------
TESTING PARSE
T{ CHAR | PARSE 1234| DUP ROT ROT EVALUATE -> 4 1234 }T
T{ CHAR ^ PARSE 23 45 ^ DUP ROT ROT EVALUATE -> 7 23 45 }T
: PA1 [CHAR] $ PARSE DUP >R PAD SWAP CHARS MOVE PAD R> ;
T{ PA1 3456
DUP ROT ROT EVALUATE -> 4 3456 }T
T{ CHAR A PARSE A SWAP DROP -> 0 }T
T{ CHAR Z PARSE
SWAP DROP -> 0 }T
T{ CHAR " PARSE 4567 "DUP ROT ROT EVALUATE -> 5 4567 }T
\ -----------------------------------------------------------------------------
TESTING PARSE-NAME (Forth 2012)
\ Adapted from the PARSE-NAME RfD tests
T{ PARSE-NAME abcd STR1 S= -> TRUE }T \ No leading spaces
T{ PARSE-NAME abcde STR2 S= -> TRUE }T \ Leading spaces
\ Test empty parse area, new lines are necessary
T{ PARSE-NAME
NIP -> 0 }T
\ Empty parse area with spaces after PARSE-NAME
T{ PARSE-NAME
NIP -> 0 }T
T{ : PARSE-NAME-TEST ( "name1" "name2" -- n )
PARSE-NAME PARSE-NAME S= ; -> }T
T{ PARSE-NAME-TEST abcd abcd -> TRUE }T
T{ PARSE-NAME-TEST abcd abcd -> TRUE }T \ Leading spaces
T{ PARSE-NAME-TEST abcde abcdf -> FALSE }T
T{ PARSE-NAME-TEST abcdf abcde -> FALSE }T
T{ PARSE-NAME-TEST abcde abcde
-> TRUE }T \ Parse to end of line
T{ PARSE-NAME-TEST abcde abcde
-> TRUE }T \ Leading and trailing spaces
\ -----------------------------------------------------------------------------
TESTING DEFER DEFER@ DEFER! IS ACTION-OF (Forth 2012)
\ Adapted from the Forth 200X RfD tests
T{ DEFER DEFER1 -> }T
T{ : MY-DEFER DEFER ; -> }T
T{ : IS-DEFER1 IS DEFER1 ; -> }T
T{ : ACTION-DEFER1 ACTION-OF DEFER1 ; -> }T
T{ : DEF! DEFER! ; -> }T
T{ : DEF@ DEFER@ ; -> }T
T{ ' * ' DEFER1 DEFER! -> }T
T{ 2 3 DEFER1 -> 6 }T
T{ ' DEFER1 DEFER@ -> ' * }T
T{ ' DEFER1 DEF@ -> ' * }T
T{ ACTION-OF DEFER1 -> ' * }T
T{ ACTION-DEFER1 -> ' * }T
T{ ' + IS DEFER1 -> }T
T{ 1 2 DEFER1 -> 3 }T
T{ ' DEFER1 DEFER@ -> ' + }T
T{ ' DEFER1 DEF@ -> ' + }T
T{ ACTION-OF DEFER1 -> ' + }T
T{ ACTION-DEFER1 -> ' + }T
T{ ' - IS-DEFER1 -> }T
T{ 1 2 DEFER1 -> -1 }T
T{ ' DEFER1 DEFER@ -> ' - }T
T{ ' DEFER1 DEF@ -> ' - }T
T{ ACTION-OF DEFER1 -> ' - }T
T{ ACTION-DEFER1 -> ' - }T
T{ MY-DEFER DEFER2 -> }T
T{ ' DUP IS DEFER2 -> }T
T{ 1 DEFER2 -> 1 1 }T
\ -----------------------------------------------------------------------------
TESTING HOLDS (Forth 2012)
: HTEST S" Testing HOLDS" ;
: HTEST2 S" works" ;
: HTEST3 S" Testing HOLDS works 123" ;
T{ 0 0 <# HTEST HOLDS #> HTEST S= -> TRUE }T
T{ 123 0 <# #S BL HOLD HTEST2 HOLDS BL HOLD HTEST HOLDS #>
HTEST3 S= -> TRUE }T
T{ : HLD HOLDS ; -> }T
T{ 0 0 <# HTEST HLD #> HTEST S= -> TRUE }T
\ -----------------------------------------------------------------------------
TESTING REFILL SOURCE-ID
\ REFILL and SOURCE-ID from the user input device can't be tested from a file,
\ can only be tested from a string via EVALUATE
T{ : RF1 S" REFILL" EVALUATE ; RF1 -> FALSE }T
T{ : SID1 S" SOURCE-ID" EVALUATE ; SID1 -> -1 }T
\ ------------------------------------------------------------------------------
TESTING S\" (Forth 2012 compilation mode)
\ Extended the Forth 200X RfD tests
\ Note this tests the Core Ext definition of S\" which has unedfined
\ interpretation semantics. S\" in interpretation mode is tested in the tests on
\ the File-Access word set
T{ : SSQ1 S\" abc" S" abc" S= ; -> }T \ No escapes
T{ SSQ1 -> TRUE }T
T{ : SSQ2 S\" " ; SSQ2 SWAP DROP -> 0 }T \ Empty string
T{ : SSQ3 S\" \a\b\e\f\l\m\q\r\t\v\x0F0\x1Fa\xaBx\z\"\\" ; -> }T
T{ SSQ3 SWAP DROP -> 20 }T \ String length
T{ SSQ3 DROP C@ -> 7 }T \ \a BEL Bell
T{ SSQ3 DROP 1 CHARS + C@ -> 8 }T \ \b BS Backspace
T{ SSQ3 DROP 2 CHARS + C@ -> 27 }T \ \e ESC Escape
T{ SSQ3 DROP 3 CHARS + C@ -> 12 }T \ \f FF Form feed
T{ SSQ3 DROP 4 CHARS + C@ -> 10 }T \ \l LF Line feed
T{ SSQ3 DROP 5 CHARS + C@ -> 13 }T \ \m CR of CR/LF pair
T{ SSQ3 DROP 6 CHARS + C@ -> 10 }T \ LF of CR/LF pair
T{ SSQ3 DROP 7 CHARS + C@ -> 34 }T \ \q " Double Quote
T{ SSQ3 DROP 8 CHARS + C@ -> 13 }T \ \r CR Carriage Return
T{ SSQ3 DROP 9 CHARS + C@ -> 9 }T \ \t TAB Horizontal Tab
T{ SSQ3 DROP 10 CHARS + C@ -> 11 }T \ \v VT Vertical Tab
T{ SSQ3 DROP 11 CHARS + C@ -> 15 }T \ \x0F Given Char
T{ SSQ3 DROP 12 CHARS + C@ -> 48 }T \ 0 0 Digit follow on
T{ SSQ3 DROP 13 CHARS + C@ -> 31 }T \ \x1F Given Char
T{ SSQ3 DROP 14 CHARS + C@ -> 97 }T \ a a Hex follow on
T{ SSQ3 DROP 15 CHARS + C@ -> 171 }T \ \xaB Insensitive Given Char
T{ SSQ3 DROP 16 CHARS + C@ -> 120 }T \ x x Non hex follow on
T{ SSQ3 DROP 17 CHARS + C@ -> 0 }T \ \z NUL No Character
T{ SSQ3 DROP 18 CHARS + C@ -> 34 }T \ \" " Double Quote
T{ SSQ3 DROP 19 CHARS + C@ -> 92 }T \ \\ \ Back Slash
\ The above does not test \n as this is a system dependent value.
\ Check it displays a new line
CR .( The next test should display:)
CR .( One line...)
CR .( another line)
T{ : SSQ4 S\" \nOne line...\nanotherLine\n" TYPE ; SSQ4 -> }T
\ Test bare escapable characters appear as themselves
T{ : SSQ5 S\" abeflmnqrtvxz" S" abeflmnqrtvxz" S= ; SSQ5 -> TRUE }T
T{ : SSQ6 S\" a\""2DROP 1111 ; SSQ6 -> 1111 }T \ Parsing behaviour
T{ : SSQ7 S\" 111 : SSQ8 S\\\" 222\" EVALUATE ; SSQ8 333" EVALUATE ; -> }T
T{ SSQ7 -> 111 222 333 }T
T{ : SSQ9 S\" 11 : SSQ10 S\\\" \\x32\\x32\" EVALUATE ; SSQ10 33" EVALUATE ; -> }T
T{ SSQ9 -> 11 22 33 }T
\ -----------------------------------------------------------------------------
CORE-EXT-ERRORS SET-ERROR-COUNT
CR .( End of Core Extension word tests) CR

View File

@@ -1,66 +0,0 @@
\ From: John Hayes S1I
\ Subject: tester.fr
\ Date: Mon, 27 Nov 95 13:10:09 PST
\ (C) 1995 JOHNS HOPKINS UNIVERSITY / APPLIED PHYSICS LABORATORY
\ MAY BE DISTRIBUTED FREELY AS LONG AS THIS COPYRIGHT NOTICE REMAINS.
\ VERSION 1.2
\ 24/11/2015 Replaced Core Ext word <> with = 0=
\ 31/3/2015 Variable #ERRORS added and incremented for each error reported.
\ 22/1/09 The words { and } have been changed to T{ and }T respectively to
\ agree with the Forth 200X file ttester.fs. This avoids clashes with
\ locals using { ... } and the FSL use of }
HEX
\ SET THE FOLLOWING FLAG TO TRUE FOR MORE VERBOSE OUTPUT; THIS MAY
\ ALLOW YOU TO TELL WHICH TEST CAUSED YOUR SYSTEM TO HANG.
VARIABLE VERBOSE
FALSE VERBOSE !
\ TRUE VERBOSE !
: EMPTY-STACK \ ( ... -- ) EMPTY STACK: HANDLES UNDERFLOWED STACK TOO.
DEPTH ?DUP IF DUP 0< IF NEGATE 0 DO 0 LOOP ELSE 0 DO DROP LOOP THEN THEN ;
VARIABLE #ERRORS 0 #ERRORS !
: ERROR \ ( C-ADDR U -- ) DISPLAY AN ERROR MESSAGE FOLLOWED BY
\ THE LINE THAT HAD THE ERROR.
CR TYPE SOURCE TYPE \ DISPLAY LINE CORRESPONDING TO ERROR
EMPTY-STACK \ THROW AWAY EVERY THING ELSE
#ERRORS @ 1 + #ERRORS !
\ QUIT \ *** Uncomment this line to QUIT on an error
;
VARIABLE ACTUAL-DEPTH \ STACK RECORD
CREATE ACTUAL-RESULTS 20 CELLS ALLOT
: T{ \ ( -- ) SYNTACTIC SUGAR.
;
: -> \ ( ... -- ) RECORD DEPTH AND CONTENT OF STACK.
DEPTH DUP ACTUAL-DEPTH ! \ RECORD DEPTH
?DUP IF \ IF THERE IS SOMETHING ON STACK
0 DO ACTUAL-RESULTS I CELLS + ! LOOP \ SAVE THEM
THEN ;
: }T \ ( ... -- ) COMPARE STACK (EXPECTED) CONTENTS WITH SAVED
\ (ACTUAL) CONTENTS.
DEPTH ACTUAL-DEPTH @ = IF \ IF DEPTHS MATCH
DEPTH ?DUP IF \ IF THERE IS SOMETHING ON THE STACK
0 DO \ FOR EACH STACK ITEM
ACTUAL-RESULTS I CELLS + @ \ COMPARE ACTUAL WITH EXPECTED
= 0= IF S" INCORRECT RESULT: " ERROR LEAVE THEN
LOOP
THEN
ELSE \ DEPTH MISMATCH
S" WRONG NUMBER OF RESULTS: " ERROR
THEN ;
: TESTING \ ( -- ) TALKING COMMENT.
SOURCE VERBOSE @
IF DUP >R TYPE CR R> >IN !
ELSE >IN ! DROP [CHAR] * EMIT
THEN ;

File diff suppressed because it is too large Load Diff

View File

@@ -1,170 +0,0 @@
#!/usr/bin/env bash
# Run the Hayes/Gerry-Jackson Core conformance suite against our Forth
# interpreter and emit scoreboard.json + scoreboard.md.
#
# Method:
# 1. Preprocess lib/forth/ans-tests/core.fr — strip \ comments, ( ... )
# comments, and TESTING … metadata lines.
# 2. Split into chunks ending at each `}T` so an error in one test
# chunk doesn't abort the run.
# 3. Emit an SX file that exposes those chunks as a list.
# 4. Run our Forth + hayes-runner under sx_server; record pass/fail/error.
set -e
FORTH_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT="$(cd "$FORTH_DIR/../.." && pwd)"
SX_SERVER="${SX_SERVER:-/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe}"
SOURCE="$FORTH_DIR/ans-tests/core.fr"
OUT_JSON="$FORTH_DIR/scoreboard.json"
OUT_MD="$FORTH_DIR/scoreboard.md"
TMP="$(mktemp -d)"
PREPROC="$TMP/preproc.forth"
CHUNKS_SX="$TMP/chunks.sx"
cd "$ROOT"
# 1. preprocess
awk '
{
line = $0
# protect POSTPONE \ so the comment-strip below leaves the literal \ alone
gsub(/POSTPONE[ \t]+\\/, "POSTPONE @@BS@@", line)
# strip leading/embedded \ line comments (must be \ followed by space or EOL)
gsub(/(^|[ \t])\\([ \t].*|$)/, " ", line)
# strip ( ... ) block comments that sit on one line
gsub(/\([^)]*\)/, " ", line)
# strip TESTING … metadata lines (rest of line, incl. bare TESTING)
sub(/TESTING([ \t].*)?$/, " ", line)
# restore the protected backslash
gsub(/@@BS@@/, "\\", line)
print line
}' "$SOURCE" > "$PREPROC"
# 2 + 3: split into chunks at each `}T` and emit as a SX file
#
# Cap chunks via MAX_CHUNKS env (default 638 = full Hayes Core). Lower
# it temporarily if later tests regress into an infinite loop while you
# are iterating on primitives.
MAX_CHUNKS="${MAX_CHUNKS:-638}"
MAX_CHUNKS="$MAX_CHUNKS" python3 - "$PREPROC" "$CHUNKS_SX" <<'PY'
import os, re, sys
preproc_path, out_path = sys.argv[1], sys.argv[2]
max_chunks = int(os.environ.get("MAX_CHUNKS", "590"))
text = open(preproc_path).read()
# keep the `}T` attached to the preceding chunk
parts = re.split(r'(\}T)', text)
chunks = []
buf = ""
for p in parts:
buf += p
if p == "}T":
s = buf.strip()
if s:
chunks.append(s)
buf = ""
if buf.strip():
chunks.append(buf.strip())
chunks = chunks[:max_chunks]
def esc(s):
s = s.replace('\\', '\\\\').replace('"', '\\"')
s = s.replace('\r', ' ').replace('\n', ' ')
s = re.sub(r'\s+', ' ', s).strip()
return s
with open(out_path, "w") as f:
f.write("(define hayes-chunks (list\n")
for c in chunks:
f.write(' "' + esc(c) + '"\n')
f.write("))\n\n")
f.write("(define\n")
f.write(" hayes-run-all\n")
f.write(" (fn\n")
f.write(" ()\n")
f.write(" (hayes-reset!)\n")
f.write(" (let ((s (hayes-boot)))\n")
f.write(" (for-each (fn (c) (hayes-run-chunk s c)) hayes-chunks))\n")
f.write(" (hayes-summary)))\n")
PY
# 4. run it
OUT=$(printf '(epoch 1)\n(load "lib/forth/runtime.sx")\n(epoch 2)\n(load "lib/forth/reader.sx")\n(epoch 3)\n(load "lib/forth/interpreter.sx")\n(epoch 4)\n(load "lib/forth/compiler.sx")\n(epoch 5)\n(load "lib/forth/hayes-runner.sx")\n(epoch 6)\n(load "%s")\n(epoch 7)\n(eval "(hayes-run-all)")\n' "$CHUNKS_SX" \
| timeout 180 "$SX_SERVER" 2>&1)
STATUS=$?
SUMMARY=$(printf '%s\n' "$OUT" | awk '/^\{:pass / {print; exit}')
PASS=$(printf '%s' "$SUMMARY" | sed -n 's/.*:pass \([0-9-]*\).*/\1/p')
FAIL=$(printf '%s' "$SUMMARY" | sed -n 's/.*:fail \([0-9-]*\).*/\1/p')
ERR=$(printf '%s' "$SUMMARY" | sed -n 's/.*:error \([0-9-]*\).*/\1/p')
TOTAL=$(printf '%s' "$SUMMARY" | sed -n 's/.*:total \([0-9-]*\).*/\1/p')
CHUNK_COUNT=$(grep -c '^ "' "$CHUNKS_SX" || echo 0)
TOTAL_AVAILABLE=$(grep -c '}T' "$PREPROC" || echo 0)
NOW="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
if [ -z "$PASS" ]; then
PASS=0; FAIL=0; ERR=0; TOTAL=0
NOTE="runner halted before completing (timeout or SX error)"
else
NOTE="completed"
fi
PCT=0
if [ "$TOTAL" -gt 0 ]; then
PCT=$((PASS * 100 / TOTAL))
fi
cat > "$OUT_JSON" <<JSON
{
"source": "gerryjackson/forth2012-test-suite src/core.fr",
"generated_at": "$NOW",
"chunks_available": $TOTAL_AVAILABLE,
"chunks_fed": $CHUNK_COUNT,
"total": $TOTAL,
"pass": $PASS,
"fail": $FAIL,
"error": $ERR,
"percent": $PCT,
"note": "$NOTE"
}
JSON
cat > "$OUT_MD" <<MD
# Forth Hayes Core scoreboard
| metric | value |
| ----------------- | ----: |
| chunks available | $TOTAL_AVAILABLE |
| chunks fed | $CHUNK_COUNT |
| total | $TOTAL |
| pass | $PASS |
| fail | $FAIL |
| error | $ERR |
| percent | ${PCT}% |
- **Source**: \`gerryjackson/forth2012-test-suite\` \`src/core.fr\`
- **Generated**: $NOW
- **Note**: $NOTE
A "chunk" is any preprocessed segment ending at a \`}T\` (every Hayes test
is one chunk, plus the small declaration blocks between tests).
The runner catches raised errors at chunk boundaries so one bad chunk
does not abort the rest. \`error\` covers chunks that raised; \`fail\`
covers tests whose \`->\` / \`}T\` comparison mismatched.
### Chunk cap
\`conformance.sh\` processes the first \`\$MAX_CHUNKS\` chunks (default
**638**, i.e. the whole Hayes Core file). Lower the cap temporarily
while iterating on primitives if a regression re-opens an infinite
loop in later tests.
MD
echo "$SUMMARY"
echo "Scoreboard: $OUT_JSON"
echo " $OUT_MD"
if [ "$STATUS" -ne 0 ] && [ "$TOTAL" -eq 0 ]; then
exit 1
fi

View File

@@ -1,158 +0,0 @@
;; Hayes conformance test runner.
;; Installs T{ -> }T as Forth primitives that snapshot and compare dstack,
;; plus stub TESTING / HEX / DECIMAL so the Hayes Core file can stream
;; through the interpreter without halting on unsupported metadata words.
(define hayes-pass 0)
(define hayes-fail 0)
(define hayes-error 0)
(define hayes-start-depth 0)
(define hayes-actual (list))
(define hayes-actual-set false)
(define hayes-failures (list))
(define hayes-first-error "")
(define hayes-error-hist (dict))
(define
hayes-reset!
(fn
()
(set! hayes-pass 0)
(set! hayes-fail 0)
(set! hayes-error 0)
(set! hayes-start-depth 0)
(set! hayes-actual (list))
(set! hayes-actual-set false)
(set! hayes-failures (list))
(set! hayes-first-error "")
(set! hayes-error-hist (dict))))
(define
hayes-slice
(fn
(state base)
(let
((n (- (forth-depth state) base)))
(if (<= n 0) (list) (take (get state "dstack") n)))))
(define
hayes-truncate!
(fn
(state base)
(let
((n (- (forth-depth state) base)))
(when (> n 0) (dict-set! state "dstack" (drop (get state "dstack") n))))))
(define
hayes-install!
(fn
(state)
(forth-def-prim!
state
"T{"
(fn
(s)
(set! hayes-start-depth (forth-depth s))
(set! hayes-actual-set false)
(set! hayes-actual (list))))
(forth-def-prim!
state
"->"
(fn
(s)
(set! hayes-actual (hayes-slice s hayes-start-depth))
(set! hayes-actual-set true)
(hayes-truncate! s hayes-start-depth)))
(forth-def-prim!
state
"}T"
(fn
(s)
(let
((expected (hayes-slice s hayes-start-depth)))
(hayes-truncate! s hayes-start-depth)
(if
(and hayes-actual-set (= expected hayes-actual))
(set! hayes-pass (+ hayes-pass 1))
(begin
(set! hayes-fail (+ hayes-fail 1))
(set!
hayes-failures
(concat
hayes-failures
(list
(dict
"kind"
"fail"
"expected"
(str expected)
"actual"
(str hayes-actual))))))))))
(forth-def-prim! state "TESTING" (fn (s) nil))
;; HEX/DECIMAL are real primitives now (runtime.sx) — no stub needed.
state))
(define
hayes-boot
(fn () (let ((s (forth-boot))) (hayes-install! s) (hayes-reset!) s)))
;; Run a single preprocessed chunk (string of Forth source) on the shared
;; state. Catch any raised error and move on — the chunk boundary is a
;; safe resume point.
(define
hayes-bump-error-key!
(fn
(err)
(let
((msg (str err)))
(let
((space-idx (index-of msg " ")))
(let
((key
(if
(> space-idx 0)
(substr msg 0 space-idx)
msg)))
(dict-set!
hayes-error-hist
key
(+ 1 (or (get hayes-error-hist key) 0))))))))
(define
hayes-run-chunk
(fn
(state src)
(guard
(err
((= 1 1)
(begin
(set! hayes-error (+ hayes-error 1))
(when
(= (len hayes-first-error) 0)
(set! hayes-first-error (str err)))
(hayes-bump-error-key! err)
(dict-set! state "dstack" (list))
(dict-set! state "rstack" (list))
(dict-set! state "compiling" false)
(dict-set! state "current-def" nil)
(dict-set! state "cstack" (list))
(dict-set! state "input" (list)))))
(forth-interpret state src))))
(define
hayes-summary
(fn
()
(dict
"pass"
hayes-pass
"fail"
hayes-fail
"error"
hayes-error
"total"
(+ (+ hayes-pass hayes-fail) hayes-error)
"first-error"
hayes-first-error
"error-hist"
hayes-error-hist)))

View File

@@ -5,39 +5,7 @@
(define
forth-execute-word
(fn
(state word)
(dict-set! word "call-count" (+ 1 (or (get word "call-count") 0)))
(let ((body (get word "body"))) (body state))))
(define
forth-hot-words
(fn
(state threshold)
(forth-hot-walk
(keys (get state "dict"))
(get state "dict")
threshold
(list))))
(define
forth-hot-walk
(fn
(names dict threshold acc)
(if
(= (len names) 0)
acc
(let
((n (first names)))
(let
((w (get dict n)))
(let
((c (or (get w "call-count") 0)))
(forth-hot-walk
(rest names)
dict
threshold
(if (>= c threshold) (cons (list n c) acc) acc))))))))
(fn (state word) (let ((body (get word "body"))) (body state))))
(define
forth-interpret-token
@@ -49,7 +17,7 @@
(not (nil? w))
(forth-execute-word state w)
(let
((n (forth-parse-number tok (get (get state "vars") "base"))))
((n (forth-parse-number tok (get state "base"))))
(if
(not (nil? n))
(forth-push state n)

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +0,0 @@
{
"source": "gerryjackson/forth2012-test-suite src/core.fr",
"generated_at": "2026-05-05T21:30:21Z",
"chunks_available": 638,
"chunks_fed": 638,
"total": 638,
"pass": 632,
"fail": 6,
"error": 0,
"percent": 99,
"note": "completed"
}

View File

@@ -1,28 +0,0 @@
# Forth Hayes Core scoreboard
| metric | value |
| ----------------- | ----: |
| chunks available | 638 |
| chunks fed | 638 |
| total | 638 |
| pass | 632 |
| fail | 6 |
| error | 0 |
| percent | 99% |
- **Source**: `gerryjackson/forth2012-test-suite` `src/core.fr`
- **Generated**: 2026-05-05T21:30:21Z
- **Note**: completed
A "chunk" is any preprocessed segment ending at a `}T` (every Hayes test
is one chunk, plus the small declaration blocks between tests).
The runner catches raised errors at chunk boundaries so one bad chunk
does not abort the rest. `error` covers chunks that raised; `fail`
covers tests whose `->` / `}T` comparison mismatched.
### Chunk cap
`conformance.sh` processes the first `$MAX_CHUNKS` chunks (default
**638**, i.e. the whole Hayes Core file). Lower the cap temporarily
while iterating on primitives if a regression re-opens an infinite
loop in later tests.

View File

@@ -1,62 +0,0 @@
#!/usr/bin/env bash
# lib/forth/test.sh — smoke-test the Forth runtime layer.
set -uo pipefail
cd "$(git rev-parse --show-toplevel)"
SX_SERVER="${SX_SERVER:-hosts/ocaml/_build/default/bin/sx_server.exe}"
if [ ! -x "$SX_SERVER" ]; then
SX_SERVER="/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe"
fi
if [ ! -x "$SX_SERVER" ]; then
echo "ERROR: sx_server.exe not found."
exit 1
fi
TMPFILE=$(mktemp); trap "rm -f $TMPFILE" EXIT
cat > "$TMPFILE" << 'EPOCHS'
(epoch 1)
(load "lib/forth/runtime.sx")
(epoch 2)
(load "lib/forth/tests/runtime.sx")
(epoch 3)
(eval "(list forth-test-pass forth-test-fail)")
EPOCHS
OUTPUT=$(timeout 60 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
LINE=$(echo "$OUTPUT" | awk '/^\(ok-len 3 / {getline; print; exit}')
if [ -z "$LINE" ]; then
LINE=$(echo "$OUTPUT" | grep -E '^\(ok 3 \([0-9]+ [0-9]+\)\)' | tail -1 \
| sed -E 's/^\(ok 3 //; s/\)$//')
fi
if [ -z "$LINE" ]; then
echo "ERROR: could not extract summary"
echo "$OUTPUT" | tail -20
exit 1
fi
P=$(echo "$LINE" | sed -E 's/^\(([0-9]+) ([0-9]+)\).*/\1/')
F=$(echo "$LINE" | sed -E 's/^\(([0-9]+) ([0-9]+)\).*/\2/')
TOTAL=$((P + F))
if [ "$F" -eq 0 ]; then
echo "ok $P/$TOTAL lib/forth tests passed"
else
echo "FAIL $P/$TOTAL passed, $F failed"
TMPFILE2=$(mktemp)
cat > "$TMPFILE2" << 'EPOCHS2'
(epoch 1)
(load "lib/forth/runtime.sx")
(epoch 2)
(load "lib/forth/tests/runtime.sx")
(epoch 3)
(eval "(map (fn (f) (list (get f :name) (get f :got) (get f :expected))) forth-test-fails)")
EPOCHS2
FAILS=$(timeout 60 "$SX_SERVER" < "$TMPFILE2" 2>/dev/null | grep -E '^\(ok-len 3' -A1 | tail -1 || true)
echo " Details: $FAILS"
rm -f "$TMPFILE2"
fi
[ "$F" -eq 0 ]

View File

@@ -1,201 +0,0 @@
;; lib/forth/tests/runtime.sx — Tests for lib/forth/runtime.sx
(define forth-test-pass 0)
(define forth-test-fail 0)
(define forth-test-fails (list))
(define
(forth-test name got expected)
(if
(= got expected)
(set! forth-test-pass (+ forth-test-pass 1))
(begin
(set! forth-test-fail (+ forth-test-fail 1))
(set! forth-test-fails (append forth-test-fails (list {:got got :expected expected :name name}))))))
;; ---------------------------------------------------------------------------
;; 1. Bitwise operations
;; ---------------------------------------------------------------------------
;; AND
(forth-test "and 0b1100 0b1010" (forth-and 12 10) 8)
(forth-test "and 0xFF 0x0F" (forth-and 255 15) 15)
(forth-test "and 0 any" (forth-and 0 42) 0)
;; OR
(forth-test "or 0b1100 0b1010" (forth-or 12 10) 14)
(forth-test "or 0 x" (forth-or 0 7) 7)
;; XOR
(forth-test "xor 0b1100 0b1010" (forth-xor 12 10) 6)
(forth-test "xor x x" (forth-xor 42 42) 0)
;; INVERT
(forth-test "invert 0" (forth-invert 0) -1)
(forth-test "invert -1" (forth-invert -1) 0)
(forth-test "invert 1" (forth-invert 1) -2)
;; LSHIFT RSHIFT
(forth-test "lshift 1 3" (forth-lshift 1 3) 8)
(forth-test "lshift 3 2" (forth-lshift 3 2) 12)
(forth-test "rshift 8 3" (forth-rshift 8 3) 1)
(forth-test "rshift 16 2" (forth-rshift 16 2) 4)
;; 2* 2/
(forth-test "2* 5" (forth-2* 5) 10)
(forth-test "2/ 10" (forth-2/ 10) 5)
(forth-test "2/ 7" (forth-2/ 7) 3)
;; BIT-COUNT
(forth-test "bit-count 0" (forth-bit-count 0) 0)
(forth-test "bit-count 1" (forth-bit-count 1) 1)
(forth-test "bit-count 7" (forth-bit-count 7) 3)
(forth-test "bit-count 255" (forth-bit-count 255) 8)
(forth-test "bit-count 256" (forth-bit-count 256) 1)
;; INTEGER-LENGTH
(forth-test "integer-length 0" (forth-integer-length 0) 0)
(forth-test "integer-length 1" (forth-integer-length 1) 1)
(forth-test "integer-length 4" (forth-integer-length 4) 3)
(forth-test "integer-length 255" (forth-integer-length 255) 8)
;; WITHIN
(forth-test
"within 5 0 10"
(forth-within 5 0 10)
true)
(forth-test
"within 0 0 10"
(forth-within 0 0 10)
true)
(forth-test
"within 10 0 10"
(forth-within 10 0 10)
false)
(forth-test
"within -1 0 10"
(forth-within -1 0 10)
false)
;; Arithmetic ops
(forth-test "negate 5" (forth-negate 5) -5)
(forth-test "negate -3" (forth-negate -3) 3)
(forth-test "abs -7" (forth-abs -7) 7)
(forth-test "min 3 5" (forth-min 3 5) 3)
(forth-test "max 3 5" (forth-max 3 5) 5)
(forth-test "mod 7 3" (forth-mod 7 3) 1)
(forth-test
"divmod 7 3"
(forth-divmod 7 3)
(list 1 2))
(forth-test
"divmod 10 5"
(forth-divmod 10 5)
(list 0 2))
;; ---------------------------------------------------------------------------
;; 2. String buffer
;; ---------------------------------------------------------------------------
(define sb1 (forth-sb-new))
(forth-test "sb? new" (forth-sb? sb1) true)
(forth-test "sb? non-sb" (forth-sb? 42) false)
(forth-test "sb value empty" (forth-sb-value sb1) "")
(forth-test "sb length empty" (forth-sb-length sb1) 0)
(forth-sb-type! sb1 "HELLO")
(forth-test "sb type" (forth-sb-value sb1) "HELLO")
(forth-test "sb length after type" (forth-sb-length sb1) 5)
;; EMIT one char
(define sb2 (forth-sb-new))
(forth-sb-emit! sb2 (nth (string->list "A") 0))
(forth-sb-emit! sb2 (nth (string->list "B") 0))
(forth-sb-emit! sb2 (nth (string->list "C") 0))
(forth-test "sb emit chars" (forth-sb-value sb2) "ABC")
;; Emit integer
(define sb3 (forth-sb-new))
(forth-sb-type! sb3 "n=")
(forth-sb-emit-int! sb3 42)
(forth-test "sb emit-int" (forth-sb-value sb3) "n=42")
(forth-sb-clear! sb1)
(forth-test "sb clear" (forth-sb-value sb1) "")
(forth-test "sb length after clear" (forth-sb-length sb1) 0)
;; Build a word definition-style name
(define sb4 (forth-sb-new))
(forth-sb-type! sb4 ": ")
(forth-sb-type! sb4 "SQUARE")
(forth-sb-type! sb4 " DUP * ;")
(forth-test "sb word def" (forth-sb-value sb4) ": SQUARE DUP * ;")
;; ---------------------------------------------------------------------------
;; 3. Memory / Bytevectors
;; ---------------------------------------------------------------------------
(define m1 (forth-mem-new 8))
(forth-test "mem? yes" (forth-mem? m1) true)
(forth-test "mem? no" (forth-mem? 42) false)
(forth-test "mem size" (forth-mem-size m1) 8)
(forth-test "mem cfetch zero" (forth-cfetch m1 0) 0)
;; C! C@
(forth-cstore m1 0 65)
(forth-cstore m1 1 66)
(forth-test "mem cstore/cfetch 0" (forth-cfetch m1 0) 65)
(forth-test "mem cstore/cfetch 1" (forth-cfetch m1 1) 66)
(forth-cstore m1 2 256)
(forth-test
"mem cstore wraps 256→0"
(forth-cfetch m1 2)
0)
(forth-cstore m1 2 257)
(forth-test
"mem cstore wraps 257→1"
(forth-cfetch m1 2)
1)
;; @ ! (32-bit LE cell)
(define m2 (forth-mem-new 8))
(forth-store m2 0 305419896)
(forth-test "mem store/fetch" (forth-fetch m2 0) 305419896)
(forth-store m2 4 1)
(forth-test "mem fetch byte 4" (forth-cfetch m2 4) 1)
(forth-test "mem fetch byte 5" (forth-cfetch m2 5) 0)
;; FILL ERASE
(define m3 (forth-mem-new 4))
(forth-fill! m3 0 4 42)
(forth-test
"mem fill"
(forth-mem->list m3 0 4)
(list 42 42 42 42))
(forth-erase! m3 1 2)
(forth-test
"mem erase middle"
(forth-mem->list m3 0 4)
(list 42 0 0 42))
;; MOVE
(define m4 (forth-mem-new 4))
(forth-cstore m4 0 1)
(forth-cstore m4 1 2)
(forth-cstore m4 2 3)
(define m5 (forth-mem-new 4))
(forth-move! m4 0 m5 0 3)
(forth-test
"mem move"
(forth-mem->list m5 0 3)
(list 1 2 3))
;; mem->list
(define m6 (forth-mem-new 3))
(forth-cstore m6 0 10)
(forth-cstore m6 1 20)
(forth-cstore m6 2 30)
(forth-test
"mem->list"
(forth-mem->list m6 0 3)
(list 10 20 30))

View File

@@ -1,239 +0,0 @@
;; Phase 3 — control flow (IF/ELSE/THEN, BEGIN/UNTIL/WHILE/REPEAT/AGAIN,
;; DO/LOOP, return stack). Grows as each control construct lands.
(define forth-p3-passed 0)
(define forth-p3-failed 0)
(define forth-p3-failures (list))
(define
forth-p3-assert
(fn
(label expected actual)
(if
(= expected actual)
(set! forth-p3-passed (+ forth-p3-passed 1))
(begin
(set! forth-p3-failed (+ forth-p3-failed 1))
(set!
forth-p3-failures
(concat
forth-p3-failures
(list
(str label ": expected " (str expected) " got " (str actual)))))))))
(define
forth-p3-check-stack
(fn
(label src expected)
(let ((r (forth-run src))) (forth-p3-assert label expected (nth r 2)))))
(define
forth-p3-if-tests
(fn
()
(forth-p3-check-stack
"IF taken (-1)"
": Q -1 IF 10 THEN ; Q"
(list 10))
(forth-p3-check-stack
"IF not taken (0)"
": Q 0 IF 10 THEN ; Q"
(list))
(forth-p3-check-stack
"IF with non-zero truthy"
": Q 42 IF 10 THEN ; Q"
(list 10))
(forth-p3-check-stack
"IF ELSE — true branch"
": Q -1 IF 10 ELSE 20 THEN ; Q"
(list 10))
(forth-p3-check-stack
"IF ELSE — false branch"
": Q 0 IF 10 ELSE 20 THEN ; Q"
(list 20))
(forth-p3-check-stack
"IF consumes flag"
": Q IF 1 ELSE 2 THEN ; 0 Q"
(list 2))
(forth-p3-check-stack
"absolute value via IF"
": ABS2 DUP 0 < IF NEGATE THEN ; -7 ABS2"
(list 7))
(forth-p3-check-stack
"abs leaves positive alone"
": ABS2 DUP 0 < IF NEGATE THEN ; 7 ABS2"
(list 7))
(forth-p3-check-stack
"sign: negative"
": SIGN DUP 0 < IF DROP -1 ELSE DROP 1 THEN ; -3 SIGN"
(list -1))
(forth-p3-check-stack
"sign: positive"
": SIGN DUP 0 < IF DROP -1 ELSE DROP 1 THEN ; 3 SIGN"
(list 1))
(forth-p3-check-stack
"nested IF (both true)"
": Q 1 IF 1 IF 10 ELSE 20 THEN ELSE 30 THEN ; Q"
(list 10))
(forth-p3-check-stack
"nested IF (inner false)"
": Q 1 IF 0 IF 10 ELSE 20 THEN ELSE 30 THEN ; Q"
(list 20))
(forth-p3-check-stack
"nested IF (outer false)"
": Q 0 IF 0 IF 10 ELSE 20 THEN ELSE 30 THEN ; Q"
(list 30))
(forth-p3-check-stack
"IF before other ops"
": Q 1 IF 5 ELSE 6 THEN 2 * ; Q"
(list 10))
(forth-p3-check-stack
"IF in chained def"
": POS? 0 > ;
: DOUBLE-IF-POS DUP POS? IF 2 * THEN ;
3 DOUBLE-IF-POS"
(list 6))
(forth-p3-check-stack
"empty then branch"
": Q 1 IF THEN 99 ; Q"
(list 99))
(forth-p3-check-stack
"empty else branch"
": Q 0 IF 99 ELSE THEN ; Q"
(list))
(forth-p3-check-stack
"sequential IF blocks"
": Q -1 IF 1 THEN -1 IF 2 THEN ; Q"
(list 1 2))))
(define
forth-p3-loop-tests
(fn
()
(forth-p3-check-stack
"BEGIN UNTIL (countdown to zero)"
": CD BEGIN 1- DUP 0 = UNTIL ; 3 CD"
(list 0))
(forth-p3-check-stack
"BEGIN UNTIL — single pass (UNTIL true immediately)"
": Q BEGIN -1 UNTIL 42 ; Q"
(list 42))
(forth-p3-check-stack
"BEGIN UNTIL — accumulate sum 1+2+3"
": SUM3 0 3 BEGIN TUCK + SWAP 1- DUP 0 = UNTIL DROP ; SUM3"
(list 6))
(forth-p3-check-stack
"BEGIN WHILE REPEAT — triangular sum 5"
": TRI 0 5 BEGIN DUP 0 > WHILE TUCK + SWAP 1- REPEAT DROP ; TRI"
(list 15))
(forth-p3-check-stack
"BEGIN WHILE REPEAT — zero iterations"
": TRI 0 0 BEGIN DUP 0 > WHILE TUCK + SWAP 1- REPEAT DROP ; TRI"
(list 0))
(forth-p3-check-stack
"BEGIN WHILE REPEAT — one iteration"
": TRI 0 1 BEGIN DUP 0 > WHILE TUCK + SWAP 1- REPEAT DROP ; TRI"
(list 1))
(forth-p3-check-stack
"nested BEGIN UNTIL"
": INNER BEGIN 1- DUP 0 = UNTIL DROP ;
: OUTER BEGIN 3 INNER 1- DUP 0 = UNTIL ;
2 OUTER"
(list 0))
(forth-p3-check-stack
"BEGIN UNTIL after colon prefix"
": TEN 10 ;
: CD TEN BEGIN 1- DUP 0 = UNTIL ;
CD"
(list 0))
(forth-p3-check-stack
"WHILE inside IF branch"
": Q 1 IF 0 3 BEGIN DUP 0 > WHILE TUCK + SWAP 1- REPEAT DROP ELSE 99 THEN ; Q"
(list 6))))
(define
forth-p3-do-tests
(fn
()
(forth-p3-check-stack
"DO LOOP — simple sum 0..4"
": SUM 0 5 0 DO I + LOOP ; SUM"
(list 10))
(forth-p3-check-stack
"DO LOOP — 10..14 sum using I"
": SUM 0 15 10 DO I + LOOP ; SUM"
(list 60))
(forth-p3-check-stack
"DO LOOP — limit = start runs one pass"
": SUM 0 5 5 DO I + LOOP ; SUM"
(list 5))
(forth-p3-check-stack
"DO LOOP — count iterations"
": COUNT 0 4 0 DO 1+ LOOP ; COUNT"
(list 4))
(forth-p3-check-stack
"DO LOOP — nested, I inner / J outer"
": MATRIX 0 3 0 DO 3 0 DO I J + + LOOP LOOP ; MATRIX"
(list 18))
(forth-p3-check-stack
"DO LOOP — I used in arithmetic"
": DBL 0 5 1 DO I 2 * + LOOP ; DBL"
(list 20))
(forth-p3-check-stack
"+LOOP — count by 2"
": Q 0 10 0 DO I + 2 +LOOP ; Q"
(list 20))
(forth-p3-check-stack
"+LOOP — count by 3"
": Q 0 10 0 DO I + 3 +LOOP ; Q"
(list 18))
(forth-p3-check-stack
"+LOOP — negative step"
": Q 0 0 10 DO I + -1 +LOOP ; Q"
(list 55))
(forth-p3-check-stack
"LEAVE — early exit at I=3"
": Q 0 10 0 DO I 3 = IF LEAVE THEN I + LOOP ; Q"
(list 3))
(forth-p3-check-stack
"LEAVE — in nested loop exits only inner"
": Q 0 3 0 DO 5 0 DO I 2 = IF LEAVE THEN I + LOOP LOOP ; Q"
(list 3))
(forth-p3-check-stack
"DO LOOP preserves outer stack"
": Q 99 5 0 DO I + LOOP ; Q"
(list 109))
(forth-p3-check-stack
">R R>"
": Q 7 >R 11 R> ; Q"
(list 11 7))
(forth-p3-check-stack
">R R@ R>"
": Q 7 >R R@ R> ; Q"
(list 7 7))
(forth-p3-check-stack
"2>R 2R>"
": Q 1 2 2>R 99 2R> ; Q"
(list 99 1 2))
(forth-p3-check-stack
"2>R 2R@ 2R>"
": Q 3 4 2>R 2R@ 2R> ; Q"
(list 3 4 3 4))))
(define
forth-p3-run-all
(fn
()
(set! forth-p3-passed 0)
(set! forth-p3-failed 0)
(set! forth-p3-failures (list))
(forth-p3-if-tests)
(forth-p3-loop-tests)
(forth-p3-do-tests)
(dict
"passed"
forth-p3-passed
"failed"
forth-p3-failed
"failures"
forth-p3-failures)))

View File

@@ -1,268 +0,0 @@
;; Phase 4 — strings + more Core.
;; Uses the byte-memory model on state ("mem" dict + "here" cursor).
(define forth-p4-passed 0)
(define forth-p4-failed 0)
(define forth-p4-failures (list))
(define
forth-p4-assert
(fn
(label expected actual)
(if
(= expected actual)
(set! forth-p4-passed (+ forth-p4-passed 1))
(begin
(set! forth-p4-failed (+ forth-p4-failed 1))
(set!
forth-p4-failures
(concat
forth-p4-failures
(list
(str label ": expected " (str expected) " got " (str actual)))))))))
(define
forth-p4-check-output
(fn
(label src expected)
(let ((r (forth-run src))) (forth-p4-assert label expected (nth r 1)))))
(define
forth-p4-check-stack-size
(fn
(label src expected-n)
(let
((r (forth-run src)))
(forth-p4-assert label expected-n (len (nth r 2))))))
(define
forth-p4-check-top
(fn
(label src expected)
(let
((r (forth-run src)))
(let
((stk (nth r 2)))
(forth-p4-assert label expected (nth stk (- (len stk) 1)))))))
(define
forth-p4-check-typed
(fn
(label src expected)
(forth-p4-check-output label (str src " TYPE") expected)))
(define
forth-p4-string-tests
(fn
()
(forth-p4-check-typed
"S\" + TYPE — hello"
"S\" HELLO\""
"HELLO")
(forth-p4-check-typed
"S\" + TYPE — two words"
"S\" HELLO WORLD\""
"HELLO WORLD")
(forth-p4-check-typed
"S\" + TYPE — empty"
"S\" \""
"")
(forth-p4-check-typed
"S\" + TYPE — single char"
"S\" X\""
"X")
(forth-p4-check-stack-size
"S\" pushes (addr len)"
"S\" HI\""
2)
(forth-p4-check-top
"S\" length is correct"
"S\" HELLO\""
5)
(forth-p4-check-output
".\" prints at interpret time"
".\" HELLO\""
"HELLO")
(forth-p4-check-output
".\" in colon def"
": GREET .\" HI \" ; GREET GREET"
"HI HI ")))
(define
forth-p4-count-tests
(fn
()
(forth-p4-check-typed
"C\" + COUNT + TYPE"
"C\" ABC\" COUNT"
"ABC")
(forth-p4-check-typed
"C\" then COUNT leaves right len"
"C\" HI THERE\" COUNT"
"HI THERE")))
(define
forth-p4-fill-tests
(fn
()
(forth-p4-check-typed
"FILL overwrites prefix bytes"
"S\" ABCDE\" 2DUP DROP 3 65 FILL"
"AAADE")
(forth-p4-check-typed
"BLANK sets spaces"
"S\" XYZAB\" 2DUP DROP 3 BLANK"
" AB")))
(define
forth-p4-cmove-tests
(fn
()
(forth-p4-check-output
"CMOVE copies HELLO forward"
": MKH 72 0 C! 69 1 C! 76 2 C! 76 3 C! 79 4 C! ;
: T MKH 0 10 5 CMOVE 10 5 TYPE ; T"
"HELLO")
(forth-p4-check-output
"CMOVE> copies overlapping backward"
": MKA 65 0 C! 66 1 C! 67 2 C! ;
: T MKA 0 1 2 CMOVE> 0 3 TYPE ; T"
"AAB")
(forth-p4-check-output
"MOVE picks direction for overlap"
": MKA 65 0 C! 66 1 C! 67 2 C! ;
: T MKA 0 1 2 MOVE 0 3 TYPE ; T"
"AAB")))
(define
forth-p4-charplus-tests
(fn
()
(forth-p4-check-top
"CHAR+ increments"
"5 CHAR+"
6)))
(define
forth-p4-char-tests
(fn
()
(forth-p4-check-top "CHAR A -> 65" "CHAR A" 65)
(forth-p4-check-top "CHAR x -> 120" "CHAR x" 120)
(forth-p4-check-top "CHAR takes only first char" "CHAR HELLO" 72)
(forth-p4-check-top
"[CHAR] compiles literal"
": AA [CHAR] A ; AA"
65)
(forth-p4-check-top
"[CHAR] reads past IMMEDIATE"
": ZZ [CHAR] Z ; ZZ"
90)
(forth-p4-check-stack-size
"[CHAR] doesn't leak at compile time"
": FOO [CHAR] A ; "
0)))
(define
forth-p4-key-accept-tests
(fn
()
(let
((r (forth-run "1000 2 ACCEPT")))
(let ((stk (nth r 2))) (forth-p4-assert "ACCEPT empty buf -> 0" (list 0) stk)))))
(define
forth-p4-shift-tests
(fn
()
(forth-p4-check-top "1 0 LSHIFT" "1 0 LSHIFT" 1)
(forth-p4-check-top "1 1 LSHIFT" "1 1 LSHIFT" 2)
(forth-p4-check-top "1 2 LSHIFT" "1 2 LSHIFT" 4)
(forth-p4-check-top "1 15 LSHIFT" "1 15 LSHIFT" 32768)
(forth-p4-check-top "1 31 LSHIFT" "1 31 LSHIFT" -2147483648)
(forth-p4-check-top "1 0 RSHIFT" "1 0 RSHIFT" 1)
(forth-p4-check-top "1 1 RSHIFT" "1 1 RSHIFT" 0)
(forth-p4-check-top "2 1 RSHIFT" "2 1 RSHIFT" 1)
(forth-p4-check-top "4 2 RSHIFT" "4 2 RSHIFT" 1)
(forth-p4-check-top "-1 1 RSHIFT (logical, not arithmetic)" "-1 1 RSHIFT" 2147483647)
(forth-p4-check-top "MSB via 1S 1 RSHIFT INVERT" "0 INVERT 1 RSHIFT INVERT" -2147483648)))
(define
forth-p4-sp-tests
(fn
()
(forth-p4-check-top "SP@ returns depth (0)" "SP@" 0)
(forth-p4-check-top
"SP@ after pushes"
"1 2 3 SP@ SWAP DROP SWAP DROP SWAP DROP"
3)
(forth-p4-check-stack-size
"SP! truncates"
"1 2 3 4 5 2 SP!"
2)
(forth-p4-check-top
"SP! leaves base items intact"
"1 2 3 4 5 2 SP!"
2)))
(define
forth-p4-base-tests
(fn
()
(forth-p4-check-top
"BASE default is 10"
"BASE @"
10)
(forth-p4-check-top
"HEX switches base to 16"
"HEX BASE @"
16)
(forth-p4-check-top
"DECIMAL resets to 10"
"HEX DECIMAL BASE @"
10)
(forth-p4-check-top
"HEX parses 10 as 16"
"HEX 10"
16)
(forth-p4-check-top
"HEX parses FF as 255"
"HEX FF"
255)
(forth-p4-check-top
"DECIMAL parses 10 as 10"
"HEX DECIMAL 10"
10)
(forth-p4-check-top
"OCTAL parses 17 as 15"
"OCTAL 17"
15)
(forth-p4-check-top
"BASE @ ; 16 BASE ! ; BASE @"
"BASE @ 16 BASE ! BASE @ SWAP DROP"
16)))
(define
forth-p4-run-all
(fn
()
(set! forth-p4-passed 0)
(set! forth-p4-failed 0)
(set! forth-p4-failures (list))
(forth-p4-string-tests)
(forth-p4-count-tests)
(forth-p4-fill-tests)
(forth-p4-cmove-tests)
(forth-p4-charplus-tests)
(forth-p4-char-tests)
(forth-p4-key-accept-tests)
(forth-p4-base-tests)
(forth-p4-shift-tests)
(forth-p4-sp-tests)
(dict
"passed"
forth-p4-passed
"failed"
forth-p4-failed
"failures"
forth-p4-failures)))

View File

@@ -1,333 +0,0 @@
;; Phase 5 — Core Extension + memory primitives.
(define forth-p5-passed 0)
(define forth-p5-failed 0)
(define forth-p5-failures (list))
(define
forth-p5-assert
(fn
(label expected actual)
(if
(= expected actual)
(set! forth-p5-passed (+ forth-p5-passed 1))
(begin
(set! forth-p5-failed (+ forth-p5-failed 1))
(set!
forth-p5-failures
(concat
forth-p5-failures
(list
(str label ": expected " (str expected) " got " (str actual)))))))))
(define
forth-p5-check-stack
(fn
(label src expected)
(let ((r (forth-run src))) (forth-p5-assert label expected (nth r 2)))))
(define
forth-p5-check-top
(fn
(label src expected)
(let
((r (forth-run src)))
(let
((stk (nth r 2)))
(forth-p5-assert label expected (nth stk (- (len stk) 1)))))))
(define
forth-p5-create-tests
(fn
()
(forth-p5-check-top
"CREATE pushes HERE-at-creation"
"HERE CREATE FOO FOO ="
-1)
(forth-p5-check-top
"CREATE + ALLOT advances HERE"
"HERE 5 ALLOT HERE SWAP -"
5)
(forth-p5-check-top
"CREATE + , stores cell"
"CREATE FOO 42 , FOO @"
42)
(forth-p5-check-stack
"CREATE multiple ,"
"CREATE TBL 1 , 2 , 3 , TBL @ TBL CELL+ @ TBL CELL+ CELL+ @"
(list 1 2 3))
(forth-p5-check-top
"C, stores byte"
"CREATE B 65 C, 66 C, B C@"
65)))
(define
forth-p5-unsigned-tests
(fn
()
(forth-p5-check-top "1 2 U<" "1 2 U<" -1)
(forth-p5-check-top "2 1 U<" "2 1 U<" 0)
(forth-p5-check-top "0 1 U<" "0 1 U<" -1)
(forth-p5-check-top "-1 1 U< (since -1 unsigned is huge)" "-1 1 U<" 0)
(forth-p5-check-top "1 -1 U<" "1 -1 U<" -1)
(forth-p5-check-top "1 2 U>" "1 2 U>" 0)
(forth-p5-check-top "-1 1 U>" "-1 1 U>" -1)))
(define
forth-p5-2bang-tests
(fn
()
(forth-p5-check-stack
"2! / 2@"
"CREATE X 0 , 0 , 11 22 X 2! X 2@"
(list 11 22))))
(define
forth-p5-mixed-tests
(fn
()
(forth-p5-check-stack "S>D positive" "5 S>D" (list 5 0))
(forth-p5-check-stack "S>D negative" "-5 S>D" (list -5 -1))
(forth-p5-check-stack "S>D zero" "0 S>D" (list 0 0))
(forth-p5-check-top "D>S keeps low" "5 0 D>S" 5)
(forth-p5-check-stack "M* small positive" "3 4 M*" (list 12 0))
(forth-p5-check-stack "M* negative" "-3 4 M*" (list -12 -1))
(forth-p5-check-stack
"M* negative * negative"
"-3 -4 M*"
(list 12 0))
(forth-p5-check-stack "UM* small" "3 4 UM*" (list 12 0))
(forth-p5-check-stack
"UM/MOD: 100 0 / 5"
"100 0 5 UM/MOD"
(list 0 20))
(forth-p5-check-stack
"FM/MOD: -7 / 2 floored"
"-7 -1 2 FM/MOD"
(list 1 -4))
(forth-p5-check-stack
"SM/REM: -7 / 2 truncated"
"-7 -1 2 SM/REM"
(list -1 -3))
(forth-p5-check-top "*/ truncated" "7 11 13 */" 5)
(forth-p5-check-stack "*/MOD" "7 11 13 */MOD" (list 12 5))))
(define
forth-p5-double-tests
(fn
()
(forth-p5-check-stack "D+ small" "5 0 7 0 D+" (list 12 0))
(forth-p5-check-stack "D+ negative" "-5 -1 -3 -1 D+" (list -8 -1))
(forth-p5-check-stack "D- small" "10 0 3 0 D-" (list 7 0))
(forth-p5-check-stack "DNEGATE positive" "5 0 DNEGATE" (list -5 -1))
(forth-p5-check-stack "DNEGATE negative" "-5 -1 DNEGATE" (list 5 0))
(forth-p5-check-stack "DABS negative" "-7 -1 DABS" (list 7 0))
(forth-p5-check-stack "DABS positive" "7 0 DABS" (list 7 0))
(forth-p5-check-top "D= equal" "5 0 5 0 D=" -1)
(forth-p5-check-top "D= unequal lo" "5 0 7 0 D=" 0)
(forth-p5-check-top "D= unequal hi" "5 0 5 1 D=" 0)
(forth-p5-check-top "D< lt" "5 0 7 0 D<" -1)
(forth-p5-check-top "D< gt" "7 0 5 0 D<" 0)
(forth-p5-check-top "D0= zero" "0 0 D0=" -1)
(forth-p5-check-top "D0= nonzero" "5 0 D0=" 0)
(forth-p5-check-top "D0< neg" "-5 -1 D0<" -1)
(forth-p5-check-top "D0< pos" "5 0 D0<" 0)
(forth-p5-check-stack "DMAX" "5 0 7 0 DMAX" (list 7 0))
(forth-p5-check-stack "DMIN" "5 0 7 0 DMIN" (list 5 0))))
(define
forth-p5-format-tests
(fn
()
(forth-p4-check-output-passthrough
"U. prints with trailing space"
"123 U."
"123 ")
(forth-p4-check-output-passthrough
"<# #S #> TYPE — decimal"
"123 0 <# #S #> TYPE"
"123")
(forth-p4-check-output-passthrough
"<# #S #> TYPE — hex"
"255 HEX 0 <# #S #> TYPE"
"FF")
(forth-p4-check-output-passthrough
"<# # # #> partial"
"1234 0 <# # # #> TYPE"
"34")
(forth-p4-check-output-passthrough
"SIGN holds minus"
"<# -1 SIGN -1 SIGN 0 0 #> TYPE"
"--")
(forth-p4-check-output-passthrough
".R right-justifies"
"42 5 .R"
" 42")
(forth-p4-check-output-passthrough
".R negative"
"-42 5 .R"
" -42")
(forth-p4-check-output-passthrough
"U.R"
"42 5 U.R"
" 42")
(forth-p4-check-output-passthrough
"HOLD char"
"<# 0 0 65 HOLD #> TYPE"
"A")))
(define
forth-p5-dict-tests
(fn
()
(forth-p5-check-top
"EXECUTE via tick"
": INC 1+ ; 9 ' INC EXECUTE"
10)
(forth-p5-check-top
"['] inside def"
": DUB 2* ; : APPLY ['] DUB EXECUTE ; 5 APPLY"
10)
(forth-p5-check-top
">BODY of CREATE word"
"CREATE C 99 , ' C >BODY @"
99)
(forth-p5-check-stack
"WORD parses next token to counted-string"
": A 5 ; BL WORD A COUNT TYPE"
(list))
(forth-p5-check-top
"FIND on known word -> non-zero"
": A 5 ; BL WORD A FIND SWAP DROP"
-1)))
(define
forth-p5-state-tests
(fn
()
(forth-p5-check-top
"STATE @ in interpret mode"
"STATE @"
0)
(forth-p5-check-top
"STATE @ via IMMEDIATE inside compile"
": GT8 STATE @ ; IMMEDIATE : T GT8 LITERAL ; T"
-1)
(forth-p5-check-top
"[ ] LITERAL captures"
": SEVEN [ 7 ] LITERAL ; SEVEN"
7)
(forth-p5-check-top
"EVALUATE in interpret mode"
"S\" 5 7 +\" EVALUATE"
12)
(forth-p5-check-top
"EVALUATE inside def"
": A 100 ; : B S\" A\" EVALUATE ; B"
100)))
(define
forth-p5-misc-tests
(fn
()
(forth-p5-check-top "WITHIN inclusive lower" "3 2 10 WITHIN" -1)
(forth-p5-check-top "WITHIN exclusive upper" "10 2 10 WITHIN" 0)
(forth-p5-check-top "WITHIN below range" "1 2 10 WITHIN" 0)
(forth-p5-check-top "WITHIN at lower" "2 2 10 WITHIN" -1)
(forth-p5-check-top
"EXIT leaves colon-def early"
": F 5 EXIT 99 ; F"
5)
(forth-p5-check-stack
"EXIT in IF branch"
": F 5 0 IF DROP 99 EXIT THEN ; F"
(list 5))
(forth-p5-check-top
"UNLOOP + EXIT in DO"
": SUM 0 10 0 DO I 5 = IF I UNLOOP EXIT THEN LOOP ; SUM"
5)))
(define
forth-p5-fa-tests
(fn
()
(forth-p5-check-top
"R/O R/W W/O constants"
"R/O R/W W/O + +"
3)
(forth-p5-check-top
"CREATE-FILE returns ior=0"
"CREATE PAD 50 ALLOT PAD S\" /tmp/test.fxf\" ROT SWAP CMOVE S\" /tmp/test.fxf\" R/W CREATE-FILE SWAP DROP"
0)
(forth-p5-check-top
"WRITE-FILE then CLOSE"
"S\" /tmp/t2.fxf\" R/W CREATE-FILE DROP >R S\" HI\" R@ WRITE-FILE R> CLOSE-FILE +"
0)
(forth-p5-check-top
"OPEN-FILE on unknown path returns ior!=0"
"S\" /tmp/nope.fxf\" R/O OPEN-FILE SWAP DROP 0 ="
0)))
(define
forth-p5-string-tests
(fn
()
(forth-p5-check-top "COMPARE equal" "S\" ABC\" S\" ABC\" COMPARE" 0)
(forth-p5-check-top "COMPARE less" "S\" ABC\" S\" ABD\" COMPARE" -1)
(forth-p5-check-top "COMPARE greater" "S\" ABD\" S\" ABC\" COMPARE" 1)
(forth-p5-check-top
"COMPARE prefix less"
"S\" AB\" S\" ABC\" COMPARE"
-1)
(forth-p5-check-top
"COMPARE prefix greater"
"S\" ABC\" S\" AB\" COMPARE"
1)
(forth-p5-check-top
"SEARCH found flag"
"S\" HELLO WORLD\" S\" WORLD\" SEARCH"
-1)
(forth-p5-check-top
"SEARCH not found flag"
"S\" HELLO\" S\" XYZ\" SEARCH"
0)
(forth-p5-check-top
"SEARCH empty needle flag"
"S\" HELLO\" S\" \" SEARCH"
-1)
(forth-p5-check-top
"SLITERAL via [ S\" ... \" ]"
": A [ S\" HI\" ] SLITERAL ; A SWAP DROP"
2)))
(define
forth-p4-check-output-passthrough
(fn
(label src expected)
(let ((r (forth-run src))) (forth-p5-assert label expected (nth r 1)))))
(define
forth-p5-run-all
(fn
()
(set! forth-p5-passed 0)
(set! forth-p5-failed 0)
(set! forth-p5-failures (list))
(forth-p5-create-tests)
(forth-p5-unsigned-tests)
(forth-p5-2bang-tests)
(forth-p5-mixed-tests)
(forth-p5-double-tests)
(forth-p5-format-tests)
(forth-p5-dict-tests)
(forth-p5-state-tests)
(forth-p5-misc-tests)
(forth-p5-fa-tests)
(forth-p5-string-tests)
(dict
"passed"
forth-p5-passed
"failed"
forth-p5-failed
"failures"
forth-p5-failures)))

140
lib/haskell/conformance.sh Executable file
View File

@@ -0,0 +1,140 @@
#!/usr/bin/env bash
# lib/haskell/conformance.sh — run the 5 classic-program test suites.
# Writes lib/haskell/scoreboard.json and lib/haskell/scoreboard.md.
#
# Usage:
# bash lib/haskell/conformance.sh # run + write scoreboards
# bash lib/haskell/conformance.sh --check # run only, exit 1 on failure
set -euo pipefail
cd "$(git rev-parse --show-toplevel)"
SX_SERVER="hosts/ocaml/_build/default/bin/sx_server.exe"
if [ ! -x "$SX_SERVER" ]; then
MAIN_ROOT=$(git worktree list | head -1 | awk '{print $1}')
if [ -x "$MAIN_ROOT/$SX_SERVER" ]; then
SX_SERVER="$MAIN_ROOT/$SX_SERVER"
else
echo "ERROR: sx_server.exe not found. Run: cd hosts/ocaml && dune build"
exit 1
fi
fi
PROGRAMS=(fib sieve quicksort nqueens calculator)
PASS_COUNTS=()
FAIL_COUNTS=()
run_suite() {
local prog="$1"
local FILE="lib/haskell/tests/program-${prog}.sx"
local TMPFILE
TMPFILE=$(mktemp)
cat > "$TMPFILE" <<EPOCHS
(epoch 1)
(load "lib/haskell/tokenizer.sx")
(load "lib/haskell/layout.sx")
(load "lib/haskell/parser.sx")
(load "lib/haskell/desugar.sx")
(load "lib/haskell/runtime.sx")
(load "lib/haskell/match.sx")
(load "lib/haskell/eval.sx")
(load "lib/haskell/testlib.sx")
(epoch 2)
(load "$FILE")
(epoch 3)
(eval "(list hk-test-pass hk-test-fail)")
EPOCHS
local OUTPUT
OUTPUT=$(timeout 120 "$SX_SERVER" < "$TMPFILE" 2>&1 || true)
rm -f "$TMPFILE"
local LINE
LINE=$(echo "$OUTPUT" | awk '/^\(ok-len 3 / {getline; print; exit}')
if [ -z "$LINE" ]; then
LINE=$(echo "$OUTPUT" | grep -E '^\(ok 3 \([0-9]+ [0-9]+\)\)' | tail -1 \
| sed -E 's/^\(ok 3 //; s/\)$//' || true)
fi
if [ -z "$LINE" ]; then
echo "0 1"
else
local P F
P=$(echo "$LINE" | sed -E 's/^\(([0-9]+) ([0-9]+)\).*/\1/' || echo "0")
F=$(echo "$LINE" | sed -E 's/^\(([0-9]+) ([0-9]+)\).*/\2/' || echo "1")
echo "$P $F"
fi
}
for prog in "${PROGRAMS[@]}"; do
RESULT=$(run_suite "$prog")
P=$(echo "$RESULT" | cut -d' ' -f1)
F=$(echo "$RESULT" | cut -d' ' -f2)
PASS_COUNTS+=("$P")
FAIL_COUNTS+=("$F")
T=$((P + F))
if [ "$F" -eq 0 ]; then
printf '✓ %-14s %d/%d\n' "${prog}.hs" "$P" "$T"
else
printf '✗ %-14s %d/%d\n' "${prog}.hs" "$P" "$T"
fi
done
TOTAL_PASS=0
TOTAL_FAIL=0
PROG_PASS=0
for i in "${!PROGRAMS[@]}"; do
TOTAL_PASS=$((TOTAL_PASS + PASS_COUNTS[i]))
TOTAL_FAIL=$((TOTAL_FAIL + FAIL_COUNTS[i]))
[ "${FAIL_COUNTS[$i]}" -eq 0 ] && PROG_PASS=$((PROG_PASS + 1))
done
PROG_TOTAL=${#PROGRAMS[@]}
echo ""
echo "Classic programs: ${TOTAL_PASS}/$((TOTAL_PASS + TOTAL_FAIL)) tests | ${PROG_PASS}/${PROG_TOTAL} programs passing"
if [[ "${1:-}" == "--check" ]]; then
[ $TOTAL_FAIL -eq 0 ]
exit $?
fi
DATE=$(date '+%Y-%m-%d')
# scoreboard.json
{
printf '{\n'
printf ' "date": "%s",\n' "$DATE"
printf ' "total_pass": %d,\n' "$TOTAL_PASS"
printf ' "total_fail": %d,\n' "$TOTAL_FAIL"
printf ' "programs": {\n'
last=$((${#PROGRAMS[@]} - 1))
for i in "${!PROGRAMS[@]}"; do
prog="${PROGRAMS[$i]}"
if [ $i -lt $last ]; then
printf ' "%s": {"pass": %d, "fail": %d},\n' "$prog" "${PASS_COUNTS[$i]}" "${FAIL_COUNTS[$i]}"
else
printf ' "%s": {"pass": %d, "fail": %d}\n' "$prog" "${PASS_COUNTS[$i]}" "${FAIL_COUNTS[$i]}"
fi
done
printf ' }\n'
printf '}\n'
} > lib/haskell/scoreboard.json
# scoreboard.md
{
printf '# Haskell-on-SX Scoreboard\n\n'
printf 'Updated %s · Phase 3 (laziness + classic programs)\n\n' "$DATE"
printf '| Program | Tests | Status |\n'
printf '|---------|-------|--------|\n'
for i in "${!PROGRAMS[@]}"; do
prog="${PROGRAMS[$i]}"
P=${PASS_COUNTS[$i]}
F=${FAIL_COUNTS[$i]}
T=$((P + F))
[ "$F" -eq 0 ] && STATUS="✓" || STATUS="✗"
printf '| %s | %d/%d | %s |\n' "${prog}.hs" "$P" "$T" "$STATUS"
done
printf '| **Total** | **%d/%d** | **%d/%d programs** |\n' \
"$TOTAL_PASS" "$((TOTAL_PASS + TOTAL_FAIL))" "$PROG_PASS" "$PROG_TOTAL"
} > lib/haskell/scoreboard.md
echo "Wrote lib/haskell/scoreboard.json and lib/haskell/scoreboard.md"
[ $TOTAL_FAIL -eq 0 ]

249
lib/haskell/desugar.sx Normal file
View File

@@ -0,0 +1,249 @@
;; Desugar the Haskell surface AST into a smaller core AST.
;;
;; Eliminates the three surface-only shapes produced by the parser:
;; :where BODY DECLS → :let DECLS BODY
;; :guarded GUARDS → :if C1 E1 (:if C2 E2 … (:app error …))
;; :list-comp EXPR QUALS → concatMap-based expression (§3.11)
;;
;; Everything else (:app, :op, :lambda, :let, :case, :do, :tuple,
;; :list, :range, :if, :neg, :sect-left / :sect-right, plus all
;; leaf forms and pattern / type nodes) is passed through after
;; recursing into children.
(define
hk-guards-to-if
(fn
(guards)
(cond
((empty? guards)
(list
:app
(list :var "error")
(list :string "Non-exhaustive guards")))
(:else
(let
((g (first guards)))
(list
:if
(hk-desugar (nth g 1))
(hk-desugar (nth g 2))
(hk-guards-to-if (rest guards))))))))
;; do-notation desugaring (Haskell 98 §3.14):
;; do { e } = e
;; do { e ; ss } = e >> do { ss }
;; do { p <- e ; ss } = e >>= \p -> do { ss }
;; do { let decls ; ss } = let decls in do { ss }
(define
hk-desugar-do
(fn
(stmts)
(cond
((empty? stmts) (raise "empty do block"))
((empty? (rest stmts))
(let ((s (first stmts)))
(cond
((= (first s) "do-expr") (hk-desugar (nth s 1)))
(:else
(raise "do block must end with an expression")))))
(:else
(let
((s (first stmts)) (rest-stmts (rest stmts)))
(let
((rest-do (hk-desugar-do rest-stmts)))
(cond
((= (first s) "do-expr")
(list
:app
(list
:app
(list :var ">>")
(hk-desugar (nth s 1)))
rest-do))
((= (first s) "do-bind")
(list
:app
(list
:app
(list :var ">>=")
(hk-desugar (nth s 2)))
(list :lambda (list (nth s 1)) rest-do)))
((= (first s) "do-let")
(list
:let
(map hk-desugar (nth s 1))
rest-do))
(:else (raise "unknown do-stmt tag")))))))))
;; List-comprehension desugaring (Haskell 98 §3.11):
;; [e | ] = [e]
;; [e | b, Q ] = if b then [e | Q] else []
;; [e | p <- l, Q ] = concatMap (\p -> [e | Q]) l
;; [e | let ds, Q ] = let ds in [e | Q]
(define
hk-lc-desugar
(fn
(e quals)
(cond
((empty? quals) (list :list (list e)))
(:else
(let
((q (first quals)))
(let
((qtag (first q)))
(cond
((= qtag "q-guard")
(list
:if
(hk-desugar (nth q 1))
(hk-lc-desugar e (rest quals))
(list :list (list))))
((= qtag "q-gen")
(list
:app
(list
:app
(list :var "concatMap")
(list
:lambda
(list (nth q 1))
(hk-lc-desugar e (rest quals))))
(hk-desugar (nth q 2))))
((= qtag "q-let")
(list
:let
(map hk-desugar (nth q 1))
(hk-lc-desugar e (rest quals))))
(:else
(raise
(str
"hk-lc-desugar: unknown qualifier tag "
qtag))))))))))
(define
hk-desugar
(fn
(node)
(cond
((not (list? node)) node)
((empty? node) node)
(:else
(let
((tag (first node)))
(cond
;; Transformations
((= tag "where")
(list
:let
(map hk-desugar (nth node 2))
(hk-desugar (nth node 1))))
((= tag "guarded") (hk-guards-to-if (nth node 1)))
((= tag "list-comp")
(hk-lc-desugar
(hk-desugar (nth node 1))
(nth node 2)))
;; Expression nodes
((= tag "app")
(list
:app
(hk-desugar (nth node 1))
(hk-desugar (nth node 2))))
((= tag "op")
(list
:op
(nth node 1)
(hk-desugar (nth node 2))
(hk-desugar (nth node 3))))
((= tag "neg") (list :neg (hk-desugar (nth node 1))))
((= tag "if")
(list
:if
(hk-desugar (nth node 1))
(hk-desugar (nth node 2))
(hk-desugar (nth node 3))))
((= tag "tuple")
(list :tuple (map hk-desugar (nth node 1))))
((= tag "list")
(list :list (map hk-desugar (nth node 1))))
((= tag "range")
(list
:range
(hk-desugar (nth node 1))
(hk-desugar (nth node 2))))
((= tag "range-step")
(list
:range-step
(hk-desugar (nth node 1))
(hk-desugar (nth node 2))
(hk-desugar (nth node 3))))
((= tag "lambda")
(list
:lambda
(nth node 1)
(hk-desugar (nth node 2))))
((= tag "let")
(list
:let
(map hk-desugar (nth node 1))
(hk-desugar (nth node 2))))
((= tag "case")
(list
:case
(hk-desugar (nth node 1))
(map hk-desugar (nth node 2))))
((= tag "alt")
(list :alt (nth node 1) (hk-desugar (nth node 2))))
((= tag "do") (hk-desugar-do (nth node 1)))
((= tag "sect-left")
(list
:sect-left
(nth node 1)
(hk-desugar (nth node 2))))
((= tag "sect-right")
(list
:sect-right
(nth node 1)
(hk-desugar (nth node 2))))
;; Top-level
((= tag "program")
(list :program (map hk-desugar (nth node 1))))
((= tag "module")
(list
:module
(nth node 1)
(nth node 2)
(nth node 3)
(map hk-desugar (nth node 4))))
;; Decls carrying a body
((= tag "fun-clause")
(list
:fun-clause
(nth node 1)
(nth node 2)
(hk-desugar (nth node 3))))
((= tag "pat-bind")
(list
:pat-bind
(nth node 1)
(hk-desugar (nth node 2))))
((= tag "bind")
(list
:bind
(nth node 1)
(hk-desugar (nth node 2))))
;; Everything else: leaf literals, vars, cons, patterns,
;; types, imports, type-sigs, data / newtype / fixity, …
(:else node)))))))
;; Convenience — tokenize + layout + parse + desugar.
(define
hk-core
(fn (src) (hk-desugar (hk-parse-top src))))
(define
hk-core-expr
(fn (src) (hk-desugar (hk-parse src))))

792
lib/haskell/eval.sx Normal file
View File

@@ -0,0 +1,792 @@
;; Haskell strict evaluator (Phase 2).
;;
;; Consumes the post-desugar core AST and produces SX values. Strict
;; throughout — laziness and thunks are Phase 3.
;;
;; Value representation:
;; numbers / strings / chars → raw SX values
;; constructor values → tagged lists (con-name first)
;; functions: closure / multifun → {:type "fn" :kind … …}
;; constructor partials → {:type "con-partial" …}
;; built-ins → {:type "builtin" …}
;;
;; Multi-clause top-level definitions are bundled into a single
;; multifun keyed by name; arguments are gathered through currying
;; until arity is reached, then each clause's pattern list is matched
;; in order. Recursive let bindings work because the binding env is
;; built mutably so closures captured during evaluation see the
;; eventual full env.
(define
hk-dict-copy
(fn
(d)
(let ((nd (dict)))
(for-each
(fn (k) (dict-set! nd k (get d k)))
(keys d))
nd)))
;; ── Thunks (Phase 3 — laziness) ─────────────────────────────
;; A thunk wraps an unevaluated AST plus the env in which it was
;; created. The first call to `hk-force` evaluates the body, replaces
;; the body with the cached value, and flips `forced`. Subsequent
;; forces return the cached value directly.
(define
hk-mk-thunk
(fn
(body env)
{:type "thunk" :body body :env env :forced false :value nil}))
(define
hk-is-thunk?
(fn (v) (and (dict? v) (= (get v "type") "thunk"))))
(define
hk-force
(fn
(v)
(cond
((hk-is-thunk? v)
(cond
((get v "forced") (get v "value"))
(:else
(let
((res (hk-force (hk-eval (get v "body") (get v "env")))))
(dict-set! v "forced" true)
(dict-set! v "value" res)
res))))
(:else v))))
;; Recursive force — used at the test/output boundary so test
;; expectations can compare against fully-evaluated structures.
(define
hk-deep-force
(fn
(v)
(let ((fv (hk-force v)))
(cond
((not (list? fv)) fv)
((empty? fv) fv)
(:else (map hk-deep-force fv))))))
;; ── Function value constructors ──────────────────────────────
(define
hk-mk-closure
(fn
(params body env)
{:type "fn" :kind "closure" :params params :body body :env env}))
(define
hk-mk-multifun
(fn
(arity clauses env)
{:type "fn" :kind "multi" :arity arity :clauses clauses :env env :collected (list)}))
(define
hk-mk-builtin
(fn
(name fn arity)
{:type "builtin" :name name :fn fn :arity arity :lazy false :collected (list)}))
;; A lazy built-in receives its collected args as raw thunks (or
;; values, if those happened to be eager) — the implementation is
;; responsible for forcing exactly what it needs. Used for `seq`
;; and `deepseq`, which are non-strict in their second argument.
(define
hk-mk-lazy-builtin
(fn
(name fn arity)
{:type "builtin" :name name :fn fn :arity arity :lazy true :collected (list)}))
;; ── Apply a function value to one argument ──────────────────
(define
hk-apply
(fn
(f arg)
(let ((f (hk-force f)))
(cond
((not (dict? f))
(raise (str "apply: not a function value: " f)))
((= (get f "type") "fn")
(cond
((= (get f "kind") "closure") (hk-apply-closure f arg))
((= (get f "kind") "multi") (hk-apply-multi f arg))
(:else (raise "apply: unknown fn kind"))))
((= (get f "type") "con-partial") (hk-apply-con-partial f arg))
((= (get f "type") "builtin") (hk-apply-builtin f arg))
(:else (raise "apply: not a function dict"))))))
(define
hk-apply-closure
(fn
(cl arg)
(let
((params (get cl "params"))
(body (get cl "body"))
(env (get cl "env")))
(cond
((empty? params) (raise "apply-closure: no params"))
(:else
(let
((p1 (first params)) (rest-p (rest params)))
(let
((env-after (hk-match p1 arg env)))
(cond
((nil? env-after)
(raise "pattern match failure in lambda"))
((empty? rest-p) (hk-eval body env-after))
(:else
(hk-mk-closure rest-p body env-after))))))))))
(define
hk-apply-multi
(fn
(mf arg)
(let
((arity (get mf "arity"))
(clauses (get mf "clauses"))
(env (get mf "env"))
(collected (append (get mf "collected") (list arg))))
(cond
((< (len collected) arity)
(assoc mf "collected" collected))
(:else (hk-dispatch-multi clauses collected env))))))
(define
hk-dispatch-multi
(fn
(clauses args env)
(cond
((empty? clauses)
(raise "non-exhaustive patterns in function definition"))
(:else
(let
((c (first clauses)))
(let
((pats (first c)) (body (first (rest c))))
(let
((env-after (hk-match-args pats args env)))
(cond
((nil? env-after)
(hk-dispatch-multi (rest clauses) args env))
(:else (hk-eval body env-after))))))))))
(define
hk-match-args
(fn
(pats args env)
(cond
((empty? pats) env)
(:else
(let
((res (hk-match (first pats) (first args) env)))
(cond
((nil? res) nil)
(:else
(hk-match-args (rest pats) (rest args) res))))))))
(define
hk-apply-con-partial
(fn
(cp arg)
(let
((name (get cp "name"))
(arity (get cp "arity"))
(args (append (get cp "args") (list arg))))
(cond
((= (len args) arity) (hk-mk-con name args))
(:else (assoc cp "args" args))))))
(define
hk-apply-builtin
(fn
(b arg)
(let
((arity (get b "arity"))
(collected (append (get b "collected") (list arg))))
(cond
((< (len collected) arity)
(assoc b "collected" collected))
(:else
;; Strict built-ins force every collected arg before
;; calling. Lazy ones (`seq`, `deepseq`) receive the raw
;; thunks so they can choose what to force.
(cond
((get b "lazy") (apply (get b "fn") collected))
(:else
(apply
(get b "fn")
(map hk-force collected)))))))))
;; ── Bool helpers (Bool values are tagged conses) ────────────
(define
hk-truthy?
(fn
(v)
(and (list? v) (not (empty? v)) (= (first v) "True"))))
(define hk-true (hk-mk-con "True" (list)))
(define hk-false (hk-mk-con "False" (list)))
(define hk-of-bool (fn (b) (if b hk-true hk-false)))
;; ── Core eval ───────────────────────────────────────────────
(define
hk-eval
(fn
(node env)
(cond
((not (list? node)) (raise (str "eval: not a list: " node)))
((empty? node) (raise "eval: empty list node"))
(:else
(let
((tag (first node)))
(cond
((= tag "int") (nth node 1))
((= tag "float") (nth node 1))
((= tag "string") (nth node 1))
((= tag "char") (nth node 1))
((= tag "var") (hk-eval-var (nth node 1) env))
((= tag "con") (hk-eval-con-ref (nth node 1)))
((= tag "neg")
(- 0 (hk-force (hk-eval (nth node 1) env))))
((= tag "if") (hk-eval-if node env))
((= tag "let") (hk-eval-let (nth node 1) (nth node 2) env))
((= tag "lambda")
(hk-mk-closure (nth node 1) (nth node 2) env))
((= tag "app")
(hk-apply
(hk-eval (nth node 1) env)
(hk-mk-thunk (nth node 2) env)))
((= tag "op")
(hk-eval-op
(nth node 1)
(nth node 2)
(nth node 3)
env))
((= tag "case")
(hk-eval-case (nth node 1) (nth node 2) env))
((= tag "tuple")
(hk-mk-tuple
(map (fn (e) (hk-eval e env)) (nth node 1))))
((= tag "list")
(hk-mk-list
(map (fn (e) (hk-eval e env)) (nth node 1))))
((= tag "range")
(let
((from (hk-force (hk-eval (nth node 1) env)))
(to (hk-force (hk-eval (nth node 2) env))))
(hk-build-range from to 1)))
((= tag "range-step")
(let
((from (hk-force (hk-eval (nth node 1) env)))
(nxt (hk-force (hk-eval (nth node 2) env)))
(to (hk-force (hk-eval (nth node 3) env))))
(hk-build-range from to (- nxt from))))
((= tag "range-from")
;; [from..] = iterate (+ 1) from — uses the Prelude.
(hk-eval
(list
:app
(list
:app
(list :var "iterate")
(list
:sect-right
"+"
(list :int 1)))
(nth node 1))
env))
((= tag "sect-left")
(hk-eval-sect-left (nth node 1) (nth node 2) env))
((= tag "sect-right")
(hk-eval-sect-right (nth node 1) (nth node 2) env))
(:else
(raise (str "eval: unknown node tag '" tag "'")))))))))
(define
hk-eval-var
(fn
(name env)
(cond
((has-key? env name) (get env name))
((hk-is-con? name) (hk-eval-con-ref name))
(:else (raise (str "unbound variable: " name))))))
(define
hk-eval-con-ref
(fn
(name)
(let ((arity (hk-con-arity name)))
(cond
((nil? arity) (raise (str "unknown constructor: " name)))
((= arity 0) (hk-mk-con name (list)))
(:else
{:type "con-partial" :name name :arity arity :args (list)})))))
(define
hk-eval-if
(fn
(node env)
(let ((cv (hk-force (hk-eval (nth node 1) env))))
(cond
((hk-truthy? cv) (hk-eval (nth node 2) env))
((and (list? cv) (= (first cv) "False"))
(hk-eval (nth node 3) env))
((= cv true) (hk-eval (nth node 2) env))
((= cv false) (hk-eval (nth node 3) env))
(:else (raise "if: condition is not Bool"))))))
(define
hk-extend-env-with-match!
(fn
(env match-env)
(for-each
(fn (k) (dict-set! env k (get match-env k)))
(keys match-env))))
(define
hk-eval-let-bind!
(fn
(b env)
(let ((tag (first b)))
(cond
((= tag "fun-clause")
(let
((name (nth b 1))
(pats (nth b 2))
(body (nth b 3)))
(cond
((empty? pats)
(dict-set! env name (hk-eval body env)))
(:else
(dict-set! env name (hk-mk-closure pats body env))))))
((or (= tag "bind") (= tag "pat-bind"))
(let ((pat (nth b 1)) (body (nth b 2)))
(let ((val (hk-eval body env)))
(let ((res (hk-match pat val env)))
(cond
((nil? res)
(raise "let: pattern bind failure"))
(:else
(hk-extend-env-with-match! env res)))))))
(:else nil)))))
(define
hk-eval-let
(fn
(binds body env)
;; Reuse hk-bind-decls! so multi-clause fun bindings in where/let
;; are grouped into multifuns, enabling patterns like:
;; let { go 0 = [[]]; go k = [...] } in go n
(let ((new-env (hk-dict-copy env)))
(hk-bind-decls! new-env binds)
(hk-eval body new-env))))
(define
hk-eval-case
(fn
(scrut alts env)
(let ((sv (hk-force (hk-eval scrut env))))
(hk-try-alts alts sv env))))
(define
hk-try-alts
(fn
(alts val env)
(cond
((empty? alts) (raise "case: non-exhaustive patterns"))
(:else
(let
((alt (first alts)))
(let
((pat (nth alt 1)) (body (nth alt 2)))
(let
((res (hk-match pat val env)))
(cond
((nil? res) (hk-try-alts (rest alts) val env))
(:else (hk-eval body res))))))))))
(define
hk-eval-op
(fn
(op left right env)
(cond
;; Cons is non-strict in both args: build a cons cell whose
;; head and tail are deferred. This is what makes `repeat x =
;; x : repeat x` and `fibs = 0 : 1 : zipWith (+) fibs (tail
;; fibs)` terminate.
((= op ":")
(hk-mk-cons
(hk-mk-thunk left env)
(hk-mk-thunk right env)))
(:else
(let
((lv (hk-force (hk-eval left env)))
(rv (hk-force (hk-eval right env))))
(hk-binop op lv rv))))))
(define
hk-list-append
(fn
(a b)
(cond
((and (list? a) (= (first a) "[]")) b)
((and (list? a) (= (first a) ":"))
(hk-mk-cons (nth a 1) (hk-list-append (nth a 2) b)))
(:else (raise "++: not a list")))))
;; Eager finite-range spine — handles [from..to] and [from,next..to].
;; Step direction is governed by the sign of `step`; when step > 0 we
;; stop at to; when step < 0 we stop at to going down.
(define
hk-build-range
(fn
(from to step)
(cond
((and (> step 0) (> from to)) (hk-mk-nil))
((and (< step 0) (< from to)) (hk-mk-nil))
((= step 0) (hk-mk-nil))
(:else
(hk-mk-cons from (hk-build-range (+ from step) to step))))))
(define
hk-binop
(fn
(op lv rv)
(cond
((= op "+") (+ lv rv))
((= op "-") (- lv rv))
((= op "*") (* lv rv))
((= op "/") (/ lv rv))
((= op "==") (hk-of-bool (= lv rv)))
((= op "/=") (hk-of-bool (not (= lv rv))))
((= op "<") (hk-of-bool (< lv rv)))
((= op "<=") (hk-of-bool (<= lv rv)))
((= op ">") (hk-of-bool (> lv rv)))
((= op ">=") (hk-of-bool (>= lv rv)))
((= op "&&") (hk-of-bool (and (hk-truthy? lv) (hk-truthy? rv))))
((= op "||") (hk-of-bool (or (hk-truthy? lv) (hk-truthy? rv))))
((= op ":") (hk-mk-cons lv rv))
((= op "++") (hk-list-append lv rv))
((= op "mod") (mod lv rv))
((= op "div") (floor (/ lv rv)))
((= op "rem") (mod lv rv))
((= op "quot") (truncate (/ lv rv)))
(:else (raise (str "unknown operator: " op))))))
(define
hk-eval-sect-left
(fn
(op e env)
;; (e op) = \x -> e op x — bind e once, defer the operator call.
(let ((ev (hk-eval e env)))
(let ((cenv (hk-dict-copy env)))
(dict-set! cenv "__hk-sect-l" ev)
(hk-mk-closure
(list (list :p-var "__hk-sect-x"))
(list
:op
op
(list :var "__hk-sect-l")
(list :var "__hk-sect-x"))
cenv)))))
(define
hk-eval-sect-right
(fn
(op e env)
(let ((ev (hk-eval e env)))
(let ((cenv (hk-dict-copy env)))
(dict-set! cenv "__hk-sect-r" ev)
(hk-mk-closure
(list (list :p-var "__hk-sect-x"))
(list
:op
op
(list :var "__hk-sect-x")
(list :var "__hk-sect-r"))
cenv)))))
;; ── Top-level program evaluation ────────────────────────────
;; Operator-as-value built-ins — let `(+)`, `(*)`, etc. work as
;; first-class functions for `zipWith (+)` and friends. Strict in
;; both args (built-ins are forced via hk-apply-builtin).
(define
hk-make-binop-builtin
(fn
(name op-name)
(hk-mk-builtin
name
(fn (a b) (hk-binop op-name a b))
2)))
;; Inline Prelude source — loaded into the initial env so simple
;; programs can use `head`, `take`, `repeat`, etc. without each
;; user file redefining them. The Prelude itself uses lazy `:` for
;; the recursive list-building functions.
(define
hk-prelude-src
"head (x:_) = x
tail (_:xs) = xs
fst (a, _) = a
snd (_, b) = b
take 0 _ = []
take _ [] = []
take n (x:xs) = x : take (n - 1) xs
drop 0 xs = xs
drop _ [] = []
drop n (_:xs) = drop (n - 1) xs
repeat x = x : repeat x
iterate f x = x : iterate f (f x)
length [] = 0
length (_:xs) = 1 + length xs
map _ [] = []
map f (x:xs) = f x : map f xs
filter _ [] = []
filter p (x:xs) = if p x then x : filter p xs else filter p xs
zipWith _ [] _ = []
zipWith _ _ [] = []
zipWith f (x:xs) (y:ys) = f x y : zipWith f xs ys
fibs = 0 : 1 : zipWith plus fibs (tail fibs)
plus a b = a + b
concat [] = []
concat (xs:xss) = xs ++ concat xss
concatMap f [] = []
concatMap f (x:xs) = f x ++ concatMap f xs
abs x = if x < 0 then 0 - x else x
negate x = 0 - x
")
(define
hk-load-into!
(fn
(env src)
(let ((ast (hk-core src)))
(hk-register-program! ast)
(let
((decls
(cond
((= (first ast) "program") (nth ast 1))
((= (first ast) "module") (nth ast 4))
(:else (list)))))
(hk-bind-decls! env decls)))))
(define
hk-init-env
(fn
()
(let ((env (dict)))
(dict-set! env "otherwise" hk-true)
(dict-set!
env
"error"
(hk-mk-builtin
"error"
(fn (msg) (raise (str "*** Exception: " msg)))
1))
(dict-set!
env
"not"
(hk-mk-builtin
"not"
(fn (b) (hk-of-bool (not (hk-truthy? b))))
1))
(dict-set!
env
"id"
(hk-mk-builtin "id" (fn (x) x) 1))
;; `seq a b` — strict in `a`, lazy in `b`. Forces `a` to WHNF
;; and returns `b` unchanged (still a thunk if it was one).
(dict-set!
env
"seq"
(hk-mk-lazy-builtin
"seq"
(fn (a b) (do (hk-force a) b))
2))
;; `deepseq a b` — like seq but forces `a` to normal form.
(dict-set!
env
"deepseq"
(hk-mk-lazy-builtin
"deepseq"
(fn (a b) (do (hk-deep-force a) b))
2))
;; ── Stub IO monad ─────────────────────────────────────
;; IO actions are tagged values `("IO" payload)`; `>>=` and
;; `>>` chain them. Lazy in the action arguments so do-blocks
;; can be deeply structured without forcing the whole chain
;; up front.
(dict-set!
env
"return"
(hk-mk-lazy-builtin
"return"
(fn (x) (list "IO" x))
1))
(dict-set!
env
">>="
(hk-mk-lazy-builtin
">>="
(fn (m f)
(let ((io-val (hk-force m)))
(cond
((and
(list? io-val)
(= (first io-val) "IO"))
(hk-apply (hk-force f) (nth io-val 1)))
(:else
(raise "(>>=): left side is not an IO action")))))
2))
(dict-set!
env
">>"
(hk-mk-lazy-builtin
">>"
(fn (m n)
(let ((io-val (hk-force m)))
(cond
((and
(list? io-val)
(= (first io-val) "IO"))
(hk-force n))
(:else
(raise "(>>): left side is not an IO action")))))
2))
;; Operators as first-class values
(dict-set! env "+" (hk-make-binop-builtin "+" "+"))
(dict-set! env "-" (hk-make-binop-builtin "-" "-"))
(dict-set! env "*" (hk-make-binop-builtin "*" "*"))
(dict-set! env "/" (hk-make-binop-builtin "/" "/"))
(dict-set! env "==" (hk-make-binop-builtin "==" "=="))
(dict-set! env "/=" (hk-make-binop-builtin "/=" "/="))
(dict-set! env "<" (hk-make-binop-builtin "<" "<"))
(dict-set! env "<=" (hk-make-binop-builtin "<=" "<="))
(dict-set! env ">" (hk-make-binop-builtin ">" ">"))
(dict-set! env ">=" (hk-make-binop-builtin ">=" ">="))
(dict-set! env "&&" (hk-make-binop-builtin "&&" "&&"))
(dict-set! env "||" (hk-make-binop-builtin "||" "||"))
(dict-set! env "++" (hk-make-binop-builtin "++" "++"))
(dict-set! env "mod" (hk-make-binop-builtin "mod" "mod"))
(dict-set! env "div" (hk-make-binop-builtin "div" "div"))
(dict-set! env "rem" (hk-make-binop-builtin "rem" "rem"))
(dict-set! env "quot" (hk-make-binop-builtin "quot" "quot"))
(hk-load-into! env hk-prelude-src)
env)))
(define
hk-bind-decls!
(fn
(env decls)
(let
((groups (dict))
(group-order (list))
(pat-binds (list)))
;; Pass 1: collect fun-clause groups by name; track first-seen
;; order so pass 3 can evaluate 0-arity bodies in source order
;; (forward references to other 0-arity definitions still need
;; the earlier name to be bound first).
(for-each
(fn (d)
(cond
((= (first d) "fun-clause")
(let
((name (nth d 1)))
(when (not (has-key? groups name))
(append! group-order name))
(dict-set!
groups
name
(append
(if
(has-key? groups name)
(get groups name)
(list))
(list (list (nth d 2) (nth d 3)))))
(when
(not (has-key? env name))
(dict-set! env name nil))))
((or (= (first d) "bind") (= (first d) "pat-bind"))
(append! pat-binds d))
(:else nil)))
decls)
;; Pass 2: install multifuns (arity > 0) — order doesn't matter
;; because they're closures; collect 0-arity names in source
;; order for pass 3.
(let ((zero-arity (list)))
(for-each
(fn (name)
(let ((clauses (get groups name)))
(let ((arity (len (first (first clauses)))))
(cond
((> arity 0)
(dict-set!
env
name
(hk-mk-multifun arity clauses env)))
(:else (append! zero-arity name))))))
group-order)
;; Pass 3: evaluate 0-arity bodies and pat-binds in source
;; order — forward references to a later 0-arity name will
;; still see its placeholder (nil) and fail noisily, but the
;; common case of a top-down program works.
(for-each
(fn (name)
(let ((clauses (get groups name)))
(dict-set!
env
name
(hk-eval (first (rest (first clauses))) env))))
zero-arity)
(for-each
(fn (d)
(let ((pat (nth d 1)) (body (nth d 2)))
(let ((val (hk-eval body env)))
(let ((res (hk-match pat val env)))
(cond
((nil? res)
(raise "top-level pattern bind failure"))
(:else (hk-extend-env-with-match! env res)))))))
pat-binds))
env)))
(define
hk-eval-program
(fn
(ast)
(cond
((nil? ast) (raise "eval-program: nil ast"))
((not (list? ast)) (raise "eval-program: not a list"))
(:else
(do
(hk-register-program! ast)
(let ((env (hk-init-env)))
(let
((decls
(cond
((= (first ast) "program") (nth ast 1))
((= (first ast) "module") (nth ast 4))
(:else (raise "eval-program: bad shape")))))
(hk-bind-decls! env decls))))))))
;; ── Source-level convenience ────────────────────────────────
(define
hk-run
(fn
(src)
(let ((env (hk-eval-program (hk-core src))))
(cond
((has-key? env "main") (get env "main"))
(:else env)))))
;; Eagerly build the Prelude env once at load time; each call to
;; hk-eval-expr-source copies it instead of re-parsing the whole Prelude.
(define hk-env0 (hk-init-env))
(define
hk-eval-expr-source
(fn
(src)
(hk-deep-force (hk-eval (hk-core-expr src) (hk-dict-copy hk-env0)))))

329
lib/haskell/layout.sx Normal file
View File

@@ -0,0 +1,329 @@
;; Haskell 98 layout algorithm (§10.3).
;;
;; Consumes the raw token stream produced by hk-tokenize and inserts
;; virtual braces / semicolons (types vlbrace / vrbrace / vsemi) based
;; on indentation. Newline tokens are consumed and stripped.
;;
;; (hk-layout (hk-tokenize src)) → tokens-with-virtual-layout
;; ── Pre-pass ──────────────────────────────────────────────────────
;;
;; Walks the raw token list and emits an augmented stream containing
;; two fresh pseudo-tokens:
;;
;; {:type "layout-open" :col N :keyword K}
;; At stream start (K = "<module>") unless the first real token is
;; `module` or `{`. Also immediately after every `let` / `where` /
;; `do` / `of` whose following token is NOT `{`. N is the column
;; of the token that follows.
;;
;; {:type "layout-indent" :col N}
;; Before any token whose line is strictly greater than the line
;; of the previously emitted real token, EXCEPT when that token
;; is already preceded by a layout-open (Haskell 98 §10.3 note 3).
;;
;; Raw newline tokens are dropped.
(define
hk-layout-keyword?
(fn
(tok)
(and
(= (get tok "type") "reserved")
(or
(= (get tok "value") "let")
(= (get tok "value") "where")
(= (get tok "value") "do")
(= (get tok "value") "of")))))
(define
hk-layout-pre
(fn
(tokens)
(let
((result (list))
(n (len tokens))
(i 0)
(prev-line -1)
(first-real-emitted false)
(suppress-next-indent false))
(define
hk-next-real-idx
(fn
(start)
(let
((j start))
(define
hk-nri-loop
(fn
()
(when
(and
(< j n)
(= (get (nth tokens j) "type") "newline"))
(do (set! j (+ j 1)) (hk-nri-loop)))))
(hk-nri-loop)
j)))
(define
hk-pre-step
(fn
()
(when
(< i n)
(let
((tok (nth tokens i)) (ty (get tok "type")))
(cond
((= ty "newline") (do (set! i (+ i 1)) (hk-pre-step)))
(:else
(do
(when
(not first-real-emitted)
(do
(set! first-real-emitted true)
(when
(not
(or
(and
(= ty "reserved")
(= (get tok "value") "module"))
(= ty "lbrace")))
(do
(append!
result
{:type "layout-open"
:col (get tok "col")
:keyword "<module>"
:line (get tok "line")})
(set! suppress-next-indent true)))))
(when
(and
(>= prev-line 0)
(> (get tok "line") prev-line)
(not suppress-next-indent))
(append!
result
{:type "layout-indent"
:col (get tok "col")
:line (get tok "line")}))
(set! suppress-next-indent false)
(set! prev-line (get tok "line"))
(append! result tok)
(when
(hk-layout-keyword? tok)
(let
((j (hk-next-real-idx (+ i 1))))
(cond
((>= j n)
(do
(append!
result
{:type "layout-open"
:col 0
:keyword (get tok "value")
:line (get tok "line")})
(set! suppress-next-indent true)))
((= (get (nth tokens j) "type") "lbrace") nil)
(:else
(do
(append!
result
{:type "layout-open"
:col (get (nth tokens j) "col")
:keyword (get tok "value")
:line (get tok "line")})
(set! suppress-next-indent true))))))
(set! i (+ i 1))
(hk-pre-step))))))))
(hk-pre-step)
result)))
;; ── Main pass: L algorithm ────────────────────────────────────────
;;
;; Stack is a list; the head is the top of stack. Each entry is
;; either the keyword :explicit (pushed by an explicit `{`) or a dict
;; {:col N :keyword K} pushed by a layout-open marker.
;;
;; Rules (following Haskell 98 §10.3):
;;
;; layout-open(n) vs stack:
;; empty or explicit top → push n; emit {
;; n > top-col → push n; emit {
;; otherwise → emit { }; retry as indent(n)
;;
;; layout-indent(n) vs stack:
;; empty or explicit top → drop
;; n == top-col → emit ;
;; n < top-col → emit }; pop; recurse
;; n > top-col → drop
;;
;; lbrace → push :explicit; emit {
;; rbrace → pop if :explicit; emit }
;; `in` with implicit let on top → emit }; pop; emit in
;; any other token → emit
;;
;; EOF: emit } for every remaining implicit context.
(define
hk-layout-L
(fn
(pre-toks)
(let
((result (list))
(stack (list))
(n (len pre-toks))
(i 0))
(define hk-emit (fn (t) (append! result t)))
(define
hk-indent-at
(fn
(col line)
(cond
((or (empty? stack) (= (first stack) :explicit)) nil)
(:else
(let
((top-col (get (first stack) "col")))
(cond
((= col top-col)
(hk-emit
{:type "vsemi" :value ";" :line line :col col}))
((< col top-col)
(do
(hk-emit
{:type "vrbrace" :value "}" :line line :col col})
(set! stack (rest stack))
(hk-indent-at col line)))
(:else nil)))))))
(define
hk-open-at
(fn
(col keyword line)
(cond
((and
(> col 0)
(or
(empty? stack)
(= (first stack) :explicit)
(> col (get (first stack) "col"))))
(do
(hk-emit
{:type "vlbrace" :value "{" :line line :col col})
(set! stack (cons {:col col :keyword keyword} stack))))
(:else
(do
(hk-emit
{:type "vlbrace" :value "{" :line line :col col})
(hk-emit
{:type "vrbrace" :value "}" :line line :col col})
(hk-indent-at col line))))))
(define
hk-close-eof
(fn
()
(when
(and
(not (empty? stack))
(not (= (first stack) :explicit)))
(do
(hk-emit {:type "vrbrace" :value "}" :line 0 :col 0})
(set! stack (rest stack))
(hk-close-eof)))))
;; Peek past further layout-indent / layout-open markers to find
;; the next real token's value when its type is `reserved`.
;; Returns nil if no such token.
(define
hk-peek-next-reserved
(fn
(start)
(let ((j (+ start 1)) (found nil) (done false))
(define
hk-pnr-loop
(fn
()
(when
(and (not done) (< j n))
(let
((t (nth pre-toks j)) (ty (get t "type")))
(cond
((or
(= ty "layout-indent")
(= ty "layout-open"))
(do (set! j (+ j 1)) (hk-pnr-loop)))
((= ty "reserved")
(do (set! found (get t "value")) (set! done true)))
(:else (set! done true)))))))
(hk-pnr-loop)
found)))
(define
hk-layout-step
(fn
()
(when
(< i n)
(let
((tok (nth pre-toks i)) (ty (get tok "type")))
(cond
((= ty "eof")
(do
(hk-close-eof)
(hk-emit tok)
(set! i (+ i 1))
(hk-layout-step)))
((= ty "layout-open")
(do
(hk-open-at
(get tok "col")
(get tok "keyword")
(get tok "line"))
(set! i (+ i 1))
(hk-layout-step)))
((= ty "layout-indent")
(cond
((= (hk-peek-next-reserved i) "in")
(do (set! i (+ i 1)) (hk-layout-step)))
(:else
(do
(hk-indent-at (get tok "col") (get tok "line"))
(set! i (+ i 1))
(hk-layout-step)))))
((= ty "lbrace")
(do
(set! stack (cons :explicit stack))
(hk-emit tok)
(set! i (+ i 1))
(hk-layout-step)))
((= ty "rbrace")
(do
(when
(and
(not (empty? stack))
(= (first stack) :explicit))
(set! stack (rest stack)))
(hk-emit tok)
(set! i (+ i 1))
(hk-layout-step)))
((and
(= ty "reserved")
(= (get tok "value") "in")
(not (empty? stack))
(not (= (first stack) :explicit))
(= (get (first stack) "keyword") "let"))
(do
(hk-emit
{:type "vrbrace"
:value "}"
:line (get tok "line")
:col (get tok "col")})
(set! stack (rest stack))
(hk-emit tok)
(set! i (+ i 1))
(hk-layout-step)))
(:else
(do
(hk-emit tok)
(set! i (+ i 1))
(hk-layout-step))))))))
(hk-layout-step)
(hk-close-eof)
result)))
(define hk-layout (fn (tokens) (hk-layout-L (hk-layout-pre tokens))))

201
lib/haskell/match.sx Normal file
View File

@@ -0,0 +1,201 @@
;; Value-level pattern matching.
;;
;; Constructor values are tagged lists whose first element is the
;; constructor name (a string). Tuples use the special tag "Tuple".
;; Lists use the spine of `:` cons and `[]` nil.
;;
;; Just 5 → ("Just" 5)
;; Nothing → ("Nothing")
;; (1, 2) → ("Tuple" 1 2)
;; [1, 2] → (":" 1 (":" 2 ("[]")))
;; () → ("()")
;;
;; Primitive values (numbers, strings, chars) are stored raw.
;;
;; The matcher takes a pattern AST node, a value, and an environment
;; dict; it returns an extended dict on success, or `nil` on failure.
;; ── Value builders ──────────────────────────────────────────
(define
hk-mk-con
(fn
(cname args)
(let ((result (list cname)))
(for-each (fn (a) (append! result a)) args)
result)))
(define
hk-mk-tuple
(fn
(items)
(let ((result (list "Tuple")))
(for-each (fn (x) (append! result x)) items)
result)))
(define hk-mk-nil (fn () (list "[]")))
(define hk-mk-cons (fn (h t) (list ":" h t)))
(define
hk-mk-list
(fn
(items)
(cond
((empty? items) (hk-mk-nil))
(:else
(hk-mk-cons (first items) (hk-mk-list (rest items)))))))
;; ── Predicates / accessors on constructor values ───────────
(define
hk-is-con-val?
(fn
(v)
(and
(list? v)
(not (empty? v))
(string? (first v)))))
(define hk-val-con-name (fn (v) (first v)))
(define hk-val-con-args (fn (v) (rest v)))
;; ── The matcher ────────────────────────────────────────────
;;
;; Pattern match forces the scrutinee to WHNF before inspecting it
;; — except for `p-wild`, `p-var`, and `p-lazy`, which never need
;; to look at the value. Args of constructor / tuple / list values
;; remain thunked (they're forced only when their own pattern needs
;; to inspect them, recursively).
(define
hk-match
(fn
(pat val env)
(cond
((not (list? pat)) nil)
((empty? pat) nil)
(:else
(let
((tag (first pat)))
(cond
((= tag "p-wild") env)
((= tag "p-var") (assoc env (nth pat 1) val))
((= tag "p-lazy") (hk-match (nth pat 1) val env))
((= tag "p-as")
(let
((res (hk-match (nth pat 2) val env)))
(cond
((nil? res) nil)
(:else (assoc res (nth pat 1) val)))))
(:else
(let ((fv (hk-force val)))
(cond
((= tag "p-int")
(if
(and (number? fv) (= fv (nth pat 1)))
env
nil))
((= tag "p-float")
(if
(and (number? fv) (= fv (nth pat 1)))
env
nil))
((= tag "p-string")
(if
(and (string? fv) (= fv (nth pat 1)))
env
nil))
((= tag "p-char")
(if
(and (string? fv) (= fv (nth pat 1)))
env
nil))
((= tag "p-con")
(let
((pat-name (nth pat 1)) (pat-args (nth pat 2)))
(cond
((not (hk-is-con-val? fv)) nil)
((not (= (hk-val-con-name fv) pat-name)) nil)
(:else
(let
((val-args (hk-val-con-args fv)))
(cond
((not (= (len pat-args) (len val-args)))
nil)
(:else
(hk-match-all
pat-args
val-args
env))))))))
((= tag "p-tuple")
(let
((items (nth pat 1)))
(cond
((not (hk-is-con-val? fv)) nil)
((not (= (hk-val-con-name fv) "Tuple")) nil)
((not (= (len (hk-val-con-args fv)) (len items)))
nil)
(:else
(hk-match-all
items
(hk-val-con-args fv)
env)))))
((= tag "p-list")
(hk-match-list-pat (nth pat 1) fv env))
(:else nil))))))))))
(define
hk-match-all
(fn
(pats vals env)
(cond
((empty? pats) env)
(:else
(let
((res (hk-match (first pats) (first vals) env)))
(cond
((nil? res) nil)
(:else
(hk-match-all (rest pats) (rest vals) res))))))))
(define
hk-match-list-pat
(fn
(items val env)
(let ((fv (hk-force val)))
(cond
((empty? items)
(if
(and
(hk-is-con-val? fv)
(= (hk-val-con-name fv) "[]"))
env
nil))
(:else
(cond
((not (hk-is-con-val? fv)) nil)
((not (= (hk-val-con-name fv) ":")) nil)
(:else
(let
((args (hk-val-con-args fv)))
(let
((h (first args)) (t (first (rest args))))
(let
((res (hk-match (first items) h env)))
(cond
((nil? res) nil)
(:else
(hk-match-list-pat
(rest items)
t
res)))))))))))))
;; ── Convenience: parse a pattern from source for tests ─────
;; (Uses the parser's case-alt entry — `case _ of pat -> 0` —
;; to extract a pattern AST.)
(define
hk-parse-pat-source
(fn
(src)
(let
((expr (hk-parse (str "case 0 of " src " -> 0"))))
(nth (nth (nth expr 2) 0) 1))))

1994
lib/haskell/parser.sx Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,507 +1,130 @@
;; lib/haskell/runtime.sx — Haskell-on-SX runtime layer
;; Haskell runtime: constructor registry.
;;
;; Covers the Haskell primitives now reachable via SX spec:
;; 1. Numeric type class helpers (Num / Integral / Fractional)
;; 2. Rational numbers (dict-based: {:_rational true :num n :den d})
;; 3. Lazy evaluation — hk-force for promises created by delay
;; 4. Char utilities (Data.Char)
;; 5. Data.Set wrappers
;; 6. Data.List utilities
;; 7. Maybe / Either ADTs
;; 8. Tuples (lists, since list->vector unreliable in sx_server)
;; 9. String helpers (words/lines/isPrefixOf/etc.)
;; 10. Show helper
;; A mutable dict keyed by constructor name (e.g. "Just", "[]") with
;; entries of shape {:arity N :type TYPE-NAME-STRING}.
;; Populated by ingesting `data` / `newtype` decls from parsed ASTs.
;; Pre-registers a small set of constructors tied to Haskell syntactic
;; forms (Bool, list, unit) — every nontrivial program depends on
;; these, and the parser/desugar pipeline emits them as (:var "True")
;; etc. without a corresponding `data` decl.
;; ===========================================================================
;; 1. Numeric type class helpers
;; ===========================================================================
(define hk-constructors (dict))
(define hk-is-integer? integer?)
(define hk-is-float? float?)
(define hk-is-num? number?)
;; fromIntegral — coerce integer to Float
(define (hk-to-float x) (exact->inexact x))
;; truncate / round toward zero
(define hk-to-integer truncate)
(define hk-from-integer (fn (n) n))
;; Haskell div: floor division (rounds toward -inf)
(define
(hk-div a b)
(let
((q (quotient a b)) (r (remainder a b)))
hk-register-con!
(fn
(cname arity type-name)
(dict-set!
hk-constructors
cname
{:arity arity :type type-name})))
(define hk-is-con? (fn (name) (has-key? hk-constructors name)))
(define
hk-con-arity
(fn
(name)
(if
(and
(not (= r 0))
(or
(and (< a 0) (> b 0))
(and (> a 0) (< b 0))))
(- q 1)
q)))
;; Haskell mod: result has same sign as divisor
(define hk-mod modulo)
;; Haskell rem: result has same sign as dividend
(define hk-rem remainder)
;; Haskell quot: truncation division
(define hk-quot quotient)
;; divMod and quotRem return pairs (lists)
(define (hk-div-mod a b) (list (hk-div a b) (hk-mod a b)))
(define (hk-quot-rem a b) (list (hk-quot a b) (hk-rem a b)))
(define (hk-abs x) (if (< x 0) (- 0 x) x))
(define
(hk-signum x)
(cond
((> x 0) 1)
((< x 0) -1)
(else 0)))
(define hk-gcd gcd)
(define hk-lcm lcm)
(define (hk-even? n) (= (modulo n 2) 0))
(define (hk-odd? n) (not (= (modulo n 2) 0)))
;; ===========================================================================
;; 2. Rational numbers (dict implementation — no built-in rational in sx_server)
;; ===========================================================================
(has-key? hk-constructors name)
(get (get hk-constructors name) "arity")
nil)))
(define
(hk-make-rational n d)
(let
((g (gcd (hk-abs n) (hk-abs d))))
(if (< d 0) {:num (quotient (- 0 n) g) :den (quotient (- 0 d) g) :_rational true} {:num (quotient n g) :den (quotient d g) :_rational true})))
hk-con-type
(fn
(name)
(if
(has-key? hk-constructors name)
(get (get hk-constructors name) "type")
nil)))
(define hk-con-names (fn () (keys hk-constructors)))
;; ── Registration from AST ────────────────────────────────────
;; (:data NAME TVARS ((:con-def CNAME FIELDS) …))
(define
hk-register-data!
(fn
(data-node)
(let
((type-name (nth data-node 1))
(cons-list (nth data-node 3)))
(for-each
(fn
(cd)
(hk-register-con!
(nth cd 1)
(len (nth cd 2))
type-name))
cons-list))))
;; (:newtype NAME TVARS CNAME FIELD)
(define
hk-register-newtype!
(fn
(nt-node)
(hk-register-con!
(nth nt-node 3)
1
(nth nt-node 1))))
;; Walk a decls list, registering every `data` / `newtype` decl.
(define
hk-register-decls!
(fn
(decls)
(for-each
(fn
(d)
(cond
((and
(list? d)
(not (empty? d))
(= (first d) "data"))
(hk-register-data! d))
((and
(list? d)
(not (empty? d))
(= (first d) "newtype"))
(hk-register-newtype! d))
(:else nil)))
decls)))
(define
(hk-rational? x)
(and (dict? x) (not (= (get x :_rational) nil))))
(define (hk-numerator r) (get r :num))
(define (hk-denominator r) (get r :den))
(define
(hk-rational-add r1 r2)
(hk-make-rational
(+
(* (hk-numerator r1) (hk-denominator r2))
(* (hk-numerator r2) (hk-denominator r1)))
(* (hk-denominator r1) (hk-denominator r2))))
(define
(hk-rational-sub r1 r2)
(hk-make-rational
(-
(* (hk-numerator r1) (hk-denominator r2))
(* (hk-numerator r2) (hk-denominator r1)))
(* (hk-denominator r1) (hk-denominator r2))))
(define
(hk-rational-mul r1 r2)
(hk-make-rational
(* (hk-numerator r1) (hk-numerator r2))
(* (hk-denominator r1) (hk-denominator r2))))
(define
(hk-rational-div r1 r2)
(hk-make-rational
(* (hk-numerator r1) (hk-denominator r2))
(* (hk-denominator r1) (hk-numerator r2))))
(define
(hk-rational-to-float r)
(exact->inexact (/ (hk-numerator r) (hk-denominator r))))
(define (hk-show-rational r) (str (hk-numerator r) "%" (hk-denominator r)))
;; ===========================================================================
;; 3. Lazy evaluation — promises (created via SX delay)
;; ===========================================================================
(define
(hk-force p)
(if
(and (dict? p) (not (= (get p :_promise) nil)))
(if (get p :forced) (get p :value) ((get p :thunk)))
p))
;; ===========================================================================
;; 4. Char utilities (Data.Char)
;; ===========================================================================
(define hk-ord char->integer)
(define hk-chr integer->char)
;; Inline ASCII predicates — char-alphabetic?/char-numeric? unreliable in sx_server
(define
(hk-is-alpha? c)
(let
((n (char->integer c)))
(or
(and (>= n 65) (<= n 90))
(and (>= n 97) (<= n 122)))))
(define
(hk-is-digit? c)
(let ((n (char->integer c))) (and (>= n 48) (<= n 57))))
(define
(hk-is-alnum? c)
(let
((n (char->integer c)))
(or
(and (>= n 48) (<= n 57))
(and (>= n 65) (<= n 90))
(and (>= n 97) (<= n 122)))))
(define
(hk-is-upper? c)
(let ((n (char->integer c))) (and (>= n 65) (<= n 90))))
(define
(hk-is-lower? c)
(let ((n (char->integer c))) (and (>= n 97) (<= n 122))))
(define
(hk-is-space? c)
(let
((n (char->integer c)))
(or
(= n 32)
(= n 9)
(= n 10)
(= n 13)
(= n 12)
(= n 11))))
(define hk-to-upper char-upcase)
(define hk-to-lower char-downcase)
;; digitToInt: '0'-'9' → 0-9, 'a'-'f'/'A'-'F' → 10-15
(define
(hk-digit-to-int c)
(let
((n (char->integer c)))
hk-register-program!
(fn
(ast)
(cond
((and (>= n 48) (<= n 57)) (- n 48))
((and (>= n 65) (<= n 70)) (- n 55))
((and (>= n 97) (<= n 102)) (- n 87))
(else (error (str "hk-digit-to-int: not a hex digit: " c))))))
((nil? ast) nil)
((not (list? ast)) nil)
((empty? ast) nil)
((= (first ast) "program")
(hk-register-decls! (nth ast 1)))
((= (first ast) "module")
(hk-register-decls! (nth ast 4)))
(:else nil))))
;; intToDigit: 0-15 → char
;; Convenience: source → AST → desugar → register.
(define
(hk-int-to-digit n)
(cond
((and (>= n 0) (<= n 9))
(integer->char (+ n 48)))
((and (>= n 10) (<= n 15))
(integer->char (+ n 87)))
(else (error (str "hk-int-to-digit: out of range: " n)))))
hk-load-source!
(fn (src) (hk-register-program! (hk-core src))))
;; ===========================================================================
;; 5. Data.Set wrappers
;; ===========================================================================
(define (hk-set-empty) (make-set))
(define hk-set? set?)
(define hk-set-member? set-member?)
(define (hk-set-insert x s) (begin (set-add! s x) s))
(define (hk-set-delete x s) (begin (set-remove! s x) s))
(define hk-set-union set-union)
(define hk-set-intersection set-intersection)
(define hk-set-difference set-difference)
(define hk-set-from-list list->set)
(define hk-set-to-list set->list)
(define (hk-set-null? s) (= (len (set->list s)) 0))
(define (hk-set-size s) (len (set->list s)))
(define (hk-set-singleton x) (let ((s (make-set))) (set-add! s x) s))
;; ===========================================================================
;; 6. Data.List utilities
;; ===========================================================================
(define hk-head first)
(define hk-tail rest)
(define (hk-null? lst) (= (len lst) 0))
(define hk-length len)
(define
(hk-take n lst)
(if
(or (= n 0) (= (len lst) 0))
(list)
(cons (first lst) (hk-take (- n 1) (rest lst)))))
(define
(hk-drop n lst)
(if
(or (= n 0) (= (len lst) 0))
lst
(hk-drop (- n 1) (rest lst))))
(define
(hk-take-while pred lst)
(if
(or (= (len lst) 0) (not (pred (first lst))))
(list)
(cons (first lst) (hk-take-while pred (rest lst)))))
(define
(hk-drop-while pred lst)
(if
(or (= (len lst) 0) (not (pred (first lst))))
lst
(hk-drop-while pred (rest lst))))
(define
(hk-zip a b)
(if
(or (= (len a) 0) (= (len b) 0))
(list)
(cons (list (first a) (first b)) (hk-zip (rest a) (rest b)))))
(define
(hk-zip-with f a b)
(if
(or (= (len a) 0) (= (len b) 0))
(list)
(cons (f (first a) (first b)) (hk-zip-with f (rest a) (rest b)))))
(define
(hk-unzip pairs)
(list
(map (fn (p) (first p)) pairs)
(map (fn (p) (nth p 1)) pairs)))
(define
(hk-elem x lst)
(cond
((= (len lst) 0) false)
((= x (first lst)) true)
(else (hk-elem x (rest lst)))))
(define (hk-not-elem x lst) (not (hk-elem x lst)))
(define
(hk-nub lst)
(letrec
((go (fn (seen acc items) (if (= (len items) 0) (reverse acc) (let ((h (first items)) (t (rest items))) (if (hk-elem h seen) (go seen acc t) (go (cons h seen) (cons h acc) t)))))))
(go (list) (list) lst)))
(define (hk-sum lst) (reduce + 0 lst))
(define (hk-product lst) (reduce * 1 lst))
(define
(hk-maximum lst)
(reduce (fn (a b) (if (> a b) a b)) (first lst) (rest lst)))
(define
(hk-minimum lst)
(reduce (fn (a b) (if (< a b) a b)) (first lst) (rest lst)))
(define (hk-concat lsts) (reduce append (list) lsts))
(define (hk-concat-map f lst) (hk-concat (map f lst)))
(define hk-sort sort)
(define
(hk-span pred lst)
(list (hk-take-while pred lst) (hk-drop-while pred lst)))
(define (hk-break pred lst) (hk-span (fn (x) (not (pred x))) lst))
(define
(hk-foldl f acc lst)
(if
(= (len lst) 0)
acc
(hk-foldl f (f acc (first lst)) (rest lst))))
(define
(hk-foldr f z lst)
(if
(= (len lst) 0)
z
(f (first lst) (hk-foldr f z (rest lst)))))
(define
(hk-scanl f acc lst)
(if
(= (len lst) 0)
(list acc)
(cons acc (hk-scanl f (f acc (first lst)) (rest lst)))))
(define
(hk-replicate n x)
(if (= n 0) (list) (cons x (hk-replicate (- n 1) x))))
(define
(hk-intersperse sep lst)
(if
(or (= (len lst) 0) (= (len lst) 1))
lst
(cons (first lst) (cons sep (hk-intersperse sep (rest lst))))))
;; ===========================================================================
;; 7. Maybe / Either ADTs
;; ===========================================================================
(define hk-nothing {:_maybe true :_tag "nothing"})
(define (hk-just x) {:_maybe true :value x :_tag "just"})
(define (hk-is-nothing? m) (= (get m :_tag) "nothing"))
(define (hk-is-just? m) (= (get m :_tag) "just"))
(define (hk-from-just m) (get m :value))
(define (hk-from-maybe def m) (if (hk-is-nothing? m) def (hk-from-just m)))
(define
(hk-maybe def f m)
(if (hk-is-nothing? m) def (f (hk-from-just m))))
(define (hk-left x) {:value x :_either true :_tag "left"})
(define (hk-right x) {:value x :_either true :_tag "right"})
(define (hk-is-left? e) (= (get e :_tag) "left"))
(define (hk-is-right? e) (= (get e :_tag) "right"))
(define (hk-from-left e) (get e :value))
(define (hk-from-right e) (get e :value))
(define
(hk-either f g e)
(if (hk-is-left? e) (f (hk-from-left e)) (g (hk-from-right e))))
;; ===========================================================================
;; 8. Tuples (lists — list->vector unreliable in sx_server)
;; ===========================================================================
(define (hk-pair a b) (list a b))
(define hk-fst first)
(define (hk-snd t) (nth t 1))
(define (hk-triple a b c) (list a b c))
(define hk-fst3 first)
(define (hk-snd3 t) (nth t 1))
(define (hk-thd3 t) (nth t 2))
(define (hk-curry f) (fn (a) (fn (b) (f a b))))
(define (hk-uncurry f) (fn (p) (f (hk-fst p) (hk-snd p))))
;; ===========================================================================
;; 9. String helpers (Data.List / Data.Char for strings)
;; ===========================================================================
;; words: split on whitespace
(define
(hk-words s)
(letrec
((slen (len s))
(skip-ws
(fn
(i)
(if
(>= i slen)
(list)
(let
((c (substring s i (+ i 1))))
(if
(or (= c " ") (= c "\t") (= c "\n"))
(skip-ws (+ i 1))
(collect-word i (+ i 1)))))))
(collect-word
(fn
(start i)
(if
(>= i slen)
(list (substring s start i))
(let
((c (substring s i (+ i 1))))
(if
(or (= c " ") (= c "\t") (= c "\n"))
(cons (substring s start i) (skip-ws (+ i 1)))
(collect-word start (+ i 1))))))))
(skip-ws 0)))
;; unwords: join with spaces
(define
(hk-unwords lst)
(if
(= (len lst) 0)
""
(reduce (fn (a b) (str a " " b)) (first lst) (rest lst))))
;; lines: split on newline
(define
(hk-lines s)
(letrec
((slen (len s))
(go
(fn
(start i acc)
(if
(>= i slen)
(reverse (cons (substring s start i) acc))
(if
(= (substring s i (+ i 1)) "\n")
(go
(+ i 1)
(+ i 1)
(cons (substring s start i) acc))
(go start (+ i 1) acc))))))
(if (= slen 0) (list) (go 0 0 (list)))))
;; unlines: join, each with trailing newline
(define (hk-unlines lst) (reduce (fn (a b) (str a b "\n")) "" lst))
;; isPrefixOf
(define
(hk-is-prefix-of pre s)
(and (<= (len pre) (len s)) (= pre (substring s 0 (len pre)))))
;; isSuffixOf
(define
(hk-is-suffix-of suf s)
(let
((sl (len suf)) (tl (len s)))
(and (<= sl tl) (= suf (substring s (- tl sl) tl)))))
;; isInfixOf — linear scan
(define
(hk-is-infix-of pat s)
(let
((plen (len pat)) (slen (len s)))
(letrec
((go (fn (i) (if (> (+ i plen) slen) false (if (= pat (substring s i (+ i plen))) true (go (+ i 1)))))))
(if (= plen 0) true (go 0)))))
;; ===========================================================================
;; 10. Show helper
;; ===========================================================================
(define
(hk-show x)
(cond
((= x nil) "Nothing")
((= x true) "True")
((= x false) "False")
((hk-rational? x) (hk-show-rational x))
((integer? x) (str x))
((float? x) (str x))
((= (type-of x) "string") (str "\"" x "\""))
((= (type-of x) "char") (str "'" (str x) "'"))
((list? x)
(str
"["
(if
(= (len x) 0)
""
(reduce
(fn (a b) (str a "," (hk-show b)))
(hk-show (first x))
(rest x)))
"]"))
(else (str x))))
;; ── Built-in constructors pre-registered ─────────────────────
;; Bool — used implicitly by `if`, comparison operators.
(hk-register-con! "True" 0 "Bool")
(hk-register-con! "False" 0 "Bool")
;; List — used by list literals, range syntax, and cons operator.
(hk-register-con! "[]" 0 "List")
(hk-register-con! ":" 2 "List")
;; Unit — produced by empty parens `()`.
(hk-register-con! "()" 0 "Unit")
;; Standard Prelude types — pre-registered so expression-level
;; programs can use them without a `data` decl.
(hk-register-con! "Nothing" 0 "Maybe")
(hk-register-con! "Just" 1 "Maybe")
(hk-register-con! "Left" 1 "Either")
(hk-register-con! "Right" 1 "Either")
(hk-register-con! "LT" 0 "Ordering")
(hk-register-con! "EQ" 0 "Ordering")
(hk-register-con! "GT" 0 "Ordering")

View File

@@ -0,0 +1,12 @@
{
"date": "2026-04-25",
"total_pass": 16,
"total_fail": 0,
"programs": {
"fib": {"pass": 2, "fail": 0},
"sieve": {"pass": 2, "fail": 0},
"quicksort": {"pass": 5, "fail": 0},
"nqueens": {"pass": 2, "fail": 0},
"calculator": {"pass": 5, "fail": 0}
}
}

12
lib/haskell/scoreboard.md Normal file
View File

@@ -0,0 +1,12 @@
# Haskell-on-SX Scoreboard
Updated 2026-04-25 · Phase 3 (laziness + classic programs)
| Program | Tests | Status |
|---------|-------|--------|
| fib.hs | 2/2 | ✓ |
| sieve.hs | 2/2 | ✓ |
| quicksort.hs | 5/5 | ✓ |
| nqueens.hs | 2/2 | ✓ |
| calculator.hs | 5/5 | ✓ |
| **Total** | **16/16** | **5/5 programs** |

View File

@@ -46,7 +46,13 @@ for FILE in "${FILES[@]}"; do
cat > "$TMPFILE" <<EPOCHS
(epoch 1)
(load "lib/haskell/tokenizer.sx")
(load "lib/haskell/layout.sx")
(load "lib/haskell/parser.sx")
(load "lib/haskell/desugar.sx")
(load "lib/haskell/runtime.sx")
(load "lib/haskell/match.sx")
(load "lib/haskell/eval.sx")
(load "lib/haskell/testlib.sx")
(epoch 2)
(load "$FILE")
(epoch 3)
@@ -82,7 +88,13 @@ EPOCHS
cat > "$TMPFILE2" <<EPOCHS
(epoch 1)
(load "lib/haskell/tokenizer.sx")
(load "lib/haskell/layout.sx")
(load "lib/haskell/parser.sx")
(load "lib/haskell/desugar.sx")
(load "lib/haskell/runtime.sx")
(load "lib/haskell/match.sx")
(load "lib/haskell/eval.sx")
(load "lib/haskell/testlib.sx")
(epoch 2)
(load "$FILE")
(epoch 3)

58
lib/haskell/testlib.sx Normal file
View File

@@ -0,0 +1,58 @@
;; Shared test harness for Haskell-on-SX tests.
;; Each test file expects hk-test / hk-deep=? / counters to already be bound.
(define
hk-deep=?
(fn
(a b)
(cond
((= a b) true)
((and (dict? a) (dict? b))
(let
((ak (keys a)) (bk (keys b)))
(if
(not (= (len ak) (len bk)))
false
(every?
(fn
(k)
(and (has-key? b k) (hk-deep=? (get a k) (get b k))))
ak))))
((and (list? a) (list? b))
(if
(not (= (len a) (len b)))
false
(let
((i 0) (ok true))
(define
hk-de-loop
(fn
()
(when
(and ok (< i (len a)))
(do
(when
(not (hk-deep=? (nth a i) (nth b i)))
(set! ok false))
(set! i (+ i 1))
(hk-de-loop)))))
(hk-de-loop)
ok)))
(:else false))))
(define hk-test-pass 0)
(define hk-test-fail 0)
(define hk-test-fails (list))
(define
hk-test
(fn
(name actual expected)
(if
(hk-deep=? actual expected)
(set! hk-test-pass (+ hk-test-pass 1))
(do
(set! hk-test-fail (+ hk-test-fail 1))
(append!
hk-test-fails
{:actual actual :expected expected :name name})))))

View File

@@ -0,0 +1,305 @@
;; Desugar tests — surface AST → core AST.
;; :guarded → nested :if
;; :where → :let
;; :list-comp → concatMap-based tree
(define
hk-prog
(fn (&rest decls) (list :program decls)))
;; ── Guards → if ──
(hk-test
"two-way guarded rhs"
(hk-desugar (hk-parse-top "abs x | x < 0 = - x\n | otherwise = x"))
(hk-prog
(list
:fun-clause
"abs"
(list (list :p-var "x"))
(list
:if
(list :op "<" (list :var "x") (list :int 0))
(list :neg (list :var "x"))
(list
:if
(list :var "otherwise")
(list :var "x")
(list
:app
(list :var "error")
(list :string "Non-exhaustive guards")))))))
(hk-test
"three-way guarded rhs"
(hk-desugar
(hk-parse-top "sign n | n > 0 = 1\n | n < 0 = -1\n | otherwise = 0"))
(hk-prog
(list
:fun-clause
"sign"
(list (list :p-var "n"))
(list
:if
(list :op ">" (list :var "n") (list :int 0))
(list :int 1)
(list
:if
(list :op "<" (list :var "n") (list :int 0))
(list :neg (list :int 1))
(list
:if
(list :var "otherwise")
(list :int 0)
(list
:app
(list :var "error")
(list :string "Non-exhaustive guards"))))))))
(hk-test
"case-alt guards desugared too"
(hk-desugar
(hk-parse "case x of\n Just y | y > 0 -> y\n | otherwise -> 0\n Nothing -> -1"))
(list
:case
(list :var "x")
(list
(list
:alt
(list :p-con "Just" (list (list :p-var "y")))
(list
:if
(list :op ">" (list :var "y") (list :int 0))
(list :var "y")
(list
:if
(list :var "otherwise")
(list :int 0)
(list
:app
(list :var "error")
(list :string "Non-exhaustive guards")))))
(list
:alt
(list :p-con "Nothing" (list))
(list :neg (list :int 1))))))
;; ── Where → let ──
(hk-test
"where with single binding"
(hk-desugar (hk-parse-top "f x = y\n where y = x + 1"))
(hk-prog
(list
:fun-clause
"f"
(list (list :p-var "x"))
(list
:let
(list
(list
:fun-clause
"y"
(list)
(list :op "+" (list :var "x") (list :int 1))))
(list :var "y")))))
(hk-test
"where with two bindings"
(hk-desugar
(hk-parse-top "f x = y + z\n where y = x + 1\n z = x - 1"))
(hk-prog
(list
:fun-clause
"f"
(list (list :p-var "x"))
(list
:let
(list
(list
:fun-clause
"y"
(list)
(list :op "+" (list :var "x") (list :int 1)))
(list
:fun-clause
"z"
(list)
(list :op "-" (list :var "x") (list :int 1))))
(list :op "+" (list :var "y") (list :var "z"))))))
(hk-test
"guards + where — guarded body inside let"
(hk-desugar
(hk-parse-top "f x | x > 0 = y\n | otherwise = 0\n where y = 99"))
(hk-prog
(list
:fun-clause
"f"
(list (list :p-var "x"))
(list
:let
(list (list :fun-clause "y" (list) (list :int 99)))
(list
:if
(list :op ">" (list :var "x") (list :int 0))
(list :var "y")
(list
:if
(list :var "otherwise")
(list :int 0)
(list
:app
(list :var "error")
(list :string "Non-exhaustive guards"))))))))
;; ── List comprehensions → concatMap / if / let ──
(hk-test
"list-comp: single generator"
(hk-core-expr "[x | x <- xs]")
(list
:app
(list
:app
(list :var "concatMap")
(list
:lambda
(list (list :p-var "x"))
(list :list (list (list :var "x")))))
(list :var "xs")))
(hk-test
"list-comp: generator then guard"
(hk-core-expr "[x * 2 | x <- xs, x > 0]")
(list
:app
(list
:app
(list :var "concatMap")
(list
:lambda
(list (list :p-var "x"))
(list
:if
(list :op ">" (list :var "x") (list :int 0))
(list
:list
(list (list :op "*" (list :var "x") (list :int 2))))
(list :list (list)))))
(list :var "xs")))
(hk-test
"list-comp: generator then let"
(hk-core-expr "[y | x <- xs, let y = x + 1]")
(list
:app
(list
:app
(list :var "concatMap")
(list
:lambda
(list (list :p-var "x"))
(list
:let
(list
(list
:bind
(list :p-var "y")
(list :op "+" (list :var "x") (list :int 1))))
(list :list (list (list :var "y"))))))
(list :var "xs")))
(hk-test
"list-comp: two generators (nested concatMap)"
(hk-core-expr "[(x, y) | x <- xs, y <- ys]")
(list
:app
(list
:app
(list :var "concatMap")
(list
:lambda
(list (list :p-var "x"))
(list
:app
(list
:app
(list :var "concatMap")
(list
:lambda
(list (list :p-var "y"))
(list
:list
(list
(list
:tuple
(list (list :var "x") (list :var "y")))))))
(list :var "ys"))))
(list :var "xs")))
;; ── Pass-through cases ──
(hk-test
"plain int literal unchanged"
(hk-core-expr "42")
(list :int 42))
(hk-test
"lambda + if passes through"
(hk-core-expr "\\x -> if x > 0 then x else - x")
(list
:lambda
(list (list :p-var "x"))
(list
:if
(list :op ">" (list :var "x") (list :int 0))
(list :var "x")
(list :neg (list :var "x")))))
(hk-test
"simple fun-clause (no guards/where) passes through"
(hk-desugar (hk-parse-top "id x = x"))
(hk-prog
(list
:fun-clause
"id"
(list (list :p-var "x"))
(list :var "x"))))
(hk-test
"data decl passes through"
(hk-desugar (hk-parse-top "data Maybe a = Nothing | Just a"))
(hk-prog
(list
:data
"Maybe"
(list "a")
(list
(list :con-def "Nothing" (list))
(list :con-def "Just" (list (list :t-var "a")))))))
(hk-test
"module header passes through, body desugared"
(hk-desugar
(hk-parse-top "module M where\nf x | x > 0 = 1\n | otherwise = 0"))
(list
:module
"M"
nil
(list)
(list
(list
:fun-clause
"f"
(list (list :p-var "x"))
(list
:if
(list :op ">" (list :var "x") (list :int 0))
(list :int 1)
(list
:if
(list :var "otherwise")
(list :int 0)
(list
:app
(list :var "error")
(list :string "Non-exhaustive guards"))))))))
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

117
lib/haskell/tests/do-io.sx Normal file
View File

@@ -0,0 +1,117 @@
;; do-notation + stub IO monad. Desugaring is per Haskell 98 §3.14:
;; do { e ; ss } = e >> do { ss }
;; do { p <- e ; ss } = e >>= \p -> do { ss }
;; do { let ds ; ss } = let ds in do { ss }
;; do { e } = e
;; The IO type is just `("IO" payload)` for now — no real side
;; effects yet. `return`, `>>=`, `>>` are built-ins.
(define
hk-prog-val
(fn
(src name)
(hk-deep-force (get (hk-eval-program (hk-core src)) name))))
;; ── Single-statement do ──
(hk-test
"do with a single expression"
(hk-eval-expr-source "do { return 5 }")
(list "IO" 5))
(hk-test
"return wraps any expression"
(hk-eval-expr-source "return (1 + 2 * 3)")
(list "IO" 7))
;; ── Bind threads results ──
(hk-test
"single bind"
(hk-eval-expr-source
"do { x <- return 5 ; return (x + 1) }")
(list "IO" 6))
(hk-test
"two binds"
(hk-eval-expr-source
"do\n x <- return 5\n y <- return 7\n return (x + y)")
(list "IO" 12))
(hk-test
"three binds — accumulating"
(hk-eval-expr-source
"do\n a <- return 1\n b <- return 2\n c <- return 3\n return (a + b + c)")
(list "IO" 6))
;; ── Mixing >> and >>= ──
(hk-test
">> sequencing — last wins"
(hk-eval-expr-source
"do\n return 1\n return 2\n return 3")
(list "IO" 3))
(hk-test
">> then >>= — last bind wins"
(hk-eval-expr-source
"do\n return 99\n x <- return 5\n return x")
(list "IO" 5))
;; ── do-let ──
(hk-test
"do-let single binding"
(hk-eval-expr-source
"do\n let x = 3\n return (x * 2)")
(list "IO" 6))
(hk-test
"do-let multi-bind, used after"
(hk-eval-expr-source
"do\n let x = 4\n y = 5\n return (x * y)")
(list "IO" 20))
(hk-test
"do-let interleaved with bind"
(hk-eval-expr-source
"do\n x <- return 10\n let y = x + 1\n return (x * y)")
(list "IO" 110))
;; ── Bind + pattern ──
(hk-test
"bind to constructor pattern"
(hk-eval-expr-source
"do\n Just x <- return (Just 7)\n return (x + 100)")
(list "IO" 107))
(hk-test
"bind to tuple pattern"
(hk-eval-expr-source
"do\n (a, b) <- return (3, 4)\n return (a * b)")
(list "IO" 12))
;; ── User-defined IO functions ──
(hk-test
"do inside top-level fun"
(hk-prog-val
"addM x y = do\n a <- return x\n b <- return y\n return (a + b)\nresult = addM 5 6"
"result")
(list "IO" 11))
(hk-test
"nested do"
(hk-eval-expr-source
"do\n x <- do { y <- return 3 ; return (y + 1) }\n return (x * 2)")
(list "IO" 8))
;; ── (>>=) and (>>) used directly as functions ──
(hk-test
">>= used directly"
(hk-eval-expr-source
"(return 4) >>= (\\x -> return (x + 100))")
(list "IO" 104))
(hk-test
">> used directly"
(hk-eval-expr-source
"(return 1) >> (return 2)")
(list "IO" 2))
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

278
lib/haskell/tests/eval.sx Normal file
View File

@@ -0,0 +1,278 @@
;; Strict evaluator tests. Each test parses, desugars, and evaluates
;; either an expression (hk-eval-expr-source) or a full program
;; (hk-eval-program → look up a named value).
(define
hk-prog-val
(fn
(src name)
(hk-deep-force (get (hk-eval-program (hk-core src)) name))))
;; ── Literals ──
(hk-test "int literal" (hk-eval-expr-source "42") 42)
(hk-test "float literal" (hk-eval-expr-source "3.14") 3.14)
(hk-test "string literal" (hk-eval-expr-source "\"hi\"") "hi")
(hk-test "char literal" (hk-eval-expr-source "'a'") "a")
(hk-test "negative literal" (hk-eval-expr-source "- 5") -5)
;; ── Arithmetic ──
(hk-test "addition" (hk-eval-expr-source "1 + 2") 3)
(hk-test
"precedence"
(hk-eval-expr-source "1 + 2 * 3")
7)
(hk-test
"parens override precedence"
(hk-eval-expr-source "(1 + 2) * 3")
9)
(hk-test
"subtraction left-assoc"
(hk-eval-expr-source "10 - 3 - 2")
5)
;; ── Comparison + Bool ──
(hk-test
"less than is True"
(hk-eval-expr-source "3 < 5")
(list "True"))
(hk-test
"equality is False"
(hk-eval-expr-source "1 == 2")
(list "False"))
(hk-test
"&& shortcuts"
(hk-eval-expr-source "(1 == 1) && (2 == 2)")
(list "True"))
;; ── if / otherwise ──
(hk-test
"if True"
(hk-eval-expr-source "if True then 1 else 2")
1)
(hk-test
"if comparison branch"
(hk-eval-expr-source "if 5 > 3 then \"yes\" else \"no\"")
"yes")
(hk-test "otherwise is True" (hk-eval-expr-source "otherwise") (list "True"))
;; ── let ──
(hk-test
"let single binding"
(hk-eval-expr-source "let x = 5 in x + 1")
6)
(hk-test
"let two bindings"
(hk-eval-expr-source "let x = 1; y = 2 in x + y")
3)
(hk-test
"let recursive: factorial 5"
(hk-eval-expr-source
"let f n = if n == 0 then 1 else n * f (n - 1) in f 5")
120)
;; ── Lambdas ──
(hk-test
"lambda apply"
(hk-eval-expr-source "(\\x -> x + 1) 5")
6)
(hk-test
"lambda multi-arg"
(hk-eval-expr-source "(\\x y -> x * y) 3 4")
12)
(hk-test
"lambda with constructor pattern"
(hk-eval-expr-source "(\\(Just x) -> x + 1) (Just 7)")
8)
;; ── Constructors ──
(hk-test
"0-arity constructor"
(hk-eval-expr-source "Nothing")
(list "Nothing"))
(hk-test
"1-arity constructor applied"
(hk-eval-expr-source "Just 5")
(list "Just" 5))
(hk-test
"True / False as bools"
(hk-eval-expr-source "True")
(list "True"))
;; ── case ──
(hk-test
"case Just"
(hk-eval-expr-source
"case Just 7 of Just x -> x ; Nothing -> 0")
7)
(hk-test
"case Nothing"
(hk-eval-expr-source
"case Nothing of Just x -> x ; Nothing -> 99")
99)
(hk-test
"case literal pattern"
(hk-eval-expr-source
"case 0 of 0 -> \"zero\" ; n -> \"other\"")
"zero")
(hk-test
"case tuple"
(hk-eval-expr-source
"case (1, 2) of (a, b) -> a + b")
3)
(hk-test
"case wildcard fallback"
(hk-eval-expr-source
"case 5 of 0 -> \"z\" ; _ -> \"nz\"")
"nz")
;; ── List literals + cons ──
(hk-test
"list literal as cons spine"
(hk-eval-expr-source "[1, 2, 3]")
(list ":" 1 (list ":" 2 (list ":" 3 (list "[]")))))
(hk-test
"empty list literal"
(hk-eval-expr-source "[]")
(list "[]"))
(hk-test
"cons via :"
(hk-eval-expr-source "1 : []")
(list ":" 1 (list "[]")))
(hk-test
"++ concatenates lists"
(hk-eval-expr-source "[1, 2] ++ [3]")
(list ":" 1 (list ":" 2 (list ":" 3 (list "[]")))))
;; ── Tuples ──
(hk-test
"2-tuple"
(hk-eval-expr-source "(1, 2)")
(list "Tuple" 1 2))
(hk-test
"3-tuple"
(hk-eval-expr-source "(\"a\", 5, True)")
(list "Tuple" "a" 5 (list "True")))
;; ── Sections ──
(hk-test
"right section (+ 1) applied"
(hk-eval-expr-source "(+ 1) 5")
6)
(hk-test
"left section (10 -) applied"
(hk-eval-expr-source "(10 -) 4")
6)
;; ── Multi-clause top-level functions ──
(hk-test
"multi-clause: factorial"
(hk-prog-val
"fact 0 = 1\nfact n = n * fact (n - 1)\nresult = fact 6"
"result")
720)
(hk-test
"multi-clause: list length via cons pattern"
(hk-prog-val
"len [] = 0\nlen (x:xs) = 1 + len xs\nresult = len [10, 20, 30, 40]"
"result")
4)
(hk-test
"multi-clause: Maybe handler"
(hk-prog-val
"fromMaybe d Nothing = d\nfromMaybe _ (Just x) = x\nresult = fromMaybe 0 (Just 9)"
"result")
9)
(hk-test
"multi-clause: Maybe with default"
(hk-prog-val
"fromMaybe d Nothing = d\nfromMaybe _ (Just x) = x\nresult = fromMaybe 0 Nothing"
"result")
0)
;; ── User-defined data and matching ──
(hk-test
"custom data with pattern match"
(hk-prog-val
"data Color = Red | Green | Blue\nname Red = \"red\"\nname Green = \"green\"\nname Blue = \"blue\"\nresult = name Green"
"result")
"green")
(hk-test
"custom binary tree height"
(hk-prog-val
"data Tree = Leaf | Node Tree Tree\nh Leaf = 0\nh (Node l r) = 1 + max (h l) (h r)\nmax a b = if a > b then a else b\nresult = h (Node (Node Leaf Leaf) Leaf)"
"result")
2)
;; ── Currying ──
(hk-test
"partial application"
(hk-prog-val
"add x y = x + y\nadd5 = add 5\nresult = add5 7"
"result")
12)
;; ── Higher-order ──
(hk-test
"higher-order: function as arg"
(hk-prog-val
"twice f x = f (f x)\ninc x = x + 1\nresult = twice inc 10"
"result")
12)
;; ── Error built-in ──
(hk-test
"error short-circuits via if"
(hk-eval-expr-source
"if True then 1 else error \"unreachable\"")
1)
;; ── Laziness: app args evaluate only when forced ──
(hk-test
"second arg never forced"
(hk-eval-expr-source
"(\\x y -> x) 1 (error \"never\")")
1)
(hk-test
"first arg never forced"
(hk-eval-expr-source
"(\\x y -> y) (error \"never\") 99")
99)
(hk-test
"constructor argument is lazy under wildcard pattern"
(hk-eval-expr-source
"case Just (error \"deeply\") of Just _ -> 7 ; Nothing -> 0")
7)
(hk-test
"lazy: const drops its second argument"
(hk-prog-val
"const x y = x\nresult = const 5 (error \"boom\")"
"result")
5)
(hk-test
"lazy: head ignores tail"
(hk-prog-val
"myHead (x:_) = x\nresult = myHead (1 : (error \"tail\") : [])"
"result")
1)
(hk-test
"lazy: Just on undefined evaluates only on force"
(hk-prog-val
"wrapped = Just (error \"oh no\")\nresult = case wrapped of Just _ -> True ; Nothing -> False"
"result")
(list "True"))
;; ── not / id built-ins ──
(hk-test "not True" (hk-eval-expr-source "not True") (list "False"))
(hk-test "not False" (hk-eval-expr-source "not False") (list "True"))
(hk-test "id" (hk-eval-expr-source "id 42") 42)
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -0,0 +1,137 @@
;; Infinite structures + Prelude tests. The lazy `:` operator builds
;; cons cells with thunked head/tail so recursive list-defining
;; functions terminate when only a finite prefix is consumed.
(define
hk-prog-val
(fn
(src name)
(hk-deep-force (get (hk-eval-program (hk-core src)) name))))
(define hk-as-list
(fn (xs)
(cond
((and (list? xs) (= (first xs) "[]")) (list))
((and (list? xs) (= (first xs) ":"))
(cons (nth xs 1) (hk-as-list (nth xs 2))))
(:else xs))))
(define
hk-eval-list
(fn (src) (hk-as-list (hk-eval-expr-source src))))
;; ── Prelude basics ──
(hk-test "head of literal" (hk-eval-expr-source "head [1, 2, 3]") 1)
(hk-test
"tail of literal"
(hk-eval-list "tail [1, 2, 3]")
(list 2 3))
(hk-test "length" (hk-eval-expr-source "length [10, 20, 30, 40]") 4)
(hk-test "length empty" (hk-eval-expr-source "length []") 0)
(hk-test
"map with section"
(hk-eval-list "map (+ 1) [1, 2, 3]")
(list 2 3 4))
(hk-test
"filter"
(hk-eval-list "filter (\\x -> x > 2) [1, 2, 3, 4, 5]")
(list 3 4 5))
(hk-test
"drop"
(hk-eval-list "drop 2 [10, 20, 30, 40]")
(list 30 40))
(hk-test "fst" (hk-eval-expr-source "fst (7, 9)") 7)
(hk-test "snd" (hk-eval-expr-source "snd (7, 9)") 9)
(hk-test
"zipWith"
(hk-eval-list "zipWith plus [1, 2, 3] [10, 20, 30]")
(list 11 22 33))
;; ── Infinite structures ──
(hk-test
"take from repeat"
(hk-eval-list "take 5 (repeat 7)")
(list 7 7 7 7 7))
(hk-test
"take 0 from repeat returns empty"
(hk-eval-list "take 0 (repeat 7)")
(list))
(hk-test
"take from iterate"
(hk-eval-list "take 5 (iterate (\\x -> x + 1) 0)")
(list 0 1 2 3 4))
(hk-test
"iterate with multiplication"
(hk-eval-list "take 4 (iterate (\\x -> x * 2) 1)")
(list 1 2 4 8))
(hk-test
"head of repeat"
(hk-eval-expr-source "head (repeat 99)")
99)
;; ── Fibonacci stream ──
(hk-test
"first 10 Fibonacci numbers"
(hk-eval-list "take 10 fibs")
(list 0 1 1 2 3 5 8 13 21 34))
(hk-test
"fib at position 8"
(hk-eval-expr-source "head (drop 8 fibs)")
21)
;; ── Building infinite structures in user code ──
(hk-test
"user-defined infinite ones"
(hk-prog-val
"ones = 1 : ones\nresult = take 6 ones"
"result")
(list ":" 1 (list ":" 1 (list ":" 1 (list ":" 1 (list ":" 1 (list ":" 1 (list "[]"))))))))
(hk-test
"user-defined nats"
(hk-prog-val
"nats = naturalsFrom 1\nnaturalsFrom n = n : naturalsFrom (n + 1)\nresult = take 5 nats"
"result")
(list ":" 1 (list ":" 2 (list ":" 3 (list ":" 4 (list ":" 5 (list "[]")))))))
;; ── Range syntax ──
(hk-test
"finite range [1..5]"
(hk-eval-list "[1..5]")
(list 1 2 3 4 5))
(hk-test
"empty range when from > to"
(hk-eval-list "[10..3]")
(list))
(hk-test
"stepped range"
(hk-eval-list "[1, 3..10]")
(list 1 3 5 7 9))
(hk-test
"open range — head"
(hk-eval-expr-source "head [1..]")
1)
(hk-test
"open range — drop then head"
(hk-eval-expr-source "head (drop 99 [1..])")
100)
(hk-test
"open range — take 5"
(hk-eval-list "take 5 [10..]")
(list 10 11 12 13 14))
;; ── Composing Prelude functions ──
(hk-test
"map then filter"
(hk-eval-list
"filter (\\x -> x > 5) (map (\\x -> x * 2) [1, 2, 3, 4])")
(list 6 8))
(hk-test
"sum-via-foldless"
(hk-prog-val
"mySum [] = 0\nmySum (x:xs) = x + mySum xs\nresult = mySum (take 5 (iterate (\\x -> x + 1) 1))"
"result")
15)
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

245
lib/haskell/tests/layout.sx Normal file
View File

@@ -0,0 +1,245 @@
;; Haskell layout-rule tests. hk-tokenizer + hk-layout produce a
;; virtual-brace-annotated stream; these tests cover the algorithm
;; from Haskell 98 §10.3 plus the pragmatic let/in single-line rule.
;; Convenience — tokenize, run layout, strip eof, keep :type/:value.
(define
hk-lay
(fn
(src)
(map
(fn (tok) {:value (get tok "value") :type (get tok "type")})
(filter
(fn (tok) (not (= (get tok "type") "eof")))
(hk-layout (hk-tokenize src))))))
;; ── 1. Basics ──
(hk-test
"empty input produces empty module { }"
(hk-lay "")
(list
{:value "{" :type "vlbrace"}
{:value "}" :type "vrbrace"}))
(hk-test
"single token → module open+close"
(hk-lay "foo")
(list
{:value "{" :type "vlbrace"}
{:value "foo" :type "varid"}
{:value "}" :type "vrbrace"}))
(hk-test
"two top-level decls get vsemi between"
(hk-lay "foo = 1\nbar = 2")
(list
{:value "{" :type "vlbrace"}
{:value "foo" :type "varid"}
{:value "=" :type "reservedop"}
{:value 1 :type "integer"}
{:value ";" :type "vsemi"}
{:value "bar" :type "varid"}
{:value "=" :type "reservedop"}
{:value 2 :type "integer"}
{:value "}" :type "vrbrace"}))
;; ── 2. Layout keywords — do / let / where / of ──
(hk-test
"do block with two stmts"
(hk-lay "f = do\n x\n y")
(list
{:value "{" :type "vlbrace"}
{:value "f" :type "varid"}
{:value "=" :type "reservedop"}
{:value "do" :type "reserved"}
{:value "{" :type "vlbrace"}
{:value "x" :type "varid"}
{:value ";" :type "vsemi"}
{:value "y" :type "varid"}
{:value "}" :type "vrbrace"}
{:value "}" :type "vrbrace"}))
(hk-test
"single-line let ... in"
(hk-lay "let x = 1 in x")
(list
{:value "{" :type "vlbrace"}
{:value "let" :type "reserved"}
{:value "{" :type "vlbrace"}
{:value "x" :type "varid"}
{:value "=" :type "reservedop"}
{:value 1 :type "integer"}
{:value "}" :type "vrbrace"}
{:value "in" :type "reserved"}
{:value "x" :type "varid"}
{:value "}" :type "vrbrace"}))
(hk-test
"where block with two bindings"
(hk-lay "f = g\n where\n g = 1\n h = 2")
(list
{:value "{" :type "vlbrace"}
{:value "f" :type "varid"}
{:value "=" :type "reservedop"}
{:value "g" :type "varid"}
{:value "where" :type "reserved"}
{:value "{" :type "vlbrace"}
{:value "g" :type "varid"}
{:value "=" :type "reservedop"}
{:value 1 :type "integer"}
{:value ";" :type "vsemi"}
{:value "h" :type "varid"}
{:value "=" :type "reservedop"}
{:value 2 :type "integer"}
{:value "}" :type "vrbrace"}
{:value "}" :type "vrbrace"}))
(hk-test
"case … of with arms"
(hk-lay "f x = case x of\n Just y -> y\n Nothing -> 0")
(list
{:value "{" :type "vlbrace"}
{:value "f" :type "varid"}
{:value "x" :type "varid"}
{:value "=" :type "reservedop"}
{:value "case" :type "reserved"}
{:value "x" :type "varid"}
{:value "of" :type "reserved"}
{:value "{" :type "vlbrace"}
{:value "Just" :type "conid"}
{:value "y" :type "varid"}
{:value "->" :type "reservedop"}
{:value "y" :type "varid"}
{:value ";" :type "vsemi"}
{:value "Nothing" :type "conid"}
{:value "->" :type "reservedop"}
{:value 0 :type "integer"}
{:value "}" :type "vrbrace"}
{:value "}" :type "vrbrace"}))
;; ── 3. Explicit braces disable layout ──
(hk-test
"explicit braces — no implicit vlbrace/vsemi/vrbrace inside"
(hk-lay "do { x ; y }")
(list
{:value "{" :type "vlbrace"}
{:value "do" :type "reserved"}
{:value "{" :type "lbrace"}
{:value "x" :type "varid"}
{:value ";" :type "semi"}
{:value "y" :type "varid"}
{:value "}" :type "rbrace"}
{:value "}" :type "vrbrace"}))
;; ── 4. Dedent closes nested blocks ──
(hk-test
"dedent back to module level closes do block"
(hk-lay "f = do\n x\n y\ng = 2")
(list
{:value "{" :type "vlbrace"}
{:value "f" :type "varid"}
{:value "=" :type "reservedop"}
{:value "do" :type "reserved"}
{:value "{" :type "vlbrace"}
{:value "x" :type "varid"}
{:value ";" :type "vsemi"}
{:value "y" :type "varid"}
{:value "}" :type "vrbrace"}
{:value ";" :type "vsemi"}
{:value "g" :type "varid"}
{:value "=" :type "reservedop"}
{:value 2 :type "integer"}
{:value "}" :type "vrbrace"}))
(hk-test
"dedent closes inner let, emits vsemi at outer do level"
(hk-lay "main = do\n let x = 1\n print x")
(list
{:value "{" :type "vlbrace"}
{:value "main" :type "varid"}
{:value "=" :type "reservedop"}
{:value "do" :type "reserved"}
{:value "{" :type "vlbrace"}
{:value "let" :type "reserved"}
{:value "{" :type "vlbrace"}
{:value "x" :type "varid"}
{:value "=" :type "reservedop"}
{:value 1 :type "integer"}
{:value "}" :type "vrbrace"}
{:value ";" :type "vsemi"}
{:value "print" :type "varid"}
{:value "x" :type "varid"}
{:value "}" :type "vrbrace"}
{:value "}" :type "vrbrace"}))
;; ── 5. Module header skips outer implicit open ──
(hk-test
"module M where — only where opens a block"
(hk-lay "module M where\n f = 1")
(list
{:value "module" :type "reserved"}
{:value "M" :type "conid"}
{:value "where" :type "reserved"}
{:value "{" :type "vlbrace"}
{:value "f" :type "varid"}
{:value "=" :type "reservedop"}
{:value 1 :type "integer"}
{:value "}" :type "vrbrace"}))
;; ── 6. Newlines are stripped ──
(hk-test
"newline tokens do not appear in output"
(let
((toks (hk-layout (hk-tokenize "foo\nbar"))))
(every?
(fn (t) (not (= (get t "type") "newline")))
toks))
true)
;; ── 7. Continuation — deeper indent does NOT emit vsemi ──
(hk-test
"line continuation (deeper indent) just merges"
(hk-lay "foo = 1 +\n 2")
(list
{:value "{" :type "vlbrace"}
{:value "foo" :type "varid"}
{:value "=" :type "reservedop"}
{:value 1 :type "integer"}
{:value "+" :type "varsym"}
{:value 2 :type "integer"}
{:value "}" :type "vrbrace"}))
;; ── 8. Stack closing at EOF ──
(hk-test
"EOF inside nested do closes all implicit blocks"
(let
((toks (hk-lay "main = do\n do\n x")))
(let
((n (len toks)))
(list
(get (nth toks (- n 1)) "type")
(get (nth toks (- n 2)) "type")
(get (nth toks (- n 3)) "type"))))
(list "vrbrace" "vrbrace" "vrbrace"))
;; ── 9. Qualified-newline: x at deeper col than stack top does nothing ──
(hk-test
"mixed where + do"
(hk-lay "f = do\n x\n where\n x = 1")
(list
{:value "{" :type "vlbrace"}
{:value "f" :type "varid"}
{:value "=" :type "reservedop"}
{:value "do" :type "reserved"}
{:value "{" :type "vlbrace"}
{:value "x" :type "varid"}
{:value "}" :type "vrbrace"}
{:value "where" :type "reserved"}
{:value "{" :type "vlbrace"}
{:value "x" :type "varid"}
{:value "=" :type "reservedop"}
{:value 1 :type "integer"}
{:value "}" :type "vrbrace"}
{:value "}" :type "vrbrace"}))
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

256
lib/haskell/tests/match.sx Normal file
View File

@@ -0,0 +1,256 @@
;; Pattern-matcher tests. The matcher takes (pat val env) and returns
;; an extended env dict on success, or `nil` on failure. Constructor
;; values are tagged lists (con-name first); tuples use the "Tuple"
;; tag; lists use chained `:` cons with `[]` nil.
;; ── Atomic patterns ──
(hk-test
"wildcard always matches"
(hk-match (list :p-wild) 42 (dict))
(dict))
(hk-test
"var binds value"
(hk-match (list :p-var "x") 42 (dict))
{:x 42})
(hk-test
"var preserves prior env"
(hk-match (list :p-var "y") 7 {:x 1})
{:x 1 :y 7})
(hk-test
"int literal matches equal"
(hk-match (list :p-int 5) 5 (dict))
(dict))
(hk-test
"int literal fails on mismatch"
(hk-match (list :p-int 5) 6 (dict))
nil)
(hk-test
"negative int literal matches"
(hk-match (list :p-int -3) -3 (dict))
(dict))
(hk-test
"string literal matches"
(hk-match (list :p-string "hi") "hi" (dict))
(dict))
(hk-test
"string literal fails"
(hk-match (list :p-string "hi") "bye" (dict))
nil)
(hk-test
"char literal matches"
(hk-match (list :p-char "a") "a" (dict))
(dict))
;; ── Constructor patterns ──
(hk-test
"0-arity con matches"
(hk-match
(list :p-con "Nothing" (list))
(hk-mk-con "Nothing" (list))
(dict))
(dict))
(hk-test
"1-arity con matches and binds"
(hk-match
(list :p-con "Just" (list (list :p-var "y")))
(hk-mk-con "Just" (list 9))
(dict))
{:y 9})
(hk-test
"con name mismatch fails"
(hk-match
(list :p-con "Just" (list (list :p-var "y")))
(hk-mk-con "Nothing" (list))
(dict))
nil)
(hk-test
"con arity mismatch fails"
(hk-match
(list :p-con "Pair" (list (list :p-var "a") (list :p-var "b")))
(hk-mk-con "Pair" (list 1))
(dict))
nil)
(hk-test
"nested con: Just (Just x)"
(hk-match
(list
:p-con
"Just"
(list
(list
:p-con
"Just"
(list (list :p-var "x")))))
(hk-mk-con "Just" (list (hk-mk-con "Just" (list 42))))
(dict))
{:x 42})
;; ── Tuple patterns ──
(hk-test
"2-tuple matches and binds"
(hk-match
(list
:p-tuple
(list (list :p-var "a") (list :p-var "b")))
(hk-mk-tuple (list 10 20))
(dict))
{:a 10 :b 20})
(hk-test
"tuple arity mismatch fails"
(hk-match
(list
:p-tuple
(list (list :p-var "a") (list :p-var "b")))
(hk-mk-tuple (list 10 20 30))
(dict))
nil)
;; ── List patterns ──
(hk-test
"[] pattern matches empty list"
(hk-match (list :p-list (list)) (hk-mk-nil) (dict))
(dict))
(hk-test
"[] pattern fails on non-empty"
(hk-match (list :p-list (list)) (hk-mk-list (list 1)) (dict))
nil)
(hk-test
"[a] pattern matches singleton"
(hk-match
(list :p-list (list (list :p-var "a")))
(hk-mk-list (list 7))
(dict))
{:a 7})
(hk-test
"[a, b] pattern matches pair-list and binds"
(hk-match
(list
:p-list
(list (list :p-var "a") (list :p-var "b")))
(hk-mk-list (list 1 2))
(dict))
{:a 1 :b 2})
(hk-test
"[a, b] fails on too-long list"
(hk-match
(list
:p-list
(list (list :p-var "a") (list :p-var "b")))
(hk-mk-list (list 1 2 3))
(dict))
nil)
;; Cons-style infix pattern (which the parser produces as :p-con ":")
(hk-test
"cons (h:t) on non-empty list"
(hk-match
(list
:p-con
":"
(list (list :p-var "h") (list :p-var "t")))
(hk-mk-list (list 1 2 3))
(dict))
{:h 1 :t (list ":" 2 (list ":" 3 (list "[]")))})
(hk-test
"cons fails on empty list"
(hk-match
(list
:p-con
":"
(list (list :p-var "h") (list :p-var "t")))
(hk-mk-nil)
(dict))
nil)
;; ── as patterns ──
(hk-test
"as binds whole + sub-pattern"
(hk-match
(list
:p-as
"all"
(list :p-con "Just" (list (list :p-var "x"))))
(hk-mk-con "Just" (list 99))
(dict))
{:all (list "Just" 99) :x 99})
(hk-test
"as on wildcard binds whole"
(hk-match
(list :p-as "v" (list :p-wild))
"anything"
(dict))
{:v "anything"})
(hk-test
"as fails when sub-pattern fails"
(hk-match
(list
:p-as
"n"
(list :p-con "Just" (list (list :p-var "x"))))
(hk-mk-con "Nothing" (list))
(dict))
nil)
;; ── lazy ~ pattern (eager equivalent for now) ──
(hk-test
"lazy pattern eager-matches its inner"
(hk-match
(list :p-lazy (list :p-var "y"))
42
(dict))
{:y 42})
;; ── Source-driven: parse a real Haskell pattern, match a value ──
(hk-test
"parsed pattern: Just x against Just 5"
(hk-match
(hk-parse-pat-source "Just x")
(hk-mk-con "Just" (list 5))
(dict))
{:x 5})
(hk-test
"parsed pattern: x : xs against [10, 20, 30]"
(hk-match
(hk-parse-pat-source "x : xs")
(hk-mk-list (list 10 20 30))
(dict))
{:x 10 :xs (list ":" 20 (list ":" 30 (list "[]")))})
(hk-test
"parsed pattern: (a, b) against (1, 2)"
(hk-match
(hk-parse-pat-source "(a, b)")
(hk-mk-tuple (list 1 2))
(dict))
{:a 1 :b 2})
(hk-test
"parsed pattern: n@(Just x) against Just 7"
(hk-match
(hk-parse-pat-source "n@(Just x)")
(hk-mk-con "Just" (list 7))
(dict))
{:n (list "Just" 7) :x 7})
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -3,60 +3,8 @@
;; Lightweight runner: each test checks actual vs expected with
;; structural (deep) equality and accumulates pass/fail counters.
;; Final value of this file is a summary dict with :pass :fail :fails.
(define
hk-deep=?
(fn
(a b)
(cond
((= a b) true)
((and (dict? a) (dict? b))
(let
((ak (keys a)) (bk (keys b)))
(if
(not (= (len ak) (len bk)))
false
(every?
(fn
(k)
(and (has-key? b k) (hk-deep=? (get a k) (get b k))))
ak))))
((and (list? a) (list? b))
(if
(not (= (len a) (len b)))
false
(let
((i 0) (ok true))
(define
hk-de-loop
(fn
()
(when
(and ok (< i (len a)))
(do
(when
(not (hk-deep=? (nth a i) (nth b i)))
(set! ok false))
(set! i (+ i 1))
(hk-de-loop)))))
(hk-de-loop)
ok)))
(:else false))))
(define hk-test-pass 0)
(define hk-test-fail 0)
(define hk-test-fails (list))
(define
hk-test
(fn
(name actual expected)
(if
(hk-deep=? actual expected)
(set! hk-test-pass (+ hk-test-pass 1))
(do
(set! hk-test-fail (+ hk-test-fail 1))
(append! hk-test-fails {:actual actual :expected expected :name name})))))
;; The hk-test / hk-deep=? helpers live in lib/haskell/testlib.sx
;; and are preloaded by lib/haskell/test.sh.
;; Convenience: tokenize and drop newline + eof tokens so tests focus
;; on meaningful content. Returns list of {:type :value} pairs.

View File

@@ -0,0 +1,278 @@
;; case-of and do-notation parser tests.
;; Covers the minimal patterns needed to make these meaningful: var,
;; wildcard, literal, constructor (with and without args), tuple, list.
;; ── Patterns (in case arms) ──
(hk-test
"wildcard pat"
(hk-parse "case x of _ -> 0")
(list
:case
(list :var "x")
(list (list :alt (list :p-wild) (list :int 0)))))
(hk-test
"var pat"
(hk-parse "case x of y -> y")
(list
:case
(list :var "x")
(list
(list :alt (list :p-var "y") (list :var "y")))))
(hk-test
"0-arity constructor pat"
(hk-parse "case x of\n Nothing -> 0\n Just y -> y")
(list
:case
(list :var "x")
(list
(list :alt (list :p-con "Nothing" (list)) (list :int 0))
(list
:alt
(list :p-con "Just" (list (list :p-var "y")))
(list :var "y")))))
(hk-test
"int literal pat"
(hk-parse "case n of\n 0 -> 1\n _ -> n")
(list
:case
(list :var "n")
(list
(list :alt (list :p-int 0) (list :int 1))
(list :alt (list :p-wild) (list :var "n")))))
(hk-test
"string literal pat"
(hk-parse "case s of\n \"hi\" -> 1\n _ -> 0")
(list
:case
(list :var "s")
(list
(list :alt (list :p-string "hi") (list :int 1))
(list :alt (list :p-wild) (list :int 0)))))
(hk-test
"tuple pat"
(hk-parse "case p of (a, b) -> a")
(list
:case
(list :var "p")
(list
(list
:alt
(list
:p-tuple
(list (list :p-var "a") (list :p-var "b")))
(list :var "a")))))
(hk-test
"list pat"
(hk-parse "case xs of\n [] -> 0\n [a] -> a")
(list
:case
(list :var "xs")
(list
(list :alt (list :p-list (list)) (list :int 0))
(list
:alt
(list :p-list (list (list :p-var "a")))
(list :var "a")))))
(hk-test
"nested constructor pat"
(hk-parse "case x of\n Just (a, b) -> a\n _ -> 0")
(list
:case
(list :var "x")
(list
(list
:alt
(list
:p-con
"Just"
(list
(list
:p-tuple
(list (list :p-var "a") (list :p-var "b")))))
(list :var "a"))
(list :alt (list :p-wild) (list :int 0)))))
(hk-test
"constructor with multiple var args"
(hk-parse "case t of Pair a b -> a")
(list
:case
(list :var "t")
(list
(list
:alt
(list
:p-con
"Pair"
(list (list :p-var "a") (list :p-var "b")))
(list :var "a")))))
;; ── case-of shapes ──
(hk-test
"case with explicit braces"
(hk-parse "case x of { Just y -> y ; Nothing -> 0 }")
(list
:case
(list :var "x")
(list
(list
:alt
(list :p-con "Just" (list (list :p-var "y")))
(list :var "y"))
(list :alt (list :p-con "Nothing" (list)) (list :int 0)))))
(hk-test
"case scrutinee is a full expression"
(hk-parse "case f x + 1 of\n y -> y")
(list
:case
(list
:op
"+"
(list :app (list :var "f") (list :var "x"))
(list :int 1))
(list (list :alt (list :p-var "y") (list :var "y")))))
(hk-test
"case arm body is full expression"
(hk-parse "case x of\n Just y -> y + 1")
(list
:case
(list :var "x")
(list
(list
:alt
(list :p-con "Just" (list (list :p-var "y")))
(list :op "+" (list :var "y") (list :int 1))))))
;; ── do blocks ──
(hk-test
"do with two expressions"
(hk-parse "do\n putStrLn \"hi\"\n return 0")
(list
:do
(list
(list
:do-expr
(list :app (list :var "putStrLn") (list :string "hi")))
(list
:do-expr
(list :app (list :var "return") (list :int 0))))))
(hk-test
"do with bind"
(hk-parse "do\n x <- getLine\n putStrLn x")
(list
:do
(list
(list :do-bind (list :p-var "x") (list :var "getLine"))
(list
:do-expr
(list :app (list :var "putStrLn") (list :var "x"))))))
(hk-test
"do with let"
(hk-parse "do\n let y = 5\n print y")
(list
:do
(list
(list
:do-let
(list (list :bind (list :p-var "y") (list :int 5))))
(list
:do-expr
(list :app (list :var "print") (list :var "y"))))))
(hk-test
"do with multiple let bindings"
(hk-parse "do\n let x = 1\n y = 2\n print (x + y)")
(list
:do
(list
(list
:do-let
(list
(list :bind (list :p-var "x") (list :int 1))
(list :bind (list :p-var "y") (list :int 2))))
(list
:do-expr
(list
:app
(list :var "print")
(list :op "+" (list :var "x") (list :var "y")))))))
(hk-test
"do with bind using constructor pat"
(hk-parse "do\n Just x <- getMaybe\n return x")
(list
:do
(list
(list
:do-bind
(list :p-con "Just" (list (list :p-var "x")))
(list :var "getMaybe"))
(list
:do-expr
(list :app (list :var "return") (list :var "x"))))))
(hk-test
"do with explicit braces"
(hk-parse "do { x <- a ; y <- b ; return (x + y) }")
(list
:do
(list
(list :do-bind (list :p-var "x") (list :var "a"))
(list :do-bind (list :p-var "y") (list :var "b"))
(list
:do-expr
(list
:app
(list :var "return")
(list :op "+" (list :var "x") (list :var "y")))))))
;; ── Mixing case/do inside expressions ──
(hk-test
"case inside let"
(hk-parse "let f = \\x -> case x of\n Just y -> y\n _ -> 0\nin f 5")
(list
:let
(list
(list
:bind
(list :p-var "f")
(list
:lambda
(list (list :p-var "x"))
(list
:case
(list :var "x")
(list
(list
:alt
(list :p-con "Just" (list (list :p-var "y")))
(list :var "y"))
(list :alt (list :p-wild) (list :int 0)))))))
(list :app (list :var "f") (list :int 5))))
(hk-test
"lambda containing do"
(hk-parse "\\x -> do\n y <- x\n return y")
(list
:lambda
(list (list :p-var "x"))
(list
:do
(list
(list :do-bind (list :p-var "y") (list :var "x"))
(list
:do-expr
(list :app (list :var "return") (list :var "y")))))))
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -0,0 +1,273 @@
;; Top-level declarations: function clauses, type signatures, data,
;; type, newtype, fixity. Driven by hk-parse-top which produces
;; a (:program DECLS) node.
(define
hk-prog
(fn
(&rest decls)
(list :program decls)))
;; ── Function clauses & pattern bindings ──
(hk-test
"simple fun-clause"
(hk-parse-top "f x = x + 1")
(hk-prog
(list
:fun-clause
"f"
(list (list :p-var "x"))
(list :op "+" (list :var "x") (list :int 1)))))
(hk-test
"nullary decl"
(hk-parse-top "answer = 42")
(hk-prog
(list :fun-clause "answer" (list) (list :int 42))))
(hk-test
"multi-clause fn (separate defs for each pattern)"
(hk-parse-top "fact 0 = 1\nfact n = n")
(hk-prog
(list :fun-clause "fact" (list (list :p-int 0)) (list :int 1))
(list
:fun-clause
"fact"
(list (list :p-var "n"))
(list :var "n"))))
(hk-test
"constructor pattern in fn args"
(hk-parse-top "fromJust (Just x) = x")
(hk-prog
(list
:fun-clause
"fromJust"
(list (list :p-con "Just" (list (list :p-var "x"))))
(list :var "x"))))
(hk-test
"pattern binding at top level"
(hk-parse-top "(a, b) = pair")
(hk-prog
(list
:pat-bind
(list
:p-tuple
(list (list :p-var "a") (list :p-var "b")))
(list :var "pair"))))
;; ── Type signatures ──
(hk-test
"single-name sig"
(hk-parse-top "f :: Int -> Int")
(hk-prog
(list
:type-sig
(list "f")
(list :t-fun (list :t-con "Int") (list :t-con "Int")))))
(hk-test
"multi-name sig"
(hk-parse-top "f, g, h :: Int -> Bool")
(hk-prog
(list
:type-sig
(list "f" "g" "h")
(list :t-fun (list :t-con "Int") (list :t-con "Bool")))))
(hk-test
"sig with type application"
(hk-parse-top "f :: Maybe a -> a")
(hk-prog
(list
:type-sig
(list "f")
(list
:t-fun
(list :t-app (list :t-con "Maybe") (list :t-var "a"))
(list :t-var "a")))))
(hk-test
"sig with list type"
(hk-parse-top "len :: [a] -> Int")
(hk-prog
(list
:type-sig
(list "len")
(list
:t-fun
(list :t-list (list :t-var "a"))
(list :t-con "Int")))))
(hk-test
"sig with tuple and right-assoc ->"
(hk-parse-top "pair :: a -> b -> (a, b)")
(hk-prog
(list
:type-sig
(list "pair")
(list
:t-fun
(list :t-var "a")
(list
:t-fun
(list :t-var "b")
(list
:t-tuple
(list (list :t-var "a") (list :t-var "b"))))))))
(hk-test
"sig + implementation together"
(hk-parse-top "id :: a -> a\nid x = x")
(hk-prog
(list
:type-sig
(list "id")
(list :t-fun (list :t-var "a") (list :t-var "a")))
(list
:fun-clause
"id"
(list (list :p-var "x"))
(list :var "x"))))
;; ── data declarations ──
(hk-test
"data Maybe"
(hk-parse-top "data Maybe a = Nothing | Just a")
(hk-prog
(list
:data
"Maybe"
(list "a")
(list
(list :con-def "Nothing" (list))
(list :con-def "Just" (list (list :t-var "a")))))))
(hk-test
"data Either"
(hk-parse-top "data Either a b = Left a | Right b")
(hk-prog
(list
:data
"Either"
(list "a" "b")
(list
(list :con-def "Left" (list (list :t-var "a")))
(list :con-def "Right" (list (list :t-var "b")))))))
(hk-test
"data with no type parameters"
(hk-parse-top "data Bool = True | False")
(hk-prog
(list
:data
"Bool"
(list)
(list
(list :con-def "True" (list))
(list :con-def "False" (list))))))
(hk-test
"recursive data type"
(hk-parse-top "data Tree a = Leaf | Node (Tree a) a (Tree a)")
(hk-prog
(list
:data
"Tree"
(list "a")
(list
(list :con-def "Leaf" (list))
(list
:con-def
"Node"
(list
(list :t-app (list :t-con "Tree") (list :t-var "a"))
(list :t-var "a")
(list :t-app (list :t-con "Tree") (list :t-var "a"))))))))
;; ── type synonyms ──
(hk-test
"simple type synonym"
(hk-parse-top "type Name = String")
(hk-prog
(list :type-syn "Name" (list) (list :t-con "String"))))
(hk-test
"parameterised type synonym"
(hk-parse-top "type Pair a = (a, a)")
(hk-prog
(list
:type-syn
"Pair"
(list "a")
(list
:t-tuple
(list (list :t-var "a") (list :t-var "a"))))))
;; ── newtype ──
(hk-test
"newtype"
(hk-parse-top "newtype Age = Age Int")
(hk-prog (list :newtype "Age" (list) "Age" (list :t-con "Int"))))
(hk-test
"parameterised newtype"
(hk-parse-top "newtype Wrap a = Wrap a")
(hk-prog
(list :newtype "Wrap" (list "a") "Wrap" (list :t-var "a"))))
;; ── fixity declarations ──
(hk-test
"infixl with precedence"
(hk-parse-top "infixl 5 +:, -:")
(hk-prog (list :fixity "l" 5 (list "+:" "-:"))))
(hk-test
"infixr"
(hk-parse-top "infixr 9 .")
(hk-prog (list :fixity "r" 9 (list "."))))
(hk-test
"infix (non-assoc) default prec"
(hk-parse-top "infix ==")
(hk-prog (list :fixity "n" 9 (list "=="))))
(hk-test
"fixity with backtick operator name"
(hk-parse-top "infixl 7 `div`")
(hk-prog (list :fixity "l" 7 (list "div"))))
;; ── Several decls combined ──
(hk-test
"mixed: data + sig + fn + type"
(hk-parse-top "data Maybe a = Nothing | Just a\ntype Entry = Maybe Int\nf :: Entry -> Int\nf (Just x) = x\nf Nothing = 0")
(hk-prog
(list
:data
"Maybe"
(list "a")
(list
(list :con-def "Nothing" (list))
(list :con-def "Just" (list (list :t-var "a")))))
(list
:type-syn
"Entry"
(list)
(list :t-app (list :t-con "Maybe") (list :t-con "Int")))
(list
:type-sig
(list "f")
(list :t-fun (list :t-con "Entry") (list :t-con "Int")))
(list
:fun-clause
"f"
(list (list :p-con "Just" (list (list :p-var "x"))))
(list :var "x"))
(list
:fun-clause
"f"
(list (list :p-con "Nothing" (list)))
(list :int 0))))
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -0,0 +1,258 @@
;; Haskell expression parser tests.
;; hk-parse tokenises, runs layout, then parses. Output is an AST
;; whose head is a keyword tag (evaluates to its string name).
;; ── 1. Literals ──
(hk-test "integer" (hk-parse "42") (list :int 42))
(hk-test "float" (hk-parse "3.14") (list :float 3.14))
(hk-test "string" (hk-parse "\"hi\"") (list :string "hi"))
(hk-test "char" (hk-parse "'a'") (list :char "a"))
;; ── 2. Variables and constructors ──
(hk-test "varid" (hk-parse "foo") (list :var "foo"))
(hk-test "conid" (hk-parse "Nothing") (list :con "Nothing"))
(hk-test "qvarid" (hk-parse "Data.Map.lookup") (list :var "Data.Map.lookup"))
(hk-test "qconid" (hk-parse "Data.Map") (list :con "Data.Map"))
;; ── 3. Parens / unit / tuple ──
(hk-test "parens strip" (hk-parse "(42)") (list :int 42))
(hk-test "unit" (hk-parse "()") (list :con "()"))
(hk-test
"2-tuple"
(hk-parse "(1, 2)")
(list :tuple (list (list :int 1) (list :int 2))))
(hk-test
"3-tuple"
(hk-parse "(x, y, z)")
(list
:tuple
(list (list :var "x") (list :var "y") (list :var "z"))))
;; ── 4. Lists ──
(hk-test "empty list" (hk-parse "[]") (list :list (list)))
(hk-test
"singleton list"
(hk-parse "[1]")
(list :list (list (list :int 1))))
(hk-test
"list of ints"
(hk-parse "[1, 2, 3]")
(list
:list
(list (list :int 1) (list :int 2) (list :int 3))))
(hk-test
"range"
(hk-parse "[1..10]")
(list :range (list :int 1) (list :int 10)))
(hk-test
"range with step"
(hk-parse "[1, 3..10]")
(list
:range-step
(list :int 1)
(list :int 3)
(list :int 10)))
;; ── 5. Application ──
(hk-test
"one-arg app"
(hk-parse "f x")
(list :app (list :var "f") (list :var "x")))
(hk-test
"multi-arg app is left-assoc"
(hk-parse "f x y z")
(list
:app
(list
:app
(list :app (list :var "f") (list :var "x"))
(list :var "y"))
(list :var "z")))
(hk-test
"app with con"
(hk-parse "Just 5")
(list :app (list :con "Just") (list :int 5)))
;; ── 6. Infix operators ──
(hk-test
"simple +"
(hk-parse "1 + 2")
(list :op "+" (list :int 1) (list :int 2)))
(hk-test
"precedence: * binds tighter than +"
(hk-parse "1 + 2 * 3")
(list
:op
"+"
(list :int 1)
(list :op "*" (list :int 2) (list :int 3))))
(hk-test
"- is left-assoc"
(hk-parse "10 - 3 - 2")
(list
:op
"-"
(list :op "-" (list :int 10) (list :int 3))
(list :int 2)))
(hk-test
": is right-assoc"
(hk-parse "a : b : c")
(list
:op
":"
(list :var "a")
(list :op ":" (list :var "b") (list :var "c"))))
(hk-test
"app binds tighter than op"
(hk-parse "f x + g y")
(list
:op
"+"
(list :app (list :var "f") (list :var "x"))
(list :app (list :var "g") (list :var "y"))))
(hk-test
"$ is lowest precedence, right-assoc"
(hk-parse "f $ g x")
(list
:op
"$"
(list :var "f")
(list :app (list :var "g") (list :var "x"))))
;; ── 7. Backticks (varid-as-operator) ──
(hk-test
"backtick operator"
(hk-parse "x `mod` 3")
(list :op "mod" (list :var "x") (list :int 3)))
;; ── 8. Unary negation ──
(hk-test
"unary -"
(hk-parse "- 5")
(list :neg (list :int 5)))
(hk-test
"unary - on application"
(hk-parse "- f x")
(list :neg (list :app (list :var "f") (list :var "x"))))
(hk-test
"- n + m → (- n) + m"
(hk-parse "- 1 + 2")
(list
:op
"+"
(list :neg (list :int 1))
(list :int 2)))
;; ── 9. Lambda ──
(hk-test
"lambda single param"
(hk-parse "\\x -> x")
(list :lambda (list (list :p-var "x")) (list :var "x")))
(hk-test
"lambda multi-param"
(hk-parse "\\x y -> x + y")
(list
:lambda
(list (list :p-var "x") (list :p-var "y"))
(list :op "+" (list :var "x") (list :var "y"))))
(hk-test
"lambda body is full expression"
(hk-parse "\\f -> f 1 + f 2")
(list
:lambda
(list (list :p-var "f"))
(list
:op
"+"
(list :app (list :var "f") (list :int 1))
(list :app (list :var "f") (list :int 2)))))
;; ── 10. if-then-else ──
(hk-test
"if basic"
(hk-parse "if x then 1 else 2")
(list :if (list :var "x") (list :int 1) (list :int 2)))
(hk-test
"if with infix cond"
(hk-parse "if x == 0 then y else z")
(list
:if
(list :op "==" (list :var "x") (list :int 0))
(list :var "y")
(list :var "z")))
;; ── 11. let-in ──
(hk-test
"let single binding"
(hk-parse "let x = 1 in x")
(list
:let
(list (list :bind (list :p-var "x") (list :int 1)))
(list :var "x")))
(hk-test
"let two bindings (multi-line)"
(hk-parse "let x = 1\n y = 2\nin x + y")
(list
:let
(list
(list :bind (list :p-var "x") (list :int 1))
(list :bind (list :p-var "y") (list :int 2)))
(list :op "+" (list :var "x") (list :var "y"))))
(hk-test
"let with explicit braces"
(hk-parse "let { x = 1 ; y = 2 } in x + y")
(list
:let
(list
(list :bind (list :p-var "x") (list :int 1))
(list :bind (list :p-var "y") (list :int 2)))
(list :op "+" (list :var "x") (list :var "y"))))
;; ── 12. Mixed / nesting ──
(hk-test
"nested application"
(hk-parse "f (g x) y")
(list
:app
(list
:app
(list :var "f")
(list :app (list :var "g") (list :var "x")))
(list :var "y")))
(hk-test
"lambda applied"
(hk-parse "(\\x -> x + 1) 5")
(list
:app
(list
:lambda
(list (list :p-var "x"))
(list :op "+" (list :var "x") (list :int 1)))
(list :int 5)))
(hk-test
"lambda + if"
(hk-parse "\\n -> if n == 0 then 1 else n")
(list
:lambda
(list (list :p-var "n"))
(list
:if
(list :op "==" (list :var "n") (list :int 0))
(list :int 1)
(list :var "n"))))
;; ── 13. Precedence corners ──
(hk-test
". is right-assoc (prec 9)"
(hk-parse "f . g . h")
(list
:op
"."
(list :var "f")
(list :op "." (list :var "g") (list :var "h"))))
(hk-test
"== is non-associative (single use)"
(hk-parse "x == y")
(list :op "==" (list :var "x") (list :var "y")))
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -0,0 +1,261 @@
;; Guards and where-clauses — on fun-clauses, case alts, and
;; let-bindings (which now also accept funclause-style LHS like
;; `let f x = e` or `let f x | g = e | g = e`).
(define
hk-prog
(fn (&rest decls) (list :program decls)))
;; ── Guarded fun-clauses ──
(hk-test
"simple guards (two branches)"
(hk-parse-top "abs x | x < 0 = - x\n | otherwise = x")
(hk-prog
(list
:fun-clause
"abs"
(list (list :p-var "x"))
(list
:guarded
(list
(list
:guard
(list :op "<" (list :var "x") (list :int 0))
(list :neg (list :var "x")))
(list :guard (list :var "otherwise") (list :var "x")))))))
(hk-test
"three-way guard"
(hk-parse-top "sign n | n > 0 = 1\n | n < 0 = -1\n | otherwise = 0")
(hk-prog
(list
:fun-clause
"sign"
(list (list :p-var "n"))
(list
:guarded
(list
(list
:guard
(list :op ">" (list :var "n") (list :int 0))
(list :int 1))
(list
:guard
(list :op "<" (list :var "n") (list :int 0))
(list :neg (list :int 1)))
(list
:guard
(list :var "otherwise")
(list :int 0)))))))
(hk-test
"mixed: one eq clause plus one guarded clause"
(hk-parse-top "sign 0 = 0\nsign n | n > 0 = 1\n | otherwise = -1")
(hk-prog
(list
:fun-clause
"sign"
(list (list :p-int 0))
(list :int 0))
(list
:fun-clause
"sign"
(list (list :p-var "n"))
(list
:guarded
(list
(list
:guard
(list :op ">" (list :var "n") (list :int 0))
(list :int 1))
(list
:guard
(list :var "otherwise")
(list :neg (list :int 1))))))))
;; ── where on fun-clauses ──
(hk-test
"where with one binding"
(hk-parse-top "f x = y + y\n where y = x + 1")
(hk-prog
(list
:fun-clause
"f"
(list (list :p-var "x"))
(list
:where
(list :op "+" (list :var "y") (list :var "y"))
(list
(list
:fun-clause
"y"
(list)
(list :op "+" (list :var "x") (list :int 1))))))))
(hk-test
"where with multiple bindings"
(hk-parse-top "f x = y * z\n where y = x + 1\n z = x - 1")
(hk-prog
(list
:fun-clause
"f"
(list (list :p-var "x"))
(list
:where
(list :op "*" (list :var "y") (list :var "z"))
(list
(list
:fun-clause
"y"
(list)
(list :op "+" (list :var "x") (list :int 1)))
(list
:fun-clause
"z"
(list)
(list :op "-" (list :var "x") (list :int 1))))))))
(hk-test
"guards + where"
(hk-parse-top "f x | x > 0 = y\n | otherwise = 0\n where y = 99")
(hk-prog
(list
:fun-clause
"f"
(list (list :p-var "x"))
(list
:where
(list
:guarded
(list
(list
:guard
(list :op ">" (list :var "x") (list :int 0))
(list :var "y"))
(list
:guard
(list :var "otherwise")
(list :int 0))))
(list
(list :fun-clause "y" (list) (list :int 99)))))))
;; ── Guards in case alts ──
(hk-test
"case alt with guards"
(hk-parse "case x of\n Just y | y > 0 -> y\n | otherwise -> 0\n Nothing -> 0")
(list
:case
(list :var "x")
(list
(list
:alt
(list :p-con "Just" (list (list :p-var "y")))
(list
:guarded
(list
(list
:guard
(list :op ">" (list :var "y") (list :int 0))
(list :var "y"))
(list
:guard
(list :var "otherwise")
(list :int 0)))))
(list :alt (list :p-con "Nothing" (list)) (list :int 0)))))
(hk-test
"case alt with where"
(hk-parse "case x of\n Just y -> y + z where z = 5\n Nothing -> 0")
(list
:case
(list :var "x")
(list
(list
:alt
(list :p-con "Just" (list (list :p-var "y")))
(list
:where
(list :op "+" (list :var "y") (list :var "z"))
(list
(list :fun-clause "z" (list) (list :int 5)))))
(list :alt (list :p-con "Nothing" (list)) (list :int 0)))))
;; ── let-bindings: funclause form, guards, where ──
(hk-test
"let with funclause shorthand"
(hk-parse "let f x = x + 1 in f 5")
(list
:let
(list
(list
:fun-clause
"f"
(list (list :p-var "x"))
(list :op "+" (list :var "x") (list :int 1))))
(list :app (list :var "f") (list :int 5))))
(hk-test
"let with guards"
(hk-parse "let f x | x > 0 = x\n | otherwise = 0\nin f 3")
(list
:let
(list
(list
:fun-clause
"f"
(list (list :p-var "x"))
(list
:guarded
(list
(list
:guard
(list :op ">" (list :var "x") (list :int 0))
(list :var "x"))
(list
:guard
(list :var "otherwise")
(list :int 0))))))
(list :app (list :var "f") (list :int 3))))
(hk-test
"let funclause + where"
(hk-parse "let f x = y where y = x + 1\nin f 7")
(list
:let
(list
(list
:fun-clause
"f"
(list (list :p-var "x"))
(list
:where
(list :var "y")
(list
(list
:fun-clause
"y"
(list)
(list :op "+" (list :var "x") (list :int 1)))))))
(list :app (list :var "f") (list :int 7))))
;; ── Nested: where inside where (via recursive hk-parse-decl) ──
(hk-test
"where block can contain a type signature"
(hk-parse-top "f x = y\n where y :: Int\n y = x")
(hk-prog
(list
:fun-clause
"f"
(list (list :p-var "x"))
(list
:where
(list :var "y")
(list
(list :type-sig (list "y") (list :t-con "Int"))
(list
:fun-clause
"y"
(list)
(list :var "x")))))))
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -0,0 +1,202 @@
;; Module header + imports. The parser switches from (:program DECLS)
;; to (:module NAME EXPORTS IMPORTS DECLS) as soon as a module header
;; or any `import` decl appears.
;; ── Module header ──
(hk-test
"simple module, no exports"
(hk-parse-top "module M where\n f = 1")
(list
:module
"M"
nil
(list)
(list (list :fun-clause "f" (list) (list :int 1)))))
(hk-test
"module with dotted name"
(hk-parse-top "module Data.Map where\nf = 1")
(list
:module
"Data.Map"
nil
(list)
(list (list :fun-clause "f" (list) (list :int 1)))))
(hk-test
"module with empty export list"
(hk-parse-top "module M () where\nf = 1")
(list
:module
"M"
(list)
(list)
(list (list :fun-clause "f" (list) (list :int 1)))))
(hk-test
"module with exports (var, tycon-all, tycon-with)"
(hk-parse-top "module M (f, g, Maybe(..), List(Cons, Nil)) where\nf = 1\ng = 2")
(list
:module
"M"
(list
(list :ent-var "f")
(list :ent-var "g")
(list :ent-all "Maybe")
(list :ent-with "List" (list "Cons" "Nil")))
(list)
(list
(list :fun-clause "f" (list) (list :int 1))
(list :fun-clause "g" (list) (list :int 2)))))
(hk-test
"module export list including another module"
(hk-parse-top "module M (module Foo, f) where\nf = 1")
(list
:module
"M"
(list (list :ent-module "Foo") (list :ent-var "f"))
(list)
(list (list :fun-clause "f" (list) (list :int 1)))))
(hk-test
"module export with operator"
(hk-parse-top "module M ((+:), f) where\nf = 1")
(list
:module
"M"
(list (list :ent-var "+:") (list :ent-var "f"))
(list)
(list (list :fun-clause "f" (list) (list :int 1)))))
(hk-test
"empty module body"
(hk-parse-top "module M where")
(list :module "M" nil (list) (list)))
;; ── Imports ──
(hk-test
"plain import"
(hk-parse-top "import Foo")
(list
:module
nil
nil
(list (list :import false "Foo" nil nil))
(list)))
(hk-test
"qualified import"
(hk-parse-top "import qualified Data.Map")
(list
:module
nil
nil
(list (list :import true "Data.Map" nil nil))
(list)))
(hk-test
"import with alias"
(hk-parse-top "import Data.Map as M")
(list
:module
nil
nil
(list (list :import false "Data.Map" "M" nil))
(list)))
(hk-test
"import with explicit list"
(hk-parse-top "import Foo (bar, Baz(..), Quux(X, Y))")
(list
:module
nil
nil
(list
(list
:import
false
"Foo"
nil
(list
:spec-items
(list
(list :ent-var "bar")
(list :ent-all "Baz")
(list :ent-with "Quux" (list "X" "Y"))))))
(list)))
(hk-test
"import hiding"
(hk-parse-top "import Foo hiding (x, y)")
(list
:module
nil
nil
(list
(list
:import
false
"Foo"
nil
(list
:spec-hiding
(list (list :ent-var "x") (list :ent-var "y")))))
(list)))
(hk-test
"qualified + alias + hiding"
(hk-parse-top "import qualified Data.List as L hiding (sort)")
(list
:module
nil
nil
(list
(list
:import
true
"Data.List"
"L"
(list :spec-hiding (list (list :ent-var "sort")))))
(list)))
;; ── Combinations ──
(hk-test
"module with multiple imports and a decl"
(hk-parse-top "module M where\nimport Foo\nimport qualified Bar as B\nf = 1")
(list
:module
"M"
nil
(list
(list :import false "Foo" nil nil)
(list :import true "Bar" "B" nil))
(list (list :fun-clause "f" (list) (list :int 1)))))
(hk-test
"headerless file with imports"
(hk-parse-top "import Foo\nimport Bar (baz)\nf = 1")
(list
:module
nil
nil
(list
(list :import false "Foo" nil nil)
(list
:import
false
"Bar"
nil
(list :spec-items (list (list :ent-var "baz")))))
(list (list :fun-clause "f" (list) (list :int 1)))))
(hk-test
"plain program (no header, no imports) still uses :program"
(hk-parse-top "f = 1\ng = 2")
(list
:program
(list
(list :fun-clause "f" (list) (list :int 1))
(list :fun-clause "g" (list) (list :int 2)))))
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -0,0 +1,234 @@
;; Full-pattern parser tests: as-patterns, lazy ~, negative literals,
;; infix constructor patterns (`:`, any consym), lambda pattern args,
;; and let pattern-bindings.
;; ── as-patterns ──
(hk-test
"as pattern, wraps constructor"
(hk-parse "case x of n@(Just y) -> n")
(list
:case
(list :var "x")
(list
(list
:alt
(list
:p-as
"n"
(list :p-con "Just" (list (list :p-var "y"))))
(list :var "n")))))
(hk-test
"as pattern, wraps wildcard"
(hk-parse "case x of all@_ -> all")
(list
:case
(list :var "x")
(list
(list
:alt
(list :p-as "all" (list :p-wild))
(list :var "all")))))
(hk-test
"as in lambda"
(hk-parse "\\xs@(a : rest) -> xs")
(list
:lambda
(list
(list
:p-as
"xs"
(list
:p-con
":"
(list (list :p-var "a") (list :p-var "rest")))))
(list :var "xs")))
;; ── lazy patterns ──
(hk-test
"lazy var"
(hk-parse "case x of ~y -> y")
(list
:case
(list :var "x")
(list
(list :alt (list :p-lazy (list :p-var "y")) (list :var "y")))))
(hk-test
"lazy constructor"
(hk-parse "\\(~(Just x)) -> x")
(list
:lambda
(list
(list
:p-lazy
(list :p-con "Just" (list (list :p-var "x")))))
(list :var "x")))
;; ── negative literal patterns ──
(hk-test
"negative int pattern"
(hk-parse "case n of\n -1 -> 0\n _ -> n")
(list
:case
(list :var "n")
(list
(list :alt (list :p-int -1) (list :int 0))
(list :alt (list :p-wild) (list :var "n")))))
(hk-test
"negative float pattern"
(hk-parse "case x of -0.5 -> 1")
(list
:case
(list :var "x")
(list (list :alt (list :p-float -0.5) (list :int 1)))))
;; ── infix constructor patterns (`:` and any consym) ──
(hk-test
"cons pattern"
(hk-parse "case xs of x : rest -> x")
(list
:case
(list :var "xs")
(list
(list
:alt
(list
:p-con
":"
(list (list :p-var "x") (list :p-var "rest")))
(list :var "x")))))
(hk-test
"cons is right-associative in pats"
(hk-parse "case xs of a : b : rest -> rest")
(list
:case
(list :var "xs")
(list
(list
:alt
(list
:p-con
":"
(list
(list :p-var "a")
(list
:p-con
":"
(list (list :p-var "b") (list :p-var "rest")))))
(list :var "rest")))))
(hk-test
"consym pattern"
(hk-parse "case p of a :+: b -> a")
(list
:case
(list :var "p")
(list
(list
:alt
(list
:p-con
":+:"
(list (list :p-var "a") (list :p-var "b")))
(list :var "a")))))
;; ── lambda with pattern args ──
(hk-test
"lambda with constructor pattern"
(hk-parse "\\(Just x) -> x")
(list
:lambda
(list (list :p-con "Just" (list (list :p-var "x"))))
(list :var "x")))
(hk-test
"lambda with tuple pattern"
(hk-parse "\\(a, b) -> a + b")
(list
:lambda
(list
(list
:p-tuple
(list (list :p-var "a") (list :p-var "b"))))
(list :op "+" (list :var "a") (list :var "b"))))
(hk-test
"lambda with wildcard"
(hk-parse "\\_ -> 42")
(list :lambda (list (list :p-wild)) (list :int 42)))
(hk-test
"lambda with mixed apats"
(hk-parse "\\x _ (Just y) -> y")
(list
:lambda
(list
(list :p-var "x")
(list :p-wild)
(list :p-con "Just" (list (list :p-var "y"))))
(list :var "y")))
;; ── let pattern-bindings ──
(hk-test
"let tuple pattern-binding"
(hk-parse "let (x, y) = pair in x + y")
(list
:let
(list
(list
:bind
(list
:p-tuple
(list (list :p-var "x") (list :p-var "y")))
(list :var "pair")))
(list :op "+" (list :var "x") (list :var "y"))))
(hk-test
"let constructor pattern-binding"
(hk-parse "let Just x = m in x")
(list
:let
(list
(list
:bind
(list :p-con "Just" (list (list :p-var "x")))
(list :var "m")))
(list :var "x")))
(hk-test
"let cons pattern-binding"
(hk-parse "let (x : rest) = xs in x")
(list
:let
(list
(list
:bind
(list
:p-con
":"
(list (list :p-var "x") (list :p-var "rest")))
(list :var "xs")))
(list :var "x")))
;; ── do with constructor-pattern binds ──
(hk-test
"do bind to tuple pattern"
(hk-parse "do\n (a, b) <- pairs\n return a")
(list
:do
(list
(list
:do-bind
(list
:p-tuple
(list (list :p-var "a") (list :p-var "b")))
(list :var "pairs"))
(list
:do-expr
(list :app (list :var "return") (list :var "a"))))))
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -0,0 +1,191 @@
;; Operator sections and list comprehensions.
;; ── Operator references (unchanged expr shape) ──
(hk-test
"op as value (+)"
(hk-parse "(+)")
(list :var "+"))
(hk-test
"op as value (-)"
(hk-parse "(-)")
(list :var "-"))
(hk-test
"op as value (:)"
(hk-parse "(:)")
(list :var ":"))
(hk-test
"backtick op as value"
(hk-parse "(`div`)")
(list :var "div"))
;; ── Right sections (op expr) ──
(hk-test
"right section (+ 5)"
(hk-parse "(+ 5)")
(list :sect-right "+" (list :int 5)))
(hk-test
"right section (* x)"
(hk-parse "(* x)")
(list :sect-right "*" (list :var "x")))
(hk-test
"right section with backtick op"
(hk-parse "(`div` 2)")
(list :sect-right "div" (list :int 2)))
;; `-` is unary in expr position — (- 5) is negation, not a right section
(hk-test
"(- 5) is negation, not a section"
(hk-parse "(- 5)")
(list :neg (list :int 5)))
;; ── Left sections (expr op) ──
(hk-test
"left section (5 +)"
(hk-parse "(5 +)")
(list :sect-left "+" (list :int 5)))
(hk-test
"left section with backtick"
(hk-parse "(x `mod`)")
(list :sect-left "mod" (list :var "x")))
(hk-test
"left section with cons (x :)"
(hk-parse "(x :)")
(list :sect-left ":" (list :var "x")))
;; ── Mixed / nesting ──
(hk-test
"map (+ 1) xs"
(hk-parse "map (+ 1) xs")
(list
:app
(list
:app
(list :var "map")
(list :sect-right "+" (list :int 1)))
(list :var "xs")))
(hk-test
"filter (< 0) xs"
(hk-parse "filter (< 0) xs")
(list
:app
(list
:app
(list :var "filter")
(list :sect-right "<" (list :int 0)))
(list :var "xs")))
;; ── Plain parens and tuples still work ──
(hk-test
"plain parens unwrap"
(hk-parse "(1 + 2)")
(list :op "+" (list :int 1) (list :int 2)))
(hk-test
"tuple still parses"
(hk-parse "(a, b, c)")
(list
:tuple
(list (list :var "a") (list :var "b") (list :var "c"))))
;; ── List comprehensions ──
(hk-test
"simple list comprehension"
(hk-parse "[x | x <- xs]")
(list
:list-comp
(list :var "x")
(list
(list :q-gen (list :p-var "x") (list :var "xs")))))
(hk-test
"comprehension with filter"
(hk-parse "[x * 2 | x <- xs, x > 0]")
(list
:list-comp
(list :op "*" (list :var "x") (list :int 2))
(list
(list :q-gen (list :p-var "x") (list :var "xs"))
(list
:q-guard
(list :op ">" (list :var "x") (list :int 0))))))
(hk-test
"comprehension with let"
(hk-parse "[y | x <- xs, let y = x + 1]")
(list
:list-comp
(list :var "y")
(list
(list :q-gen (list :p-var "x") (list :var "xs"))
(list
:q-let
(list
(list
:bind
(list :p-var "y")
(list :op "+" (list :var "x") (list :int 1))))))))
(hk-test
"nested generators"
(hk-parse "[(x, y) | x <- xs, y <- ys]")
(list
:list-comp
(list :tuple (list (list :var "x") (list :var "y")))
(list
(list :q-gen (list :p-var "x") (list :var "xs"))
(list :q-gen (list :p-var "y") (list :var "ys")))))
(hk-test
"comprehension with constructor pattern"
(hk-parse "[v | Just v <- xs]")
(list
:list-comp
(list :var "v")
(list
(list
:q-gen
(list :p-con "Just" (list (list :p-var "v")))
(list :var "xs")))))
(hk-test
"comprehension with tuple pattern"
(hk-parse "[x + y | (x, y) <- pairs]")
(list
:list-comp
(list :op "+" (list :var "x") (list :var "y"))
(list
(list
:q-gen
(list
:p-tuple
(list (list :p-var "x") (list :p-var "y")))
(list :var "pairs")))))
(hk-test
"combination: generator, let, guard"
(hk-parse "[z | x <- xs, let z = x * 2, z > 10]")
(list
:list-comp
(list :var "z")
(list
(list :q-gen (list :p-var "x") (list :var "xs"))
(list
:q-let
(list
(list
:bind
(list :p-var "z")
(list :op "*" (list :var "x") (list :int 2)))))
(list
:q-guard
(list :op ">" (list :var "z") (list :int 10))))))
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -0,0 +1,55 @@
;; calculator.hs — recursive descent expression evaluator.
;;
;; Exercises:
;; - ADTs with constructor fields: TNum Int, TOp String, R Int [Token]
;; - Nested constructor pattern matching: (R v (TOp "+":rest))
;; - let bindings in function bodies
;; - Integer arithmetic including `div` (backtick infix)
;; - Left-associative multi-level operator precedence
(define
hk-prog-val
(fn
(src name)
(hk-deep-force (get (hk-eval-program (hk-core src)) name))))
(define
hk-calc-src
"data Token = TNum Int | TOp String\ndata Result = R Int [Token]\ngetV (R v _) = v\ngetR (R _ r) = r\neval ts = getV (parseExpr ts)\nparseExpr ts = parseExprRest (parseTerm ts)\nparseExprRest (R v (TOp \"+\":rest)) =\n let t = parseTerm rest\n in parseExprRest (R (v + getV t) (getR t))\nparseExprRest (R v (TOp \"-\":rest)) =\n let t = parseTerm rest\n in parseExprRest (R (v - getV t) (getR t))\nparseExprRest r = r\nparseTerm ts = parseTermRest (parseFactor ts)\nparseTermRest (R v (TOp \"*\":rest)) =\n let t = parseFactor rest\n in parseTermRest (R (v * getV t) (getR t))\nparseTermRest (R v (TOp \"/\":rest)) =\n let t = parseFactor rest\n in parseTermRest (R (v `div` getV t) (getR t))\nparseTermRest r = r\nparseFactor (TNum n:rest) = R n rest\n")
(hk-test
"calculator: 2 + 3 = 5"
(hk-prog-val
(str hk-calc-src "result = eval [TNum 2, TOp \"+\", TNum 3]\n")
"result")
5)
(hk-test
"calculator: 2 + 3 * 4 = 14 (precedence)"
(hk-prog-val
(str hk-calc-src "result = eval [TNum 2, TOp \"+\", TNum 3, TOp \"*\", TNum 4]\n")
"result")
14)
(hk-test
"calculator: 10 - 3 - 2 = 5 (left-assoc)"
(hk-prog-val
(str hk-calc-src "result = eval [TNum 10, TOp \"-\", TNum 3, TOp \"-\", TNum 2]\n")
"result")
5)
(hk-test
"calculator: 6 / 2 * 3 = 9 (left-assoc)"
(hk-prog-val
(str hk-calc-src "result = eval [TNum 6, TOp \"/\", TNum 2, TOp \"*\", TNum 3]\n")
"result")
9)
(hk-test
"calculator: single number"
(hk-prog-val
(str hk-calc-src "result = eval [TNum 42]\n")
"result")
42)
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -0,0 +1,45 @@
;; fib.hs — infinite Fibonacci stream classic program.
;;
;; The canonical artefact lives at lib/haskell/tests/programs/fib.hs.
;; The source is mirrored here as an SX string because the evaluator
;; doesn't have read-file in the default env. If you change one, keep
;; the other in sync — there's a runner-level cross-check against the
;; expected first-15 list.
(define
hk-prog-val
(fn
(src name)
(hk-deep-force (get (hk-eval-program (hk-core src)) name))))
(define hk-as-list
(fn (xs)
(cond
((and (list? xs) (= (first xs) "[]")) (list))
((and (list? xs) (= (first xs) ":"))
(cons (nth xs 1) (hk-as-list (nth xs 2))))
(:else xs))))
(define
hk-fib-source
"zipPlus (x:xs) (y:ys) = x + y : zipPlus xs ys
zipPlus _ _ = []
myFibs = 0 : 1 : zipPlus myFibs (tail myFibs)
result = take 15 myFibs
")
(hk-test
"fib.hs — first 15 Fibonacci numbers"
(hk-as-list (hk-prog-val hk-fib-source "result"))
(list 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377))
;; Spot-check that the user-defined zipPlus is also reachable
(hk-test
"fib.hs — zipPlus is a multi-clause user fn"
(hk-as-list
(hk-prog-val
(str hk-fib-source "extra = zipPlus [1, 2, 3] [10, 20, 30]\n")
"extra"))
(list 11 22 33))
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -0,0 +1,38 @@
;; nqueens.hs — n-queens solver via list comprehension + where.
;;
;; Also exercises:
;; - multi-clause let/where binding (go 0 = ...; go k = ...)
;; - list comprehensions (desugared to concatMap)
;; - abs (from Prelude)
;; - [1..n] finite range
;;
;; n=8 is too slow for a 60s timeout; n=4 and n=5 run in ~17s combined.
(define
hk-prog-val
(fn
(src name)
(hk-deep-force (get (hk-eval-program (hk-core src)) name))))
(define
hk-nq-base
"queens n = go n
where
go 0 = [[]]
go k = [q:qs | qs <- go (k - 1), q <- [1..n], safe q qs]
safe q qs = check q qs 1
check q [] _ = True
check q (c:cs) d = q /= c && abs (q - c) /= d && check q cs (d + 1)
")
(hk-test
"nqueens: queens 4 has 2 solutions"
(hk-prog-val (str hk-nq-base "result = length (queens 4)\n") "result")
2)
(hk-test
"nqueens: queens 5 has 10 solutions"
(hk-prog-val (str hk-nq-base "result = length (queens 5)\n") "result")
10)
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -0,0 +1,65 @@
;; quicksort.hs — naive functional quicksort.
(define
hk-as-list
(fn (xs)
(cond
((and (list? xs) (= (first xs) "[]")) (list))
((and (list? xs) (= (first xs) ":"))
(cons (nth xs 1) (hk-as-list (nth xs 2))))
(:else xs))))
(define
hk-prog-val
(fn
(src name)
(hk-deep-force (get (hk-eval-program (hk-core src)) name))))
(define
hk-qs-source
"qsort [] = []
qsort (x:xs) = qsort smaller ++ [x] ++ qsort larger
where
smaller = filter (< x) xs
larger = filter (>= x) xs
result = qsort [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
")
(hk-test
"quicksort.hs — sort a list of ints"
(hk-as-list (hk-prog-val hk-qs-source "result"))
(list 1 1 2 3 3 4 5 5 5 6 9))
(hk-test
"quicksort.hs — empty list"
(hk-as-list
(hk-prog-val
(str hk-qs-source "e = qsort []\n")
"e"))
(list))
(hk-test
"quicksort.hs — singleton"
(hk-as-list
(hk-prog-val
(str hk-qs-source "s = qsort [42]\n")
"s"))
(list 42))
(hk-test
"quicksort.hs — already sorted"
(hk-as-list
(hk-prog-val
(str hk-qs-source "asc = qsort [1, 2, 3, 4, 5]\n")
"asc"))
(list 1 2 3 4 5))
(hk-test
"quicksort.hs — reverse sorted"
(hk-as-list
(hk-prog-val
(str hk-qs-source "desc = qsort [5, 4, 3, 2, 1]\n")
"desc"))
(list 1 2 3 4 5))
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -0,0 +1,48 @@
;; sieve.hs — lazy sieve of Eratosthenes.
;;
;; The canonical artefact lives at lib/haskell/tests/programs/sieve.hs.
;; Mirrored here as an SX string because the default eval env has no
;; read-file. Uses filter + backtick `mod` + lazy [2..] — all of which
;; are now wired in via Phase 3 + the mod/div additions to hk-binop.
(define
hk-as-list
(fn (xs)
(cond
((and (list? xs) (= (first xs) "[]")) (list))
((and (list? xs) (= (first xs) ":"))
(cons (nth xs 1) (hk-as-list (nth xs 2))))
(:else xs))))
(define
hk-prog-val
(fn
(src name)
(hk-deep-force (get (hk-eval-program (hk-core src)) name))))
(define
hk-sieve-source
"sieve (p:xs) = p : sieve (filter (\\x -> x `mod` p /= 0) xs)
sieve [] = []
primes = sieve [2..]
result = take 10 primes
")
(hk-test
"sieve.hs — first 10 primes"
(hk-as-list (hk-prog-val hk-sieve-source "result"))
(list 2 3 5 7 11 13 17 19 23 29))
(hk-test
"sieve.hs — 20th prime is 71"
(nth
(hk-as-list
(hk-prog-val
(str
hk-sieve-source
"result20 = take 20 primes\n")
"result20"))
19)
71)
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -0,0 +1,40 @@
-- calculator.hs — recursive descent expression evaluator.
--
-- Tokens are represented as an ADT; the parser threads a [Token] list
-- through a custom Result type so pattern matching can destructure the
-- pair (value, remaining-tokens) directly inside constructor patterns.
--
-- Operator precedence: * and / bind tighter than + and -.
-- All operators are left-associative.
data Token = TNum Int | TOp String
data Result = R Int [Token]
getV (R v _) = v
getR (R _ r) = r
eval ts = getV (parseExpr ts)
parseExpr ts = parseExprRest (parseTerm ts)
parseExprRest (R v (TOp "+":rest)) =
let t = parseTerm rest
in parseExprRest (R (v + getV t) (getR t))
parseExprRest (R v (TOp "-":rest)) =
let t = parseTerm rest
in parseExprRest (R (v - getV t) (getR t))
parseExprRest r = r
parseTerm ts = parseTermRest (parseFactor ts)
parseTermRest (R v (TOp "*":rest)) =
let t = parseFactor rest
in parseTermRest (R (v * getV t) (getR t))
parseTermRest (R v (TOp "/":rest)) =
let t = parseFactor rest
in parseTermRest (R (v `div` getV t) (getR t))
parseTermRest r = r
parseFactor (TNum n:rest) = R n rest
result = eval [TNum 2, TOp "+", TNum 3, TOp "*", TNum 4]

View File

@@ -0,0 +1,15 @@
-- fib.hs — infinite Fibonacci stream.
--
-- The classic two-line definition: `fibs` is a self-referential
-- lazy list built by zipping itself with its own tail, summing the
-- pair at each step. Without lazy `:` (cons cell with thunked head
-- and tail) this would diverge before producing any output; with
-- it, `take 15 fibs` evaluates exactly as much of the spine as
-- demanded.
zipPlus (x:xs) (y:ys) = x + y : zipPlus xs ys
zipPlus _ _ = []
myFibs = 0 : 1 : zipPlus myFibs (tail myFibs)
result = take 15 myFibs

View File

@@ -0,0 +1,18 @@
-- nqueens.hs — n-queens backtracking solver.
--
-- `queens n` returns all solutions as lists of column positions,
-- one per row. Each call to `go k` extends all partial `(k-1)`-row
-- solutions by one safe queen, using a list comprehension whose guard
-- checks the new queen against all already-placed queens.
queens n = go n
where
go 0 = [[]]
go k = [q:qs | qs <- go (k - 1), q <- [1..n], safe q qs]
safe q qs = check q qs 1
check q [] _ = True
check q (c:cs) d = q /= c && abs (q - c) /= d && check q cs (d + 1)
result = length (queens 8)

View File

@@ -0,0 +1,12 @@
-- quicksort.hs — naive functional quicksort.
--
-- Partition by pivot, recurse on each half, concatenate.
-- Uses right sections `(< x)` and `(>= x)` with filter.
qsort [] = []
qsort (x:xs) = qsort smaller ++ [x] ++ qsort larger
where
smaller = filter (< x) xs
larger = filter (>= x) xs
result = qsort [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]

View File

@@ -0,0 +1,13 @@
-- sieve.hs — lazy sieve of Eratosthenes.
--
-- Each recursive call to `sieve` consumes one prime `p` off the front
-- of the input stream and produces an infinite stream of composites
-- filtered out via `filter`. Because cons is lazy, only as much of
-- the stream is forced as demanded by `take`.
sieve (p:xs) = p : sieve (filter (\x -> x `mod` p /= 0) xs)
sieve [] = []
primes = sieve [2..]
result = take 10 primes

View File

@@ -1,451 +1,127 @@
;; lib/haskell/tests/runtime.sx — smoke-tests for lib/haskell/runtime.sx
;;
;; Uses the same hk-test framework as tests/parse.sx.
;; Loaded by test.sh after: tokenizer.sx + runtime.sx are pre-loaded.
;; Runtime constructor-registry tests. Built-ins are pre-registered
;; when lib/haskell/runtime.sx loads; user types are registered by
;; walking a parsed+desugared AST with hk-register-program! (or the
;; `hk-load-source!` convenience).
;; ---------------------------------------------------------------------------
;; Test framework boilerplate (mirrors parse.sx)
;; ---------------------------------------------------------------------------
;; ── Pre-registered built-ins ──
(hk-test "True is a con" (hk-is-con? "True") true)
(hk-test "False is a con" (hk-is-con? "False") true)
(hk-test "[] is a con" (hk-is-con? "[]") true)
(hk-test ": (cons) is a con" (hk-is-con? ":") true)
(hk-test "() is a con" (hk-is-con? "()") true)
(define hk-test-pass 0)
(define hk-test-fail 0)
(define hk-test-fails (list))
(hk-test "True arity 0" (hk-con-arity "True") 0)
(hk-test ": arity 2" (hk-con-arity ":") 2)
(hk-test "[] arity 0" (hk-con-arity "[]") 0)
(hk-test "True type Bool" (hk-con-type "True") "Bool")
(hk-test "False type Bool" (hk-con-type "False") "Bool")
(hk-test ": type List" (hk-con-type ":") "List")
(hk-test "() type Unit" (hk-con-type "()") "Unit")
(define
(hk-test name actual expected)
(if
(= actual expected)
(set! hk-test-pass (+ hk-test-pass 1))
(do
(set! hk-test-fail (+ hk-test-fail 1))
(append! hk-test-fails {:actual actual :expected expected :name name}))))
;; ── Unknown names ──
(hk-test "is-con? false for varid" (hk-is-con? "foo") false)
(hk-test "arity nil for unknown" (hk-con-arity "NotACon") nil)
(hk-test "type nil for unknown" (hk-con-type "NotACon") nil)
;; ---------------------------------------------------------------------------
;; 1. Numeric type class helpers
;; ---------------------------------------------------------------------------
(hk-test "is-integer? int" (hk-is-integer? 42) true)
(hk-test "is-integer? float" (hk-is-integer? 1.5) false)
(hk-test "is-float? float" (hk-is-float? 3.14) true)
(hk-test "is-float? int" (hk-is-float? 3) false)
(hk-test "is-num? int" (hk-is-num? 10) true)
(hk-test "is-num? float" (hk-is-num? 1) true)
(hk-test "to-float" (hk-to-float 5) 5)
(hk-test "to-integer trunc" (hk-to-integer 3.7) 3)
(hk-test "div pos pos" (hk-div 7 2) 3)
(hk-test "div neg pos" (hk-div -7 2) -4)
(hk-test "div pos neg" (hk-div 7 -2) -4)
(hk-test "div neg neg" (hk-div -7 -2) 3)
(hk-test "div exact" (hk-div 6 2) 3)
(hk-test "mod pos pos" (hk-mod 10 3) 1)
(hk-test "mod neg pos" (hk-mod -7 3) 2)
(hk-test "rem pos pos" (hk-rem 10 3) 1)
(hk-test "rem neg pos" (hk-rem -7 3) -1)
(hk-test "abs pos" (hk-abs 5) 5)
(hk-test "abs neg" (hk-abs -5) 5)
(hk-test "signum pos" (hk-signum 42) 1)
(hk-test "signum neg" (hk-signum -7) -1)
(hk-test "signum zero" (hk-signum 0) 0)
(hk-test "gcd" (hk-gcd 12 8) 4)
(hk-test "lcm" (hk-lcm 4 6) 12)
(hk-test "even?" (hk-even? 4) true)
(hk-test "even? odd" (hk-even? 3) false)
(hk-test "odd?" (hk-odd? 7) true)
;; ---------------------------------------------------------------------------
;; 2. Rational numbers
;; ---------------------------------------------------------------------------
(let
((r (hk-make-rational 1 2)))
;; ── data MyBool = Yes | No ──
(hk-test
"register simple data"
(do
(hk-test "rational?" (hk-rational? r) true)
(hk-test "numerator" (hk-numerator r) 1)
(hk-test "denominator" (hk-denominator r) 2)))
(hk-load-source! "data MyBool = Yes | No")
(list
(hk-con-arity "Yes")
(hk-con-arity "No")
(hk-con-type "Yes")
(hk-con-type "No")))
(list 0 0 "MyBool" "MyBool"))
(let
((r (hk-make-rational 2 4)))
;; ── data Maybe a = Nothing | Just a ──
(hk-test
"register Maybe"
(do
(hk-test "rat normalise num" (hk-numerator r) 1)
(hk-test "rat normalise den" (hk-denominator r) 2)))
(hk-load-source! "data Maybe a = Nothing | Just a")
(list
(hk-con-arity "Nothing")
(hk-con-arity "Just")
(hk-con-type "Nothing")
(hk-con-type "Just")))
(list 0 1 "Maybe" "Maybe"))
(let
((sum (hk-rational-add (hk-make-rational 1 2) (hk-make-rational 1 3))))
;; ── data Either a b = Left a | Right b ──
(hk-test
"register Either"
(do
(hk-test "rat-add num" (hk-numerator sum) 5)
(hk-test "rat-add den" (hk-denominator sum) 6)))
(hk-load-source! "data Either a b = Left a | Right b")
(list
(hk-con-arity "Left")
(hk-con-arity "Right")
(hk-con-type "Left")
(hk-con-type "Right")))
(list 1 1 "Either" "Either"))
;; ── Recursive data ──
(hk-test
"rat-to-float"
(hk-rational-to-float (hk-make-rational 1 2))
0.5)
(hk-test "rational? int" (hk-rational? 42) false)
;; ---------------------------------------------------------------------------
;; 3. Lazy evaluation (promises via SX delay)
;; ---------------------------------------------------------------------------
(let
((p (delay 42)))
(hk-test "force promise" (hk-force p) 42))
(hk-test "force non-promise" (hk-force 99) 99)
;; ---------------------------------------------------------------------------
;; 4. Char utilities — compare via hk-ord to avoid = on char type
;; ---------------------------------------------------------------------------
(hk-test "ord A" (hk-ord (integer->char 65)) 65)
(hk-test "chr 65" (hk-ord (hk-chr 65)) 65)
(hk-test "is-alpha? A" (hk-is-alpha? (integer->char 65)) true)
(hk-test "is-alpha? 0" (hk-is-alpha? (integer->char 48)) false)
(hk-test "is-digit? 5" (hk-is-digit? (integer->char 53)) true)
(hk-test "is-digit? A" (hk-is-digit? (integer->char 65)) false)
(hk-test "is-upper? A" (hk-is-upper? (integer->char 65)) true)
(hk-test "is-upper? a" (hk-is-upper? (integer->char 97)) false)
(hk-test "is-lower? a" (hk-is-lower? (integer->char 97)) true)
(hk-test "is-space? spc" (hk-is-space? (integer->char 32)) true)
(hk-test "is-space? A" (hk-is-space? (integer->char 65)) false)
(hk-test
"to-upper a"
(hk-ord (hk-to-upper (integer->char 97)))
65)
(hk-test
"to-lower A"
(hk-ord (hk-to-lower (integer->char 65)))
97)
(hk-test
"digit-to-int 0"
(hk-digit-to-int (integer->char 48))
0)
(hk-test
"digit-to-int 9"
(hk-digit-to-int (integer->char 57))
9)
(hk-test
"digit-to-int a"
(hk-digit-to-int (integer->char 97))
10)
(hk-test
"digit-to-int F"
(hk-digit-to-int (integer->char 70))
15)
(hk-test "int-to-digit 0" (hk-ord (hk-int-to-digit 0)) 48)
(hk-test "int-to-digit 10" (hk-ord (hk-int-to-digit 10)) 97)
;; ---------------------------------------------------------------------------
;; 5. Data.Set
;; ---------------------------------------------------------------------------
(hk-test "set-empty is set?" (hk-set? (hk-set-empty)) true)
(hk-test "set-null? empty" (hk-set-null? (hk-set-empty)) true)
(let
((s (hk-set-singleton 42)))
"register recursive Tree"
(do
(hk-test "singleton member" (hk-set-member? 42 s) true)
(hk-test "singleton size" (hk-set-size s) 1)))
(hk-load-source!
"data Tree a = Leaf | Node (Tree a) a (Tree a)")
(list
(hk-con-arity "Leaf")
(hk-con-arity "Node")
(hk-con-type "Leaf")
(hk-con-type "Node")))
(list 0 3 "Tree" "Tree"))
(let
((s (hk-set-from-list (list 1 2 3))))
;; ── newtype ──
(hk-test
"register newtype"
(do
(hk-test "from-list member" (hk-set-member? 2 s) true)
(hk-test "from-list absent" (hk-set-member? 9 s) false)
(hk-test "from-list size" (hk-set-size s) 3)))
(hk-load-source! "newtype Age = MkAge Int")
(list
(hk-con-arity "MkAge")
(hk-con-type "MkAge")))
(list 1 "Age"))
;; ---------------------------------------------------------------------------
;; 6. Data.List
;; ---------------------------------------------------------------------------
;; ── Multiple data decls in one program ──
(hk-test
"multiple data decls"
(do
(hk-load-source!
"data Color = Red | Green | Blue\ndata Shape = Circle | Square\nf x = x")
(list
(hk-con-type "Red")
(hk-con-type "Green")
(hk-con-type "Blue")
(hk-con-type "Circle")
(hk-con-type "Square")))
(list "Color" "Color" "Color" "Shape" "Shape"))
(hk-test "head" (hk-head (list 1 2 3)) 1)
;; ── Inside a module header ──
(hk-test
"tail length"
(len (hk-tail (list 1 2 3)))
2)
(hk-test "null? empty" (hk-null? (list)) true)
(hk-test "null? non-empty" (hk-null? (list 1)) false)
(hk-test
"length"
(hk-length (list 1 2 3))
3)
"register from module body"
(do
(hk-load-source!
"module M where\ndata Pair a = Pair a a")
(list
(hk-con-arity "Pair")
(hk-con-type "Pair")))
(list 2 "Pair"))
;; ── Non-data decls are ignored ──
(hk-test
"take 2"
(hk-take 2 (list 1 2 3))
(list 1 2))
(hk-test "take 0" (hk-take 0 (list 1 2)) (list))
(hk-test
"take overflow"
(hk-take 5 (list 1 2))
(list 1 2))
(hk-test
"drop 1"
(hk-drop 1 (list 1 2 3))
(list 2 3))
(hk-test
"drop 0"
(hk-drop 0 (list 1 2))
(list 1 2))
(hk-test
"take-while"
(hk-take-while
(fn (x) (< x 3))
(list 1 2 3 4))
(list 1 2))
(hk-test
"drop-while"
(hk-drop-while
(fn (x) (< x 3))
(list 1 2 3 4))
(list 3 4))
(hk-test
"zip"
(hk-zip (list 1 2) (list 3 4))
(list (list 1 3) (list 2 4)))
(hk-test
"zip uneven"
(hk-zip
(list 1 2 3)
(list 4 5))
(list (list 1 4) (list 2 5)))
(hk-test
"zip-with +"
(hk-zip-with
+
(list 1 2 3)
(list 10 20 30))
(list 11 22 33))
(hk-test
"unzip fst"
(first
(hk-unzip
(list (list 1 3) (list 2 4))))
(list 1 2))
(hk-test
"unzip snd"
(nth
(hk-unzip
(list (list 1 3) (list 2 4)))
1)
(list 3 4))
(hk-test
"elem hit"
(hk-elem 2 (list 1 2 3))
true)
(hk-test
"elem miss"
(hk-elem 9 (list 1 2 3))
"program with only fun-decl leaves registry unchanged for that name"
(do
(hk-load-source! "myFunctionNotACon x = x + 1")
(hk-is-con? "myFunctionNotACon"))
false)
(hk-test
"not-elem"
(hk-not-elem 9 (list 1 2 3))
true)
;; ── Re-registering overwrites (last wins) ──
(hk-test
"nub"
(hk-nub (list 1 2 1 3 2))
(list 1 2 3))
"re-registration overwrites the entry"
(do
(hk-load-source! "data Foo = Bar Int")
(hk-load-source! "data Foo = Bar Int Int")
(hk-con-arity "Bar"))
2)
(hk-test
"sum"
(hk-sum (list 1 2 3 4))
10)
(hk-test
"product"
(hk-product (list 1 2 3 4))
24)
(hk-test
"maximum"
(hk-maximum (list 3 1 4 1 5))
5)
(hk-test
"minimum"
(hk-minimum (list 3 1 4 1 5))
1)
(hk-test
"concat"
(hk-concat
(list (list 1 2) (list 3 4)))
(list 1 2 3 4))
(hk-test
"concat-map"
(hk-concat-map
(fn (x) (list x (* x x)))
(list 1 2 3))
(list 1 1 2 4 3 9))
(hk-test
"sort"
(hk-sort (list 3 1 4 1 5))
(list 1 1 3 4 5))
(hk-test
"replicate"
(hk-replicate 3 0)
(list 0 0 0))
(hk-test "replicate 0" (hk-replicate 0 99) (list))
(hk-test
"intersperse"
(hk-intersperse 0 (list 1 2 3))
(list 1 0 2 0 3))
(hk-test
"intersperse 1"
(hk-intersperse 0 (list 1))
(list 1))
(hk-test "intersperse empty" (hk-intersperse 0 (list)) (list))
(hk-test
"span"
(hk-span
(fn (x) (< x 3))
(list 1 2 3 4))
(list (list 1 2) (list 3 4)))
(hk-test
"break"
(hk-break
(fn (x) (>= x 3))
(list 1 2 3 4))
(list (list 1 2) (list 3 4)))
(hk-test
"foldl"
(hk-foldl
(fn (a b) (- a b))
10
(list 1 2 3))
4)
(hk-test
"foldr"
(hk-foldr cons (list) (list 1 2 3))
(list 1 2 3))
(hk-test
"scanl"
(hk-scanl + 0 (list 1 2 3))
(list 0 1 3 6))
;; ---------------------------------------------------------------------------
;; 7. Maybe / Either
;; ---------------------------------------------------------------------------
(hk-test "nothing is-nothing?" (hk-is-nothing? hk-nothing) true)
(hk-test "nothing is-just?" (hk-is-just? hk-nothing) false)
(hk-test "just is-just?" (hk-is-just? (hk-just 42)) true)
(hk-test "just is-nothing?" (hk-is-nothing? (hk-just 42)) false)
(hk-test "from-just" (hk-from-just (hk-just 99)) 99)
(hk-test
"from-maybe nothing"
(hk-from-maybe 0 hk-nothing)
0)
(hk-test
"from-maybe just"
(hk-from-maybe 0 (hk-just 42))
42)
(hk-test
"maybe nothing"
(hk-maybe 0 (fn (x) (* x 2)) hk-nothing)
0)
(hk-test
"maybe just"
(hk-maybe 0 (fn (x) (* x 2)) (hk-just 5))
10)
(hk-test "left is-left?" (hk-is-left? (hk-left "e")) true)
(hk-test "right is-right?" (hk-is-right? (hk-right 42)) true)
(hk-test "from-right" (hk-from-right (hk-right 7)) 7)
(hk-test
"either left"
(hk-either (fn (x) (str "L" x)) (fn (x) (str "R" x)) (hk-left "err"))
"Lerr")
(hk-test
"either right"
(hk-either
(fn (x) (str "L" x))
(fn (x) (str "R" x))
(hk-right 42))
"R42")
;; ---------------------------------------------------------------------------
;; 8. Tuples
;; ---------------------------------------------------------------------------
(hk-test "pair" (hk-pair 1 2) (list 1 2))
(hk-test "fst" (hk-fst (hk-pair 3 4)) 3)
(hk-test "snd" (hk-snd (hk-pair 3 4)) 4)
(hk-test
"triple"
(hk-triple 1 2 3)
(list 1 2 3))
(hk-test
"fst3"
(hk-fst3 (hk-triple 7 8 9))
7)
(hk-test
"thd3"
(hk-thd3 (hk-triple 7 8 9))
9)
(hk-test "curry" ((hk-curry +) 3 4) 7)
(hk-test
"uncurry"
((hk-uncurry (fn (a b) (* a b))) (list 3 4))
12)
;; ---------------------------------------------------------------------------
;; 9. String helpers
;; ---------------------------------------------------------------------------
(hk-test "words" (hk-words "hello world") (list "hello" "world"))
(hk-test "words leading ws" (hk-words " foo bar") (list "foo" "bar"))
(hk-test "words empty" (hk-words "") (list))
(hk-test "unwords" (hk-unwords (list "a" "b" "c")) "a b c")
(hk-test "unwords single" (hk-unwords (list "x")) "x")
(hk-test "lines" (hk-lines "a\nb\nc") (list "a" "b" "c"))
(hk-test "lines single" (hk-lines "hello") (list "hello"))
(hk-test "unlines" (hk-unlines (list "a" "b")) "a\nb\n")
(hk-test "is-prefix-of yes" (hk-is-prefix-of "he" "hello") true)
(hk-test "is-prefix-of no" (hk-is-prefix-of "wo" "hello") false)
(hk-test "is-prefix-of eq" (hk-is-prefix-of "hi" "hi") true)
(hk-test "is-prefix-of empty" (hk-is-prefix-of "" "hi") true)
(hk-test "is-suffix-of yes" (hk-is-suffix-of "lo" "hello") true)
(hk-test "is-suffix-of no" (hk-is-suffix-of "he" "hello") false)
(hk-test "is-suffix-of empty" (hk-is-suffix-of "" "hi") true)
(hk-test "is-infix-of yes" (hk-is-infix-of "ell" "hello") true)
(hk-test "is-infix-of no" (hk-is-infix-of "xyz" "hello") false)
(hk-test "is-infix-of empty" (hk-is-infix-of "" "hello") true)
;; ---------------------------------------------------------------------------
;; 10. Show
;; ---------------------------------------------------------------------------
(hk-test "show nil" (hk-show nil) "Nothing")
(hk-test "show true" (hk-show true) "True")
(hk-test "show false" (hk-show false) "False")
(hk-test "show int" (hk-show 42) "42")
(hk-test "show string" (hk-show "hi") "\"hi\"")
(hk-test
"show list"
(hk-show (list 1 2 3))
"[1,2,3]")
(hk-test "show empty list" (hk-show (list)) "[]")
;; ---------------------------------------------------------------------------
;; Summary (required by test.sh — last expression is the return value)
;; ---------------------------------------------------------------------------
(list hk-test-pass hk-test-fail)
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

85
lib/haskell/tests/seq.sx Normal file
View File

@@ -0,0 +1,85 @@
;; seq / deepseq tests. seq is strict in its first arg (forces to
;; WHNF) and returns the second arg unchanged. deepseq additionally
;; forces the first arg to normal form.
(define
hk-prog-val
(fn
(src name)
(hk-deep-force (get (hk-eval-program (hk-core src)) name))))
(define hk-as-list
(fn (xs)
(cond
((and (list? xs) (= (first xs) "[]")) (list))
((and (list? xs) (= (first xs) ":"))
(cons (nth xs 1) (hk-as-list (nth xs 2))))
(:else xs))))
(define
hk-eval-list
(fn (src) (hk-as-list (hk-eval-expr-source src))))
;; ── seq returns its second arg ──
(hk-test
"seq with primitive first arg"
(hk-eval-expr-source "seq 1 99")
99)
(hk-test
"seq forces first arg via let"
(hk-eval-expr-source "let x = 1 + 2 in seq x x")
3)
(hk-test
"seq second arg is whatever shape"
(hk-eval-expr-source "seq 0 \"hello\"")
"hello")
;; ── seq enables previously-lazy bottom to be forced ──
;; Without seq the let-binding `x = error …` is never forced;
;; with seq it must be forced because seq is strict in its first
;; argument. We don't run that error case here (it would terminate
;; the test), but we do verify the negative — that without seq,
;; the bottom bound is never demanded.
(hk-test
"lazy let — bottom never forced when unused"
(hk-eval-expr-source "let x = error \"never\" in 42")
42)
;; ── deepseq forces nested structure ──
(hk-test
"deepseq with finite list"
(hk-eval-expr-source "deepseq [1, 2, 3] 7")
7)
(hk-test
"deepseq with constructor value"
(hk-eval-expr-source "deepseq (Just 5) 11")
11)
(hk-test
"deepseq with tuple"
(hk-eval-expr-source "deepseq (1, 2) 13")
13)
;; ── seq + arithmetic ──
(hk-test
"seq used inside arithmetic doesn't poison the result"
(hk-eval-expr-source "(seq 1 5) + (seq 2 7)")
12)
;; ── seq in user code ──
(hk-test
"seq via fun-clause"
(hk-prog-val
"f x = seq x (x + 1)\nresult = f 10"
"result")
11)
(hk-test
"seq sequences list construction"
(hk-eval-list "[seq 1 10, seq 2 20]")
(list 10 20))
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -49,8 +49,6 @@ trap "rm -f $TMPFILE" EXIT
echo '(load "lib/js/transpile.sx")'
echo '(epoch 5)'
echo '(load "lib/js/runtime.sx")'
echo '(epoch 6)'
echo '(load "lib/js/regex.sx")'
epoch=100
for f in "${FIXTURES[@]}"; do

View File

@@ -1,943 +0,0 @@
;; lib/js/regex.sx — pure-SX recursive backtracking regex engine
;;
;; Installed via (js-regex-platform-override! ...) at load time.
;; Covers: character classes (\d\w\s . [abc] [^abc] [a-z]),
;; anchors (^ $ \b \B), quantifiers (* + ? {n,m} lazy variants),
;; groups (capturing + non-capturing), alternation (a|b),
;; flags: i (case-insensitive), g (global), m (multiline).
;;
;; Architecture:
;; 1. rx-parse-pattern — pattern string → compiled node list
;; 2. rx-match-nodes — recursive backtracker
;; 3. rx-exec / rx-test — public interface
;; 4. Install as {:test rx-test :exec rx-exec}
;; ── Utilities ─────────────────────────────────────────────────────
(define
rx-char-at
(fn (s i) (if (and (>= i 0) (< i (len s))) (char-at s i) "")))
(define
rx-digit?
(fn
(c)
(and (not (= c "")) (>= (char-code c) 48) (<= (char-code c) 57))))
(define
rx-word?
(fn
(c)
(and
(not (= c ""))
(or
(and (>= (char-code c) 65) (<= (char-code c) 90))
(and (>= (char-code c) 97) (<= (char-code c) 122))
(and (>= (char-code c) 48) (<= (char-code c) 57))
(= c "_")))))
(define
rx-space?
(fn
(c)
(or (= c " ") (= c "\t") (= c "\n") (= c "\r") (= c "\\f") (= c ""))))
(define rx-newline? (fn (c) (or (= c "\n") (= c "\r"))))
(define
rx-downcase-char
(fn
(c)
(let
((cc (char-code c)))
(if (and (>= cc 65) (<= cc 90)) (char-from-code (+ cc 32)) c))))
(define
rx-char-eq?
(fn
(a b ci?)
(if ci? (= (rx-downcase-char a) (rx-downcase-char b)) (= a b))))
(define
rx-parse-int
(fn
(pat i acc)
(let
((c (rx-char-at pat i)))
(if
(rx-digit? c)
(rx-parse-int pat (+ i 1) (+ (* acc 10) (- (char-code c) 48)))
(list acc i)))))
(define
rx-hex-digit-val
(fn
(c)
(cond
((and (>= (char-code c) 48) (<= (char-code c) 57))
(- (char-code c) 48))
((and (>= (char-code c) 65) (<= (char-code c) 70))
(+ 10 (- (char-code c) 65)))
((and (>= (char-code c) 97) (<= (char-code c) 102))
(+ 10 (- (char-code c) 97)))
(else -1))))
(define
rx-parse-hex-n
(fn
(pat i n acc)
(if
(= n 0)
(list (char-from-code acc) i)
(let
((v (rx-hex-digit-val (rx-char-at pat i))))
(if
(< v 0)
(list (char-from-code acc) i)
(rx-parse-hex-n pat (+ i 1) (- n 1) (+ (* acc 16) v)))))))
;; ── Pattern compiler ──────────────────────────────────────────────
;; Node types (stored in dicts with "__t__" key):
;; literal : {:__t__ "literal" :__c__ char}
;; any : {:__t__ "any"}
;; class-d : {:__t__ "class-d" :__neg__ bool}
;; class-w : {:__t__ "class-w" :__neg__ bool}
;; class-s : {:__t__ "class-s" :__neg__ bool}
;; char-class: {:__t__ "char-class" :__neg__ bool :__items__ list}
;; anchor-start / anchor-end / anchor-word / anchor-nonword
;; quant : {:__t__ "quant" :__node__ n :__min__ m :__max__ mx :__lazy__ bool}
;; group : {:__t__ "group" :__idx__ i :__nodes__ list}
;; ncgroup : {:__t__ "ncgroup" :__nodes__ list}
;; alt : {:__t__ "alt" :__branches__ list-of-node-lists}
;; parse one escape after `\`, returns (node new-i)
(define
rx-parse-escape
(fn
(pat i)
(let
((c (rx-char-at pat i)))
(cond
((= c "d") (list (dict "__t__" "class-d" "__neg__" false) (+ i 1)))
((= c "D") (list (dict "__t__" "class-d" "__neg__" true) (+ i 1)))
((= c "w") (list (dict "__t__" "class-w" "__neg__" false) (+ i 1)))
((= c "W") (list (dict "__t__" "class-w" "__neg__" true) (+ i 1)))
((= c "s") (list (dict "__t__" "class-s" "__neg__" false) (+ i 1)))
((= c "S") (list (dict "__t__" "class-s" "__neg__" true) (+ i 1)))
((= c "b") (list (dict "__t__" "anchor-word") (+ i 1)))
((= c "B") (list (dict "__t__" "anchor-nonword") (+ i 1)))
((= c "n") (list (dict "__t__" "literal" "__c__" "\n") (+ i 1)))
((= c "r") (list (dict "__t__" "literal" "__c__" "\r") (+ i 1)))
((= c "t") (list (dict "__t__" "literal" "__c__" "\t") (+ i 1)))
((= c "f") (list (dict "__t__" "literal" "__c__" "\\f") (+ i 1)))
((= c "v") (list (dict "__t__" "literal" "__c__" "") (+ i 1)))
((= c "u")
(let
((res (rx-parse-hex-n pat (+ i 1) 4 0)))
(list (dict "__t__" "literal" "__c__" (nth res 0)) (nth res 1))))
((= c "x")
(let
((res (rx-parse-hex-n pat (+ i 1) 2 0)))
(list (dict "__t__" "literal" "__c__" (nth res 0)) (nth res 1))))
(else (list (dict "__t__" "literal" "__c__" c) (+ i 1)))))))
;; parse a char-class item inside [...], returns (item new-i)
(define
rx-parse-class-item
(fn
(pat i)
(let
((c (rx-char-at pat i)))
(cond
((= c "\\")
(let
((esc (rx-parse-escape pat (+ i 1))))
(let
((node (nth esc 0)) (ni (nth esc 1)))
(let
((t (get node "__t__")))
(cond
((= t "class-d")
(list
(dict "kind" "class-d" "neg" (get node "__neg__"))
ni))
((= t "class-w")
(list
(dict "kind" "class-w" "neg" (get node "__neg__"))
ni))
((= t "class-s")
(list
(dict "kind" "class-s" "neg" (get node "__neg__"))
ni))
(else
(let
((lc (get node "__c__")))
(if
(and
(= (rx-char-at pat ni) "-")
(not (= (rx-char-at pat (+ ni 1)) "]")))
(let
((hi-c (rx-char-at pat (+ ni 1))))
(list
(dict "kind" "range" "lo" lc "hi" hi-c)
(+ ni 2)))
(list (dict "kind" "lit" "c" lc) ni)))))))))
(else
(if
(and
(not (= c ""))
(= (rx-char-at pat (+ i 1)) "-")
(not (= (rx-char-at pat (+ i 2)) "]"))
(not (= (rx-char-at pat (+ i 2)) "")))
(let
((hi-c (rx-char-at pat (+ i 2))))
(list (dict "kind" "range" "lo" c "hi" hi-c) (+ i 3)))
(list (dict "kind" "lit" "c" c) (+ i 1))))))))
(define
rx-parse-class-items
(fn
(pat i items)
(let
((c (rx-char-at pat i)))
(if
(or (= c "]") (= c ""))
(list items i)
(let
((res (rx-parse-class-item pat i)))
(begin
(append! items (nth res 0))
(rx-parse-class-items pat (nth res 1) items)))))))
;; parse a sequence until stop-ch or EOF; returns (nodes new-i groups-count)
(define
rx-parse-seq
(fn
(pat i stop-ch ds)
(let
((c (rx-char-at pat i)))
(cond
((= c "") (list (get ds "nodes") i (get ds "groups")))
((= c stop-ch) (list (get ds "nodes") i (get ds "groups")))
((= c "|") (rx-parse-alt-rest pat i ds))
(else
(let
((res (rx-parse-atom pat i ds)))
(let
((node (nth res 0)) (ni (nth res 1)) (ds2 (nth res 2)))
(let
((qres (rx-parse-quant pat ni node)))
(begin
(append! (get ds2 "nodes") (nth qres 0))
(rx-parse-seq pat (nth qres 1) stop-ch ds2))))))))))
;; when we hit | inside a sequence, collect all alternatives
(define
rx-parse-alt-rest
(fn
(pat i ds)
(let
((left-branch (get ds "nodes")) (branches (list)))
(begin
(append! branches left-branch)
(rx-parse-alt-branches pat i (get ds "groups") branches)))))
(define
rx-parse-alt-branches
(fn
(pat i n-groups branches)
(let
((new-nodes (list)) (ds2 (dict "groups" n-groups "nodes" new-nodes)))
(let
((res (rx-parse-seq pat (+ i 1) "|" ds2)))
(begin
(append! branches (nth res 0))
(let
((ni2 (nth res 1)) (g2 (nth res 2)))
(if
(= (rx-char-at pat ni2) "|")
(rx-parse-alt-branches pat ni2 g2 branches)
(list
(list (dict "__t__" "alt" "__branches__" branches))
ni2
g2))))))))
;; parse quantifier suffix, returns (node new-i)
(define
rx-parse-quant
(fn
(pat i node)
(let
((c (rx-char-at pat i)))
(cond
((= c "*")
(let
((lazy? (= (rx-char-at pat (+ i 1)) "?")))
(list
(dict
"__t__"
"quant"
"__node__"
node
"__min__"
0
"__max__"
-1
"__lazy__"
lazy?)
(if lazy? (+ i 2) (+ i 1)))))
((= c "+")
(let
((lazy? (= (rx-char-at pat (+ i 1)) "?")))
(list
(dict
"__t__"
"quant"
"__node__"
node
"__min__"
1
"__max__"
-1
"__lazy__"
lazy?)
(if lazy? (+ i 2) (+ i 1)))))
((= c "?")
(let
((lazy? (= (rx-char-at pat (+ i 1)) "?")))
(list
(dict
"__t__"
"quant"
"__node__"
node
"__min__"
0
"__max__"
1
"__lazy__"
lazy?)
(if lazy? (+ i 2) (+ i 1)))))
((= c "{")
(let
((mres (rx-parse-int pat (+ i 1) 0)))
(let
((mn (nth mres 0)) (mi (nth mres 1)))
(let
((sep (rx-char-at pat mi)))
(cond
((= sep "}")
(let
((lazy? (= (rx-char-at pat (+ mi 1)) "?")))
(list
(dict
"__t__"
"quant"
"__node__"
node
"__min__"
mn
"__max__"
mn
"__lazy__"
lazy?)
(if lazy? (+ mi 2) (+ mi 1)))))
((= sep ",")
(let
((c2 (rx-char-at pat (+ mi 1))))
(if
(= c2 "}")
(let
((lazy? (= (rx-char-at pat (+ mi 2)) "?")))
(list
(dict
"__t__"
"quant"
"__node__"
node
"__min__"
mn
"__max__"
-1
"__lazy__"
lazy?)
(if lazy? (+ mi 3) (+ mi 2))))
(let
((mxres (rx-parse-int pat (+ mi 1) 0)))
(let
((mx (nth mxres 0)) (mxi (nth mxres 1)))
(let
((lazy? (= (rx-char-at pat (+ mxi 1)) "?")))
(list
(dict
"__t__"
"quant"
"__node__"
node
"__min__"
mn
"__max__"
mx
"__lazy__"
lazy?)
(if lazy? (+ mxi 2) (+ mxi 1)))))))))
(else (list node i)))))))
(else (list node i))))))
;; parse one atom, returns (node new-i new-ds)
(define
rx-parse-atom
(fn
(pat i ds)
(let
((c (rx-char-at pat i)))
(cond
((= c ".") (list (dict "__t__" "any") (+ i 1) ds))
((= c "^") (list (dict "__t__" "anchor-start") (+ i 1) ds))
((= c "$") (list (dict "__t__" "anchor-end") (+ i 1) ds))
((= c "\\")
(let
((esc (rx-parse-escape pat (+ i 1))))
(list (nth esc 0) (nth esc 1) ds)))
((= c "[")
(let
((neg? (= (rx-char-at pat (+ i 1)) "^")))
(let
((start (if neg? (+ i 2) (+ i 1))) (items (list)))
(let
((res (rx-parse-class-items pat start items)))
(let
((ci (nth res 1)))
(list
(dict
"__t__"
"char-class"
"__neg__"
neg?
"__items__"
items)
(+ ci 1)
ds))))))
((= c "(")
(let
((c2 (rx-char-at pat (+ i 1))))
(if
(and (= c2 "?") (= (rx-char-at pat (+ i 2)) ":"))
(let
((inner-nodes (list))
(inner-ds
(dict "groups" (get ds "groups") "nodes" inner-nodes)))
(let
((res (rx-parse-seq pat (+ i 3) ")" inner-ds)))
(list
(dict "__t__" "ncgroup" "__nodes__" (nth res 0))
(+ (nth res 1) 1)
(dict "groups" (nth res 2) "nodes" (get ds "nodes")))))
(let
((gidx (+ (get ds "groups") 1)) (inner-nodes (list)))
(let
((inner-ds (dict "groups" gidx "nodes" inner-nodes)))
(let
((res (rx-parse-seq pat (+ i 1) ")" inner-ds)))
(list
(dict
"__t__"
"group"
"__idx__"
gidx
"__nodes__"
(nth res 0))
(+ (nth res 1) 1)
(dict "groups" (nth res 2) "nodes" (get ds "nodes")))))))))
(else (list (dict "__t__" "literal" "__c__" c) (+ i 1) ds))))))
;; top-level compile
(define
rx-compile
(fn
(pattern)
(let
((nodes (list)) (ds (dict "groups" 0 "nodes" nodes)))
(let
((res (rx-parse-seq pattern 0 "" ds)))
(dict "nodes" (nth res 0) "ngroups" (nth res 2))))))
;; ── Matcher ───────────────────────────────────────────────────────
;; Match a char-class item against character c
(define
rx-item-matches?
(fn
(item c ci?)
(let
((kind (get item "kind")))
(cond
((= kind "lit") (rx-char-eq? c (get item "c") ci?))
((= kind "range")
(let
((lo (if ci? (rx-downcase-char (get item "lo")) (get item "lo")))
(hi
(if ci? (rx-downcase-char (get item "hi")) (get item "hi")))
(dc (if ci? (rx-downcase-char c) c)))
(and
(>= (char-code dc) (char-code lo))
(<= (char-code dc) (char-code hi)))))
((= kind "class-d")
(let ((m (rx-digit? c))) (if (get item "neg") (not m) m)))
((= kind "class-w")
(let ((m (rx-word? c))) (if (get item "neg") (not m) m)))
((= kind "class-s")
(let ((m (rx-space? c))) (if (get item "neg") (not m) m)))
(else false)))))
(define
rx-class-items-any?
(fn
(items c ci?)
(if
(empty? items)
false
(if
(rx-item-matches? (first items) c ci?)
true
(rx-class-items-any? (rest items) c ci?)))))
(define
rx-class-matches?
(fn
(node c ci?)
(let
((neg? (get node "__neg__")) (items (get node "__items__")))
(let
((hit (rx-class-items-any? items c ci?)))
(if neg? (not hit) hit)))))
;; Word boundary check
(define
rx-is-word-boundary?
(fn
(s i slen)
(let
((before (if (> i 0) (rx-word? (char-at s (- i 1))) false))
(after (if (< i slen) (rx-word? (char-at s i)) false)))
(not (= before after)))))
;; ── Core matcher ──────────────────────────────────────────────────
;;
;; rx-match-nodes : nodes s i slen ci? mi? groups → end-pos or -1
;;
;; Matches `nodes` starting at position `i` in string `s`.
;; Returns the position after the last character consumed, or -1 on failure.
;; Mutates `groups` dict to record captures.
(define
rx-match-nodes
(fn
(nodes s i slen ci? mi? groups)
(if
(empty? nodes)
i
(let
((node (first nodes)) (rest-nodes (rest nodes)))
(let
((t (get node "__t__")))
(cond
((= t "literal")
(if
(and
(< i slen)
(rx-char-eq? (char-at s i) (get node "__c__") ci?))
(rx-match-nodes rest-nodes s (+ i 1) slen ci? mi? groups)
-1))
((= t "any")
(if
(and (< i slen) (not (rx-newline? (char-at s i))))
(rx-match-nodes rest-nodes s (+ i 1) slen ci? mi? groups)
-1))
((= t "class-d")
(let
((m (and (< i slen) (rx-digit? (char-at s i)))))
(if
(if (get node "__neg__") (not m) m)
(rx-match-nodes rest-nodes s (+ i 1) slen ci? mi? groups)
-1)))
((= t "class-w")
(let
((m (and (< i slen) (rx-word? (char-at s i)))))
(if
(if (get node "__neg__") (not m) m)
(rx-match-nodes rest-nodes s (+ i 1) slen ci? mi? groups)
-1)))
((= t "class-s")
(let
((m (and (< i slen) (rx-space? (char-at s i)))))
(if
(if (get node "__neg__") (not m) m)
(rx-match-nodes rest-nodes s (+ i 1) slen ci? mi? groups)
-1)))
((= t "char-class")
(if
(and (< i slen) (rx-class-matches? node (char-at s i) ci?))
(rx-match-nodes rest-nodes s (+ i 1) slen ci? mi? groups)
-1))
((= t "anchor-start")
(if
(or
(= i 0)
(and mi? (rx-newline? (rx-char-at s (- i 1)))))
(rx-match-nodes rest-nodes s i slen ci? mi? groups)
-1))
((= t "anchor-end")
(if
(or (= i slen) (and mi? (rx-newline? (rx-char-at s i))))
(rx-match-nodes rest-nodes s i slen ci? mi? groups)
-1))
((= t "anchor-word")
(if
(rx-is-word-boundary? s i slen)
(rx-match-nodes rest-nodes s i slen ci? mi? groups)
-1))
((= t "anchor-nonword")
(if
(not (rx-is-word-boundary? s i slen))
(rx-match-nodes rest-nodes s i slen ci? mi? groups)
-1))
((= t "group")
(let
((gidx (get node "__idx__"))
(inner (get node "__nodes__")))
(let
((g-end (rx-match-nodes inner s i slen ci? mi? groups)))
(if
(>= g-end 0)
(begin
(dict-set!
groups
(js-to-string gidx)
(substring s i g-end))
(let
((final-end (rx-match-nodes rest-nodes s g-end slen ci? mi? groups)))
(if
(>= final-end 0)
final-end
(begin
(dict-set! groups (js-to-string gidx) nil)
-1))))
-1))))
((= t "ncgroup")
(let
((inner (get node "__nodes__")))
(rx-match-nodes
(append inner rest-nodes)
s
i
slen
ci?
mi?
groups)))
((= t "alt")
(let
((branches (get node "__branches__")))
(rx-try-branches branches rest-nodes s i slen ci? mi? groups)))
((= t "quant")
(let
((inner-node (get node "__node__"))
(mn (get node "__min__"))
(mx (get node "__max__"))
(lazy? (get node "__lazy__")))
(if
lazy?
(rx-quant-lazy
inner-node
mn
mx
rest-nodes
s
i
slen
ci?
mi?
groups
0)
(rx-quant-greedy
inner-node
mn
mx
rest-nodes
s
i
slen
ci?
mi?
groups
0))))
(else -1)))))))
(define
rx-try-branches
(fn
(branches rest-nodes s i slen ci? mi? groups)
(if
(empty? branches)
-1
(let
((res (rx-match-nodes (append (first branches) rest-nodes) s i slen ci? mi? groups)))
(if
(>= res 0)
res
(rx-try-branches (rest branches) rest-nodes s i slen ci? mi? groups))))))
;; Greedy: expand as far as possible, then try rest from the longest match
;; Strategy: recurse forward (extend first); only try rest when extension fails
(define
rx-quant-greedy
(fn
(inner-node mn mx rest-nodes s i slen ci? mi? groups count)
(let
((can-extend (and (< i slen) (or (= mx -1) (< count mx)))))
(if
can-extend
(let
((ni (rx-match-one inner-node s i slen ci? mi? groups)))
(if
(>= ni 0)
(let
((res (rx-quant-greedy inner-node mn mx rest-nodes s ni slen ci? mi? groups (+ count 1))))
(if
(>= res 0)
res
(if
(>= count mn)
(rx-match-nodes rest-nodes s i slen ci? mi? groups)
-1)))
(if
(>= count mn)
(rx-match-nodes rest-nodes s i slen ci? mi? groups)
-1)))
(if
(>= count mn)
(rx-match-nodes rest-nodes s i slen ci? mi? groups)
-1)))))
;; Lazy: try rest first, extend only if rest fails
(define
rx-quant-lazy
(fn
(inner-node mn mx rest-nodes s i slen ci? mi? groups count)
(if
(>= count mn)
(let
((res (rx-match-nodes rest-nodes s i slen ci? mi? groups)))
(if
(>= res 0)
res
(if
(and (< i slen) (or (= mx -1) (< count mx)))
(let
((ni (rx-match-one inner-node s i slen ci? mi? groups)))
(if
(>= ni 0)
(rx-quant-lazy
inner-node
mn
mx
rest-nodes
s
ni
slen
ci?
mi?
groups
(+ count 1))
-1))
-1)))
(if
(< i slen)
(let
((ni (rx-match-one inner-node s i slen ci? mi? groups)))
(if
(>= ni 0)
(rx-quant-lazy
inner-node
mn
mx
rest-nodes
s
ni
slen
ci?
mi?
groups
(+ count 1))
-1))
-1))))
;; Match a single node at position i, return new pos or -1
(define
rx-match-one
(fn
(node s i slen ci? mi? groups)
(rx-match-nodes (list node) s i slen ci? mi? groups)))
;; ── Engine entry points ───────────────────────────────────────────
;; Try matching at exactly position i. Returns result dict or nil.
(define
rx-try-at
(fn
(compiled s i slen ci? mi?)
(let
((nodes (get compiled "nodes")) (ngroups (get compiled "ngroups")))
(let
((groups (dict)))
(let
((end (rx-match-nodes nodes s i slen ci? mi? groups)))
(if
(>= end 0)
(dict "start" i "end" end "groups" groups "ngroups" ngroups)
nil))))))
;; Find first match scanning from search-start.
(define
rx-find-from
(fn
(compiled s search-start slen ci? mi?)
(if
(> search-start slen)
nil
(let
((res (rx-try-at compiled s search-start slen ci? mi?)))
(if
res
res
(rx-find-from compiled s (+ search-start 1) slen ci? mi?))))))
;; Build exec result dict from raw match result
(define
rx-build-exec-result
(fn
(s match-res)
(let
((start (get match-res "start"))
(end (get match-res "end"))
(groups (get match-res "groups"))
(ngroups (get match-res "ngroups")))
(let
((matched (substring s start end))
(caps (rx-build-captures groups ngroups 1)))
(dict "match" matched "index" start "input" s "groups" caps)))))
(define
rx-build-captures
(fn
(groups ngroups idx)
(if
(> idx ngroups)
(list)
(let
((cap (get groups (js-to-string idx))))
(cons
(if (= cap nil) :js-undefined cap)
(rx-build-captures groups ngroups (+ idx 1)))))))
;; ── Public interface ──────────────────────────────────────────────
;; Lazy compile: build NFA on first use, cache under "__compiled__"
(define
rx-ensure-compiled!
(fn
(rx)
(if
(dict-has? rx "__compiled__")
(get rx "__compiled__")
(let
((c (rx-compile (get rx "source"))))
(begin (dict-set! rx "__compiled__" c) c)))))
(define
rx-test
(fn
(rx s)
(let
((compiled (rx-ensure-compiled! rx))
(ci? (get rx "ignoreCase"))
(mi? (get rx "multiline"))
(slen (len s)))
(let
((start (if (get rx "global") (let ((li (get rx "lastIndex"))) (if (number? li) li 0)) 0)))
(let
((res (rx-find-from compiled s start slen ci? mi?)))
(if
(get rx "global")
(begin
(dict-set! rx "lastIndex" (if res (get res "end") 0))
(if res true false))
(if res true false)))))))
(define
rx-exec
(fn
(rx s)
(let
((compiled (rx-ensure-compiled! rx))
(ci? (get rx "ignoreCase"))
(mi? (get rx "multiline"))
(slen (len s)))
(let
((start (if (get rx "global") (let ((li (get rx "lastIndex"))) (if (number? li) li 0)) 0)))
(let
((res (rx-find-from compiled s start slen ci? mi?)))
(if
res
(begin
(when
(get rx "global")
(dict-set! rx "lastIndex" (get res "end")))
(rx-build-exec-result s res))
(begin
(when (get rx "global") (dict-set! rx "lastIndex" 0))
nil)))))))
;; match-all for String.prototype.matchAll
(define
js-regex-match-all
(fn
(rx s)
(let
((compiled (rx-ensure-compiled! rx))
(ci? (get rx "ignoreCase"))
(mi? (get rx "multiline"))
(slen (len s))
(results (list)))
(rx-match-all-loop compiled s 0 slen ci? mi? results))))
(define
rx-match-all-loop
(fn
(compiled s i slen ci? mi? results)
(if
(> i slen)
results
(let
((res (rx-find-from compiled s i slen ci? mi?)))
(if
res
(begin
(append! results (rx-build-exec-result s res))
(let
((next (get res "end")))
(rx-match-all-loop
compiled
s
(if (= next i) (+ i 1) next)
slen
ci?
mi?
results)))
results)))))
;; ── Install platform ──────────────────────────────────────────────
(js-regex-platform-override! "test" rx-test)
(js-regex-platform-override! "exec" rx-exec)

View File

@@ -2032,15 +2032,7 @@
(&rest args)
(cond
((= (len args) 0) nil)
((js-regex? (nth args 0))
(let
((rx (nth args 0)))
(let
((impl (get __js_regex_platform__ "exec")))
(if
(js-undefined? impl)
(js-regex-stub-exec rx s)
(impl rx s)))))
((js-regex? (nth args 0)) (js-regex-stub-exec (nth args 0) s))
(else
(let
((needle (js-to-string (nth args 0))))
@@ -2049,7 +2041,7 @@
(if
(= idx -1)
nil
(let ((res (list))) (begin (append! res needle) res)))))))))
(let ((res (list))) (append! res needle) res))))))))
((= name "at")
(fn
(i)
@@ -2107,20 +2099,6 @@
((= name "toWellFormed") (fn () s))
(else js-undefined))))
(define __js_tdz_sentinel__ (dict "__tdz__" true))
(define js-tdz? (fn (v) (and (dict? v) (dict-has? v "__tdz__"))))
(define
js-tdz-check
(fn
(name val)
(if
(js-tdz? val)
(raise
(TypeError (str "Cannot access '" name "' before initialization")))
val)))
(define
js-string-slice
(fn

View File

@@ -1,239 +0,0 @@
;; lib/js/stdlib.sx — Phase 22 JS additions
;;
;; Adds to lib/js/runtime.sx (already loaded):
;; 1. Bitwise binary ops (js-bitand/bitor/bitxor/lshift/rshift/urshift/bitnot)
;; 2. Map class (arbitrary-key hash map via list of pairs)
;; 3. Set class (uniqueness collection via SX make-set)
;; 4. RegExp constructor (wraps js-regex-new already in runtime)
;; 5. Wires Map / Set / RegExp into js-global
;; ---------------------------------------------------------------------------
;; 1. Bitwise binary ops
;; JS coerces operands to 32-bit signed int before applying the op.
;; Use truncate (not js-num-to-int) since integer / 0 crashes the evaluator.
;; ---------------------------------------------------------------------------
(define
(js-bitand a b)
(bitwise-and (truncate (js-to-number a)) (truncate (js-to-number b))))
(define
(js-bitor a b)
(bitwise-or (truncate (js-to-number a)) (truncate (js-to-number b))))
(define
(js-bitxor a b)
(bitwise-xor (truncate (js-to-number a)) (truncate (js-to-number b))))
;; << : left-shift by (b mod 32) positions
(define
(js-lshift a b)
(arithmetic-shift
(truncate (js-to-number a))
(modulo (truncate (js-to-number b)) 32)))
;; >> : arithmetic right-shift (sign-extending)
(define
(js-rshift a b)
(arithmetic-shift
(truncate (js-to-number a))
(- 0 (modulo (truncate (js-to-number b)) 32))))
;; >>> : logical right-shift (zero-extending)
;; Convert to uint32 first, then divide by 2^n.
(define
(js-urshift a b)
(let
((u32 (modulo (truncate (js-to-number a)) 4294967296))
(n (modulo (truncate (js-to-number b)) 32)))
(quotient u32 (arithmetic-shift 1 n))))
;; ~ : bitwise NOT — equivalent to -(n+1) in 32-bit signed arithmetic
(define (js-bitnot a) (bitwise-not (truncate (js-to-number a))))
;; ---------------------------------------------------------------------------
;; 2. Map class
;; Stored as {:__js_map__ true :size N :_pairs (list (list key val) ...)}
;; Mutation via dict-set! on the underlying dict.
;; ---------------------------------------------------------------------------
(define
(js-map-new)
(let
((m (dict)))
(dict-set! m "__js_map__" true)
(dict-set! m "size" 0)
(dict-set! m "_pairs" (list))
m))
(define (js-map? v) (and (dict? v) (dict-has? v "__js_map__")))
;; Linear scan for key using ===; returns index or -1
(define
(js-map-find-idx pairs k)
(letrec
((go (fn (ps i) (cond ((= (len ps) 0) -1) ((js-strict-eq (first (first ps)) k) i) (else (go (rest ps) (+ i 1)))))))
(go pairs 0)))
(define
(js-map-get m k)
(letrec
((go (fn (ps) (if (= (len ps) 0) js-undefined (if (js-strict-eq (first (first ps)) k) (nth (first ps) 1) (go (rest ps)))))))
(go (get m "_pairs"))))
;; Replace element at index i in list
(define
(js-list-set-nth lst i newval)
(letrec
((go (fn (ps j) (if (= (len ps) 0) (list) (cons (if (= j i) newval (first ps)) (go (rest ps) (+ j 1)))))))
(go lst 0)))
;; Remove element at index i from list
(define
(js-list-remove-nth lst i)
(letrec
((go (fn (ps j) (if (= (len ps) 0) (list) (if (= j i) (go (rest ps) (+ j 1)) (cons (first ps) (go (rest ps) (+ j 1))))))))
(go lst 0)))
(define
(js-map-set! m k v)
(let
((pairs (get m "_pairs")) (idx (js-map-find-idx (get m "_pairs") k)))
(if
(= idx -1)
(begin
(dict-set! m "_pairs" (append pairs (list (list k v))))
(dict-set! m "size" (+ (get m "size") 1)))
(dict-set! m "_pairs" (js-list-set-nth pairs idx (list k v)))))
m)
(define
(js-map-has m k)
(not (= (js-map-find-idx (get m "_pairs") k) -1)))
(define
(js-map-delete! m k)
(let
((idx (js-map-find-idx (get m "_pairs") k)))
(when
(not (= idx -1))
(dict-set! m "_pairs" (js-list-remove-nth (get m "_pairs") idx))
(dict-set! m "size" (- (get m "size") 1))))
m)
(define
(js-map-clear! m)
(dict-set! m "_pairs" (list))
(dict-set! m "size" 0)
m)
(define (js-map-keys m) (map first (get m "_pairs")))
(define
(js-map-vals m)
(map (fn (p) (nth p 1)) (get m "_pairs")))
(define (js-map-entries m) (get m "_pairs"))
(define
(js-map-for-each m cb)
(for-each
(fn (p) (cb (nth p 1) (first p) m))
(get m "_pairs"))
js-undefined)
;; Map method dispatch (called from js-object-method-call in runtime)
(define
(js-map-method m name args)
(cond
((= name "set")
(js-map-set! m (nth args 0) (nth args 1)))
((= name "get") (js-map-get m (nth args 0)))
((= name "has") (js-map-has m (nth args 0)))
((= name "delete") (js-map-delete! m (nth args 0)))
((= name "clear") (js-map-clear! m))
((= name "keys") (js-map-keys m))
((= name "values") (js-map-vals m))
((= name "entries") (js-map-entries m))
((= name "forEach") (js-map-for-each m (nth args 0)))
((= name "toString") "[object Map]")
(else js-undefined)))
(define Map {:__callable__ (fn (&rest args) (let ((m (js-map-new))) (when (and (> (len args) 0) (list? (nth args 0))) (for-each (fn (entry) (js-map-set! m (nth entry 0) (nth entry 1))) (nth args 0))) m)) :prototype {:entries (fn (&rest a) (js-map-entries (js-this))) :delete (fn (&rest a) (js-map-delete! (js-this) (nth a 0))) :get (fn (&rest a) (js-map-get (js-this) (nth a 0))) :values (fn (&rest a) (js-map-vals (js-this))) :toString (fn () "[object Map]") :has (fn (&rest a) (js-map-has (js-this) (nth a 0))) :set (fn (&rest a) (js-map-set! (js-this) (nth a 0) (nth a 1))) :forEach (fn (&rest a) (js-map-for-each (js-this) (nth a 0))) :clear (fn (&rest a) (js-map-clear! (js-this))) :keys (fn (&rest a) (js-map-keys (js-this)))}})
;; ---------------------------------------------------------------------------
;; 3. Set class
;; {:__js_set__ true :size N :_set <sx-set>}
;; Note: set-member?/set-add!/set-remove! all take (set item) order.
;; ---------------------------------------------------------------------------
(define
(js-set-new)
(let
((s (dict)))
(dict-set! s "__js_set__" true)
(dict-set! s "size" 0)
(dict-set! s "_set" (make-set))
s))
(define (js-set? v) (and (dict? v) (dict-has? v "__js_set__")))
(define
(js-set-add! s v)
(let
((sx (get s "_set")))
(when
(not (set-member? sx v))
(set-add! sx v)
(dict-set! s "size" (+ (get s "size") 1))))
s)
(define (js-set-has s v) (set-member? (get s "_set") v))
(define
(js-set-delete! s v)
(let
((sx (get s "_set")))
(when
(set-member? sx v)
(set-remove! sx v)
(dict-set! s "size" (- (get s "size") 1))))
s)
(define
(js-set-clear! s)
(dict-set! s "_set" (make-set))
(dict-set! s "size" 0)
s)
(define (js-set-vals s) (set->list (get s "_set")))
(define
(js-set-for-each s cb)
(for-each (fn (v) (cb v v s)) (set->list (get s "_set")))
js-undefined)
(define Set {:__callable__ (fn (&rest args) (let ((s (js-set-new))) (when (and (> (len args) 0) (list? (nth args 0))) (for-each (fn (v) (js-set-add! s v)) (nth args 0))) s)) :prototype {:entries (fn (&rest a) (map (fn (v) (list v v)) (js-set-vals (js-this)))) :delete (fn (&rest a) (js-set-delete! (js-this) (nth a 0))) :values (fn (&rest a) (js-set-vals (js-this))) :add (fn (&rest a) (js-set-add! (js-this) (nth a 0))) :toString (fn () "[object Set]") :has (fn (&rest a) (js-set-has (js-this) (nth a 0))) :forEach (fn (&rest a) (js-set-for-each (js-this) (nth a 0))) :clear (fn (&rest a) (js-set-clear! (js-this))) :keys (fn (&rest a) (js-set-vals (js-this)))}})
;; ---------------------------------------------------------------------------
;; 4. RegExp constructor — callable lambda wrapping js-regex-new
;; ---------------------------------------------------------------------------
(define
RegExp
(fn
(&rest args)
(cond
((= (len args) 0) (js-regex-new "" ""))
((= (len args) 1)
(js-regex-new (js-to-string (nth args 0)) ""))
(else
(js-regex-new
(js-to-string (nth args 0))
(js-to-string (nth args 1)))))))
;; ---------------------------------------------------------------------------
;; 5. Wire new globals into js-global
;; ---------------------------------------------------------------------------
(dict-set! js-global "Map" Map)
(dict-set! js-global "Set" Set)
(dict-set! js-global "RegExp" RegExp)

View File

@@ -33,10 +33,6 @@ cat > "$TMPFILE" << 'EPOCHS'
(load "lib/js/transpile.sx")
(epoch 5)
(load "lib/js/runtime.sx")
(epoch 6)
(load "lib/js/regex.sx")
(epoch 7)
(load "lib/js/stdlib.sx")
;; ── Phase 0: stubs still behave ─────────────────────────────────
(epoch 10)
@@ -1327,166 +1323,6 @@ cat > "$TMPFILE" << 'EPOCHS'
(epoch 3505)
(eval "(js-eval \"var a = {length: 3, 0: 10, 1: 20, 2: 30}; var sum = 0; Array.prototype.forEach.call(a, function(x){sum += x;}); sum\")")
;; ── Phase 12: Regex engine ────────────────────────────────────────
;; Platform is installed (test key is a function, not undefined)
(epoch 5000)
(eval "(js-undefined? (get __js_regex_platform__ \"test\"))")
(epoch 5001)
(eval "(js-eval \"/foo/.test('hi foo bar')\")")
(epoch 5002)
(eval "(js-eval \"/foo/.test('hi bar')\")")
;; Case-insensitive flag
(epoch 5003)
(eval "(js-eval \"/FOO/i.test('hello foo world')\")")
;; Anchors
(epoch 5004)
(eval "(js-eval \"/^hello/.test('hello world')\")")
(epoch 5005)
(eval "(js-eval \"/^hello/.test('say hello')\")")
(epoch 5006)
(eval "(js-eval \"/world$/.test('hello world')\")")
;; Character classes
(epoch 5007)
(eval "(js-eval \"/\\\\d+/.test('abc 123')\")")
(epoch 5008)
(eval "(js-eval \"/\\\\w+/.test('hello')\")")
(epoch 5009)
(eval "(js-eval \"/[abc]/.test('dog')\")")
(epoch 5010)
(eval "(js-eval \"/[abc]/.test('cat')\")")
;; Quantifiers
(epoch 5011)
(eval "(js-eval \"/a*b/.test('b')\")")
(epoch 5012)
(eval "(js-eval \"/a+b/.test('b')\")")
(epoch 5013)
(eval "(js-eval \"/a{2,3}/.test('aa')\")")
(epoch 5014)
(eval "(js-eval \"/a{2,3}/.test('a')\")")
;; Dot
(epoch 5015)
(eval "(js-eval \"/h.llo/.test('hello')\")")
(epoch 5016)
(eval "(js-eval \"/h.llo/.test('hllo')\")")
;; exec result
(epoch 5017)
(eval "(js-eval \"var m = /foo(\\\\w+)/.exec('foobar'); m.match\")")
(epoch 5018)
(eval "(js-eval \"var m = /foo(\\\\w+)/.exec('foobar'); m.index\")")
(epoch 5019)
(eval "(js-eval \"var m = /foo(\\\\w+)/.exec('foobar'); m.groups[0]\")")
;; Alternation
(epoch 5020)
(eval "(js-eval \"/cat|dog/.test('I have a dog')\")")
(epoch 5021)
(eval "(js-eval \"/cat|dog/.test('I have a fish')\")")
;; Non-capturing group
(epoch 5022)
(eval "(js-eval \"/(?:foo)+/.test('foofoo')\")")
;; Negated char class
(epoch 5023)
(eval "(js-eval \"/[^abc]/.test('d')\")")
(epoch 5024)
(eval "(js-eval \"/[^abc]/.test('a')\")")
;; Range inside char class
(epoch 5025)
(eval "(js-eval \"/[a-z]+/.test('hello')\")")
;; Word boundary
(epoch 5026)
(eval "(js-eval \"/\\\\bword\\\\b/.test('a word here')\")")
(epoch 5027)
(eval "(js-eval \"/\\\\bword\\\\b/.test('password')\")")
;; Lazy quantifier
(epoch 5028)
(eval "(js-eval \"var m = /a+?/.exec('aaa'); m.match\")")
;; Global flag exec
(epoch 5029)
(eval "(js-eval \"var r=/\\\\d+/g; r.exec('a1b2'); r.exec('a1b2').match\")")
;; String.prototype.match with regex
(epoch 5030)
(eval "(js-eval \"'hello world'.match(/\\\\w+/).match\")")
;; String.prototype.search
(epoch 5031)
(eval "(js-eval \"'hello world'.search(/world/)\")")
;; String.prototype.replace with regex
(epoch 5032)
(eval "(js-eval \"'hello world'.replace(/world/, 'there')\")")
;; multiline anchor
(epoch 5033)
(eval "(js-eval \"/^bar/m.test('foo\\nbar')\")")
;; ── Phase 13: let/const TDZ infrastructure ───────────────────────
;; The TDZ sentinel and checker are defined in runtime.sx.
;; let/const bindings work normally after initialization.
(epoch 5100)
(eval "(js-eval \"let x = 5; x\")")
(epoch 5101)
(eval "(js-eval \"const y = 42; y\")")
;; TDZ sentinel exists and is detectable
(epoch 5102)
(eval "(js-tdz? __js_tdz_sentinel__)")
;; js-tdz-check passes through non-sentinel values
(epoch 5103)
(eval "(js-tdz-check \"x\" 42)")
;; ── Phase 22: Bitwise ops ────────────────────────────────────────
(epoch 6000)
(eval "(js-bitand 5 3)")
(epoch 6001)
(eval "(js-bitor 5 3)")
(epoch 6002)
(eval "(js-bitxor 5 3)")
(epoch 6003)
(eval "(js-lshift 1 4)")
(epoch 6004)
(eval "(js-rshift 32 2)")
(epoch 6005)
(eval "(js-rshift -8 1)")
(epoch 6006)
(eval "(js-urshift 4294967292 2)")
(epoch 6007)
(eval "(js-bitnot 0)")
;; ── Phase 22: Map ─────────────────────────────────────────────────
(epoch 6010)
(eval "(js-map? (js-map-new))")
(epoch 6011)
(eval "(get (js-map-set! (js-map-new) \"k\" 42) \"size\")")
(epoch 6012)
(eval "(let ((m (js-map-new))) (js-map-set! m \"a\" 1) (js-map-get m \"a\"))")
(epoch 6013)
(eval "(let ((m (js-map-new))) (js-map-set! m \"x\" 9) (js-map-has m \"x\"))")
(epoch 6014)
(eval "(let ((m (js-map-new))) (js-map-set! m \"x\" 9) (js-map-has m \"y\"))")
(epoch 6015)
(eval "(let ((m (js-map-new))) (js-map-set! m \"a\" 1) (js-map-set! m \"b\" 2) (get m \"size\"))")
(epoch 6016)
(eval "(let ((m (js-map-new))) (js-map-set! m \"a\" 1) (js-map-delete! m \"a\") (get m \"size\"))")
(epoch 6017)
(eval "(let ((m (js-map-new))) (js-map-set! m \"a\" 1) (js-map-set! m \"a\" 99) (js-map-get m \"a\"))")
;; ── Phase 22: Set ─────────────────────────────────────────────────
(epoch 6020)
(eval "(js-set? (js-set-new))")
(epoch 6021)
(eval "(let ((s (js-set-new))) (js-set-add! s 1) (js-set-has s 1))")
(epoch 6022)
(eval "(let ((s (js-set-new))) (js-set-add! s 1) (js-set-has s 2))")
(epoch 6023)
(eval "(let ((s (js-set-new))) (js-set-add! s 1) (js-set-add! s 1) (get s \"size\"))")
(epoch 6024)
(eval "(let ((s (js-set-new))) (js-set-add! s 1) (js-set-add! s 2) (get s \"size\"))")
(epoch 6025)
(eval "(let ((s (js-set-new))) (js-set-add! s 1) (js-set-delete! s 1) (get s \"size\"))")
;; ── Phase 22: RegExp constructor ──────────────────────────────────
(epoch 6030)
(eval "(js-regex? (RegExp \"ab\" \"i\"))")
(epoch 6031)
(eval "(get (RegExp \"hello\" \"gi\") \"global\")")
(epoch 6032)
(eval "(get (RegExp \"foo\" \"i\") \"ignoreCase\")")
EPOCHS
@@ -2206,81 +2042,6 @@ check 3503 "indexOf.call arrLike" '1'
check 3504 "filter.call arrLike" '"2,3"'
check 3505 "forEach.call arrLike sum" '60'
# ── Phase 12: Regex engine ────────────────────────────────────────
check 5000 "regex platform installed" 'false'
check 5001 "/foo/ matches" 'true'
check 5002 "/foo/ no match" 'false'
check 5003 "/FOO/i case-insensitive" 'true'
check 5004 "/^hello/ anchor match" 'true'
check 5005 "/^hello/ anchor no-match" 'false'
check 5006 "/world$/ end anchor" 'true'
check 5007 "/\\d+/ digit class" 'true'
check 5008 "/\\w+/ word class" 'true'
check 5009 "/[abc]/ class no-match" 'false'
check 5010 "/[abc]/ class match" 'true'
check 5011 "/a*b/ zero-or-more" 'true'
check 5012 "/a+b/ one-or-more no-match" 'false'
check 5013 "/a{2,3}/ quant match" 'true'
check 5014 "/a{2,3}/ quant no-match" 'false'
check 5015 "dot matches any" 'true'
check 5016 "dot requires char" 'false'
check 5017 "exec match string" '"foobar"'
check 5018 "exec match index" '0'
check 5019 "exec capture group" '"bar"'
check 5020 "alternation cat|dog match" 'true'
check 5021 "alternation cat|dog no-match" 'false'
check 5022 "non-capturing group" 'true'
check 5023 "negated class match" 'true'
check 5024 "negated class no-match" 'false'
check 5025 "range [a-z]+" 'true'
check 5026 "word boundary match" 'true'
check 5027 "word boundary no-match" 'false'
check 5028 "lazy quantifier" '"a"'
check 5029 "global exec advances" '"2"'
check 5030 "String.match regex" '"hello"'
check 5031 "String.search regex" '6'
check 5032 "String.replace regex" '"hello there"'
check 5033 "multiline anchor" 'true'
# ── Phase 13: let/const TDZ infrastructure ───────────────────────
check 5100 "let binding initialized" '5'
check 5101 "const binding initialized" '42'
check 5102 "TDZ sentinel is detectable" 'true'
check 5103 "tdz-check passes non-sentinel" '42'
# ── Phase 22: Bitwise ops ─────────────────────────────────────────
check 6000 "bitand 5&3" '1'
check 6001 "bitor 5|3" '7'
check 6002 "bitxor 5^3" '6'
check 6003 "lshift 1<<4" '16'
check 6004 "rshift 32>>2" '8'
check 6005 "rshift -8>>1" '-4'
check 6006 "urshift >>>" '1073741823'
check 6007 "bitnot ~0" '-1'
# ── Phase 22: Map ─────────────────────────────────────────────────
check 6010 "map? new map" 'true'
check 6011 "map set→size 1" '1'
check 6012 "map get existing" '1'
check 6013 "map has key yes" 'true'
check 6014 "map has key no" 'false'
check 6015 "map size 2 entries" '2'
check 6016 "map delete→size 0" '0'
check 6017 "map set overwrites" '99'
# ── Phase 22: Set ─────────────────────────────────────────────────
check 6020 "set? new set" 'true'
check 6021 "set has after add" 'true'
check 6022 "set has absent" 'false'
check 6023 "set dedup size" '1'
check 6024 "set size 2" '2'
check 6025 "set delete→size 0" '0'
# ── Phase 22: RegExp ──────────────────────────────────────────────
check 6030 "RegExp? result" 'true'
check 6031 "RegExp global flag" 'true'
check 6032 "RegExp ignoreCase" 'true'
TOTAL=$((PASS + FAIL))
if [ $FAIL -eq 0 ]; then
echo "$PASS/$TOTAL JS-on-SX tests passed"

View File

@@ -798,7 +798,6 @@ class ServerSession:
self._run_and_collect(3, '(load "lib/js/parser.sx")', timeout=60.0)
self._run_and_collect(4, '(load "lib/js/transpile.sx")', timeout=60.0)
self._run_and_collect(5, '(load "lib/js/runtime.sx")', timeout=60.0)
self._run_and_collect(50, '(load "lib/js/regex.sx")', timeout=60.0)
# Preload the stub harness — use precomputed SX cache when available
# (huge win: ~15s js-eval HARNESS_STUB → ~0s load precomputed .sx).
cache_rel = _harness_cache_rel_path()

View File

@@ -935,12 +935,12 @@
(define
js-transpile-var
(fn (kind decls) (cons (js-sym "begin") (js-vardecl-forms kind decls))))
(fn (kind decls) (cons (js-sym "begin") (js-vardecl-forms decls))))
(define
js-vardecl-forms
(fn
(kind decls)
(decls)
(cond
((empty? decls) (list))
(else
@@ -953,7 +953,7 @@
(js-sym "define")
(js-sym (nth d 1))
(js-transpile (nth d 2)))
(js-vardecl-forms kind (rest decls))))
(js-vardecl-forms (rest decls))))
((js-tag? d "js-vardecl-obj")
(let
((names (nth d 1))
@@ -964,7 +964,7 @@
(js-vardecl-obj-forms
names
tmp-sym
(js-vardecl-forms kind (rest decls))))))
(js-vardecl-forms (rest decls))))))
((js-tag? d "js-vardecl-arr")
(let
((names (nth d 1))
@@ -976,7 +976,7 @@
names
tmp-sym
0
(js-vardecl-forms kind (rest decls))))))
(js-vardecl-forms (rest decls))))))
(else (error "js-vardecl-forms: unexpected decl"))))))))
(define

View File

@@ -123,7 +123,7 @@
(fn
(i)
(if
(not (= (get a (str i)) nil))
(has? a (str i))
(begin (set! n i) (count-loop (+ i 1)))
n)))
(count-loop 1))))
@@ -152,9 +152,7 @@
(cond
((= (first f) "pos")
(begin
(set!
t
(assoc t (str array-idx) (nth f 1)))
(set! t (assoc t (str array-idx) (nth f 1)))
(set! array-idx (+ array-idx 1))))
((= (first f) "kv")
(let
@@ -171,108 +169,3 @@
(if (= t nil) nil (let ((v (get t (str k)))) (if (= v nil) nil v)))))
(define lua-set! (fn (t k v) (assoc t (str k) v)))
;; ---------------------------------------------------------------------------
;; Helpers for stdlib
;; ---------------------------------------------------------------------------
;; Apply a char function to every character in a string
(define (lua-str-map s fn) (list->string (map fn (string->list s))))
;; Repeat string s n times
(define
(lua-str-rep s n)
(letrec
((go (fn (acc i) (if (= i 0) acc (go (str acc s) (- i 1))))))
(go "" n)))
;; Force a promise created by delay
(define
(lua-force p)
(if
(and (dict? p) (get p :_promise))
(if (get p :forced) (get p :value) ((get p :thunk)))
p))
;; ---------------------------------------------------------------------------
;; math — Lua math library
;; ---------------------------------------------------------------------------
(define math {:asin asin :floor floor :exp exp :huge 1e+308 :tan tan :sqrt sqrt :log log :abs abs :ceil ceil :sin sin :max (fn (a b) (if (> a b) a b)) :acos acos :min (fn (a b) (if (< a b) a b)) :cos cos :pi 3.14159 :atan atan})
;; ---------------------------------------------------------------------------
;; string — Lua string library
;; ---------------------------------------------------------------------------
(define
(lua-string-find s pat)
(let
((m (regexp-match (make-regexp pat) s)))
(if (= m nil) nil (list (+ (get m :start) 1) (get m :end)))))
(define
(lua-string-match s pat)
(let
((m (regexp-match (make-regexp pat) s)))
(if
(= m nil)
nil
(let
((groups (get m :groups)))
(if (= (len groups) 0) (get m :match) (first groups))))))
(define
(lua-string-gmatch s pat)
(map (fn (m) (get m :match)) (regexp-match-all (make-regexp pat) s)))
(define
(lua-string-gsub s pat repl)
(regexp-replace-all (make-regexp pat) s repl))
(define string {:rep lua-str-rep :sub (fn (s i &rest j-args) (let ((slen (len s)) (j (if (= (len j-args) 0) -1 (first j-args)))) (let ((from (if (< i 0) (let ((r (+ slen i))) (if (< r 0) 0 r)) (- i 1))) (to (if (< j 0) (let ((r (+ slen j 1))) (if (< r 0) 0 r)) (if (> j slen) slen j)))) (if (> from to) "" (substring s from to))))) :len (fn (s) (len s)) :upper (fn (s) (lua-str-map s char-upcase)) :char (fn (&rest codes) (list->string (map (fn (c) (integer->char (truncate c))) codes))) :gmatch lua-string-gmatch :gsub lua-string-gsub :lower (fn (s) (lua-str-map s char-downcase)) :byte (fn (s &rest args) (char->integer (nth (string->list s) (- (if (= (len args) 0) 1 (first args)) 1)))) :match lua-string-match :find lua-string-find :reverse (fn (s) (list->string (reverse (string->list s))))})
;; ---------------------------------------------------------------------------
;; table — Lua table library
;; ---------------------------------------------------------------------------
(define
(lua-table-insert t v)
(assoc t (str (+ (lua-len t) 1)) v))
(define
(lua-table-remove t &rest args)
(let
((n (lua-len t))
(pos (if (= (len args) 0) (lua-len t) (first args))))
(letrec
((slide (fn (t i) (if (< i n) (assoc (slide t (+ i 1)) (str i) (lua-get t (+ i 1))) (assoc t (str n) nil)))))
(slide t pos))))
(define
(lua-table-concat t &rest args)
(let
((sep (if (= (len args) 0) "" (first args)))
(n (lua-len t)))
(letrec
((go (fn (acc i) (if (> i n) acc (go (str acc (if (= i 1) "" sep) (lua-to-string (lua-get t i))) (+ i 1))))))
(go "" 1))))
(define
(lua-table-sort t)
(let
((n (lua-len t)))
(letrec
((collect (fn (i acc) (if (< i 1) acc (collect (- i 1) (cons (lua-get t i) acc)))))
(rebuild
(fn
(t i items)
(if
(= (len items) 0)
t
(rebuild
(assoc t (str i) (first items))
(+ i 1)
(rest items))))))
(rebuild t 1 (sort (collect n (list)))))))
(define table {:sort lua-table-sort :concat lua-table-concat :insert lua-table-insert :remove lua-table-remove})

View File

@@ -633,116 +633,6 @@ check 482 "while i<5 count" '5'
check 483 "repeat until i>=3" '3'
check 484 "for 1..100 sum" '5050'
# ── Phase 3: stdlib — math, string, table ──────────────────────────────────
cat >> "$TMPFILE" << 'EPOCHS2'
;; ── math library ───────────────────────────────────────────────
(epoch 500)
(eval "(lua-eval-ast \"return math.abs(-7)\")")
(epoch 501)
(eval "(lua-eval-ast \"return math.floor(3.9)\")")
(epoch 502)
(eval "(lua-eval-ast \"return math.ceil(3.1)\")")
(epoch 503)
(eval "(lua-eval-ast \"return math.sqrt(9)\")")
(epoch 504)
(eval "(lua-eval-ast \"return math.sin(0)\")")
(epoch 505)
(eval "(lua-eval-ast \"return math.cos(0)\")")
(epoch 506)
(eval "(lua-eval-ast \"return math.max(3, 7)\")")
(epoch 507)
(eval "(lua-eval-ast \"return math.min(3, 7)\")")
(epoch 508)
(eval "(lua-eval-ast \"return math.pi > 3\")")
(epoch 509)
(eval "(lua-eval-ast \"return math.huge > 0\")")
;; ── string library ─────────────────────────────────────────────
(epoch 520)
(eval "(lua-eval-ast \"return string.len(\\\"hello\\\")\")")
(epoch 521)
(eval "(lua-eval-ast \"return string.upper(\\\"hello\\\")\")")
(epoch 522)
(eval "(lua-eval-ast \"return string.lower(\\\"WORLD\\\")\")")
(epoch 523)
(eval "(lua-eval-ast \"return string.sub(\\\"hello\\\", 2, 4)\")")
(epoch 524)
(eval "(lua-eval-ast \"return string.rep(\\\"ab\\\", 3)\")")
(epoch 525)
(eval "(lua-eval-ast \"return string.reverse(\\\"hello\\\")\")")
(epoch 526)
(eval "(lua-eval-ast \"return string.byte(\\\"A\\\")\")")
(epoch 527)
(eval "(lua-eval-ast \"return string.char(72, 105)\")")
(epoch 528)
(eval "(lua-eval-ast \"return string.find(\\\"hello\\\", \\\"ll\\\")\")")
(epoch 529)
(eval "(lua-eval-ast \"return string.match(\\\"hello\\\", \\\"ell\\\")\")")
(epoch 530)
(eval "(lua-eval-ast \"return string.gsub(\\\"hello\\\", \\\"l\\\", \\\"r\\\")\")")
;; ── table library ──────────────────────────────────────────────
(epoch 540)
(eval "(lua-eval-ast \"local t = {10, 20, 30} t = table.insert(t, 40) return t[4]\")")
(epoch 541)
(eval "(lua-eval-ast \"local t = {10, 20, 30} t = table.remove(t) return t[3]\")")
(epoch 542)
(eval "(lua-eval-ast \"local t = {\\\"a\\\", \\\"b\\\", \\\"c\\\"} return table.concat(t, \\\",\\\")\")")
(epoch 543)
(eval "(lua-eval-ast \"local t = {3, 1, 2} t = table.sort(t) return t[1]\")")
(epoch 544)
(eval "(lua-eval-ast \"local t = {3, 1, 2} t = table.sort(t) return t[3]\")")
;; ── delay / force ──────────────────────────────────────────────
(epoch 550)
(eval "(lua-force (delay (+ 10 5)))")
(epoch 551)
(eval "(lua-force 42)")
EPOCHS2
OUTPUT2=$(timeout 30 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
OUTPUT="$OUTPUT
$OUTPUT2"
# math
check 500 "math.abs(-7)" '7'
check 501 "math.floor(3.9)" '3'
check 502 "math.ceil(3.1)" '4'
check 503 "math.sqrt(9)" '3'
check 504 "math.sin(0)" '0'
check 505 "math.cos(0)" '1'
check 506 "math.max(3,7)" '7'
check 507 "math.min(3,7)" '3'
check 508 "math.pi > 3" 'true'
check 509 "math.huge > 0" 'true'
# string
check 520 "string.len" '5'
check 521 "string.upper" '"HELLO"'
check 522 "string.lower" '"world"'
check 523 "string.sub(2,4)" '"ell"'
check 524 "string.rep(ab,3)" '"ababab"'
check 525 "string.reverse" '"olleh"'
check 526 "string.byte(A)" '65'
check 527 "string.char(72,105)" '"Hi"'
check 528 "string.find ll" '3'
check 529 "string.match ell" '"ell"'
check 530 "string.gsub l->r" '"herro"'
# table
check 540 "table.insert" '40'
check 541 "table.remove" 'nil'
check 542 "table.concat ," '"a,b,c"'
check 543 "table.sort [1]" '1'
check 544 "table.sort [3]" '3'
# delay/force
check 550 "lua-force delay" '15'
check 551 "lua-force non-promise" '42'
TOTAL=$((PASS + FAIL))
if [ $FAIL -eq 0 ]; then
echo "ok $PASS/$TOTAL Lua-on-SX tests passed"

View File

@@ -73,10 +73,7 @@
(define string->symbol make-symbol)
(define number->string
(let ((prim-n->s number->string))
(fn (n &rest r)
(if (= (len r) 0) (str n) (prim-n->s n (first r))))))
(define number->string (fn (n) (str n)))
(define
string->number

View File

@@ -1,352 +0,0 @@
;; lib/ruby/runtime.sx — Ruby primitives on SX
;;
;; Provides Ruby-idiomatic wrappers over SX built-ins.
;; Primitives used:
;; call/cc (core evaluator)
;; make-set/set-add!/set-member?/set-remove!/set->list (Phase 18)
;; make-regexp/regexp-match/regexp-match-all/... (Phase 19)
;; make-bytevector/bytevector-u8-ref/... (Phase 20)
;; ---------------------------------------------------------------------------
;; 0. Internal list helpers
;; ---------------------------------------------------------------------------
(define
(rb-list-set-nth lst i newval)
(letrec
((go (fn (ps j) (if (= (len ps) 0) (list) (cons (if (= j i) newval (first ps)) (go (rest ps) (+ j 1)))))))
(go lst 0)))
(define
(rb-list-remove-nth lst i)
(letrec
((go (fn (ps j) (if (= (len ps) 0) (list) (if (= j i) (go (rest ps) (+ j 1)) (cons (first ps) (go (rest ps) (+ j 1))))))))
(go lst 0)))
;; ---------------------------------------------------------------------------
;; 1. Hash (mutable, any-key, dict-backed list-of-pairs)
;; ---------------------------------------------------------------------------
(define
(rb-hash-new)
(let
((h (dict)))
(dict-set! h "_rb_hash" true)
(dict-set! h "_pairs" (list))
(dict-set! h "_size" 0)
h))
(define (rb-hash? v) (and (dict? v) (dict-has? v "_rb_hash")))
(define (rb-hash-size h) (get h "_size"))
(define
(rb-hash-find-idx pairs k)
(letrec
((go (fn (ps i) (cond ((= (len ps) 0) -1) ((= (first (first ps)) k) i) (else (go (rest ps) (+ i 1)))))))
(go pairs 0)))
(define
(rb-hash-at h k)
(letrec
((go (fn (ps) (if (= (len ps) 0) nil (if (= (first (first ps)) k) (nth (first ps) 1) (go (rest ps)))))))
(go (get h "_pairs"))))
(define
(rb-hash-at-or h k default)
(if (rb-hash-has-key? h k) (rb-hash-at h k) default))
(define
(rb-hash-at-put! h k v)
(let
((pairs (get h "_pairs")) (idx (rb-hash-find-idx (get h "_pairs") k)))
(if
(= idx -1)
(begin
(dict-set! h "_pairs" (append pairs (list (list k v))))
(dict-set! h "_size" (+ (get h "_size") 1)))
(dict-set! h "_pairs" (rb-list-set-nth pairs idx (list k v)))))
h)
(define
(rb-hash-has-key? h k)
(not (= (rb-hash-find-idx (get h "_pairs") k) -1)))
(define
(rb-hash-delete! h k)
(let
((idx (rb-hash-find-idx (get h "_pairs") k)))
(when
(not (= idx -1))
(dict-set! h "_pairs" (rb-list-remove-nth (get h "_pairs") idx))
(dict-set! h "_size" (- (get h "_size") 1))))
h)
(define (rb-hash-keys h) (map first (get h "_pairs")))
(define
(rb-hash-values h)
(map (fn (p) (nth p 1)) (get h "_pairs")))
(define
(rb-hash-each h callback)
(for-each
(fn (p) (callback (first p) (nth p 1)))
(get h "_pairs")))
(define (rb-hash->list h) (get h "_pairs"))
(define
(rb-list->hash pairs)
(let
((h (rb-hash-new)))
(for-each
(fn (p) (rb-hash-at-put! h (first p) (nth p 1)))
pairs)
h))
(define
(rb-hash-merge h1 h2)
(let
((result (rb-hash-new)))
(for-each
(fn (p) (rb-hash-at-put! result (first p) (nth p 1)))
(get h1 "_pairs"))
(for-each
(fn (p) (rb-hash-at-put! result (first p) (nth p 1)))
(get h2 "_pairs"))
result))
;; ---------------------------------------------------------------------------
;; 2. Set (uniqueness collection backed by SX make-set)
;; Note: set-member?/set-add!/set-remove! take (set item) order.
;; ---------------------------------------------------------------------------
(define
(rb-set-new)
(let
((s (dict)))
(dict-set! s "_rb_set" true)
(dict-set! s "_set" (make-set))
(dict-set! s "_size" 0)
s))
(define (rb-set? v) (and (dict? v) (dict-has? v "_rb_set")))
(define (rb-set-size s) (get s "_size"))
(define
(rb-set-add! s v)
(let
((sx (get s "_set")))
(when
(not (set-member? sx v))
(set-add! sx v)
(dict-set! s "_size" (+ (get s "_size") 1))))
s)
(define (rb-set-include? s v) (set-member? (get s "_set") v))
(define
(rb-set-delete! s v)
(let
((sx (get s "_set")))
(when
(set-member? sx v)
(set-remove! sx v)
(dict-set! s "_size" (- (get s "_size") 1))))
s)
(define (rb-set->list s) (set->list (get s "_set")))
(define
(rb-set-each s callback)
(for-each callback (set->list (get s "_set"))))
(define
(rb-set-union s1 s2)
(let
((result (rb-set-new)))
(for-each (fn (v) (rb-set-add! result v)) (rb-set->list s1))
(for-each (fn (v) (rb-set-add! result v)) (rb-set->list s2))
result))
(define
(rb-set-intersection s1 s2)
(let
((result (rb-set-new)))
(for-each
(fn (v) (when (rb-set-include? s2 v) (rb-set-add! result v)))
(rb-set->list s1))
result))
(define
(rb-set-difference s1 s2)
(let
((result (rb-set-new)))
(for-each
(fn (v) (when (not (rb-set-include? s2 v)) (rb-set-add! result v)))
(rb-set->list s1))
result))
;; ---------------------------------------------------------------------------
;; 3. Regexp (thin wrappers over Phase-19 make-regexp primitives)
;; ---------------------------------------------------------------------------
(define
(rb-regexp-new pattern flags)
(make-regexp pattern (if (= flags nil) "" flags)))
(define (rb-regexp? v) (regexp? v))
(define (rb-regexp-match rx str) (regexp-match rx str))
(define (rb-regexp-match-all rx str) (regexp-match-all rx str))
(define (rb-regexp-match? rx str) (not (= (regexp-match rx str) nil)))
(define
(rb-regexp-replace rx str replacement)
(regexp-replace rx str replacement))
(define
(rb-regexp-replace-all rx str replacement)
(regexp-replace-all rx str replacement))
(define (rb-regexp-split rx str) (regexp-split rx str))
;; ---------------------------------------------------------------------------
;; 4. StringIO (write buffer + char-by-char read after rewind)
;; ---------------------------------------------------------------------------
(define
(rb-string-io-new)
(let
((io (dict)))
(dict-set! io "_rb_string_io" true)
(dict-set! io "_buf" "")
(dict-set! io "_chars" (list))
(dict-set! io "_pos" 0)
io))
(define (rb-string-io? v) (and (dict? v) (dict-has? v "_rb_string_io")))
(define
(rb-string-io-write! io s)
(dict-set! io "_buf" (str (get io "_buf") s))
io)
(define (rb-string-io-string io) (get io "_buf"))
(define
(rb-string-io-rewind! io)
(dict-set! io "_chars" (string->list (get io "_buf")))
(dict-set! io "_pos" 0)
io)
(define
(rb-string-io-eof? io)
(>= (get io "_pos") (len (get io "_chars"))))
(define
(rb-string-io-read-char io)
(if
(rb-string-io-eof? io)
nil
(let
((c (nth (get io "_chars") (get io "_pos"))))
(dict-set! io "_pos" (+ (get io "_pos") 1))
c)))
(define
(rb-string-io-read io)
(letrec
((go (fn (acc) (let ((c (rb-string-io-read-char io))) (if (= c nil) (list->string (reverse acc)) (go (cons c acc)))))))
(go (list))))
;; ---------------------------------------------------------------------------
;; 5. Bytevectors (thin wrappers over Phase-20 bytevector primitives)
;; ---------------------------------------------------------------------------
(define
(rb-bytes-new n fill)
(make-bytevector n (if (= fill nil) 0 fill)))
(define (rb-bytes? v) (bytevector? v))
(define (rb-bytes-length v) (bytevector-length v))
(define (rb-bytes-get v i) (bytevector-u8-ref v i))
(define (rb-bytes-set! v i b) (bytevector-u8-set! v i b) v)
(define (rb-bytes-copy v) (bytevector-copy v))
(define (rb-bytes-append v1 v2) (bytevector-append v1 v2))
(define (rb-bytes-to-string v) (utf8->string v))
(define (rb-bytes-from-string s) (string->utf8 s))
(define (rb-bytes->list v) (bytevector->list v))
(define (rb-list->bytes lst) (list->bytevector lst))
;; ---------------------------------------------------------------------------
;; 6. Fiber (call/cc coroutines)
;; Body wrapped so completion always routes through _resumer, ensuring
;; rb-fiber-resume always returns via the captured continuation.
;; ---------------------------------------------------------------------------
(define rb-current-fiber nil)
(define
(rb-fiber-new body)
(let
((f (dict)))
(dict-set! f "_rb_fiber" true)
(dict-set! f "_state" "new")
(dict-set! f "_cont" nil)
(dict-set! f "_resumer" nil)
(dict-set! f "_parent" nil)
(dict-set!
f
"_body"
(fn
()
(let
((result (body)))
(dict-set! f "_state" "dead")
(set! rb-current-fiber (get f "_parent"))
((get f "_resumer") result))))
f))
(define (rb-fiber? v) (and (dict? v) (dict-has? v "_rb_fiber")))
(define (rb-fiber-alive? f) (not (= (get f "_state") "dead")))
(define
(rb-fiber-yield val)
(call/cc
(fn
(resume-k)
(let
((cur rb-current-fiber))
(dict-set! cur "_cont" resume-k)
(dict-set! cur "_state" "suspended")
(set! rb-current-fiber (get cur "_parent"))
((get cur "_resumer") val)))))
(define
(rb-fiber-resume f)
(call/cc
(fn
(return-k)
(dict-set! f "_parent" rb-current-fiber)
(dict-set! f "_resumer" return-k)
(set! rb-current-fiber f)
(dict-set! f "_state" "running")
(if
(= (get f "_cont") nil)
((get f "_body"))
((get f "_cont") nil)))))

View File

@@ -1,62 +0,0 @@
#!/usr/bin/env bash
# lib/ruby/test.sh — smoke-test the Ruby runtime layer.
set -uo pipefail
cd "$(git rev-parse --show-toplevel)"
SX_SERVER="${SX_SERVER:-hosts/ocaml/_build/default/bin/sx_server.exe}"
if [ ! -x "$SX_SERVER" ]; then
SX_SERVER="/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe"
fi
if [ ! -x "$SX_SERVER" ]; then
echo "ERROR: sx_server.exe not found."
exit 1
fi
TMPFILE=$(mktemp); trap "rm -f $TMPFILE" EXIT
cat > "$TMPFILE" << 'EPOCHS'
(epoch 1)
(load "lib/ruby/runtime.sx")
(epoch 2)
(load "lib/ruby/tests/runtime.sx")
(epoch 3)
(eval "(list rb-test-pass rb-test-fail)")
EPOCHS
OUTPUT=$(timeout 60 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
LINE=$(echo "$OUTPUT" | awk '/^\(ok-len 3 / {getline; print; exit}')
if [ -z "$LINE" ]; then
LINE=$(echo "$OUTPUT" | grep -E '^\(ok 3 \([0-9]+ [0-9]+\)\)' | tail -1 \
| sed -E 's/^\(ok 3 //; s/\)$//')
fi
if [ -z "$LINE" ]; then
echo "ERROR: could not extract summary"
echo "$OUTPUT" | tail -20
exit 1
fi
P=$(echo "$LINE" | sed -E 's/^\(([0-9]+) ([0-9]+)\).*/\1/')
F=$(echo "$LINE" | sed -E 's/^\(([0-9]+) ([0-9]+)\).*/\2/')
TOTAL=$((P + F))
if [ "$F" -eq 0 ]; then
echo "ok $P/$TOTAL lib/ruby tests passed"
else
echo "FAIL $P/$TOTAL passed, $F failed"
TMPFILE2=$(mktemp)
cat > "$TMPFILE2" << 'EPOCHS2'
(epoch 1)
(load "lib/ruby/runtime.sx")
(epoch 2)
(load "lib/ruby/tests/runtime.sx")
(epoch 3)
(eval "(map (fn (f) (get f \"name\")) rb-test-fails)")
EPOCHS2
FAILS=$(timeout 60 "$SX_SERVER" < "$TMPFILE2" 2>/dev/null | grep -E '^\(ok 3 ' || true)
echo " Failed: $FAILS"
rm -f "$TMPFILE2"
fi
[ "$F" -eq 0 ]

View File

@@ -1,207 +0,0 @@
;; lib/ruby/tests/runtime.sx — Tests for lib/ruby/runtime.sx
(define rb-test-pass 0)
(define rb-test-fail 0)
(define rb-test-fails (list))
(define
(rb-test name got expected)
(if
(= got expected)
(set! rb-test-pass (+ rb-test-pass 1))
(begin
(set! rb-test-fail (+ rb-test-fail 1))
(set! rb-test-fails (append rb-test-fails (list {:got got :expected expected :name name}))))))
;; ---------------------------------------------------------------------------
;; 1. Hash
;; ---------------------------------------------------------------------------
(define h1 (rb-hash-new))
(rb-test "hash? new" (rb-hash? h1) true)
(rb-test "hash? non-hash" (rb-hash? 42) false)
(rb-test "hash size empty" (rb-hash-size h1) 0)
(rb-hash-at-put! h1 "a" 1)
(rb-hash-at-put! h1 "b" 2)
(rb-hash-at-put! h1 "c" 3)
(rb-test "hash at a" (rb-hash-at h1 "a") 1)
(rb-test "hash at b" (rb-hash-at h1 "b") 2)
(rb-test "hash at missing" (rb-hash-at h1 "z") nil)
(rb-test "hash at-or default" (rb-hash-at-or h1 "z" 99) 99)
(rb-test "hash has-key yes" (rb-hash-has-key? h1 "a") true)
(rb-test "hash has-key no" (rb-hash-has-key? h1 "z") false)
(rb-test "hash size after inserts" (rb-hash-size h1) 3)
(rb-hash-at-put! h1 "a" 10)
(rb-test "hash at-put update" (rb-hash-at h1 "a") 10)
(rb-test "hash size unchanged after update" (rb-hash-size h1) 3)
(rb-hash-delete! h1 "b")
(rb-test "hash delete" (rb-hash-has-key? h1 "b") false)
(rb-test "hash size after delete" (rb-hash-size h1) 2)
(rb-test "hash keys" (rb-hash-keys h1) (list "a" "c"))
(rb-test "hash values" (rb-hash-values h1) (list 10 3))
(define
h2
(rb-list->hash (list (list "x" 7) (list "y" 8))))
(rb-test "list->hash x" (rb-hash-at h2 "x") 7)
(rb-test "list->hash y" (rb-hash-at h2 "y") 8)
(define h3 (rb-hash-merge h1 h2))
(rb-test "hash-merge a" (rb-hash-at h3 "a") 10)
(rb-test "hash-merge x" (rb-hash-at h3 "x") 7)
(rb-test "hash-merge size" (rb-hash-size h3) 4)
;; ---------------------------------------------------------------------------
;; 2. Set
;; ---------------------------------------------------------------------------
(define s1 (rb-set-new))
(rb-test "set? new" (rb-set? s1) true)
(rb-test "set? non-set" (rb-set? "hello") false)
(rb-test "set size empty" (rb-set-size s1) 0)
(rb-set-add! s1 1)
(rb-set-add! s1 2)
(rb-set-add! s1 3)
(rb-set-add! s1 2)
(rb-test "set include yes" (rb-set-include? s1 1) true)
(rb-test "set include no" (rb-set-include? s1 9) false)
(rb-test "set size dedup" (rb-set-size s1) 3)
(rb-set-delete! s1 2)
(rb-test "set delete" (rb-set-include? s1 2) false)
(rb-test "set size after delete" (rb-set-size s1) 2)
(define s2 (rb-set-new))
(rb-set-add! s2 2)
(rb-set-add! s2 3)
(rb-set-add! s2 4)
(define su (rb-set-union s1 s2))
(rb-test "set union includes 1" (rb-set-include? su 1) true)
(rb-test "set union includes 4" (rb-set-include? su 4) true)
(rb-test "set union size" (rb-set-size su) 4)
(define si (rb-set-intersection s1 s2))
(rb-test "set intersection includes 3" (rb-set-include? si 3) true)
(rb-test "set intersection excludes 1" (rb-set-include? si 1) false)
(rb-test "set intersection size" (rb-set-size si) 1)
(define sd (rb-set-difference s1 s2))
(rb-test "set difference includes 1" (rb-set-include? sd 1) true)
(rb-test "set difference excludes 3" (rb-set-include? sd 3) false)
;; ---------------------------------------------------------------------------
;; 3. Regexp
;; ---------------------------------------------------------------------------
(define rx1 (rb-regexp-new "hel+" ""))
(rb-test "regexp?" (rb-regexp? rx1) true)
(rb-test "regexp match? yes" (rb-regexp-match? rx1 "say hello") true)
(rb-test "regexp match? no" (rb-regexp-match? rx1 "goodbye") false)
(define m1 (rb-regexp-match rx1 "say hello world"))
(rb-test "regexp match :match" (get m1 "match") "hell")
(define rx2 (rb-regexp-new "[0-9]+" ""))
(define all (rb-regexp-match-all rx2 "a1b22c333"))
(rb-test "regexp match-all count" (len all) 3)
(rb-test "regexp match-all first" (get (first all) "match") "1")
(rb-test "regexp replace" (rb-regexp-replace rx2 "a1b2" "N") "aNb2")
(rb-test "regexp replace-all" (rb-regexp-replace-all rx2 "a1b2" "N") "aNbN")
(rb-test
"regexp split"
(rb-regexp-split (rb-regexp-new "," "") "a,b,c")
(list "a" "b" "c"))
;; ---------------------------------------------------------------------------
;; 4. StringIO
;; ---------------------------------------------------------------------------
(define sio1 (rb-string-io-new))
(rb-test "string-io?" (rb-string-io? sio1) true)
(rb-string-io-write! sio1 "hello")
(rb-string-io-write! sio1 " world")
(rb-test "string-io string" (rb-string-io-string sio1) "hello world")
(rb-string-io-rewind! sio1)
(rb-test "string-io eof? no" (rb-string-io-eof? sio1) false)
(define ch1 (rb-string-io-read-char sio1))
(define ch2 (rb-string-io-read-char sio1))
;; Compare char codepoints since = uses reference equality for chars
(rb-test "string-io read-char h" (char->integer ch1) 104)
(rb-test "string-io read-char e" (char->integer ch2) 101)
(rb-test "string-io read rest" (rb-string-io-read sio1) "llo world")
(rb-test "string-io eof? yes" (rb-string-io-eof? sio1) true)
(rb-test "string-io read at eof" (rb-string-io-read sio1) "")
;; ---------------------------------------------------------------------------
;; 5. Bytevectors
;; ---------------------------------------------------------------------------
(define bv1 (rb-bytes-new 4 0))
(rb-test "bytes?" (rb-bytes? bv1) true)
(rb-test "bytes length" (rb-bytes-length bv1) 4)
(rb-test "bytes get zero" (rb-bytes-get bv1 0) 0)
(rb-bytes-set! bv1 0 65)
(rb-bytes-set! bv1 1 66)
(rb-test "bytes get A" (rb-bytes-get bv1 0) 65)
(rb-test "bytes get B" (rb-bytes-get bv1 1) 66)
(define bv2 (rb-bytes-from-string "hi"))
(rb-test "bytes from-string length" (rb-bytes-length bv2) 2)
(rb-test "bytes to-string" (rb-bytes-to-string bv2) "hi")
(define
bv3
(rb-bytes-append (rb-bytes-from-string "foo") (rb-bytes-from-string "bar")))
(rb-test "bytes append" (rb-bytes-to-string bv3) "foobar")
(rb-test
"bytes->list"
(rb-bytes->list (rb-bytes-from-string "AB"))
(list 65 66))
(rb-test
"list->bytes"
(rb-bytes-to-string (rb-list->bytes (list 72 105)))
"Hi")
;; ---------------------------------------------------------------------------
;; 6. Fiber
;; Note: rb-fiber-yield from inside a letrec (JIT-compiled) doesn't
;; properly escape via call/cc continuations. Use top-level helper fns
;; or explicit sequential yields instead of letrec-bound recursion.
;; ---------------------------------------------------------------------------
(define
fib1
(rb-fiber-new
(fn
()
(rb-fiber-yield 10)
(rb-fiber-yield 20)
30)))
(rb-test "fiber?" (rb-fiber? fib1) true)
(rb-test "fiber alive? before" (rb-fiber-alive? fib1) true)
(define fr1 (rb-fiber-resume fib1))
(rb-test "fiber resume 1" fr1 10)
(rb-test "fiber alive? mid" (rb-fiber-alive? fib1) true)
(define fr2 (rb-fiber-resume fib1))
(rb-test "fiber resume 2" fr2 20)
(define fr3 (rb-fiber-resume fib1))
(rb-test "fiber resume 3 (completion)" fr3 30)
(rb-test "fiber alive? dead" (rb-fiber-alive? fib1) false)
;; Loop via a top-level helper (avoid letrec — see note above)
(define
(rb-fiber-loop-helper i)
(when
(<= i 3)
(rb-fiber-yield i)
(rb-fiber-loop-helper (+ i 1))))
(define
fib2
(rb-fiber-new (fn () (rb-fiber-loop-helper 1) "done")))
(rb-test "fiber loop resume 1" (rb-fiber-resume fib2) 1)
(rb-test "fiber loop resume 2" (rb-fiber-resume fib2) 2)
(rb-test "fiber loop resume 3" (rb-fiber-resume fib2) 3)
(rb-test "fiber loop resume done" (rb-fiber-resume fib2) "done")
(rb-test "fiber loop dead" (rb-fiber-alive? fib2) false)

Some files were not shown because too many files have changed in this diff Show More