Compare commits
25 Commits
2defa5e739
...
hs-f
| Author | SHA1 | Date | |
|---|---|---|---|
| 95c2d0b64a | |||
| cfbab3b2f9 | |||
| 4d92eafb36 | |||
| 4db1f85fe8 | |||
| 54a890db71 | |||
| 58f019bc14 | |||
| 1f466186f9 | |||
| 29ef89d473 | |||
| f12c19eaa3 | |||
| 6e997e9382 | |||
| 30a7dd2108 | |||
| b9d63112e6 | |||
| 92619301e2 | |||
| e9d4d107a6 | |||
| b3c9d9eb3a | |||
| f4c155c9c5 | |||
| a9eb821cce | |||
| d0b358eca2 | |||
| 982b9d6be6 | |||
| 197c073308 | |||
| 21e6351657 | |||
| 0b4b7c9dbc | |||
| f0e1d2d615 | |||
| 9b0f42defb | |||
| 54b7a6aed0 |
@@ -1129,7 +1129,6 @@ PRIMITIVES_JS_MODULES: dict[str, str] = {
|
|||||||
PRIMITIVES["boolean?"] = function(x) { return x === true || x === false; };
|
PRIMITIVES["boolean?"] = function(x) { return x === true || x === false; };
|
||||||
PRIMITIVES["symbol?"] = function(x) { return x != null && x._sym === true; };
|
PRIMITIVES["symbol?"] = function(x) { return x != null && x._sym === true; };
|
||||||
PRIMITIVES["keyword?"] = function(x) { return x != null && x._kw === true; };
|
PRIMITIVES["keyword?"] = function(x) { return x != null && x._kw === true; };
|
||||||
PRIMITIVES["adt?"] = function(x) { return x !== null && typeof x === "object" && x._adtv === true; };
|
|
||||||
PRIMITIVES["component-affinity"] = componentAffinity;
|
PRIMITIVES["component-affinity"] = componentAffinity;
|
||||||
''',
|
''',
|
||||||
|
|
||||||
@@ -1476,22 +1475,6 @@ PRIMITIVES_JS_MODULES: dict[str, str] = {
|
|||||||
};
|
};
|
||||||
PRIMITIVES["string-buffer->string"] = function(buf) { return buf.parts.join(""); };
|
PRIMITIVES["string-buffer->string"] = function(buf) { return buf.parts.join(""); };
|
||||||
PRIMITIVES["string-buffer-length"] = function(buf) { return buf.len; };
|
PRIMITIVES["string-buffer-length"] = function(buf) { return buf.len; };
|
||||||
|
|
||||||
// Short aliases — terser names; append accepts any value
|
|
||||||
PRIMITIVES["make-buffer"] = function() { return new SxStringBuffer(); };
|
|
||||||
PRIMITIVES["buffer?"] = function(x) { return x instanceof SxStringBuffer; };
|
|
||||||
PRIMITIVES["buffer-append!"] = function(buf, v) {
|
|
||||||
var s;
|
|
||||||
if (v === null || v === undefined || v === NIL) s = "";
|
|
||||||
else if (typeof v === "string") s = v;
|
|
||||||
else if (typeof v === "boolean") s = v ? "true" : "false";
|
|
||||||
else if (typeof v === "number") s = String(v);
|
|
||||||
else if (v && typeof v === "object" && typeof v.name === "string" && v.constructor && v.constructor.name === "Symbol") s = v.name;
|
|
||||||
else s = (typeof inspect === "function") ? inspect(v) : String(v);
|
|
||||||
buf.parts.push(s); buf.len += s.length; return NIL;
|
|
||||||
};
|
|
||||||
PRIMITIVES["buffer->string"] = function(buf) { return buf.parts.join(""); };
|
|
||||||
PRIMITIVES["buffer-length"] = function(buf) { return buf.len; };
|
|
||||||
''',
|
''',
|
||||||
|
|
||||||
"stdlib.format": '''
|
"stdlib.format": '''
|
||||||
@@ -1950,30 +1933,12 @@ PLATFORM_JS_PRE = '''
|
|||||||
if (x._regexp) return "regexp";
|
if (x._regexp) return "regexp";
|
||||||
if (x._bytevector) return "bytevector";
|
if (x._bytevector) return "bytevector";
|
||||||
if (x._rational) return "rational";
|
if (x._rational) return "rational";
|
||||||
if (x._adtv) return x._type;
|
|
||||||
if (typeof Node !== "undefined" && x instanceof Node) return "dom-node";
|
if (typeof Node !== "undefined" && x instanceof Node) return "dom-node";
|
||||||
if (Array.isArray(x)) return "list";
|
if (Array.isArray(x)) return "list";
|
||||||
if (typeof x === "object") return "dict";
|
if (typeof x === "object") return "dict";
|
||||||
return "unknown";
|
return "unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdtValue — native algebraic data type instance (Step 6 mirror of OCaml Step 5).
|
|
||||||
// Constructed by define-type. Carries _adt:true plus _adtv:true tag so type-of
|
|
||||||
// returns the type name rather than "dict". dict? remains true (shim approach)
|
|
||||||
// so spec-level match-pattern in evaluator.sx works without changes.
|
|
||||||
function makeAdtValue(typeName, ctorName, fields) {
|
|
||||||
return {
|
|
||||||
_adtv: true,
|
|
||||||
_adt: true,
|
|
||||||
_type: typeName,
|
|
||||||
_ctor: ctorName,
|
|
||||||
_fields: fields
|
|
||||||
};
|
|
||||||
}
|
|
||||||
function isAdtValue(x) {
|
|
||||||
return x !== null && typeof x === "object" && x._adtv === true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function symbolName(s) { return s.name; }
|
function symbolName(s) { return s.name; }
|
||||||
function keywordName(k) { return k.name; }
|
function keywordName(k) { return k.name; }
|
||||||
function makeSymbol(n) { return new Symbol(n); }
|
function makeSymbol(n) { return new Symbol(n); }
|
||||||
@@ -2140,13 +2105,6 @@ PLATFORM_JS_PRE = '''
|
|||||||
// hostError — throw a host-level error that propagates out of cekRun.
|
// hostError — throw a host-level error that propagates out of cekRun.
|
||||||
function hostError(msg) { throw new Error(typeof msg === "string" ? msg : inspect(msg)); }
|
function hostError(msg) { throw new Error(typeof msg === "string" ? msg : inspect(msg)); }
|
||||||
|
|
||||||
// hostWarn — emit a host-level warning to console (no-op if console missing).
|
|
||||||
function hostWarn(msg) {
|
|
||||||
var m = typeof msg === "string" ? msg : inspect(msg);
|
|
||||||
if (typeof console !== "undefined" && console.warn) console.warn(m);
|
|
||||||
return NIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render dispatch — call the active adapter's render function.
|
// Render dispatch — call the active adapter's render function.
|
||||||
// Set by each adapter when loaded; defaults to identity (no rendering).
|
// Set by each adapter when loaded; defaults to identity (no rendering).
|
||||||
var _renderExprFn = null;
|
var _renderExprFn = null;
|
||||||
@@ -2168,16 +2126,7 @@ PLATFORM_JS_PRE = '''
|
|||||||
}
|
}
|
||||||
|
|
||||||
function error(msg) { throw new Error(msg); }
|
function error(msg) { throw new Error(msg); }
|
||||||
function inspect(x) {
|
function inspect(x) { return JSON.stringify(x); }
|
||||||
if (x !== null && typeof x === "object" && x._adtv === true) {
|
|
||||||
var fs = x._fields || [];
|
|
||||||
if (fs.length === 0) return "(" + x._ctor + ")";
|
|
||||||
var parts = [];
|
|
||||||
for (var i = 0; i < fs.length; i++) parts.push(inspect(fs[i]));
|
|
||||||
return "(" + x._ctor + " " + parts.join(" ") + ")";
|
|
||||||
}
|
|
||||||
return JSON.stringify(x);
|
|
||||||
}
|
|
||||||
function debugLog() { console.error.apply(console, ["[sx-debug]"].concat(Array.prototype.slice.call(arguments))); }
|
function debugLog() { console.error.apply(console, ["[sx-debug]"].concat(Array.prototype.slice.call(arguments))); }
|
||||||
|
|
||||||
'''
|
'''
|
||||||
@@ -2501,7 +2450,6 @@ CEK_FIXUPS_JS = '''
|
|||||||
// Platform functions — defined in platform_js.py, not in .sx spec files.
|
// Platform functions — defined in platform_js.py, not in .sx spec files.
|
||||||
// Spec defines self-register via js-emit-define; these are the platform interface.
|
// Spec defines self-register via js-emit-define; these are the platform interface.
|
||||||
PRIMITIVES["type-of"] = typeOf;
|
PRIMITIVES["type-of"] = typeOf;
|
||||||
PRIMITIVES["inspect"] = inspect;
|
|
||||||
PRIMITIVES["symbol-name"] = symbolName;
|
PRIMITIVES["symbol-name"] = symbolName;
|
||||||
PRIMITIVES["keyword-name"] = keywordName;
|
PRIMITIVES["keyword-name"] = keywordName;
|
||||||
PRIMITIVES["callable?"] = isCallable;
|
PRIMITIVES["callable?"] = isCallable;
|
||||||
@@ -4033,11 +3981,6 @@ def fixups_js(has_html, has_sx, has_dom, has_signals=False, has_deps=False, has_
|
|||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
PRIMITIVES["error"] = function(msg) { throw new Error(msg); };
|
PRIMITIVES["error"] = function(msg) { throw new Error(msg); };
|
||||||
PRIMITIVES["host-error"] = function(msg) { throw new Error(typeof msg === "string" ? msg : inspect(msg)); };
|
PRIMITIVES["host-error"] = function(msg) { throw new Error(typeof msg === "string" ? msg : inspect(msg)); };
|
||||||
PRIMITIVES["host-warn"] = function(msg) {
|
|
||||||
var m = typeof msg === "string" ? msg : inspect(msg);
|
|
||||||
if (typeof console !== "undefined" && console.warn) console.warn(m);
|
|
||||||
return NIL;
|
|
||||||
};
|
|
||||||
PRIMITIVES["try-catch"] = function(tryFn, catchFn) {
|
PRIMITIVES["try-catch"] = function(tryFn, catchFn) {
|
||||||
try {
|
try {
|
||||||
return cekRun(continueWithCall(tryFn, [], makeEnv(), [], []));
|
return cekRun(continueWithCall(tryFn, [], makeEnv(), [], []));
|
||||||
@@ -4160,56 +4103,7 @@ def fixups_js(has_html, has_sx, has_dom, has_signals=False, has_deps=False, has_
|
|||||||
function clearStores() { _storeRegistry = {}; return NIL; }
|
function clearStores() { _storeRegistry = {}; return NIL; }
|
||||||
PRIMITIVES["def-store"] = defStore;
|
PRIMITIVES["def-store"] = defStore;
|
||||||
PRIMITIVES["use-store"] = useStore;
|
PRIMITIVES["use-store"] = useStore;
|
||||||
PRIMITIVES["clear-stores"] = clearStores;
|
PRIMITIVES["clear-stores"] = clearStores;''']
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
// define-type override — produces native AdtValue instances (Step 6).
|
|
||||||
// The transpiled sfDefineType from evaluator.sx creates plain dict
|
|
||||||
// instances. We override here to construct AdtValue via makeAdtValue so
|
|
||||||
// type-of returns the type name and adt? can distinguish from dicts.
|
|
||||||
// dict? still returns true for AdtValue (shim) so spec-level match-pattern
|
|
||||||
// continues to work without changes.
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
var _sfDefineTypeAdt = function(args, env) {
|
|
||||||
var typeSym = first(args);
|
|
||||||
var ctorSpecs = rest(args);
|
|
||||||
var typeName = symbolName(typeSym);
|
|
||||||
var ctorNames = map(function(spec) { return symbolName(first(spec)); }, ctorSpecs);
|
|
||||||
if (!isSxTruthy(envHas(env, "*adt-registry*"))) {
|
|
||||||
envBind(env, "*adt-registry*", {});
|
|
||||||
}
|
|
||||||
envGet(env, "*adt-registry*")[typeName] = ctorNames;
|
|
||||||
envBind(env, typeName + "?", function(v) { return isAdtValue(v) && v._type === typeName; });
|
|
||||||
for (var _i = 0; _i < ctorSpecs.length; _i++) {
|
|
||||||
(function(spec) {
|
|
||||||
var cn = symbolName(first(spec));
|
|
||||||
var fieldNames = map(function(f) { return symbolName(f); }, rest(spec));
|
|
||||||
var arity = fieldNames.length;
|
|
||||||
envBind(env, cn, function() {
|
|
||||||
var ctorArgs = Array.prototype.slice.call(arguments, 0);
|
|
||||||
if (ctorArgs.length !== arity) {
|
|
||||||
throw new Error(cn + ": expected " + arity + " args, got " + ctorArgs.length);
|
|
||||||
}
|
|
||||||
return makeAdtValue(typeName, cn, ctorArgs);
|
|
||||||
});
|
|
||||||
envBind(env, cn + "?", function(v) { return isAdtValue(v) && v._ctor === cn; });
|
|
||||||
for (var _j = 0; _j < fieldNames.length; _j++) {
|
|
||||||
(function(idx, fieldName) {
|
|
||||||
envBind(env, cn + "-" + fieldName, function(v) {
|
|
||||||
if (!isAdtValue(v)) throw new Error(cn + "-" + fieldName + ": not an ADT");
|
|
||||||
if (idx >= v._fields.length) throw new Error(cn + "-" + fieldName + ": index out of bounds");
|
|
||||||
return v._fields[idx];
|
|
||||||
});
|
|
||||||
})(_j, fieldNames[_j]);
|
|
||||||
}
|
|
||||||
})(ctorSpecs[_i]);
|
|
||||||
}
|
|
||||||
return NIL;
|
|
||||||
};
|
|
||||||
PRIMITIVES["sf-define-type"] = _sfDefineTypeAdt;
|
|
||||||
registerSpecialForm("define-type", _sfDefineTypeAdt);
|
|
||||||
PRIMITIVES["make-adt-value"] = makeAdtValue;
|
|
||||||
PRIMITIVES["adt-value?"] = isAdtValue;''']
|
|
||||||
if has_deps:
|
if has_deps:
|
||||||
lines.append('''
|
lines.append('''
|
||||||
// Platform deps functions (native JS, not transpiled — need explicit registration)
|
// Platform deps functions (native JS, not transpiled — need explicit registration)
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
(** CEK benchmark — measures throughput of the CEK evaluator on tight loops.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
dune exec bin/bench_cek.exe
|
|
||||||
dune exec bin/bench_cek.exe -- 5 (5 runs each)
|
|
||||||
*)
|
|
||||||
|
|
||||||
open Sx_types
|
|
||||||
open Sx_parser
|
|
||||||
|
|
||||||
let parse_one s =
|
|
||||||
let exprs = parse_all s in
|
|
||||||
match exprs with
|
|
||||||
| e :: _ -> e
|
|
||||||
| [] -> failwith "empty parse"
|
|
||||||
|
|
||||||
let parse_many s = parse_all s
|
|
||||||
|
|
||||||
let bench_run name setup expr iters =
|
|
||||||
let env = Sx_types.make_env () in
|
|
||||||
(* Run setup forms in env *)
|
|
||||||
List.iter (fun e -> ignore (Sx_ref.eval_expr e (Env env))) setup;
|
|
||||||
let times = ref [] in
|
|
||||||
for _ = 1 to iters do
|
|
||||||
Gc.full_major ();
|
|
||||||
let t0 = Unix.gettimeofday () in
|
|
||||||
let _r = Sx_ref.eval_expr expr (Env env) in
|
|
||||||
let t1 = Unix.gettimeofday () in
|
|
||||||
times := (t1 -. t0) :: !times
|
|
||||||
done;
|
|
||||||
let sorted = List.sort compare !times in
|
|
||||||
let median = List.nth sorted (iters / 2) in
|
|
||||||
let min_t = List.nth sorted 0 in
|
|
||||||
let max_t = List.nth sorted (iters - 1) in
|
|
||||||
Printf.printf " %-22s min=%8.2fms median=%8.2fms max=%8.2fms\n%!"
|
|
||||||
name (min_t *. 1000.0) (median *. 1000.0) (max_t *. 1000.0);
|
|
||||||
median
|
|
||||||
|
|
||||||
let () =
|
|
||||||
let iters =
|
|
||||||
if Array.length Sys.argv > 1
|
|
||||||
then int_of_string Sys.argv.(1)
|
|
||||||
else 5
|
|
||||||
in
|
|
||||||
Printf.printf "CEK benchmark (%d runs each, taking median)\n%!" iters;
|
|
||||||
Printf.printf "==========================================\n%!";
|
|
||||||
|
|
||||||
(* fib 18 — recursive function call benchmark, smallish *)
|
|
||||||
let fib_setup = parse_many "(define (fib n) (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2)))))" in
|
|
||||||
let fib_expr = parse_one "(fib 18)" in
|
|
||||||
let _ = bench_run "fib(18)" fib_setup fib_expr iters in
|
|
||||||
|
|
||||||
(* loop 5000 — tight let loop *)
|
|
||||||
let loop_setup = parse_many "(define (loop n acc) (if (= n 0) acc (loop (- n 1) (+ acc 1))))" in
|
|
||||||
let loop_expr = parse_one "(loop 5000 0)" in
|
|
||||||
let _ = bench_run "loop(5000)" loop_setup loop_expr iters in
|
|
||||||
|
|
||||||
(* map+square over 1000 elem list *)
|
|
||||||
let map_setup = parse_many "(define (range-list n) (let loop ((i 0) (acc (list))) (if (= i n) acc (loop (+ i 1) (cons i acc))))) (define xs (range-list 1000))" in
|
|
||||||
let map_expr = parse_one "(map (fn (x) (* x x)) xs)" in
|
|
||||||
let _ = bench_run "map sq xs(1000)" map_setup map_expr iters in
|
|
||||||
|
|
||||||
(* reduce + over 2000 elem list *)
|
|
||||||
let red_setup = parse_many "(define (range-list n) (let loop ((i 0) (acc (list))) (if (= i n) acc (loop (+ i 1) (cons i acc))))) (define ys (range-list 2000))" in
|
|
||||||
let red_expr = parse_one "(reduce + 0 ys)" in
|
|
||||||
let _ = bench_run "reduce + ys(2000)" red_setup red_expr iters in
|
|
||||||
|
|
||||||
(* let-heavy: many bindings + if *)
|
|
||||||
let lh_setup = parse_many "(define (lh n) (let ((a 1) (b 2) (c 3) (d 4)) (if (= n 0) (+ a b c d) (lh (- n 1)))))" in
|
|
||||||
let lh_expr = parse_one "(lh 2000)" in
|
|
||||||
let _ = bench_run "let-heavy(2000)" lh_setup lh_expr iters in
|
|
||||||
|
|
||||||
Printf.printf "\nDone.\n%!"
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
(* Benchmark inspect on representative SX values.
|
|
||||||
Takes min of 9 runs of n iterations to dampen GC noise. *)
|
|
||||||
open Sx_types
|
|
||||||
|
|
||||||
let rec make_tree d =
|
|
||||||
if d = 0 then String "leaf"
|
|
||||||
else List [String "node"; make_tree (d - 1); make_tree (d - 1); make_tree (d - 1)]
|
|
||||||
|
|
||||||
let bench_min label f n runs =
|
|
||||||
let times = ref [] in
|
|
||||||
for _ = 1 to runs do
|
|
||||||
Gc.compact ();
|
|
||||||
let t0 = Unix.gettimeofday () in
|
|
||||||
for _ = 1 to n do ignore (f ()) done;
|
|
||||||
let t1 = Unix.gettimeofday () in
|
|
||||||
times := (t1 -. t0) :: !times
|
|
||||||
done;
|
|
||||||
let sorted = List.sort compare !times in
|
|
||||||
let min_t = List.nth sorted 0 in
|
|
||||||
let median = List.nth sorted (runs / 2) in
|
|
||||||
Printf.printf " %-30s min=%6.2fms median=%6.2fms (n=%d * %d runs)\n%!"
|
|
||||||
label (min_t *. 1000.0 /. float_of_int n)
|
|
||||||
(median *. 1000.0 /. float_of_int n) n runs
|
|
||||||
|
|
||||||
let () =
|
|
||||||
let tree8 = make_tree 8 in
|
|
||||||
let s = inspect tree8 in
|
|
||||||
Printf.printf "tree-d8 inspect len=%d\n%!" (String.length s);
|
|
||||||
bench_min "inspect tree-d8" (fun () -> inspect tree8) 50 9;
|
|
||||||
|
|
||||||
let tree10 = make_tree 10 in
|
|
||||||
let s = inspect tree10 in
|
|
||||||
Printf.printf "tree-d10 inspect len=%d\n%!" (String.length s);
|
|
||||||
bench_min "inspect tree-d10" (fun () -> inspect tree10) 5 9;
|
|
||||||
|
|
||||||
let dict_xs = make_dict () in
|
|
||||||
for i = 0 to 999 do
|
|
||||||
Hashtbl.replace dict_xs (string_of_int i) (Integer i)
|
|
||||||
done;
|
|
||||||
let d = Dict dict_xs in
|
|
||||||
bench_min "inspect dict-1000" (fun () -> inspect d) 100 9;
|
|
||||||
|
|
||||||
let xs = ref [] in
|
|
||||||
for i = 0 to 1999 do xs := Integer i :: !xs done;
|
|
||||||
let lst = List !xs in
|
|
||||||
bench_min "inspect list-2000" (fun () -> inspect lst) 200 9
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
(** VM bytecode benchmark — measures throughput of the VM (compiled bytecode).
|
|
||||||
|
|
||||||
Loads the SX compiler via CEK, then for each test:
|
|
||||||
1. Define the function via CEK (as a Lambda).
|
|
||||||
2. Trigger JIT compilation via Sx_vm.jit_compile_lambda.
|
|
||||||
3. Call the compiled VmClosure repeatedly via Sx_vm.call_closure.
|
|
||||||
|
|
||||||
This measures pure VM execution time on the JIT path. *)
|
|
||||||
|
|
||||||
open Sx_types
|
|
||||||
|
|
||||||
let load_compiler env globals =
|
|
||||||
let compiler_path =
|
|
||||||
if Sys.file_exists "lib/compiler.sx" then "lib/compiler.sx"
|
|
||||||
else if Sys.file_exists "../../lib/compiler.sx" then "../../lib/compiler.sx"
|
|
||||||
else if Sys.file_exists "../../../lib/compiler.sx" then "../../../lib/compiler.sx"
|
|
||||||
else failwith "compiler.sx not found"
|
|
||||||
in
|
|
||||||
let ic = open_in compiler_path in
|
|
||||||
let src = really_input_string ic (in_channel_length ic) in
|
|
||||||
close_in ic;
|
|
||||||
let exprs = Sx_parser.parse_all src in
|
|
||||||
List.iter (fun e -> ignore (Sx_ref.eval_expr e (Env env))) exprs;
|
|
||||||
let rec sync e =
|
|
||||||
Hashtbl.iter (fun id v ->
|
|
||||||
let name = Sx_types.unintern id in
|
|
||||||
Hashtbl.replace globals name v) e.bindings;
|
|
||||||
match e.parent with Some p -> sync p | None -> ()
|
|
||||||
in
|
|
||||||
sync env
|
|
||||||
|
|
||||||
let _make_globals env =
|
|
||||||
let g = Hashtbl.create 512 in
|
|
||||||
Hashtbl.iter (fun name fn ->
|
|
||||||
Hashtbl.replace g name (NativeFn (name, fn))
|
|
||||||
) Sx_primitives.primitives;
|
|
||||||
let rec sync e =
|
|
||||||
Hashtbl.iter (fun id v ->
|
|
||||||
let name = Sx_types.unintern id in
|
|
||||||
if not (Hashtbl.mem g name) then Hashtbl.replace g name v) e.bindings;
|
|
||||||
match e.parent with Some p -> sync p | None -> ()
|
|
||||||
in
|
|
||||||
sync env;
|
|
||||||
g
|
|
||||||
|
|
||||||
let define_fn env globals name params body_src =
|
|
||||||
(* Define via CEK so we get a Lambda value with proper closure. *)
|
|
||||||
let body_expr = match Sx_parser.parse_all body_src with
|
|
||||||
| [e] -> e
|
|
||||||
| _ -> failwith "expected one body expression"
|
|
||||||
in
|
|
||||||
let param_syms = List (List.map (fun p -> Symbol p) params) in
|
|
||||||
let define_expr = List [Symbol "define"; Symbol name; List [Symbol "fn"; param_syms; body_expr]] in
|
|
||||||
ignore (Sx_ref.eval_expr define_expr (Env env));
|
|
||||||
(* Sync env to globals so JIT can resolve free vars. *)
|
|
||||||
let rec sync e =
|
|
||||||
Hashtbl.iter (fun id v ->
|
|
||||||
let n = Sx_types.unintern id in
|
|
||||||
Hashtbl.replace globals n v) e.bindings;
|
|
||||||
match e.parent with Some p -> sync p | None -> ()
|
|
||||||
in
|
|
||||||
sync env;
|
|
||||||
(* Now find the Lambda and JIT-compile it. *)
|
|
||||||
let lam_val = Hashtbl.find globals name in
|
|
||||||
match lam_val with
|
|
||||||
| Lambda l ->
|
|
||||||
(match Sx_vm.jit_compile_lambda l globals with
|
|
||||||
| Some cl ->
|
|
||||||
l.l_compiled <- Some cl;
|
|
||||||
Hashtbl.replace globals name (NativeFn (name, fun args ->
|
|
||||||
Sx_vm.call_closure cl args globals));
|
|
||||||
cl
|
|
||||||
| None ->
|
|
||||||
failwith (Printf.sprintf "JIT failed for %s" name))
|
|
||||||
| _ -> failwith (Printf.sprintf "%s is not a Lambda after define" name)
|
|
||||||
|
|
||||||
let bench_call name cl globals args iters =
|
|
||||||
let times = ref [] in
|
|
||||||
for _ = 1 to iters do
|
|
||||||
Gc.full_major ();
|
|
||||||
let t0 = Unix.gettimeofday () in
|
|
||||||
let _r = Sx_vm.call_closure cl args globals in
|
|
||||||
let t1 = Unix.gettimeofday () in
|
|
||||||
times := (t1 -. t0) :: !times
|
|
||||||
done;
|
|
||||||
let sorted = List.sort compare !times in
|
|
||||||
let median = List.nth sorted (iters / 2) in
|
|
||||||
let min_t = List.nth sorted 0 in
|
|
||||||
let max_t = List.nth sorted (iters - 1) in
|
|
||||||
Printf.printf " %-22s min=%8.2fms median=%8.2fms max=%8.2fms\n%!"
|
|
||||||
name (min_t *. 1000.0) (median *. 1000.0) (max_t *. 1000.0);
|
|
||||||
median
|
|
||||||
|
|
||||||
let () =
|
|
||||||
let iters =
|
|
||||||
if Array.length Sys.argv > 1
|
|
||||||
then int_of_string Sys.argv.(1)
|
|
||||||
else 7
|
|
||||||
in
|
|
||||||
Printf.printf "VM (bytecode/JIT) benchmark (%d runs each, taking median)\n%!" iters;
|
|
||||||
Printf.printf "========================================================\n%!";
|
|
||||||
|
|
||||||
let env = Sx_types.make_env () in
|
|
||||||
let bind n fn = ignore (Sx_types.env_bind env n (NativeFn (n, fn))) in
|
|
||||||
(* Seed env with primitives as NativeFn so CEK lookups work. *)
|
|
||||||
Hashtbl.iter (fun name fn ->
|
|
||||||
Hashtbl.replace env.bindings (Sx_types.intern name) (NativeFn (name, fn))
|
|
||||||
) Sx_primitives.primitives;
|
|
||||||
(* Helpers the SX compiler relies on but aren't kernel primitives. *)
|
|
||||||
bind "symbol-name" (fun args -> match args with
|
|
||||||
| [Symbol s] -> String s | _ -> raise (Eval_error "symbol-name"));
|
|
||||||
bind "keyword-name" (fun args -> match args with
|
|
||||||
| [Keyword k] -> String k | _ -> raise (Eval_error "keyword-name"));
|
|
||||||
bind "make-symbol" (fun args -> match args with
|
|
||||||
| [String s] -> Symbol s
|
|
||||||
| [v] -> Symbol (Sx_types.value_to_string v)
|
|
||||||
| _ -> raise (Eval_error "make-symbol"));
|
|
||||||
bind "sx-serialize" (fun args -> match args with
|
|
||||||
| [v] -> String (Sx_types.inspect v)
|
|
||||||
| _ -> raise (Eval_error "sx-serialize"));
|
|
||||||
let globals = Hashtbl.create 1024 in
|
|
||||||
Hashtbl.iter (fun name fn ->
|
|
||||||
Hashtbl.replace globals name (NativeFn (name, fn))
|
|
||||||
) Sx_primitives.primitives;
|
|
||||||
Printf.printf "Loading compiler.sx ... %!";
|
|
||||||
let t0 = Unix.gettimeofday () in
|
|
||||||
load_compiler env globals;
|
|
||||||
Printf.printf "%.0fms\n%!" ((Unix.gettimeofday () -. t0) *. 1000.0);
|
|
||||||
|
|
||||||
(* fib(22) — recursive call benchmark *)
|
|
||||||
let fib_cl = define_fn env globals "fib" ["n"]
|
|
||||||
"(if (< n 2) n (+ (fib (- n 1)) (fib (- n 2))))" in
|
|
||||||
let _ = bench_call "fib(22)" fib_cl globals [Number 22.0] iters in
|
|
||||||
|
|
||||||
(* tight loop *)
|
|
||||||
let loop_cl = define_fn env globals "loop" ["n"; "acc"]
|
|
||||||
"(if (= n 0) acc (loop (- n 1) (+ acc 1)))" in
|
|
||||||
let _ = bench_call "loop(200000)" loop_cl globals [Number 200000.0; Number 0.0] iters in
|
|
||||||
|
|
||||||
(* sum-to *)
|
|
||||||
let sum_cl = define_fn env globals "sum_to" ["n"; "acc"]
|
|
||||||
"(if (= n 0) acc (sum_to (- n 1) (+ acc n)))" in
|
|
||||||
let _ = bench_call "sum-to(50000)" sum_cl globals [Number 50000.0; Number 0.0] iters in
|
|
||||||
|
|
||||||
(* count-lt: comparison-heavy *)
|
|
||||||
let cnt_cl = define_fn env globals "count_lt" ["n"; "acc"]
|
|
||||||
"(if (= n 0) acc (count_lt (- n 1) (if (< n 10000) (+ acc 1) acc)))" in
|
|
||||||
let _ = bench_call "count-lt(20000)" cnt_cl globals [Number 20000.0; Number 0.0] iters in
|
|
||||||
|
|
||||||
(* count-eq: equality-heavy on multiples of 7 *)
|
|
||||||
let eq_cl = define_fn env globals "count_eq" ["n"; "acc"]
|
|
||||||
"(if (= n 0) acc (count_eq (- n 1) (if (= 0 (- n (* 7 (/ n 7)))) (+ acc 1) acc)))" in
|
|
||||||
let _ = bench_call "count-eq(20000)" eq_cl globals [Number 20000.0; Number 0.0] iters in
|
|
||||||
|
|
||||||
Printf.printf "\nDone.\n%!"
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
(executables
|
(executables
|
||||||
(names run_tests debug_set sx_server integration_tests bench_cek bench_inspect bench_vm)
|
(names run_tests debug_set sx_server integration_tests)
|
||||||
(libraries sx unix threads.posix otfm yojson))
|
(libraries sx unix threads.posix otfm yojson))
|
||||||
|
|
||||||
(executable
|
(executable
|
||||||
|
|||||||
@@ -1279,7 +1279,7 @@ let run_foundation_tests () =
|
|||||||
assert_true "sx_truthy \"\"" (Bool (sx_truthy (String "")));
|
assert_true "sx_truthy \"\"" (Bool (sx_truthy (String "")));
|
||||||
assert_eq "not truthy nil" (Bool false) (Bool (sx_truthy Nil));
|
assert_eq "not truthy nil" (Bool false) (Bool (sx_truthy Nil));
|
||||||
assert_eq "not truthy false" (Bool false) (Bool (sx_truthy (Bool false)));
|
assert_eq "not truthy false" (Bool false) (Bool (sx_truthy (Bool false)));
|
||||||
let l = { l_params = ["x"]; l_body = Symbol "x"; l_closure = Sx_types.make_env (); l_name = None; l_compiled = None } in
|
let l = { l_params = ["x"]; l_body = Symbol "x"; l_closure = Sx_types.make_env (); l_name = None; l_compiled = None; l_call_count = 0; l_uid = Sx_types.next_lambda_uid () } in
|
||||||
assert_true "is_lambda" (Bool (Sx_types.is_lambda (Lambda l)));
|
assert_true "is_lambda" (Bool (Sx_types.is_lambda (Lambda l)));
|
||||||
ignore (Sx_types.set_lambda_name (Lambda l) "my-fn");
|
ignore (Sx_types.set_lambda_name (Lambda l) "my-fn");
|
||||||
assert_eq "lambda name mutated" (String "my-fn") (lambda_name (Lambda l))
|
assert_eq "lambda name mutated" (String "my-fn") (lambda_name (Lambda l))
|
||||||
@@ -2899,9 +2899,6 @@ let run_spec_tests env test_files =
|
|||||||
load_module "parser.sx" hs_dir;
|
load_module "parser.sx" hs_dir;
|
||||||
load_module "compiler.sx" hs_dir;
|
load_module "compiler.sx" hs_dir;
|
||||||
load_module "runtime.sx" hs_dir;
|
load_module "runtime.sx" hs_dir;
|
||||||
let hs_plugins_dir = Filename.concat hs_dir "plugins" in
|
|
||||||
load_module "worker.sx" hs_plugins_dir;
|
|
||||||
load_module "prolog.sx" hs_plugins_dir;
|
|
||||||
load_module "integration.sx" hs_dir;
|
load_module "integration.sx" hs_dir;
|
||||||
load_module "htmx.sx" hs_dir;
|
load_module "htmx.sx" hs_dir;
|
||||||
(* Override console-log to avoid str on circular mock DOM refs *)
|
(* Override console-log to avoid str on circular mock DOM refs *)
|
||||||
|
|||||||
@@ -703,11 +703,6 @@ let setup_evaluator_bridge env =
|
|||||||
| [expr; e] -> Sx_ref.eval_expr expr (Env (Sx_runtime.unwrap_env e))
|
| [expr; e] -> Sx_ref.eval_expr expr (Env (Sx_runtime.unwrap_env e))
|
||||||
| [expr] -> Sx_ref.eval_expr expr (Env env)
|
| [expr] -> Sx_ref.eval_expr expr (Env env)
|
||||||
| _ -> raise (Eval_error "eval-expr: expected (expr env?)"));
|
| _ -> raise (Eval_error "eval-expr: expected (expr env?)"));
|
||||||
(* eval-in-env: (env expr) → result. Evaluates expr in the given env. *)
|
|
||||||
Sx_primitives.register "eval-in-env" (fun args ->
|
|
||||||
match args with
|
|
||||||
| [e; expr] -> Sx_ref.eval_expr expr e
|
|
||||||
| _ -> raise (Eval_error "eval-in-env: (env expr)"));
|
|
||||||
bind "trampoline" (fun args ->
|
bind "trampoline" (fun args ->
|
||||||
match args with
|
match args with
|
||||||
| [v] ->
|
| [v] ->
|
||||||
@@ -769,13 +764,7 @@ let setup_evaluator_bridge env =
|
|||||||
| _ -> raise (Eval_error "register-special-form!: expected (name handler)"));
|
| _ -> raise (Eval_error "register-special-form!: expected (name handler)"));
|
||||||
ignore (env_bind env "*custom-special-forms*" Sx_ref.custom_special_forms);
|
ignore (env_bind env "*custom-special-forms*" Sx_ref.custom_special_forms);
|
||||||
ignore (Sx_ref.register_special_form (String "<>") (NativeFn ("<>", fun args ->
|
ignore (Sx_ref.register_special_form (String "<>") (NativeFn ("<>", fun args ->
|
||||||
List (List.map (fun a -> Sx_ref.eval_expr a (Env env)) args))));
|
List (List.map (fun a -> Sx_ref.eval_expr a (Env env)) args))))
|
||||||
(* current-env: special form — returns current lexical env as a first-class value *)
|
|
||||||
ignore (Sx_ref.register_special_form (String "current-env")
|
|
||||||
(NativeFn ("current-env", fun args ->
|
|
||||||
match args with
|
|
||||||
| [_arg_list; env_val] -> env_val
|
|
||||||
| _ -> Nil)))
|
|
||||||
|
|
||||||
(* ---- Type predicates and introspection ---- *)
|
(* ---- Type predicates and introspection ---- *)
|
||||||
let setup_introspection env =
|
let setup_introspection env =
|
||||||
@@ -961,24 +950,7 @@ let setup_env_operations env =
|
|||||||
bind "env-has?" (fun args -> match args with [e; String k] -> Bool (Sx_types.env_has (uw e) k) | [e; Keyword k] -> Bool (Sx_types.env_has (uw e) k) | _ -> raise (Eval_error "env-has?: expected env and string"));
|
bind "env-has?" (fun args -> match args with [e; String k] -> Bool (Sx_types.env_has (uw e) k) | [e; Keyword k] -> Bool (Sx_types.env_has (uw e) k) | _ -> raise (Eval_error "env-has?: expected env and string"));
|
||||||
bind "env-bind!" (fun args -> match args with [e; String k; v] -> Sx_types.env_bind (uw e) k v | [e; Keyword k; v] -> Sx_types.env_bind (uw e) k v | _ -> raise (Eval_error "env-bind!: expected env, key, value"));
|
bind "env-bind!" (fun args -> match args with [e; String k; v] -> Sx_types.env_bind (uw e) k v | [e; Keyword k; v] -> Sx_types.env_bind (uw e) k v | _ -> raise (Eval_error "env-bind!: expected env, key, value"));
|
||||||
bind "env-set!" (fun args -> match args with [e; String k; v] -> Sx_types.env_set (uw e) k v | [e; Keyword k; v] -> Sx_types.env_set (uw e) k v | _ -> raise (Eval_error "env-set!: expected env, key, value"));
|
bind "env-set!" (fun args -> match args with [e; String k; v] -> Sx_types.env_set (uw e) k v | [e; Keyword k; v] -> Sx_types.env_set (uw e) k v | _ -> raise (Eval_error "env-set!: expected env, key, value"));
|
||||||
bind "env-extend" (fun args ->
|
bind "env-extend" (fun args -> match args with [e] -> Env (Sx_types.env_extend (uw e)) | _ -> raise (Eval_error "env-extend: expected env"));
|
||||||
match args with
|
|
||||||
| e :: pairs ->
|
|
||||||
let child = Sx_types.env_extend (uw e) in
|
|
||||||
let rec go = function
|
|
||||||
| [] -> ()
|
|
||||||
| k :: v :: rest ->
|
|
||||||
ignore (Sx_types.env_bind child (Sx_runtime.value_to_str k) v); go rest
|
|
||||||
| [_] -> raise (Eval_error "env-extend: odd number of key-val pairs") in
|
|
||||||
go pairs; Env child
|
|
||||||
| _ -> raise (Eval_error "env-extend: expected env"));
|
|
||||||
bind "env-lookup" (fun args ->
|
|
||||||
match args with
|
|
||||||
| [e; key] ->
|
|
||||||
let k = Sx_runtime.value_to_str key in
|
|
||||||
let raw = uw e in
|
|
||||||
if Sx_types.env_has raw k then Sx_types.env_get raw k else Nil
|
|
||||||
| _ -> raise (Eval_error "env-lookup: (env key)"));
|
|
||||||
bind "env-merge" (fun args -> match args with [a; b] -> Sx_runtime.env_merge a b | _ -> raise (Eval_error "env-merge: expected 2 envs"))
|
bind "env-merge" (fun args -> match args with [a; b] -> Sx_runtime.env_merge a b | _ -> raise (Eval_error "env-merge: expected 2 envs"))
|
||||||
|
|
||||||
(* ---- Strict mode (gradual type system support) ---- *)
|
(* ---- Strict mode (gradual type system support) ---- *)
|
||||||
|
|||||||
@@ -82,10 +82,7 @@ let cek_run_iterative state =
|
|||||||
s := cek_step !s
|
s := cek_step !s
|
||||||
done;
|
done;
|
||||||
(match cek_suspended_p !s with
|
(match cek_suspended_p !s with
|
||||||
| Bool true ->
|
| Bool true -> raise (Eval_error "IO suspension in non-IO context")
|
||||||
(match !_cek_io_suspend_hook with
|
|
||||||
| Some hook -> hook !s
|
|
||||||
| None -> raise (Eval_error "IO suspension in non-IO context"))
|
|
||||||
| _ -> cek_value !s)
|
| _ -> cek_value !s)
|
||||||
with Eval_error msg ->
|
with Eval_error msg ->
|
||||||
_last_error_kont_ref := cek_kont !s;
|
_last_error_kont_ref := cek_kont !s;
|
||||||
@@ -311,23 +308,6 @@ def compile_spec_to_ml(spec_dir: str | None = None) -> str:
|
|||||||
output
|
output
|
||||||
)
|
)
|
||||||
|
|
||||||
# Patch transpiled cek_run to invoke _cek_io_suspend_hook on suspension
|
|
||||||
# instead of unconditionally raising Eval_error. This is the fix for the
|
|
||||||
# tree-walk eval_expr path: sf_letrec init exprs / non-last body exprs,
|
|
||||||
# macro bodies, qq_expand, dynamic-wind / scope / provide bodies all use
|
|
||||||
# `trampoline (eval_expr ...)` and were swallowing CEK suspensions as
|
|
||||||
# "IO suspension in non-IO context" errors. With the hook, the suspension
|
|
||||||
# propagates as VmSuspended to the outer driver (browser callFn / server
|
|
||||||
# eval_expr_io). When the hook is unset (pure-CEK harness), the legacy
|
|
||||||
# error is preserved as the fallback.
|
|
||||||
output = re.sub(
|
|
||||||
r'\(raise \(Eval_error \(value_to_str \(String "IO suspension in non-IO context"\)\)\)\)',
|
|
||||||
'(match !_cek_io_suspend_hook with Some hook -> hook final | None -> '
|
|
||||||
'(raise (Eval_error (value_to_str (String "IO suspension in non-IO context")))))',
|
|
||||||
output,
|
|
||||||
count=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -355,9 +355,7 @@ let vm_create_closure vm_val frame_val code_val =
|
|||||||
let f = unwrap_frame frame_val in
|
let f = unwrap_frame frame_val in
|
||||||
let uv_count = match code_val with
|
let uv_count = match code_val with
|
||||||
| Dict d -> (match Hashtbl.find_opt d "upvalue-count" with
|
| Dict d -> (match Hashtbl.find_opt d "upvalue-count" with
|
||||||
| Some (Integer n) -> n
|
| Some (Number n) -> int_of_float n | _ -> 0)
|
||||||
| Some (Number n) -> int_of_float n
|
|
||||||
| _ -> 0)
|
|
||||||
| _ -> 0
|
| _ -> 0
|
||||||
in
|
in
|
||||||
let upvalues = Array.init uv_count (fun _ ->
|
let upvalues = Array.init uv_count (fun _ ->
|
||||||
|
|||||||
@@ -75,9 +75,6 @@ cp "$ROOT/shared/sx/templates/tw.sx" "$DIST/sx/"
|
|||||||
for f in tokenizer parser compiler runtime integration htmx; do
|
for f in tokenizer parser compiler runtime integration htmx; do
|
||||||
cp "$ROOT/lib/hyperscript/$f.sx" "$DIST/sx/hs-$f.sx"
|
cp "$ROOT/lib/hyperscript/$f.sx" "$DIST/sx/hs-$f.sx"
|
||||||
done
|
done
|
||||||
for f in worker prolog; do
|
|
||||||
cp "$ROOT/lib/hyperscript/plugins/$f.sx" "$DIST/sx/hs-$f.sx"
|
|
||||||
done
|
|
||||||
|
|
||||||
# Summary
|
# Summary
|
||||||
WASM_SIZE=$(du -sh "$DIST/sx_browser.bc.wasm.assets" | cut -f1)
|
WASM_SIZE=$(du -sh "$DIST/sx_browser.bc.wasm.assets" | cut -f1)
|
||||||
|
|||||||
@@ -85,7 +85,6 @@ const FILES = [
|
|||||||
'harness-web.sx', 'engine.sx', 'orchestration.sx',
|
'harness-web.sx', 'engine.sx', 'orchestration.sx',
|
||||||
// Hyperscript modules — loaded on demand via transparent lazy loader
|
// Hyperscript modules — loaded on demand via transparent lazy loader
|
||||||
'hs-tokenizer.sx', 'hs-parser.sx', 'hs-compiler.sx', 'hs-runtime.sx',
|
'hs-tokenizer.sx', 'hs-parser.sx', 'hs-compiler.sx', 'hs-runtime.sx',
|
||||||
'hs-worker.sx', 'hs-prolog.sx',
|
|
||||||
'hs-integration.sx', 'hs-htmx.sx',
|
'hs-integration.sx', 'hs-htmx.sx',
|
||||||
'boot.sx',
|
'boot.sx',
|
||||||
];
|
];
|
||||||
@@ -456,10 +455,8 @@ for (const file of FILES) {
|
|||||||
'hs-parser': ['hs-tokenizer'],
|
'hs-parser': ['hs-tokenizer'],
|
||||||
'hs-compiler': ['hs-tokenizer', 'hs-parser'],
|
'hs-compiler': ['hs-tokenizer', 'hs-parser'],
|
||||||
'hs-runtime': ['hs-tokenizer', 'hs-parser', 'hs-compiler'],
|
'hs-runtime': ['hs-tokenizer', 'hs-parser', 'hs-compiler'],
|
||||||
'hs-worker': ['hs-tokenizer', 'hs-parser'],
|
'hs-integration': ['hs-tokenizer', 'hs-parser', 'hs-compiler', 'hs-runtime'],
|
||||||
'hs-prolog': ['hs-tokenizer', 'hs-parser', 'hs-compiler', 'hs-runtime'],
|
'hs-htmx': ['hs-tokenizer', 'hs-parser', 'hs-compiler', 'hs-runtime', 'hs-integration'],
|
||||||
'hs-integration': ['hs-tokenizer', 'hs-parser', 'hs-compiler', 'hs-runtime', 'hs-worker', 'hs-prolog'],
|
|
||||||
'hs-htmx': ['hs-tokenizer', 'hs-parser', 'hs-compiler', 'hs-runtime', 'hs-worker', 'hs-prolog', 'hs-integration'],
|
|
||||||
};
|
};
|
||||||
manifest[key] = {
|
manifest[key] = {
|
||||||
file: sxbcFile,
|
file: sxbcFile,
|
||||||
@@ -480,7 +477,7 @@ if (entryFile) {
|
|||||||
const lazyDeps = entryFile.deps.filter(d => LAZY_ENTRY_DEPS.has(d));
|
const lazyDeps = entryFile.deps.filter(d => LAZY_ENTRY_DEPS.has(d));
|
||||||
// Hyperscript modules aren't define-library, so not auto-detected as deps.
|
// Hyperscript modules aren't define-library, so not auto-detected as deps.
|
||||||
// Load them lazily after boot — eager loading breaks the boot sequence.
|
// Load them lazily after boot — eager loading breaks the boot sequence.
|
||||||
const HS_LAZY = ['hs-tokenizer', 'hs-parser', 'hs-compiler', 'hs-runtime', 'hs-worker', 'hs-prolog', 'hs-integration', 'hs-htmx'];
|
const HS_LAZY = ['hs-tokenizer', 'hs-parser', 'hs-compiler', 'hs-runtime', 'hs-integration', 'hs-htmx'];
|
||||||
for (const m of HS_LAZY) {
|
for (const m of HS_LAZY) {
|
||||||
if (manifest[m] && !lazyDeps.includes(m)) lazyDeps.push(m);
|
if (manifest[m] && !lazyDeps.includes(m)) lazyDeps.push(m);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -344,12 +344,6 @@ let api_eval src_js =
|
|||||||
sync_env_to_vm ();
|
sync_env_to_vm ();
|
||||||
return_via_side_channel (value_to_js result)
|
return_via_side_channel (value_to_js result)
|
||||||
with
|
with
|
||||||
| Sx_vm.VmSuspended _ ->
|
|
||||||
(* Top-level eval encountered an IO suspension propagated via the
|
|
||||||
cek_run hook (perform inside letrec init / non-last body / macro /
|
|
||||||
qq tree-walked path). K.eval doesn't drive resumption — surface as
|
|
||||||
a clear error so the caller knows to use callFn instead. *)
|
|
||||||
Js.Unsafe.inject (Js.string "Error: IO suspension in non-IO context (use callFn for IO-aware paths)")
|
|
||||||
| Eval_error msg -> Js.Unsafe.inject (Js.string ("Error: " ^ msg))
|
| Eval_error msg -> Js.Unsafe.inject (Js.string ("Error: " ^ msg))
|
||||||
| Parse_error msg -> Js.Unsafe.inject (Js.string ("Parse error: " ^ msg))
|
| Parse_error msg -> Js.Unsafe.inject (Js.string ("Parse error: " ^ msg))
|
||||||
|
|
||||||
@@ -377,8 +371,6 @@ let api_eval_vm src_js =
|
|||||||
) _vm_globals;
|
) _vm_globals;
|
||||||
return_via_side_channel (value_to_js result)
|
return_via_side_channel (value_to_js result)
|
||||||
with
|
with
|
||||||
| Sx_vm.VmSuspended _ ->
|
|
||||||
Js.Unsafe.inject (Js.string "Error: IO suspension in non-IO context (use callFn for IO-aware paths)")
|
|
||||||
| Eval_error msg -> Js.Unsafe.inject (Js.string ("Error: " ^ msg))
|
| Eval_error msg -> Js.Unsafe.inject (Js.string ("Error: " ^ msg))
|
||||||
| Parse_error msg -> Js.Unsafe.inject (Js.string ("Parse error: " ^ msg))
|
| Parse_error msg -> Js.Unsafe.inject (Js.string ("Parse error: " ^ msg))
|
||||||
| Not_found -> Js.Unsafe.inject (Js.string "Error: compile-module not loaded")
|
| Not_found -> Js.Unsafe.inject (Js.string "Error: compile-module not loaded")
|
||||||
@@ -389,10 +381,7 @@ let api_eval_expr expr_js _env_js =
|
|||||||
let result = Sx_ref.eval_expr expr (Env global_env) in
|
let result = Sx_ref.eval_expr expr (Env global_env) in
|
||||||
sync_env_to_vm ();
|
sync_env_to_vm ();
|
||||||
return_via_side_channel (value_to_js result)
|
return_via_side_channel (value_to_js result)
|
||||||
with
|
with Eval_error msg ->
|
||||||
| Sx_vm.VmSuspended _ ->
|
|
||||||
Js.Unsafe.inject (Js.string "Error: IO suspension in non-IO context (use callFn for IO-aware paths)")
|
|
||||||
| Eval_error msg ->
|
|
||||||
Js.Unsafe.inject (Js.string ("Error: " ^ msg))
|
Js.Unsafe.inject (Js.string ("Error: " ^ msg))
|
||||||
|
|
||||||
let api_load src_js =
|
let api_load src_js =
|
||||||
@@ -676,7 +665,11 @@ let () =
|
|||||||
let rec deep_equal a b =
|
let rec deep_equal a b =
|
||||||
match a, b with
|
match a, b with
|
||||||
| Nil, Nil -> true | Bool a, Bool b -> a = b
|
| Nil, Nil -> true | Bool a, Bool b -> a = b
|
||||||
| Number a, Number b -> a = b | String a, String 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
|
| Symbol a, Symbol b -> a = b | Keyword a, Keyword b -> a = b
|
||||||
| (List a | ListRef { contents = a }), (List b | ListRef { contents = b }) ->
|
| (List a | ListRef { contents = a }), (List b | ListRef { contents = b }) ->
|
||||||
List.length a = List.length b && List.for_all2 deep_equal a b
|
List.length a = List.length b && List.for_all2 deep_equal a b
|
||||||
@@ -715,10 +708,8 @@ let () =
|
|||||||
| List (Symbol "code" :: rest) ->
|
| List (Symbol "code" :: rest) ->
|
||||||
let d = Hashtbl.create 8 in
|
let d = Hashtbl.create 8 in
|
||||||
let rec parse_kv = function
|
let rec parse_kv = function
|
||||||
| Keyword "arity" :: (Number _ as n) :: rest -> Hashtbl.replace d "arity" n; parse_kv rest
|
| Keyword "arity" :: Number n :: rest -> Hashtbl.replace d "arity" (Number n); parse_kv rest
|
||||||
| Keyword "arity" :: (Integer _ as n) :: rest -> Hashtbl.replace d "arity" n; parse_kv rest
|
| Keyword "upvalue-count" :: Number n :: rest -> Hashtbl.replace d "upvalue-count" (Number n); parse_kv rest
|
||||||
| Keyword "upvalue-count" :: (Number _ as n) :: rest -> Hashtbl.replace d "upvalue-count" n; parse_kv rest
|
|
||||||
| Keyword "upvalue-count" :: (Integer _ as n) :: rest -> Hashtbl.replace d "upvalue-count" n; parse_kv rest
|
|
||||||
| Keyword "bytecode" :: List nums :: rest ->
|
| Keyword "bytecode" :: List nums :: rest ->
|
||||||
Hashtbl.replace d "bytecode" (List nums); parse_kv rest
|
Hashtbl.replace d "bytecode" (List nums); parse_kv rest
|
||||||
| Keyword "constants" :: List consts :: rest ->
|
| Keyword "constants" :: List consts :: rest ->
|
||||||
|
|||||||
@@ -1,172 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
// Repro: letrec sibling bindings nil after perform/resume in browser kernel
|
|
||||||
//
|
|
||||||
// Bug: After a CEK IO suspension (perform / hs-wait) resumes in the
|
|
||||||
// WASM browser kernel, calling a sibling letrec binding could return
|
|
||||||
// nil, with the error surfaced as `[sx] resume: Not callable: nil`.
|
|
||||||
//
|
|
||||||
// Root cause: cek-run / cek_run_iterative raised
|
|
||||||
// `"IO suspension in non-IO context"` when a tree-walked eval_expr
|
|
||||||
// (sf_letrec init exprs / non-last body, macro body, qq unquote, scope
|
|
||||||
// body, provide body, dynamic-wind) hit a perform. The CEK suspension
|
|
||||||
// was created correctly but never propagated through the OCaml-side
|
|
||||||
// _cek_io_suspend_hook, so the outer driver never saw VmSuspended.
|
|
||||||
//
|
|
||||||
// Fix: cek_run / cek_run_iterative now invoke _cek_io_suspend_hook on
|
|
||||||
// suspension (raising VmSuspended for the outer driver). When the hook
|
|
||||||
// is unset (pure-CEK harness), they fall back to the legacy error.
|
|
||||||
//
|
|
||||||
// This test exercises the WASM kernel through K.callFn — the path that
|
|
||||||
// browser event handlers use. Suspension surfaces as a JS object with
|
|
||||||
// {suspended, request, resume(result)} that the test drives synchronously.
|
|
||||||
//
|
|
||||||
// Companion: spec/tests/test-letrec-resume-treewalk.sx tests the
|
|
||||||
// CEK-only path through the OCaml test runner.
|
|
||||||
|
|
||||||
const path = require('path');
|
|
||||||
const fs = require('fs');
|
|
||||||
|
|
||||||
const KERNEL = path.join(__dirname, '..', '_build', 'default', 'browser', 'sx_browser.bc.js');
|
|
||||||
if (!fs.existsSync(KERNEL)) {
|
|
||||||
console.error('FATAL: missing ' + KERNEL + ' — run `dune build` from hosts/ocaml first');
|
|
||||||
process.exit(2);
|
|
||||||
}
|
|
||||||
require(KERNEL);
|
|
||||||
const K = globalThis.SxKernel;
|
|
||||||
|
|
||||||
let passed = 0, failed = 0;
|
|
||||||
const failures = [];
|
|
||||||
|
|
||||||
function test(name, fn) {
|
|
||||||
try {
|
|
||||||
const r = fn();
|
|
||||||
if (r === true) {
|
|
||||||
passed++;
|
|
||||||
console.log(' PASS: ' + name);
|
|
||||||
} else {
|
|
||||||
failed++;
|
|
||||||
failures.push({ name, error: 'got ' + JSON.stringify(r) });
|
|
||||||
console.log(' FAIL: ' + name + ' — got ' + JSON.stringify(r));
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
failed++;
|
|
||||||
failures.push({ name, error: e.message || String(e) });
|
|
||||||
console.log(' FAIL: ' + name + ' — ' + (e.message || e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function driveSync(result) {
|
|
||||||
while (result && typeof result === 'object' && result.suspended) {
|
|
||||||
result = result.resume(null);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function callExpr(src) {
|
|
||||||
K.eval('(define _t-fn (fn () ' + src + '))');
|
|
||||||
const fn = K.eval('_t-fn');
|
|
||||||
return driveSync(K.callFn(fn, []));
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('\n=== letrec + perform/resume regression tests ===\n');
|
|
||||||
|
|
||||||
test('basic letrec without perform', () =>
|
|
||||||
callExpr('(letrec ((f (fn () "ok"))) (f))') === 'ok');
|
|
||||||
|
|
||||||
test('callFn perform suspends and resumes with nil', () => {
|
|
||||||
K.eval('(define _t-perform (fn () (perform {:op "io"})))');
|
|
||||||
let r = K.callFn(K.eval('_t-perform'), []);
|
|
||||||
if (!r || !r.suspended) return 'no suspension: ' + JSON.stringify(r);
|
|
||||||
return r.resume(null) === null;
|
|
||||||
});
|
|
||||||
|
|
||||||
test('letrec, single binding, perform/resume', () =>
|
|
||||||
callExpr('(letrec ((f (fn () (perform {:op "io"})))) (f))') === null);
|
|
||||||
|
|
||||||
test('letrec, 2 bindings, body calls sibling after suspended call', () =>
|
|
||||||
callExpr(`
|
|
||||||
(letrec
|
|
||||||
((wait-then (fn () (do (perform {:op "io"}) "wait-done")))
|
|
||||||
(other-fn (fn () "other-result")))
|
|
||||||
(do (wait-then) (other-fn)))`) === 'other-result');
|
|
||||||
|
|
||||||
test('letrec, suspending fn calls sibling after own perform', () =>
|
|
||||||
callExpr(`
|
|
||||||
(letrec
|
|
||||||
((wait-and-call (fn () (do (perform {:op "io"}) (other-fn))))
|
|
||||||
(other-fn (fn () "from-sibling")))
|
|
||||||
(wait-and-call))`) === 'from-sibling');
|
|
||||||
|
|
||||||
test('letrec, fn references sibling value after perform/resume', () =>
|
|
||||||
callExpr(`
|
|
||||||
(letrec
|
|
||||||
((shared "shared-state")
|
|
||||||
(do-fn (fn () (do (perform {:op "io"}) shared))))
|
|
||||||
(do-fn))`) === 'shared-state');
|
|
||||||
|
|
||||||
test('letrec, recursive self-call after perform (wait-boot pattern)', () => {
|
|
||||||
K.eval('(define _wb-c 0)');
|
|
||||||
K.eval('(set! _wb-c 0)');
|
|
||||||
return callExpr(`
|
|
||||||
(letrec ((wait-boot (fn ()
|
|
||||||
(do (perform {:op "io"})
|
|
||||||
(if (>= _wb-c 1)
|
|
||||||
"done"
|
|
||||||
(do (set! _wb-c (+ 1 _wb-c))
|
|
||||||
(wait-boot)))))))
|
|
||||||
(wait-boot))`) === 'done';
|
|
||||||
});
|
|
||||||
|
|
||||||
test('top-level define + perform + sibling call after resume', () => {
|
|
||||||
K.eval('(define do-suspend-x (fn () (do (perform {:op "io"}) (do-other-x))))');
|
|
||||||
K.eval('(define do-other-x (fn () "ok-from-other"))');
|
|
||||||
return callExpr('(do-suspend-x)') === 'ok-from-other';
|
|
||||||
});
|
|
||||||
|
|
||||||
test('letrec, two performs (sequential) then sibling call', () =>
|
|
||||||
callExpr(`
|
|
||||||
(letrec
|
|
||||||
((wait-twice (fn () (do (perform {:op "io1"}) (perform {:op "io2"}) (other))))
|
|
||||||
(other (fn () "after-double")))
|
|
||||||
(wait-twice))`) === 'after-double');
|
|
||||||
|
|
||||||
// === Tree-walk paths that previously raised "IO suspension in non-IO context" ===
|
|
||||||
|
|
||||||
test('letrec init expr with perform — suspension propagates (no error)', () => {
|
|
||||||
let r;
|
|
||||||
try { r = callExpr('(letrec ((x (perform {:op "io"}))) "ok")'); }
|
|
||||||
catch (e) { return 'threw: ' + e.message; }
|
|
||||||
return r === null || r === 'ok';
|
|
||||||
});
|
|
||||||
|
|
||||||
test('letrec non-last body with perform — suspension propagates (no error)', () => {
|
|
||||||
let r;
|
|
||||||
try { r = callExpr('(letrec ((x 1)) (perform {:op "io"}) "after")'); }
|
|
||||||
catch (e) { return 'threw: ' + e.message; }
|
|
||||||
return r === null || r === 'after';
|
|
||||||
});
|
|
||||||
|
|
||||||
test('macro body with perform — suspension propagates', () => {
|
|
||||||
K.eval('(defmacro _m1 (form) (do (perform {:op "io"}) form))');
|
|
||||||
let r;
|
|
||||||
try { r = callExpr('(_m1 "macro-ok")'); }
|
|
||||||
catch (e) { return 'threw: ' + e.message; }
|
|
||||||
return r === 'macro-ok' || r === null;
|
|
||||||
});
|
|
||||||
|
|
||||||
test('quasiquote unquote with perform — suspension propagates', () => {
|
|
||||||
let r;
|
|
||||||
try { r = callExpr('(let ((y "yyy")) `(a ,(do (perform {:op "io"}) y) c))'); }
|
|
||||||
catch (e) { return 'threw: ' + e.message; }
|
|
||||||
return r !== undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('\n--- Results ---');
|
|
||||||
console.log('passed: ' + passed);
|
|
||||||
console.log('failed: ' + failed);
|
|
||||||
if (failed > 0) {
|
|
||||||
console.log('\nFailures:');
|
|
||||||
failures.forEach(f => console.log(' - ' + f.name + ': ' + f.error));
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
process.exit(0);
|
|
||||||
@@ -200,30 +200,7 @@ and compile_qq_list em items scope =
|
|||||||
|
|
||||||
(* compile-call *)
|
(* compile-call *)
|
||||||
and compile_call em head args scope tail_p =
|
and compile_call em head args scope tail_p =
|
||||||
(let is_prim = (let _and = (prim_call "=" [(type_of (head)); (String "symbol")]) in if not (sx_truthy _and) then _and else (let name = (symbol_name (head)) in (let _and = (Bool (not (sx_truthy ((prim_call "=" [(get ((scope_resolve (scope) (name))) ((String "type"))); (String "local")]))))) in if not (sx_truthy _and) then _and else (let _and = (Bool (not (sx_truthy ((prim_call "=" [(get ((scope_resolve (scope) (name))) ((String "type"))); (String "upvalue")]))))) in if not (sx_truthy _and) then _and else (is_primitive (name)))))) in (if sx_truthy (is_prim) then (let name = (symbol_name (head)) in let argc = (len (args)) in
|
(let is_prim = (let _and = (prim_call "=" [(type_of (head)); (String "symbol")]) in if not (sx_truthy _and) then _and else (let name = (symbol_name (head)) in (let _and = (Bool (not (sx_truthy ((prim_call "=" [(get ((scope_resolve (scope) (name))) ((String "type"))); (String "local")]))))) in if not (sx_truthy _and) then _and else (let _and = (Bool (not (sx_truthy ((prim_call "=" [(get ((scope_resolve (scope) (name))) ((String "type"))); (String "upvalue")]))))) in if not (sx_truthy _and) then _and else (is_primitive (name)))))) in (if sx_truthy (is_prim) then (let name = (symbol_name (head)) in let argc = (len (args)) in let name_idx = (pool_add ((get (em) ((String "pool")))) (name)) in (let () = ignore ((List.iter (fun a -> ignore ((compile_expr (em) (a) (scope) ((Bool false))))) (sx_to_list args); Nil)) in (let () = ignore ((emit_op (em) ((Number 52.0)))) in (let () = ignore ((emit_u16 (em) (name_idx))) in (emit_byte (em) (argc)))))) else (let () = ignore ((compile_expr (em) (head) (scope) ((Bool false)))) in (let () = ignore ((List.iter (fun a -> ignore ((compile_expr (em) (a) (scope) ((Bool false))))) (sx_to_list args); Nil)) in (if sx_truthy (tail_p) then (let () = ignore ((emit_op (em) ((Number 49.0)))) in (emit_byte (em) ((len (args))))) else (let () = ignore ((emit_op (em) ((Number 48.0)))) in (emit_byte (em) ((len (args))))))))))
|
||||||
(* Specialized opcode for hot 2-arg / 1-arg primitives. *)
|
|
||||||
let specialized_op = (match name, argc with
|
|
||||||
| String "+", Number 2.0 -> Some 160
|
|
||||||
| String "-", Number 2.0 -> Some 161
|
|
||||||
| String "*", Number 2.0 -> Some 162
|
|
||||||
| String "/", Number 2.0 -> Some 163
|
|
||||||
| String "=", Number 2.0 -> Some 164
|
|
||||||
| String "<", Number 2.0 -> Some 165
|
|
||||||
| String ">", Number 2.0 -> Some 166
|
|
||||||
| String "cons", Number 2.0 -> Some 172
|
|
||||||
| String "not", Number 1.0 -> Some 167
|
|
||||||
| String "len", Number 1.0 -> Some 168
|
|
||||||
| String "first", Number 1.0 -> Some 169
|
|
||||||
| String "rest", Number 1.0 -> Some 170
|
|
||||||
| _ -> None) in
|
|
||||||
(let () = ignore ((List.iter (fun a -> ignore ((compile_expr (em) (a) (scope) ((Bool false))))) (sx_to_list args); Nil)) in
|
|
||||||
(match specialized_op with
|
|
||||||
| Some op -> emit_op em (Number (float_of_int op))
|
|
||||||
| None ->
|
|
||||||
let name_idx = (pool_add ((get (em) ((String "pool")))) (name)) in
|
|
||||||
let () = ignore ((emit_op (em) ((Number 52.0)))) in
|
|
||||||
let () = ignore ((emit_u16 (em) (name_idx))) in
|
|
||||||
emit_byte (em) (argc)))) else (let () = ignore ((compile_expr (em) (head) (scope) ((Bool false)))) in (let () = ignore ((List.iter (fun a -> ignore ((compile_expr (em) (a) (scope) ((Bool false))))) (sx_to_list args); Nil)) in (if sx_truthy (tail_p) then (let () = ignore ((emit_op (em) ((Number 49.0)))) in (emit_byte (em) ((len (args))))) else (let () = ignore ((emit_op (em) ((Number 48.0)))) in (emit_byte (em) ((len (args))))))))))
|
|
||||||
|
|
||||||
(* compile *)
|
(* compile *)
|
||||||
and compile expr =
|
and compile expr =
|
||||||
|
|||||||
@@ -582,11 +582,22 @@ let () =
|
|||||||
(List lb | ListRef { contents = lb }) ->
|
(List lb | ListRef { contents = lb }) ->
|
||||||
List.length la = List.length lb &&
|
List.length la = List.length lb &&
|
||||||
List.for_all2 safe_eq la lb
|
List.for_all2 safe_eq la lb
|
||||||
(* Dict: check __host_handle for DOM node identity *)
|
(* Dict: __host_handle identity for DOM-wrapped dicts; otherwise
|
||||||
|
structural equality over keys + values. *)
|
||||||
| Dict a, Dict b ->
|
| Dict a, Dict b ->
|
||||||
(match Hashtbl.find_opt a "__host_handle", Hashtbl.find_opt b "__host_handle" with
|
(match Hashtbl.find_opt a "__host_handle", Hashtbl.find_opt b "__host_handle" with
|
||||||
| Some (Number ha), Some (Number hb) -> ha = hb
|
| Some (Number ha), Some (Number hb) -> ha = hb
|
||||||
| _ -> false)
|
| Some _, _ | _, Some _ -> false
|
||||||
|
| None, None ->
|
||||||
|
Hashtbl.length a = Hashtbl.length b &&
|
||||||
|
(let eq = ref true in
|
||||||
|
Hashtbl.iter (fun k v ->
|
||||||
|
if !eq then
|
||||||
|
match Hashtbl.find_opt b k with
|
||||||
|
| Some v' -> if not (safe_eq v v') then eq := false
|
||||||
|
| None -> eq := false
|
||||||
|
) a;
|
||||||
|
!eq))
|
||||||
(* Records: same type + structurally equal fields *)
|
(* Records: same type + structurally equal fields *)
|
||||||
| Record a, Record b ->
|
| Record a, Record b ->
|
||||||
a.r_type.rt_uid = b.r_type.rt_uid &&
|
a.r_type.rt_uid = b.r_type.rt_uid &&
|
||||||
@@ -666,9 +677,7 @@ let () =
|
|||||||
register "list?" (fun args ->
|
register "list?" (fun args ->
|
||||||
match args with [List _] | [ListRef _] -> Bool true | [_] -> Bool false | _ -> raise (Eval_error "list?: 1 arg"));
|
match args with [List _] | [ListRef _] -> Bool true | [_] -> Bool false | _ -> raise (Eval_error "list?: 1 arg"));
|
||||||
register "dict?" (fun args ->
|
register "dict?" (fun args ->
|
||||||
match args with [Dict _] -> Bool true | [AdtValue _] -> Bool true | [_] -> Bool false | _ -> raise (Eval_error "dict?: 1 arg"));
|
match args with [Dict _] -> Bool true | [_] -> Bool false | _ -> raise (Eval_error "dict?: 1 arg"));
|
||||||
register "adt?" (fun args ->
|
|
||||||
match args with [AdtValue _] -> Bool true | [_] -> Bool false | _ -> raise (Eval_error "adt?: 1 arg"));
|
|
||||||
register "symbol?" (fun args ->
|
register "symbol?" (fun args ->
|
||||||
match args with [Symbol _] -> Bool true | [_] -> Bool false | _ -> raise (Eval_error "symbol?: 1 arg"));
|
match args with [Symbol _] -> Bool true | [_] -> Bool false | _ -> raise (Eval_error "symbol?: 1 arg"));
|
||||||
register "keyword?" (fun args ->
|
register "keyword?" (fun args ->
|
||||||
@@ -1281,11 +1290,6 @@ let () =
|
|||||||
match args with [String msg] -> raise (Eval_error msg)
|
match args with [String msg] -> raise (Eval_error msg)
|
||||||
| [a] -> raise (Eval_error (to_string a))
|
| [a] -> raise (Eval_error (to_string a))
|
||||||
| _ -> raise (Eval_error "host-error: 1 arg"));
|
| _ -> raise (Eval_error "host-error: 1 arg"));
|
||||||
register "host-warn" (fun args ->
|
|
||||||
match args with
|
|
||||||
| [String msg] -> prerr_endline msg; Nil
|
|
||||||
| [a] -> prerr_endline (to_string a); Nil
|
|
||||||
| _ -> raise (Eval_error "host-warn: 1 arg"));
|
|
||||||
register "try-catch" (fun args ->
|
register "try-catch" (fun args ->
|
||||||
match args with
|
match args with
|
||||||
| [try_fn; catch_fn] ->
|
| [try_fn; catch_fn] ->
|
||||||
@@ -1607,32 +1611,6 @@ let () =
|
|||||||
match args with [StringBuffer buf] -> Integer (Buffer.length buf)
|
match args with [StringBuffer buf] -> Integer (Buffer.length buf)
|
||||||
| _ -> raise (Eval_error "string-buffer-length: expected (buffer)"));
|
| _ -> raise (Eval_error "string-buffer-length: expected (buffer)"));
|
||||||
|
|
||||||
(* Short aliases — same StringBuffer value, terser names for hot paths.
|
|
||||||
Append accepts any value: strings pass through, others get inspected/coerced. *)
|
|
||||||
register "make-buffer" (fun _ -> StringBuffer (Buffer.create 64));
|
|
||||||
register "buffer?" (fun args ->
|
|
||||||
match args with [StringBuffer _] -> Bool true | [_] -> Bool false
|
|
||||||
| _ -> raise (Eval_error "buffer?: expected 1 arg"));
|
|
||||||
register "buffer-append!" (fun args ->
|
|
||||||
match args with
|
|
||||||
| [StringBuffer buf; String s] -> Buffer.add_string buf s; Nil
|
|
||||||
| [StringBuffer buf; Integer n] -> Buffer.add_string buf (string_of_int n); Nil
|
|
||||||
| [StringBuffer buf; Number n] -> Buffer.add_string buf (Sx_types.format_number n); Nil
|
|
||||||
| [StringBuffer buf; Symbol s] -> Buffer.add_string buf s; Nil
|
|
||||||
| [StringBuffer buf; Char n] ->
|
|
||||||
Buffer.add_utf_8_uchar buf (Uchar.of_int n); Nil
|
|
||||||
| [StringBuffer buf; Nil] -> Buffer.add_string buf ""; Nil
|
|
||||||
| [StringBuffer buf; Bool true] -> Buffer.add_string buf "true"; Nil
|
|
||||||
| [StringBuffer buf; Bool false] -> Buffer.add_string buf "false"; Nil
|
|
||||||
| [StringBuffer buf; v] -> Buffer.add_string buf (inspect v); Nil
|
|
||||||
| _ -> raise (Eval_error "buffer-append!: expected (buffer value)"));
|
|
||||||
register "buffer->string" (fun args ->
|
|
||||||
match args with [StringBuffer buf] -> String (Buffer.contents buf)
|
|
||||||
| _ -> raise (Eval_error "buffer->string: expected (buffer)"));
|
|
||||||
register "buffer-length" (fun args ->
|
|
||||||
match args with [StringBuffer buf] -> Integer (Buffer.length buf)
|
|
||||||
| _ -> raise (Eval_error "buffer-length: expected (buffer)"));
|
|
||||||
|
|
||||||
(* Capability-based sandboxing — gate IO operations *)
|
(* Capability-based sandboxing — gate IO operations *)
|
||||||
let cap_stack : string list ref = ref [] in
|
let cap_stack : string list ref = ref [] in
|
||||||
register "with-capabilities" (fun args ->
|
register "with-capabilities" (fun args ->
|
||||||
@@ -3124,442 +3102,6 @@ let () =
|
|||||||
| [String pat] -> List (List.map (fun s -> String s) (glob_paths pat))
|
| [String pat] -> List (List.map (fun s -> String s) (glob_paths pat))
|
||||||
| _ -> raise (Eval_error "file-glob: (pattern)"));
|
| _ -> raise (Eval_error "file-glob: (pattern)"));
|
||||||
|
|
||||||
(* === File metadata + ops (Phase 5d) === *)
|
|
||||||
let stat_or = function
|
|
||||||
| String path -> (try Some (Unix.stat path) with _ -> None)
|
|
||||||
| _ -> raise (Eval_error "file: path must be a string")
|
|
||||||
in
|
|
||||||
register "file-size" (fun args ->
|
|
||||||
match args with
|
|
||||||
| [v] -> (match stat_or v with Some s -> Integer s.Unix.st_size | None -> Integer 0)
|
|
||||||
| _ -> raise (Eval_error "file-size: (path)"));
|
|
||||||
register "file-mtime" (fun args ->
|
|
||||||
match args with
|
|
||||||
| [v] -> (match stat_or v with Some s -> Integer (int_of_float s.Unix.st_mtime) | None -> Integer 0)
|
|
||||||
| _ -> raise (Eval_error "file-mtime: (path)"));
|
|
||||||
register "file-isfile?" (fun args ->
|
|
||||||
match args with
|
|
||||||
| [v] -> (match stat_or v with Some s -> Bool (s.Unix.st_kind = Unix.S_REG) | None -> Bool false)
|
|
||||||
| _ -> raise (Eval_error "file-isfile?: (path)"));
|
|
||||||
register "file-isdir?" (fun args ->
|
|
||||||
match args with
|
|
||||||
| [v] -> (match stat_or v with Some s -> Bool (s.Unix.st_kind = Unix.S_DIR) | None -> Bool false)
|
|
||||||
| _ -> raise (Eval_error "file-isdir?: (path)"));
|
|
||||||
register "file-readable?" (fun args ->
|
|
||||||
match args with
|
|
||||||
| [String path] ->
|
|
||||||
Bool (try Unix.access path [Unix.R_OK]; true with _ -> false)
|
|
||||||
| _ -> raise (Eval_error "file-readable?: (path)"));
|
|
||||||
register "file-writable?" (fun args ->
|
|
||||||
match args with
|
|
||||||
| [String path] ->
|
|
||||||
Bool (try Unix.access path [Unix.W_OK]; true with _ -> false)
|
|
||||||
| _ -> raise (Eval_error "file-writable?: (path)"));
|
|
||||||
register "file-stat" (fun args ->
|
|
||||||
match args with
|
|
||||||
| [v] ->
|
|
||||||
(match stat_or v with
|
|
||||||
| None -> Nil
|
|
||||||
| Some s ->
|
|
||||||
let d = Hashtbl.create 6 in
|
|
||||||
Hashtbl.replace d "size" (Integer s.Unix.st_size);
|
|
||||||
Hashtbl.replace d "mtime" (Integer (int_of_float s.Unix.st_mtime));
|
|
||||||
Hashtbl.replace d "atime" (Integer (int_of_float s.Unix.st_atime));
|
|
||||||
Hashtbl.replace d "ctime" (Integer (int_of_float s.Unix.st_ctime));
|
|
||||||
Hashtbl.replace d "mode" (Integer s.Unix.st_perm);
|
|
||||||
Hashtbl.replace d "type" (String (match s.Unix.st_kind with
|
|
||||||
| Unix.S_REG -> "file" | Unix.S_DIR -> "directory"
|
|
||||||
| Unix.S_LNK -> "link" | Unix.S_CHR -> "characterSpecial"
|
|
||||||
| Unix.S_BLK -> "blockSpecial" | Unix.S_FIFO -> "fifo"
|
|
||||||
| Unix.S_SOCK -> "socket"));
|
|
||||||
Dict d)
|
|
||||||
| _ -> raise (Eval_error "file-stat: (path)"));
|
|
||||||
register "file-delete" (fun args ->
|
|
||||||
match args with
|
|
||||||
| [String path] ->
|
|
||||||
(try
|
|
||||||
if Sys.is_directory path then Unix.rmdir path
|
|
||||||
else Unix.unlink path
|
|
||||||
with
|
|
||||||
| Unix.Unix_error (Unix.ENOENT, _, _) -> () (* tolerate missing *)
|
|
||||||
| Unix.Unix_error (e, _, _) -> raise (Eval_error ("file-delete: " ^ Unix.error_message e)));
|
|
||||||
Nil
|
|
||||||
| _ -> raise (Eval_error "file-delete: (path)"));
|
|
||||||
register "file-mkdir" (fun args ->
|
|
||||||
match args with
|
|
||||||
| [String path] ->
|
|
||||||
let rec mk p =
|
|
||||||
if p = "" || p = "." || p = "/" then ()
|
|
||||||
else if Sys.file_exists p then ()
|
|
||||||
else begin
|
|
||||||
mk (Filename.dirname p);
|
|
||||||
(try Unix.mkdir p 0o755
|
|
||||||
with Unix.Unix_error (Unix.EEXIST, _, _) -> ())
|
|
||||||
end
|
|
||||||
in
|
|
||||||
(try mk path
|
|
||||||
with Unix.Unix_error (e, _, _) -> raise (Eval_error ("file-mkdir: " ^ Unix.error_message e)));
|
|
||||||
Nil
|
|
||||||
| _ -> raise (Eval_error "file-mkdir: (path)"));
|
|
||||||
register "file-copy" (fun args ->
|
|
||||||
match args with
|
|
||||||
| [String src; String dst] ->
|
|
||||||
(try
|
|
||||||
let ic = open_in_bin src in
|
|
||||||
let oc = open_out_bin dst in
|
|
||||||
let buf = Bytes.create 8192 in
|
|
||||||
let rec loop () =
|
|
||||||
let n = input ic buf 0 (Bytes.length buf) in
|
|
||||||
if n > 0 then (output oc buf 0 n; loop ())
|
|
||||||
in
|
|
||||||
loop ();
|
|
||||||
close_in ic;
|
|
||||||
close_out oc;
|
|
||||||
Nil
|
|
||||||
with
|
|
||||||
| Sys_error msg -> raise (Eval_error ("file-copy: " ^ msg)))
|
|
||||||
| _ -> raise (Eval_error "file-copy: (src dst)"));
|
|
||||||
register "file-rename" (fun args ->
|
|
||||||
match args with
|
|
||||||
| [String src; String dst] ->
|
|
||||||
(try Sys.rename src dst with Sys_error msg -> raise (Eval_error ("file-rename: " ^ msg)));
|
|
||||||
Nil
|
|
||||||
| _ -> raise (Eval_error "file-rename: (src dst)"));
|
|
||||||
|
|
||||||
(* === Channels (random-access + blocking control) === *)
|
|
||||||
let channel_table : (string, Unix.file_descr * string * bool ref * bool ref) Hashtbl.t = Hashtbl.create 16 in
|
|
||||||
let channel_next_id = ref 0 in
|
|
||||||
let parse_open_mode mode =
|
|
||||||
match mode with
|
|
||||||
| "r" -> [Unix.O_RDONLY]
|
|
||||||
| "w" -> [Unix.O_WRONLY; Unix.O_CREAT; Unix.O_TRUNC]
|
|
||||||
| "a" -> [Unix.O_WRONLY; Unix.O_CREAT; Unix.O_APPEND]
|
|
||||||
| "r+" -> [Unix.O_RDWR]
|
|
||||||
| "w+" -> [Unix.O_RDWR; Unix.O_CREAT; Unix.O_TRUNC]
|
|
||||||
| "a+" -> [Unix.O_RDWR; Unix.O_CREAT; Unix.O_APPEND]
|
|
||||||
| _ -> raise (Eval_error ("channel-open: invalid mode " ^ mode))
|
|
||||||
in
|
|
||||||
let chan_get name =
|
|
||||||
match Hashtbl.find_opt channel_table name with
|
|
||||||
| Some c -> c
|
|
||||||
| None -> raise (Eval_error ("channel: no such channel " ^ name))
|
|
||||||
in
|
|
||||||
register "channel-open" (fun args ->
|
|
||||||
match args with
|
|
||||||
| [String path; String mode] ->
|
|
||||||
(try
|
|
||||||
let fd = Unix.openfile path (parse_open_mode mode) 0o644 in
|
|
||||||
let id = !channel_next_id in
|
|
||||||
incr channel_next_id;
|
|
||||||
let name = Printf.sprintf "file%d" id in
|
|
||||||
Hashtbl.replace channel_table name (fd, mode, ref false, ref true);
|
|
||||||
String name
|
|
||||||
with Unix.Unix_error (e, _, _) -> raise (Eval_error ("channel-open: " ^ Unix.error_message e)))
|
|
||||||
| _ -> raise (Eval_error "channel-open: (path mode)"));
|
|
||||||
|
|
||||||
register "channel-close" (fun args ->
|
|
||||||
match args with
|
|
||||||
| [String name] ->
|
|
||||||
let (fd, _, _, _) = chan_get name in
|
|
||||||
(try Unix.close fd with _ -> ());
|
|
||||||
Hashtbl.remove channel_table name;
|
|
||||||
Nil
|
|
||||||
| _ -> raise (Eval_error "channel-close: (channel)"));
|
|
||||||
|
|
||||||
register "channel-read" (fun args ->
|
|
||||||
let (name, max_n) = match args with
|
|
||||||
| [String n] -> (n, -1)
|
|
||||||
| [String n; Integer m] -> (n, m)
|
|
||||||
| [String n; Number m] -> (n, int_of_float m)
|
|
||||||
| _ -> raise (Eval_error "channel-read: (channel ?n?)")
|
|
||||||
in
|
|
||||||
let (fd, _, eof, _) = chan_get name in
|
|
||||||
let chunk = 8192 in
|
|
||||||
let buf = Bytes.create chunk in
|
|
||||||
let buffer = Buffer.create chunk in
|
|
||||||
let total = ref 0 in
|
|
||||||
let stop = ref false in
|
|
||||||
while not !stop do
|
|
||||||
let want = if max_n < 0 then chunk else min chunk (max_n - !total) in
|
|
||||||
if want <= 0 then stop := true
|
|
||||||
else begin
|
|
||||||
try
|
|
||||||
let r = Unix.read fd buf 0 want in
|
|
||||||
if r = 0 then begin eof := true; stop := true end
|
|
||||||
else begin
|
|
||||||
Buffer.add_subbytes buffer buf 0 r;
|
|
||||||
total := !total + r
|
|
||||||
end
|
|
||||||
with
|
|
||||||
| Unix.Unix_error (Unix.EAGAIN, _, _)
|
|
||||||
| Unix.Unix_error (Unix.EWOULDBLOCK, _, _) -> stop := true
|
|
||||||
end
|
|
||||||
done;
|
|
||||||
String (Buffer.contents buffer));
|
|
||||||
|
|
||||||
register "channel-read-line" (fun args ->
|
|
||||||
match args with
|
|
||||||
| [String name] ->
|
|
||||||
let (fd, _, eof, _) = chan_get name in
|
|
||||||
let buf = Buffer.create 80 in
|
|
||||||
let one = Bytes.create 1 in
|
|
||||||
let got_data = ref false in
|
|
||||||
let stop = ref false in
|
|
||||||
while not !stop do
|
|
||||||
try
|
|
||||||
let r = Unix.read fd one 0 1 in
|
|
||||||
if r = 0 then begin eof := true; stop := true end
|
|
||||||
else begin
|
|
||||||
got_data := true;
|
|
||||||
let c = Bytes.get one 0 in
|
|
||||||
if c = '\n' then stop := true
|
|
||||||
else Buffer.add_char buf c
|
|
||||||
end
|
|
||||||
with
|
|
||||||
| Unix.Unix_error (Unix.EAGAIN, _, _)
|
|
||||||
| Unix.Unix_error (Unix.EWOULDBLOCK, _, _) -> stop := true
|
|
||||||
done;
|
|
||||||
if !got_data then String (Buffer.contents buf) else Nil
|
|
||||||
| _ -> raise (Eval_error "channel-read-line: (channel)"));
|
|
||||||
|
|
||||||
register "channel-write" (fun args ->
|
|
||||||
match args with
|
|
||||||
| [String name; String s] ->
|
|
||||||
let (fd, _, _, _) = chan_get name in
|
|
||||||
let b = Bytes.of_string s in
|
|
||||||
let n = Bytes.length b in
|
|
||||||
let written = ref 0 in
|
|
||||||
while !written < n do
|
|
||||||
(try
|
|
||||||
let w = Unix.write fd b !written (n - !written) in
|
|
||||||
written := !written + w
|
|
||||||
with
|
|
||||||
| Unix.Unix_error (Unix.EAGAIN, _, _)
|
|
||||||
| Unix.Unix_error (Unix.EWOULDBLOCK, _, _) ->
|
|
||||||
(* short write — let caller retry *)
|
|
||||||
written := n)
|
|
||||||
done;
|
|
||||||
Nil
|
|
||||||
| _ -> raise (Eval_error "channel-write: (channel string)"));
|
|
||||||
|
|
||||||
register "channel-flush" (fun args ->
|
|
||||||
match args with
|
|
||||||
| [String name] -> let _ = chan_get name in Nil (* no userspace buffer *)
|
|
||||||
| _ -> raise (Eval_error "channel-flush: (channel)"));
|
|
||||||
|
|
||||||
register "channel-seek" (fun args ->
|
|
||||||
let (name, offset, whence) = match args with
|
|
||||||
| [String n; Integer o] -> (n, o, "start")
|
|
||||||
| [String n; Number o] -> (n, int_of_float o, "start")
|
|
||||||
| [String n; Integer o; String w] -> (n, o, w)
|
|
||||||
| [String n; Number o; String w] -> (n, int_of_float o, w)
|
|
||||||
| _ -> raise (Eval_error "channel-seek: (channel offset ?whence?)")
|
|
||||||
in
|
|
||||||
let (fd, _, eof, _) = chan_get name in
|
|
||||||
let cmd = match whence with
|
|
||||||
| "start" -> Unix.SEEK_SET
|
|
||||||
| "current" -> Unix.SEEK_CUR
|
|
||||||
| "end" -> Unix.SEEK_END
|
|
||||||
| _ -> raise (Eval_error ("channel-seek: invalid whence " ^ whence))
|
|
||||||
in
|
|
||||||
let _ = Unix.lseek fd offset cmd in
|
|
||||||
eof := false;
|
|
||||||
Nil);
|
|
||||||
|
|
||||||
register "channel-tell" (fun args ->
|
|
||||||
match args with
|
|
||||||
| [String name] ->
|
|
||||||
let (fd, _, _, _) = chan_get name in
|
|
||||||
Integer (Unix.lseek fd 0 Unix.SEEK_CUR)
|
|
||||||
| _ -> raise (Eval_error "channel-tell: (channel)"));
|
|
||||||
|
|
||||||
register "channel-eof?" (fun args ->
|
|
||||||
match args with
|
|
||||||
| [String name] ->
|
|
||||||
let (_, _, eof, _) = chan_get name in
|
|
||||||
Bool !eof
|
|
||||||
| _ -> raise (Eval_error "channel-eof?: (channel)"));
|
|
||||||
|
|
||||||
register "channel-blocking?" (fun args ->
|
|
||||||
match args with
|
|
||||||
| [String name] ->
|
|
||||||
let (_, _, _, blocking) = chan_get name in
|
|
||||||
Bool !blocking
|
|
||||||
| _ -> raise (Eval_error "channel-blocking?: (channel)"));
|
|
||||||
|
|
||||||
register "channel-set-blocking!" (fun args ->
|
|
||||||
match args with
|
|
||||||
| [String name; Bool b] ->
|
|
||||||
let (fd, _, _, blocking) = chan_get name in
|
|
||||||
blocking := b;
|
|
||||||
(try
|
|
||||||
if b then Unix.clear_nonblock fd
|
|
||||||
else Unix.set_nonblock fd
|
|
||||||
with _ -> ());
|
|
||||||
Nil
|
|
||||||
| _ -> raise (Eval_error "channel-set-blocking!: (channel bool)"));
|
|
||||||
|
|
||||||
(* === Sockets === wrapping Unix.socket/connect/bind/listen/accept *)
|
|
||||||
let resolve_inet_addr host =
|
|
||||||
if host = "" || host = "0.0.0.0" then Unix.inet_addr_any
|
|
||||||
else if host = "localhost" then Unix.inet_addr_loopback
|
|
||||||
else
|
|
||||||
try Unix.inet_addr_of_string host
|
|
||||||
with _ ->
|
|
||||||
try
|
|
||||||
let entry = Unix.gethostbyname host in
|
|
||||||
if Array.length entry.Unix.h_addr_list = 0 then
|
|
||||||
raise (Eval_error ("socket: cannot resolve " ^ host))
|
|
||||||
else entry.Unix.h_addr_list.(0)
|
|
||||||
with Not_found -> raise (Eval_error ("socket: cannot resolve " ^ host))
|
|
||||||
in
|
|
||||||
let port_of v = match v with
|
|
||||||
| Integer n -> n
|
|
||||||
| Number n -> int_of_float n
|
|
||||||
| _ -> raise (Eval_error "socket: port must be a number")
|
|
||||||
in
|
|
||||||
let alloc_chan_name () =
|
|
||||||
let id = !channel_next_id in
|
|
||||||
incr channel_next_id;
|
|
||||||
Printf.sprintf "sock%d" id
|
|
||||||
in
|
|
||||||
|
|
||||||
register "socket-connect" (fun args ->
|
|
||||||
match args with
|
|
||||||
| [String host; port_v] ->
|
|
||||||
let port = port_of port_v in
|
|
||||||
let addr = Unix.ADDR_INET (resolve_inet_addr host, port) in
|
|
||||||
let sock = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
|
|
||||||
(try Unix.connect sock addr
|
|
||||||
with Unix.Unix_error (e, _, _) ->
|
|
||||||
(try Unix.close sock with _ -> ());
|
|
||||||
raise (Eval_error ("socket-connect: " ^ Unix.error_message e)));
|
|
||||||
let name = alloc_chan_name () in
|
|
||||||
Hashtbl.replace channel_table name (sock, "rw", ref false, ref true);
|
|
||||||
String name
|
|
||||||
| _ -> raise (Eval_error "socket-connect: (host port)"));
|
|
||||||
|
|
||||||
(* Non-blocking connect: returns channel immediately. Connection completes
|
|
||||||
when the channel becomes writable; query channel-async-error? after to
|
|
||||||
confirm success or get the error. *)
|
|
||||||
register "socket-connect-async" (fun args ->
|
|
||||||
match args with
|
|
||||||
| [String host; port_v] ->
|
|
||||||
let port = port_of port_v in
|
|
||||||
let addr = Unix.ADDR_INET (resolve_inet_addr host, port) in
|
|
||||||
let sock = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
|
|
||||||
Unix.set_nonblock sock;
|
|
||||||
(try Unix.connect sock addr
|
|
||||||
with
|
|
||||||
| Unix.Unix_error (Unix.EINPROGRESS, _, _)
|
|
||||||
| Unix.Unix_error (Unix.EWOULDBLOCK, _, _) -> ()
|
|
||||||
| Unix.Unix_error (e, _, _) ->
|
|
||||||
(try Unix.close sock with _ -> ());
|
|
||||||
raise (Eval_error ("socket-connect-async: " ^ Unix.error_message e)));
|
|
||||||
let name = alloc_chan_name () in
|
|
||||||
Hashtbl.replace channel_table name (sock, "rw", ref false, ref false);
|
|
||||||
String name
|
|
||||||
| _ -> raise (Eval_error "socket-connect-async: (host port)"));
|
|
||||||
|
|
||||||
(* After a non-blocking connect completes (channel writable), check whether
|
|
||||||
the connect succeeded. Returns "" on success, error message on failure. *)
|
|
||||||
register "channel-async-error" (fun args ->
|
|
||||||
match args with
|
|
||||||
| [String name] ->
|
|
||||||
let (fd, _, _, _) = chan_get name in
|
|
||||||
(try
|
|
||||||
let err = Unix.getsockopt_error fd in
|
|
||||||
match err with
|
|
||||||
| None -> String ""
|
|
||||||
| Some e -> String (Unix.error_message e)
|
|
||||||
with
|
|
||||||
| Unix.Unix_error (e, _, _) -> String (Unix.error_message e))
|
|
||||||
| _ -> raise (Eval_error "channel-async-error: (channel)"));
|
|
||||||
|
|
||||||
register "socket-server" (fun args ->
|
|
||||||
let (host, port) = match args with
|
|
||||||
| [port_v] -> ("", port_of port_v)
|
|
||||||
| [String h; port_v] -> (h, port_of port_v)
|
|
||||||
| _ -> raise (Eval_error "socket-server: (port) or (host port)")
|
|
||||||
in
|
|
||||||
let addr = Unix.ADDR_INET (resolve_inet_addr host, port) in
|
|
||||||
let sock = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
|
|
||||||
Unix.setsockopt sock Unix.SO_REUSEADDR true;
|
|
||||||
(try Unix.bind sock addr
|
|
||||||
with Unix.Unix_error (e, _, _) ->
|
|
||||||
(try Unix.close sock with _ -> ());
|
|
||||||
raise (Eval_error ("socket-server: bind: " ^ Unix.error_message e)));
|
|
||||||
Unix.listen sock 8;
|
|
||||||
let name = alloc_chan_name () in
|
|
||||||
Hashtbl.replace channel_table name (sock, "server", ref false, ref true);
|
|
||||||
String name);
|
|
||||||
|
|
||||||
register "socket-accept" (fun args ->
|
|
||||||
match args with
|
|
||||||
| [String name] ->
|
|
||||||
let (sock, _, _, _) = chan_get name in
|
|
||||||
let (client_sock, client_addr) =
|
|
||||||
try Unix.accept sock
|
|
||||||
with Unix.Unix_error (e, _, _) ->
|
|
||||||
raise (Eval_error ("socket-accept: " ^ Unix.error_message e))
|
|
||||||
in
|
|
||||||
let (host_str, port) = match client_addr with
|
|
||||||
| Unix.ADDR_INET (addr, p) -> (Unix.string_of_inet_addr addr, p)
|
|
||||||
| Unix.ADDR_UNIX path -> (path, 0)
|
|
||||||
in
|
|
||||||
let client_name = alloc_chan_name () in
|
|
||||||
Hashtbl.replace channel_table client_name (client_sock, "rw", ref false, ref true);
|
|
||||||
let d = Hashtbl.create 3 in
|
|
||||||
Hashtbl.replace d "channel" (String client_name);
|
|
||||||
Hashtbl.replace d "host" (String host_str);
|
|
||||||
Hashtbl.replace d "port" (Integer port);
|
|
||||||
Dict d
|
|
||||||
| _ -> raise (Eval_error "socket-accept: (server-channel)"));
|
|
||||||
|
|
||||||
(* io-select-channels: (read-list write-list timeout-ms) → {:readable [...] :writable [...]}
|
|
||||||
timeout-ms < 0 → block indefinitely; 0 → poll. Returns ready channel names. *)
|
|
||||||
register "io-select-channels" (fun args ->
|
|
||||||
let to_ms v = match v with
|
|
||||||
| Integer n -> n
|
|
||||||
| Number n -> int_of_float n
|
|
||||||
| _ -> raise (Eval_error "io-select-channels: timeout must be a number")
|
|
||||||
in
|
|
||||||
let to_list v = match v with
|
|
||||||
| List xs | ListRef { contents = xs } -> xs
|
|
||||||
| Nil -> []
|
|
||||||
| _ -> raise (Eval_error "io-select-channels: expected list")
|
|
||||||
in
|
|
||||||
let chan_name_of v = match v with
|
|
||||||
| String s -> s
|
|
||||||
| _ -> raise (Eval_error "io-select-channels: channel must be a string")
|
|
||||||
in
|
|
||||||
let (read_list, write_list, timeout_ms) = match args with
|
|
||||||
| [r; w; t] -> (to_list r, to_list w, to_ms t)
|
|
||||||
| _ -> raise (Eval_error "io-select-channels: (read-list write-list timeout-ms)")
|
|
||||||
in
|
|
||||||
let read_pairs = List.map (fun v ->
|
|
||||||
let name = chan_name_of v in
|
|
||||||
let (fd, _, _, _) = chan_get name in (name, fd)) read_list in
|
|
||||||
let write_pairs = List.map (fun v ->
|
|
||||||
let name = chan_name_of v in
|
|
||||||
let (fd, _, _, _) = chan_get name in (name, fd)) write_list in
|
|
||||||
let read_fds = List.map snd read_pairs in
|
|
||||||
let write_fds = List.map snd write_pairs in
|
|
||||||
let timeout = if timeout_ms < 0 then -1.0 else float_of_int timeout_ms /. 1000.0 in
|
|
||||||
let (ready_r, ready_w, _) =
|
|
||||||
try Unix.select read_fds write_fds [] timeout
|
|
||||||
with Unix.Unix_error (Unix.EINTR, _, _) -> ([], [], [])
|
|
||||||
in
|
|
||||||
let names_of pairs ready =
|
|
||||||
List.filter_map (fun (n, fd) ->
|
|
||||||
if List.exists (fun rfd -> rfd = fd) ready then Some (String n) else None
|
|
||||||
) pairs
|
|
||||||
in
|
|
||||||
let d = Hashtbl.create 2 in
|
|
||||||
Hashtbl.replace d "readable" (List (names_of read_pairs ready_r));
|
|
||||||
Hashtbl.replace d "writable" (List (names_of write_pairs ready_w));
|
|
||||||
Dict d);
|
|
||||||
|
|
||||||
(* === Clock === *)
|
(* === Clock === *)
|
||||||
register "clock-seconds" (fun args ->
|
register "clock-seconds" (fun args ->
|
||||||
match args with
|
match args with
|
||||||
@@ -3571,8 +3113,11 @@ let () =
|
|||||||
| [] -> Integer (int_of_float (Unix.gettimeofday () *. 1000.0))
|
| [] -> Integer (int_of_float (Unix.gettimeofday () *. 1000.0))
|
||||||
| _ -> raise (Eval_error "clock-milliseconds: no args"));
|
| _ -> raise (Eval_error "clock-milliseconds: no args"));
|
||||||
|
|
||||||
let format_tm tm tz_label =
|
register "clock-format" (fun args ->
|
||||||
fun fmt ->
|
match args with
|
||||||
|
| [Integer t] | [Integer t; String _] ->
|
||||||
|
let fmt = (match args with [_; String f] -> f | _ -> "%a %b %e %H:%M:%S %Z %Y") in
|
||||||
|
let tm = Unix.gmtime (float_of_int t) in
|
||||||
let buf = Buffer.create 32 in
|
let buf = Buffer.create 32 in
|
||||||
let n = String.length fmt in
|
let n = String.length fmt in
|
||||||
let i = ref 0 in
|
let i = ref 0 in
|
||||||
@@ -3580,19 +3125,14 @@ let () =
|
|||||||
if fmt.[!i] = '%' && !i + 1 < n then begin
|
if fmt.[!i] = '%' && !i + 1 < n then begin
|
||||||
(match fmt.[!i + 1] with
|
(match fmt.[!i + 1] with
|
||||||
| 'Y' -> Buffer.add_string buf (Printf.sprintf "%04d" (1900 + tm.Unix.tm_year))
|
| 'Y' -> Buffer.add_string buf (Printf.sprintf "%04d" (1900 + tm.Unix.tm_year))
|
||||||
| 'y' -> Buffer.add_string buf (Printf.sprintf "%02d" ((1900 + tm.Unix.tm_year) mod 100))
|
|
||||||
| 'm' -> Buffer.add_string buf (Printf.sprintf "%02d" (tm.Unix.tm_mon + 1))
|
| 'm' -> Buffer.add_string buf (Printf.sprintf "%02d" (tm.Unix.tm_mon + 1))
|
||||||
| 'd' -> Buffer.add_string buf (Printf.sprintf "%02d" tm.Unix.tm_mday)
|
| 'd' -> Buffer.add_string buf (Printf.sprintf "%02d" tm.Unix.tm_mday)
|
||||||
| 'e' -> Buffer.add_string buf (Printf.sprintf "%2d" tm.Unix.tm_mday)
|
| 'e' -> Buffer.add_string buf (Printf.sprintf "%2d" tm.Unix.tm_mday)
|
||||||
| 'H' -> Buffer.add_string buf (Printf.sprintf "%02d" tm.Unix.tm_hour)
|
| 'H' -> Buffer.add_string buf (Printf.sprintf "%02d" tm.Unix.tm_hour)
|
||||||
| 'I' -> let h = tm.Unix.tm_hour mod 12 in
|
|
||||||
Buffer.add_string buf (Printf.sprintf "%02d" (if h = 0 then 12 else h))
|
|
||||||
| 'p' -> Buffer.add_string buf (if tm.Unix.tm_hour < 12 then "AM" else "PM")
|
|
||||||
| 'M' -> Buffer.add_string buf (Printf.sprintf "%02d" tm.Unix.tm_min)
|
| 'M' -> Buffer.add_string buf (Printf.sprintf "%02d" tm.Unix.tm_min)
|
||||||
| 'S' -> Buffer.add_string buf (Printf.sprintf "%02d" tm.Unix.tm_sec)
|
| 'S' -> Buffer.add_string buf (Printf.sprintf "%02d" tm.Unix.tm_sec)
|
||||||
| 'j' -> Buffer.add_string buf (Printf.sprintf "%03d" (tm.Unix.tm_yday + 1))
|
| 'j' -> Buffer.add_string buf (Printf.sprintf "%03d" (tm.Unix.tm_yday + 1))
|
||||||
| 'w' -> Buffer.add_string buf (string_of_int tm.Unix.tm_wday)
|
| 'Z' -> Buffer.add_string buf "UTC"
|
||||||
| 'Z' -> Buffer.add_string buf tz_label
|
|
||||||
| 'a' -> let days = [|"Sun";"Mon";"Tue";"Wed";"Thu";"Fri";"Sat"|] in
|
| 'a' -> let days = [|"Sun";"Mon";"Tue";"Wed";"Thu";"Fri";"Sat"|] in
|
||||||
Buffer.add_string buf days.(tm.Unix.tm_wday)
|
Buffer.add_string buf days.(tm.Unix.tm_wday)
|
||||||
| 'A' -> let days = [|"Sunday";"Monday";"Tuesday";"Wednesday";"Thursday";"Friday";"Saturday"|] in
|
| 'A' -> let days = [|"Sunday";"Monday";"Tuesday";"Wednesday";"Thursday";"Friday";"Saturday"|] in
|
||||||
@@ -3601,7 +3141,6 @@ let () =
|
|||||||
Buffer.add_string buf mons.(tm.Unix.tm_mon)
|
Buffer.add_string buf mons.(tm.Unix.tm_mon)
|
||||||
| 'B' -> let mons = [|"January";"February";"March";"April";"May";"June";"July";"August";"September";"October";"November";"December"|] in
|
| 'B' -> let mons = [|"January";"February";"March";"April";"May";"June";"July";"August";"September";"October";"November";"December"|] in
|
||||||
Buffer.add_string buf mons.(tm.Unix.tm_mon)
|
Buffer.add_string buf mons.(tm.Unix.tm_mon)
|
||||||
| '%' -> Buffer.add_char buf '%'
|
|
||||||
| c -> Buffer.add_char buf '%'; Buffer.add_char buf c);
|
| c -> Buffer.add_char buf '%'; Buffer.add_char buf c);
|
||||||
i := !i + 2
|
i := !i + 2
|
||||||
end else begin
|
end else begin
|
||||||
@@ -3609,129 +3148,43 @@ let () =
|
|||||||
incr i
|
incr i
|
||||||
end
|
end
|
||||||
done;
|
done;
|
||||||
Buffer.contents buf
|
String (Buffer.contents buf)
|
||||||
in
|
| _ -> raise (Eval_error "clock-format: (seconds [format])"));
|
||||||
register "clock-format" (fun args ->
|
|
||||||
let (t, fmt, tz) = match args with
|
|
||||||
| [Integer t] -> (t, "%a %b %e %H:%M:%S %Z %Y", "utc")
|
|
||||||
| [Integer t; String f] -> (t, f, "utc")
|
|
||||||
| [Integer t; String f; String z] -> (t, f, z)
|
|
||||||
| _ -> raise (Eval_error "clock-format: (seconds [format [tz]])")
|
|
||||||
in
|
|
||||||
let tm =
|
|
||||||
if tz = "local" then Unix.localtime (float_of_int t)
|
|
||||||
else Unix.gmtime (float_of_int t)
|
|
||||||
in
|
|
||||||
let label = if tz = "local" then "" else "UTC" in
|
|
||||||
String (format_tm tm label fmt));
|
|
||||||
|
|
||||||
(* clock-scan: parse a date string with format, return seconds.
|
(* JIT cache control & observability — backed by refs in sx_types.ml to
|
||||||
Supports the same format specifiers as clock-format (fixed-width ones).
|
avoid creating a sx_primitives → sx_vm dependency cycle. sx_vm reads
|
||||||
tz: "utc" (default) or "local". *)
|
these refs to decide when to JIT. *)
|
||||||
let timegm (tm : Unix.tm) =
|
register "jit-stats" (fun _args ->
|
||||||
let is_leap y = y mod 4 = 0 && (y mod 100 <> 0 || y mod 400 = 0) in
|
let d = Hashtbl.create 8 in
|
||||||
let days_in_month = [|31;28;31;30;31;30;31;31;30;31;30;31|] in
|
Hashtbl.replace d "threshold" (Number (float_of_int !Sx_types.jit_threshold));
|
||||||
let year = tm.Unix.tm_year + 1900 in
|
Hashtbl.replace d "budget" (Number (float_of_int !Sx_types.jit_budget));
|
||||||
let mon = tm.Unix.tm_mon in
|
Hashtbl.replace d "cache-size" (Number (float_of_int (Sx_types.jit_cache_size ())));
|
||||||
let mday = tm.Unix.tm_mday in
|
Hashtbl.replace d "compiled" (Number (float_of_int !Sx_types.jit_compiled_count));
|
||||||
let total_days = ref 0 in
|
Hashtbl.replace d "compile-failed" (Number (float_of_int !Sx_types.jit_skipped_count));
|
||||||
if year >= 1970 then begin
|
Hashtbl.replace d "below-threshold" (Number (float_of_int !Sx_types.jit_threshold_skipped_count));
|
||||||
for y = 1970 to year - 1 do
|
Hashtbl.replace d "evicted" (Number (float_of_int !Sx_types.jit_evicted_count));
|
||||||
total_days := !total_days + (if is_leap y then 366 else 365)
|
Dict d);
|
||||||
done
|
register "jit-set-threshold!" (fun args ->
|
||||||
end else begin
|
|
||||||
for y = year to 1969 do
|
|
||||||
total_days := !total_days - (if is_leap y then 366 else 365)
|
|
||||||
done
|
|
||||||
end;
|
|
||||||
for m = 0 to mon - 1 do
|
|
||||||
total_days := !total_days + days_in_month.(m);
|
|
||||||
if m = 1 && is_leap year then incr total_days
|
|
||||||
done;
|
|
||||||
total_days := !total_days + mday - 1;
|
|
||||||
!total_days * 86400
|
|
||||||
+ tm.Unix.tm_hour * 3600
|
|
||||||
+ tm.Unix.tm_min * 60
|
|
||||||
+ tm.Unix.tm_sec
|
|
||||||
in
|
|
||||||
register "clock-scan" (fun args ->
|
|
||||||
let (str, fmt, tz) = match args with
|
|
||||||
| [String s; String f] -> (s, f, "utc")
|
|
||||||
| [String s; String f; String z] -> (s, f, z)
|
|
||||||
| _ -> raise (Eval_error "clock-scan: (str fmt [tz])")
|
|
||||||
in
|
|
||||||
let n = String.length fmt and sn = String.length str in
|
|
||||||
let tm = ref { Unix.tm_year = 70; tm_mon = 0; tm_mday = 1;
|
|
||||||
tm_hour = 0; tm_min = 0; tm_sec = 0;
|
|
||||||
tm_wday = 0; tm_yday = 0; tm_isdst = false } in
|
|
||||||
let i = ref 0 and j = ref 0 in
|
|
||||||
let read_n_digits k =
|
|
||||||
let s = ref "" in
|
|
||||||
let cnt = ref 0 in
|
|
||||||
while !cnt < k && !j < sn && str.[!j] >= '0' && str.[!j] <= '9' do
|
|
||||||
s := !s ^ String.make 1 str.[!j];
|
|
||||||
incr j; incr cnt
|
|
||||||
done;
|
|
||||||
if !s = "" then 0 else int_of_string !s
|
|
||||||
in
|
|
||||||
let skip_ws () =
|
|
||||||
while !j < sn && (str.[!j] = ' ' || str.[!j] = '\t') do incr j done
|
|
||||||
in
|
|
||||||
while !i < n do
|
|
||||||
if fmt.[!i] = '%' && !i + 1 < n then begin
|
|
||||||
(match fmt.[!i + 1] with
|
|
||||||
| 'Y' -> tm := { !tm with tm_year = read_n_digits 4 - 1900 }
|
|
||||||
| 'y' -> let y = read_n_digits 2 in
|
|
||||||
tm := { !tm with tm_year = (if y < 70 then 100 + y else y) }
|
|
||||||
| 'm' -> tm := { !tm with tm_mon = read_n_digits 2 - 1 }
|
|
||||||
| 'd' | 'e' -> skip_ws (); tm := { !tm with tm_mday = read_n_digits 2 }
|
|
||||||
| 'H' | 'I' -> tm := { !tm with tm_hour = read_n_digits 2 }
|
|
||||||
| 'M' -> tm := { !tm with tm_min = read_n_digits 2 }
|
|
||||||
| 'S' -> tm := { !tm with tm_sec = read_n_digits 2 }
|
|
||||||
| '%' -> if !j < sn && str.[!j] = '%' then incr j
|
|
||||||
| _ -> () (* unsupported specifier — skip *)
|
|
||||||
);
|
|
||||||
i := !i + 2
|
|
||||||
end else begin
|
|
||||||
if fmt.[!i] = ' ' then skip_ws ()
|
|
||||||
else if !j < sn && str.[!j] = fmt.[!i] then incr j;
|
|
||||||
incr i
|
|
||||||
end
|
|
||||||
done;
|
|
||||||
let secs =
|
|
||||||
if tz = "local" then int_of_float (fst (Unix.mktime !tm))
|
|
||||||
else timegm !tm
|
|
||||||
in
|
|
||||||
Integer secs);
|
|
||||||
|
|
||||||
(* === Env-as-value (Phase 4) === *)
|
|
||||||
|
|
||||||
(* env-lookup: (env key) → value or nil. Works on Env, Dict, or Nil. *)
|
|
||||||
register "env-lookup" (fun args ->
|
|
||||||
let unwrap = function
|
|
||||||
| Env e -> e
|
|
||||||
| Nil -> make_env ()
|
|
||||||
| _ -> raise (Eval_error "env-lookup: first arg must be an environment") in
|
|
||||||
match args with
|
match args with
|
||||||
| [env_val; key] ->
|
| [Number n] -> Sx_types.jit_threshold := int_of_float n; Nil
|
||||||
let e = unwrap env_val in
|
| [Integer n] -> Sx_types.jit_threshold := n; Nil
|
||||||
let k = value_to_string key in
|
| _ -> raise (Eval_error "jit-set-threshold!: (n) where n is integer"));
|
||||||
if env_has e k then env_get e k else Nil
|
register "jit-set-budget!" (fun args ->
|
||||||
| _ -> raise (Eval_error "env-lookup: (env key)"));
|
|
||||||
|
|
||||||
(* env-extend: (env [key val ...]) → new child env with optional bindings. *)
|
|
||||||
register "env-extend" (fun args ->
|
|
||||||
match args with
|
match args with
|
||||||
| [] -> raise (Eval_error "env-extend: requires at least one arg")
|
| [Number n] -> Sx_types.jit_budget := int_of_float n; Nil
|
||||||
| env_val :: pairs ->
|
| [Integer n] -> Sx_types.jit_budget := n; Nil
|
||||||
let parent_env = match env_val with
|
| _ -> raise (Eval_error "jit-set-budget!: (n) where n is integer"));
|
||||||
| Env e -> e
|
register "jit-reset-cache!" (fun _args ->
|
||||||
| Nil -> make_env ()
|
(* Phase 3 manual cache reset — clear all compiled VmClosures.
|
||||||
| _ -> raise (Eval_error "env-extend: first arg must be an environment") in
|
Hot paths will re-JIT on next call (after re-hitting threshold). *)
|
||||||
let child = env_extend parent_env in
|
Queue.iter (fun (_, v) ->
|
||||||
let rec add_bindings = function
|
match v with Lambda l -> l.l_compiled <- None | _ -> ()
|
||||||
| [] -> ()
|
) Sx_types.jit_cache_queue;
|
||||||
| k :: v :: rest -> ignore (env_bind child (value_to_string k) v); add_bindings rest
|
Queue.clear Sx_types.jit_cache_queue;
|
||||||
| [_] -> raise (Eval_error "env-extend: odd number of key-val pairs") in
|
Nil);
|
||||||
add_bindings pairs;
|
register "jit-reset-counters!" (fun _args ->
|
||||||
Env child)
|
Sx_types.jit_compiled_count := 0;
|
||||||
|
Sx_types.jit_skipped_count := 0;
|
||||||
|
Sx_types.jit_threshold_skipped_count := 0;
|
||||||
|
Sx_types.jit_evicted_count := 0;
|
||||||
|
Nil)
|
||||||
|
|||||||
@@ -614,7 +614,7 @@ and cek_step_loop state =
|
|||||||
|
|
||||||
(* cek-run *)
|
(* cek-run *)
|
||||||
and cek_run state =
|
and cek_run state =
|
||||||
(let final = (cek_step_loop (state)) in (if sx_truthy ((cek_suspended_p (final))) then (match !_cek_io_suspend_hook with Some hook -> hook final | None -> (raise (Eval_error (value_to_str (String "IO suspension in non-IO context"))))) else (cek_value (final))))
|
(let final = (cek_step_loop (state)) in (if sx_truthy ((cek_suspended_p (final))) then (raise (Eval_error (value_to_str (String "IO suspension in non-IO context")))) else (cek_value (final))))
|
||||||
|
|
||||||
(* cek-resume *)
|
(* cek-resume *)
|
||||||
and cek_resume suspended_state result' =
|
and cek_resume suspended_state result' =
|
||||||
@@ -759,78 +759,7 @@ and match_pattern pattern value env =
|
|||||||
|
|
||||||
(* step-sf-match *)
|
(* step-sf-match *)
|
||||||
and step_sf_match args env kont =
|
and step_sf_match args env kont =
|
||||||
(let val' = (trampoline ((eval_expr ((first (args))) (env)))) in let clauses = (rest (args)) in (let () = ignore (match_check_exhaustiveness val' clauses env) in (let result' = (match_find_clause (val') (clauses) (env)) in (if sx_truthy ((is_nil (result'))) then (make_cek_value ((String (sx_str [(String "match: no clause matched "); (inspect (val'))]))) (env) ((kont_push ((make_raise_eval_frame (env) ((Bool false)))) (kont)))) else (make_cek_state ((nth (result') ((Number 1.0)))) ((first (result'))) (kont))))))
|
(let val' = (trampoline ((eval_expr ((first (args))) (env)))) in let clauses = (rest (args)) in (let result' = (match_find_clause (val') (clauses) (env)) in (if sx_truthy ((is_nil (result'))) then (make_cek_value ((String (sx_str [(String "match: no clause matched "); (inspect (val'))]))) (env) ((kont_push ((make_raise_eval_frame (env) ((Bool false)))) (kont)))) else (make_cek_state ((nth (result') ((Number 1.0)))) ((first (result'))) (kont)))))
|
||||||
|
|
||||||
(* match-check-exhaustiveness — Step 8 hand-patched into sx_ref.ml *)
|
|
||||||
and match_check_exhaustiveness val' clauses env =
|
|
||||||
let is_else_pat p =
|
|
||||||
match p with
|
|
||||||
| Symbol "_" | Symbol "else" -> true
|
|
||||||
| Keyword "else" -> true
|
|
||||||
| _ -> false
|
|
||||||
in
|
|
||||||
let clause_is_else c =
|
|
||||||
match c with
|
|
||||||
| List (p :: _) -> is_else_pat p
|
|
||||||
| _ -> false
|
|
||||||
in
|
|
||||||
let clause_ctor_name c =
|
|
||||||
match c with
|
|
||||||
| List (List (Symbol n :: _) :: _) -> Some n
|
|
||||||
| _ -> None
|
|
||||||
in
|
|
||||||
let type_name_opt = match val' with
|
|
||||||
| AdtValue a -> Some a.av_type
|
|
||||||
| Dict d ->
|
|
||||||
(match Hashtbl.find_opt d "_adt" with
|
|
||||||
| Some (Bool true) ->
|
|
||||||
(match Hashtbl.find_opt d "_type" with
|
|
||||||
| Some (String s) -> Some s
|
|
||||||
| _ -> None)
|
|
||||||
| _ -> None)
|
|
||||||
| _ -> None
|
|
||||||
in
|
|
||||||
match type_name_opt with
|
|
||||||
| None -> Nil
|
|
||||||
| Some type_name ->
|
|
||||||
if not (sx_truthy (env_has env (String "*adt-registry*"))) then Nil
|
|
||||||
else
|
|
||||||
let registry = env_get env (String "*adt-registry*") in
|
|
||||||
let registered = match registry with
|
|
||||||
| Dict r ->
|
|
||||||
(match Hashtbl.find_opt r type_name with
|
|
||||||
| Some (List ctors) -> Some ctors
|
|
||||||
| _ -> None)
|
|
||||||
| _ -> None in
|
|
||||||
(match registered with
|
|
||||||
| None -> Nil
|
|
||||||
| Some ctor_vals ->
|
|
||||||
let clauses_list = match clauses with List xs -> xs | _ -> [] in
|
|
||||||
if List.exists clause_is_else clauses_list then Nil
|
|
||||||
else
|
|
||||||
let clause_ctors = List.filter_map clause_ctor_name clauses_list in
|
|
||||||
let registered_names = List.filter_map (function
|
|
||||||
| String s -> Some s | _ -> None) ctor_vals in
|
|
||||||
let missing = List.filter (fun c -> not (List.mem c clause_ctors)) registered_names in
|
|
||||||
if missing = [] then Nil
|
|
||||||
else begin
|
|
||||||
if not (sx_truthy (env_has env (String "*adt-warned*"))) then
|
|
||||||
ignore (env_bind env (String "*adt-warned*") (Dict (Hashtbl.create 4)));
|
|
||||||
let warned = env_get env (String "*adt-warned*") in
|
|
||||||
let key = type_name ^ "|" ^ String.concat "," missing in
|
|
||||||
let already = match warned with
|
|
||||||
| Dict w -> (match Hashtbl.find_opt w key with Some (Bool true) -> true | _ -> false)
|
|
||||||
| _ -> false in
|
|
||||||
if already then Nil
|
|
||||||
else begin
|
|
||||||
(match warned with
|
|
||||||
| Dict w -> Hashtbl.replace w key (Bool true)
|
|
||||||
| _ -> ());
|
|
||||||
let msg = "[sx] match: non-exhaustive — " ^ type_name ^ ": missing " ^ String.concat ", " missing in
|
|
||||||
ignore (host_warn (String msg));
|
|
||||||
Nil
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
(* step-sf-handler-bind *)
|
(* step-sf-handler-bind *)
|
||||||
and step_sf_handler_bind args env kont =
|
and step_sf_handler_bind args env kont =
|
||||||
@@ -1052,14 +981,7 @@ let cek_run_iterative state =
|
|||||||
s := cek_step !s
|
s := cek_step !s
|
||||||
done;
|
done;
|
||||||
(match cek_suspended_p !s with
|
(match cek_suspended_p !s with
|
||||||
| Bool true ->
|
| Bool true -> raise (Eval_error "IO suspension in non-IO context")
|
||||||
(* Propagate suspension via the OCaml-side hook so it converts to
|
|
||||||
VmSuspended and flows to the outer driver (value_to_js / resume
|
|
||||||
callback). Without the hook (pure CEK harness), keep the legacy
|
|
||||||
error so test runners surface the misuse. *)
|
|
||||||
(match !_cek_io_suspend_hook with
|
|
||||||
| Some hook -> hook !s
|
|
||||||
| None -> raise (Eval_error "IO suspension in non-IO context"))
|
|
||||||
| _ -> cek_value !s)
|
| _ -> cek_value !s)
|
||||||
with Eval_error msg ->
|
with Eval_error msg ->
|
||||||
_last_error_kont_ref := cek_kont !s;
|
_last_error_kont_ref := cek_kont !s;
|
||||||
@@ -1132,7 +1054,8 @@ let sf_define_type args env_val =
|
|||||||
(match pargs with
|
(match pargs with
|
||||||
| [v] ->
|
| [v] ->
|
||||||
(match v with
|
(match v with
|
||||||
| AdtValue a -> Bool (a.av_type = type_name)
|
| 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)
|
||||||
| _ -> Bool false)));
|
| _ -> Bool false)));
|
||||||
List.iter (fun spec ->
|
List.iter (fun spec ->
|
||||||
@@ -1146,18 +1069,21 @@ let sf_define_type args env_val =
|
|||||||
if List.length ctor_args <> arity then
|
if List.length ctor_args <> arity then
|
||||||
raise (Eval_error (Printf.sprintf "%s: expected %d args, got %d"
|
raise (Eval_error (Printf.sprintf "%s: expected %d args, got %d"
|
||||||
cn arity (List.length ctor_args)))
|
cn arity (List.length ctor_args)))
|
||||||
else
|
else begin
|
||||||
AdtValue {
|
let d = Hashtbl.create 4 in
|
||||||
av_type = type_name;
|
Hashtbl.replace d "_adt" (Bool true);
|
||||||
av_ctor = cn;
|
Hashtbl.replace d "_type" (String type_name);
|
||||||
av_fields = Array.of_list ctor_args;
|
Hashtbl.replace d "_ctor" (String cn);
|
||||||
}));
|
Hashtbl.replace d "_fields" (List ctor_args);
|
||||||
|
Dict d
|
||||||
|
end));
|
||||||
env_bind_v (cn ^ "?")
|
env_bind_v (cn ^ "?")
|
||||||
(NativeFn (cn ^ "?", fun pargs ->
|
(NativeFn (cn ^ "?", fun pargs ->
|
||||||
(match pargs with
|
(match pargs with
|
||||||
| [v] ->
|
| [v] ->
|
||||||
(match v with
|
(match v with
|
||||||
| AdtValue a -> Bool (a.av_ctor = cn)
|
| Dict d -> Bool (Hashtbl.mem d "_adt" &&
|
||||||
|
(match Hashtbl.find_opt d "_ctor" with Some (String c) -> c = cn | _ -> false))
|
||||||
| _ -> Bool false)
|
| _ -> Bool false)
|
||||||
| _ -> Bool false)));
|
| _ -> Bool false)));
|
||||||
List.iteri (fun idx fname ->
|
List.iteri (fun idx fname ->
|
||||||
@@ -1166,10 +1092,13 @@ let sf_define_type args env_val =
|
|||||||
(match pargs with
|
(match pargs with
|
||||||
| [v] ->
|
| [v] ->
|
||||||
(match v with
|
(match v with
|
||||||
| AdtValue a ->
|
| Dict d ->
|
||||||
if idx < Array.length a.av_fields then a.av_fields.(idx)
|
(match Hashtbl.find_opt d "_fields" with
|
||||||
else raise (Eval_error (cn ^ "-" ^ fname ^ ": index out of bounds"))
|
| Some (List fs) ->
|
||||||
| _ -> raise (Eval_error (cn ^ "-" ^ fname ^ ": not an ADT")))
|
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")))))
|
| _ -> raise (Eval_error (cn ^ "-" ^ fname ^ ": expected 1 arg")))))
|
||||||
) field_names
|
) field_names
|
||||||
| _ -> ())
|
| _ -> ())
|
||||||
|
|||||||
@@ -6,72 +6,11 @@
|
|||||||
|
|
||||||
open Sx_types
|
open Sx_types
|
||||||
|
|
||||||
(** Fast path equality — same as Sx_primitives.safe_eq for the common cases
|
(** Call a registered primitive by name. *)
|
||||||
that show up in hot dispatch (string vs string, etc). Falls through to
|
|
||||||
the registered "=" primitive for complex cases. *)
|
|
||||||
let rec _fast_eq a b =
|
|
||||||
if a == b then true
|
|
||||||
else match a, b with
|
|
||||||
| String x, String y -> x = y
|
|
||||||
| 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
|
|
||||||
| Bool x, Bool y -> x = y
|
|
||||||
| Nil, Nil -> true
|
|
||||||
| Symbol x, Symbol y -> x = y
|
|
||||||
| Keyword x, Keyword y -> x = y
|
|
||||||
| List la, List lb ->
|
|
||||||
(try List.for_all2 _fast_eq la lb with Invalid_argument _ -> false)
|
|
||||||
| _ -> false
|
|
||||||
|
|
||||||
(** Call a registered primitive by name.
|
|
||||||
Fast path for hot dispatch primitives ([=], [<], [>], [<=], [>=], [empty?],
|
|
||||||
[first], [rest], [len]) skips the Hashtbl lookup entirely — these are
|
|
||||||
called millions of times in the CEK [step_continue]/[step_eval] dispatch. *)
|
|
||||||
let prim_call name args =
|
let prim_call name args =
|
||||||
(* Hot path: most-frequently-called primitives by step_continue dispatch *)
|
match Hashtbl.find_opt Sx_primitives.primitives name with
|
||||||
match name, args with
|
| Some f -> f args
|
||||||
| "=", [a; b] -> Bool (_fast_eq a b)
|
| None -> raise (Eval_error ("Unknown primitive: " ^ name))
|
||||||
| "empty?", [List []] -> Bool true
|
|
||||||
| "empty?", [List _] -> Bool false
|
|
||||||
| "empty?", [ListRef { contents = [] }] -> Bool true
|
|
||||||
| "empty?", [ListRef _] -> Bool false
|
|
||||||
| "empty?", [Nil] -> Bool true
|
|
||||||
| "first", [List (x :: _)] -> x
|
|
||||||
| "first", [List []] -> Nil
|
|
||||||
| "first", [ListRef { contents = (x :: _) }] -> x
|
|
||||||
| "first", [ListRef _] -> Nil
|
|
||||||
| "first", [Nil] -> Nil
|
|
||||||
| "rest", [List (_ :: xs)] -> List xs
|
|
||||||
| "rest", [List []] -> List []
|
|
||||||
| "rest", [ListRef { contents = (_ :: xs) }] -> List xs
|
|
||||||
| "rest", [ListRef _] -> List []
|
|
||||||
| "rest", [Nil] -> List []
|
|
||||||
| "len", [List l] -> Integer (List.length l)
|
|
||||||
| "len", [ListRef r] -> Integer (List.length !r)
|
|
||||||
| "len", [String s] -> Integer (String.length s)
|
|
||||||
| "len", [Nil] -> Integer 0
|
|
||||||
| "<", [Integer x; Integer y] -> Bool (x < y)
|
|
||||||
| "<", [Number x; Number y] -> Bool (x < y)
|
|
||||||
| "<", [Integer x; Number y] -> Bool (float_of_int x < y)
|
|
||||||
| "<", [Number x; Integer y] -> Bool (x < float_of_int y)
|
|
||||||
| ">", [Integer x; Integer y] -> Bool (x > y)
|
|
||||||
| ">", [Number x; Number y] -> Bool (x > y)
|
|
||||||
| ">", [Integer x; Number y] -> Bool (float_of_int x > y)
|
|
||||||
| ">", [Number x; Integer y] -> Bool (x > float_of_int y)
|
|
||||||
| "<=", [Integer x; Integer y] -> Bool (x <= y)
|
|
||||||
| "<=", [Number x; Number y] -> Bool (x <= y)
|
|
||||||
| "<=", [Integer x; Number y] -> Bool (float_of_int x <= y)
|
|
||||||
| "<=", [Number x; Integer y] -> Bool (x <= float_of_int y)
|
|
||||||
| ">=", [Integer x; Integer y] -> Bool (x >= y)
|
|
||||||
| ">=", [Number x; Number y] -> Bool (x >= y)
|
|
||||||
| ">=", [Integer x; Number y] -> Bool (float_of_int x >= y)
|
|
||||||
| ">=", [Number x; Integer y] -> Bool (x >= float_of_int y)
|
|
||||||
| _ ->
|
|
||||||
match Hashtbl.find_opt Sx_primitives.primitives name with
|
|
||||||
| Some f -> f args
|
|
||||||
| None -> raise (Eval_error ("Unknown primitive: " ^ name))
|
|
||||||
|
|
||||||
(** Convert any SX value to an OCaml string (internal). *)
|
(** Convert any SX value to an OCaml string (internal). *)
|
||||||
let value_to_str = function
|
let value_to_str = function
|
||||||
@@ -270,13 +209,6 @@ let get_val container key =
|
|||||||
| _ -> Nil)
|
| _ -> Nil)
|
||||||
| Dict d, String k -> dict_get d k
|
| Dict d, String k -> dict_get d k
|
||||||
| Dict d, Keyword k -> dict_get d k
|
| Dict d, Keyword k -> dict_get d k
|
||||||
| AdtValue a, String k | AdtValue a, Keyword k ->
|
|
||||||
(match k with
|
|
||||||
| "_adt" -> Bool true
|
|
||||||
| "_type" -> String a.av_type
|
|
||||||
| "_ctor" -> String a.av_ctor
|
|
||||||
| "_fields" -> List (Array.to_list a.av_fields)
|
|
||||||
| _ -> Nil)
|
|
||||||
| (List l | ListRef { contents = l }), Number n ->
|
| (List l | ListRef { contents = l }), Number n ->
|
||||||
(try List.nth l (int_of_float n) with _ -> Nil)
|
(try List.nth l (int_of_float n) with _ -> Nil)
|
||||||
| (List l | ListRef { contents = l }), Integer n ->
|
| (List l | ListRef { contents = l }), Integer n ->
|
||||||
@@ -472,10 +404,6 @@ let callcc_continuation_winders_len v = match v with
|
|||||||
let host_error msg =
|
let host_error msg =
|
||||||
raise (Eval_error (value_to_str msg))
|
raise (Eval_error (value_to_str msg))
|
||||||
|
|
||||||
let host_warn msg =
|
|
||||||
prerr_endline (value_to_str msg);
|
|
||||||
Nil
|
|
||||||
|
|
||||||
let dynamic_wind_call before body after _env =
|
let dynamic_wind_call before body after _env =
|
||||||
ignore (sx_call before []);
|
ignore (sx_call before []);
|
||||||
let result = sx_call body [] in
|
let result = sx_call body [] in
|
||||||
@@ -611,4 +539,3 @@ let jit_try_call f args =
|
|||||||
(match hook f arg_list with Some result -> incr _jit_hit; result | None -> incr _jit_miss; _jit_skip_sentinel)
|
(match hook f arg_list with Some result -> incr _jit_hit; result | None -> incr _jit_miss; _jit_skip_sentinel)
|
||||||
| _ -> incr _jit_skip; _jit_skip_sentinel
|
| _ -> incr _jit_skip; _jit_skip_sentinel
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -82,16 +82,6 @@ and value =
|
|||||||
| SxSet of (string, value) Hashtbl.t (** Mutable set keyed by inspect(value). *)
|
| SxSet of (string, value) Hashtbl.t (** Mutable set keyed by inspect(value). *)
|
||||||
| SxRegexp of string * string * Re.re (** Regexp: source, flags, compiled. *)
|
| SxRegexp of string * string * Re.re (** Regexp: source, flags, compiled. *)
|
||||||
| SxBytevector of bytes (** Mutable bytevector — R7RS bytevector type. *)
|
| SxBytevector of bytes (** Mutable bytevector — R7RS bytevector type. *)
|
||||||
| AdtValue of adt_value (** Native algebraic data type instance — opaque sum type. *)
|
|
||||||
|
|
||||||
(** Algebraic data type instance — produced by [define-type] constructors.
|
|
||||||
[av_type] is the type name (e.g. "Maybe"), [av_ctor] is the constructor
|
|
||||||
name (e.g. "Just"), [av_fields] are the positional field values. *)
|
|
||||||
and adt_value = {
|
|
||||||
av_type : string;
|
|
||||||
av_ctor : string;
|
|
||||||
av_fields : value array;
|
|
||||||
}
|
|
||||||
|
|
||||||
(** String input port: source string + mutable cursor position. *)
|
(** String input port: source string + mutable cursor position. *)
|
||||||
and sx_port_kind =
|
and sx_port_kind =
|
||||||
@@ -138,6 +128,8 @@ and lambda = {
|
|||||||
l_closure : env;
|
l_closure : env;
|
||||||
mutable l_name : string option;
|
mutable l_name : string option;
|
||||||
mutable l_compiled : vm_closure option; (** Lazy JIT cache *)
|
mutable l_compiled : vm_closure option; (** Lazy JIT cache *)
|
||||||
|
mutable l_call_count : int; (** Tiered-compilation counter — JIT after threshold calls *)
|
||||||
|
l_uid : int; (** Unique identity for LRU cache tracking *)
|
||||||
}
|
}
|
||||||
|
|
||||||
and component = {
|
and component = {
|
||||||
@@ -444,12 +436,60 @@ let unwrap_env_val = function
|
|||||||
| Env e -> e
|
| Env e -> e
|
||||||
| _ -> raise (Eval_error "make_lambda: expected env for closure")
|
| _ -> raise (Eval_error "make_lambda: expected env for closure")
|
||||||
|
|
||||||
|
(* Lambda UID — minted on construction, used as LRU cache key (Phase 2). *)
|
||||||
|
let lambda_uid_counter = ref 0
|
||||||
|
let next_lambda_uid () = incr lambda_uid_counter; !lambda_uid_counter
|
||||||
|
|
||||||
let make_lambda params body closure =
|
let make_lambda params body closure =
|
||||||
let ps = match params with
|
let ps = match params with
|
||||||
| List items -> List.map value_to_string items
|
| List items -> List.map value_to_string items
|
||||||
| _ -> value_to_string_list params
|
| _ -> value_to_string_list params
|
||||||
in
|
in
|
||||||
Lambda { l_params = ps; l_body = body; l_closure = unwrap_env_val closure; l_name = None; l_compiled = None }
|
Lambda { l_params = ps; l_body = body; l_closure = unwrap_env_val closure; l_name = None; l_compiled = None; l_call_count = 0; l_uid = next_lambda_uid () }
|
||||||
|
|
||||||
|
(** {1 JIT cache control}
|
||||||
|
|
||||||
|
Tiered compilation: only JIT a lambda after it's been called [jit_threshold]
|
||||||
|
times. This filters out one-shot lambdas (test harness, dynamic eval, REPLs)
|
||||||
|
so they never enter the JIT cache. Counters are exposed to SX as [(jit-stats)].
|
||||||
|
|
||||||
|
These live here (in sx_types) rather than sx_vm so [sx_primitives] can read
|
||||||
|
them without creating a sx_primitives → sx_vm dependency cycle. *)
|
||||||
|
let jit_threshold = ref 4
|
||||||
|
let jit_compiled_count = ref 0
|
||||||
|
let jit_skipped_count = ref 0
|
||||||
|
let jit_threshold_skipped_count = ref 0
|
||||||
|
|
||||||
|
(** {2 JIT cache LRU eviction — Phase 2}
|
||||||
|
|
||||||
|
Once a lambda crosses the threshold, its [l_compiled] slot is filled.
|
||||||
|
To bound memory under unbounded compilation pressure, track all live
|
||||||
|
compiled lambdas in FIFO order, and evict from the head when the count
|
||||||
|
exceeds [jit_budget].
|
||||||
|
|
||||||
|
[lambda_uid_counter] mints unique identities on lambda creation; the
|
||||||
|
LRU queue holds these IDs paired with a back-reference to the lambda
|
||||||
|
so we can clear its [l_compiled] slot on eviction.
|
||||||
|
|
||||||
|
Budget of 0 = no cache (disable JIT entirely).
|
||||||
|
Budget of [max_int] = unbounded (legacy behaviour). Default 5000 is
|
||||||
|
a generous ceiling for any realistic page; the test harness compiles
|
||||||
|
~3000 distinct one-shot lambdas in a full run but tiered compilation
|
||||||
|
(Phase 1) means most never enter the cache, so steady-state count
|
||||||
|
stays small.
|
||||||
|
|
||||||
|
[lambda_uid_counter] and [next_lambda_uid] are defined above
|
||||||
|
[make_lambda] (which uses them on construction). *)
|
||||||
|
let jit_budget = ref 5000
|
||||||
|
let jit_evicted_count = ref 0
|
||||||
|
|
||||||
|
(** Live compiled lambdas in FIFO order — front is oldest, back is newest.
|
||||||
|
Each entry is (uid, lambda); on eviction we clear lambda.l_compiled and
|
||||||
|
drop from the queue. Using a mutable Queue rather than a hand-rolled
|
||||||
|
linked list because eviction is amortised O(1) at the head and inserts
|
||||||
|
are O(1) at the tail. *)
|
||||||
|
let jit_cache_queue : (int * value) Queue.t = Queue.create ()
|
||||||
|
let jit_cache_size () = Queue.length jit_cache_queue
|
||||||
|
|
||||||
let make_component name params has_children body closure affinity =
|
let make_component name params has_children body closure affinity =
|
||||||
let n = value_to_string name in
|
let n = value_to_string name in
|
||||||
@@ -530,7 +570,6 @@ let type_of = function
|
|||||||
| SxSet _ -> "set"
|
| SxSet _ -> "set"
|
||||||
| SxRegexp _ -> "regexp"
|
| SxRegexp _ -> "regexp"
|
||||||
| SxBytevector _ -> "bytevector"
|
| SxBytevector _ -> "bytevector"
|
||||||
| AdtValue a -> a.av_type
|
|
||||||
|
|
||||||
let is_nil = function Nil -> true | _ -> false
|
let is_nil = function Nil -> true | _ -> false
|
||||||
let is_lambda = function Lambda _ -> true | _ -> false
|
let is_lambda = function Lambda _ -> true | _ -> false
|
||||||
@@ -817,15 +856,14 @@ let dict_vals (d : dict) =
|
|||||||
|
|
||||||
(** {1 Value display} *)
|
(** {1 Value display} *)
|
||||||
|
|
||||||
(* Single shared buffer for the entire inspect recursion — eliminates
|
let rec inspect = function
|
||||||
the per-level [String.concat (List.map inspect ...)] allocation. *)
|
| Nil -> "nil"
|
||||||
let rec inspect_into buf = function
|
| Bool true -> "true"
|
||||||
| Nil -> Buffer.add_string buf "nil"
|
| Bool false -> "false"
|
||||||
| Bool true -> Buffer.add_string buf "true"
|
| Integer n -> string_of_int n
|
||||||
| Bool false -> Buffer.add_string buf "false"
|
| Number n -> format_number n
|
||||||
| Integer n -> Buffer.add_string buf (string_of_int n)
|
|
||||||
| Number n -> Buffer.add_string buf (format_number n)
|
|
||||||
| String s ->
|
| String s ->
|
||||||
|
let buf = Buffer.create (String.length s + 2) in
|
||||||
Buffer.add_char buf '"';
|
Buffer.add_char buf '"';
|
||||||
String.iter (function
|
String.iter (function
|
||||||
| '"' -> Buffer.add_string buf "\\\""
|
| '"' -> Buffer.add_string buf "\\\""
|
||||||
@@ -834,129 +872,66 @@ let rec inspect_into buf = function
|
|||||||
| '\r' -> Buffer.add_string buf "\\r"
|
| '\r' -> Buffer.add_string buf "\\r"
|
||||||
| '\t' -> Buffer.add_string buf "\\t"
|
| '\t' -> Buffer.add_string buf "\\t"
|
||||||
| c -> Buffer.add_char buf c) s;
|
| c -> Buffer.add_char buf c) s;
|
||||||
Buffer.add_char buf '"'
|
Buffer.add_char buf '"';
|
||||||
| Symbol s -> Buffer.add_string buf s
|
Buffer.contents buf
|
||||||
| Keyword k -> Buffer.add_char buf ':'; Buffer.add_string buf k
|
| Symbol s -> s
|
||||||
|
| Keyword k -> ":" ^ k
|
||||||
| List items | ListRef { contents = items } ->
|
| List items | ListRef { contents = items } ->
|
||||||
Buffer.add_char buf '(';
|
"(" ^ String.concat " " (List.map inspect items) ^ ")"
|
||||||
(match items with
|
|
||||||
| [] -> ()
|
|
||||||
| x :: rest ->
|
|
||||||
inspect_into buf x;
|
|
||||||
List.iter (fun v -> Buffer.add_char buf ' '; inspect_into buf v) rest);
|
|
||||||
Buffer.add_char buf ')'
|
|
||||||
| Dict d ->
|
| Dict d ->
|
||||||
Buffer.add_char buf '{';
|
let pairs = Hashtbl.fold (fun k v acc ->
|
||||||
let first = ref true in
|
(Printf.sprintf ":%s %s" k (inspect v)) :: acc) d [] in
|
||||||
Hashtbl.iter (fun k v ->
|
"{" ^ String.concat " " pairs ^ "}"
|
||||||
if !first then first := false else Buffer.add_char buf ' ';
|
|
||||||
Buffer.add_char buf ':'; Buffer.add_string buf k;
|
|
||||||
Buffer.add_char buf ' '; inspect_into buf v) d;
|
|
||||||
Buffer.add_char buf '}'
|
|
||||||
| Lambda l ->
|
| Lambda l ->
|
||||||
let tag = match l.l_name with Some n -> n | None -> "lambda" in
|
let tag = match l.l_name with Some n -> n | None -> "lambda" in
|
||||||
Buffer.add_char buf '<'; Buffer.add_string buf tag;
|
Printf.sprintf "<%s(%s)>" tag (String.concat ", " l.l_params)
|
||||||
Buffer.add_char buf '('; Buffer.add_string buf (String.concat ", " l.l_params);
|
|
||||||
Buffer.add_string buf ")>"
|
|
||||||
| Component c ->
|
| Component c ->
|
||||||
Buffer.add_string buf "<Component ~"; Buffer.add_string buf c.c_name;
|
Printf.sprintf "<Component ~%s(%s)>" c.c_name (String.concat ", " c.c_params)
|
||||||
Buffer.add_char buf '('; Buffer.add_string buf (String.concat ", " c.c_params);
|
|
||||||
Buffer.add_string buf ")>"
|
|
||||||
| Island i ->
|
| Island i ->
|
||||||
Buffer.add_string buf "<Island ~"; Buffer.add_string buf i.i_name;
|
Printf.sprintf "<Island ~%s(%s)>" i.i_name (String.concat ", " i.i_params)
|
||||||
Buffer.add_char buf '('; Buffer.add_string buf (String.concat ", " i.i_params);
|
|
||||||
Buffer.add_string buf ")>"
|
|
||||||
| Macro m ->
|
| Macro m ->
|
||||||
let tag = match m.m_name with Some n -> n | None -> "macro" in
|
let tag = match m.m_name with Some n -> n | None -> "macro" in
|
||||||
Buffer.add_char buf '<'; Buffer.add_string buf tag;
|
Printf.sprintf "<%s(%s)>" tag (String.concat ", " m.m_params)
|
||||||
Buffer.add_char buf '('; Buffer.add_string buf (String.concat ", " m.m_params);
|
| Thunk _ -> "<thunk>"
|
||||||
Buffer.add_string buf ")>"
|
| Continuation (_, _) -> "<continuation>"
|
||||||
| Thunk _ -> Buffer.add_string buf "<thunk>"
|
| CallccContinuation (_, _) -> "<callcc-continuation>"
|
||||||
| Continuation (_, _) -> Buffer.add_string buf "<continuation>"
|
| NativeFn (name, _) -> Printf.sprintf "<native:%s>" name
|
||||||
| CallccContinuation (_, _) -> Buffer.add_string buf "<callcc-continuation>"
|
| Signal _ -> "<signal>"
|
||||||
| NativeFn (name, _) ->
|
| RawHTML s -> Printf.sprintf "\"<raw-html:%d>\"" (String.length s)
|
||||||
Buffer.add_string buf "<native:"; Buffer.add_string buf name; Buffer.add_char buf '>'
|
| Spread _ -> "<spread>"
|
||||||
| Signal _ -> Buffer.add_string buf "<signal>"
|
| SxExpr s -> Printf.sprintf "\"<sx-expr:%d>\"" (String.length s)
|
||||||
| RawHTML s ->
|
| Env _ -> "<env>"
|
||||||
Buffer.add_string buf "\"<raw-html:";
|
| CekState _ -> "<cek-state>"
|
||||||
Buffer.add_string buf (string_of_int (String.length s));
|
| CekFrame f -> Printf.sprintf "<frame:%s>" f.cf_type
|
||||||
Buffer.add_string buf ">\""
|
| VmClosure cl -> Printf.sprintf "<vm:%s>" (match cl.vm_name with Some n -> n | None -> "anon")
|
||||||
| Spread _ -> Buffer.add_string buf "<spread>"
|
|
||||||
| SxExpr s ->
|
|
||||||
Buffer.add_string buf "\"<sx-expr:";
|
|
||||||
Buffer.add_string buf (string_of_int (String.length s));
|
|
||||||
Buffer.add_string buf ">\""
|
|
||||||
| Env _ -> Buffer.add_string buf "<env>"
|
|
||||||
| CekState _ -> Buffer.add_string buf "<cek-state>"
|
|
||||||
| CekFrame f ->
|
|
||||||
Buffer.add_string buf "<frame:"; Buffer.add_string buf f.cf_type; Buffer.add_char buf '>'
|
|
||||||
| VmClosure cl ->
|
|
||||||
Buffer.add_string buf "<vm:";
|
|
||||||
Buffer.add_string buf (match cl.vm_name with Some n -> n | None -> "anon");
|
|
||||||
Buffer.add_char buf '>'
|
|
||||||
| Record r ->
|
| Record r ->
|
||||||
Buffer.add_string buf "<record:"; Buffer.add_string buf r.r_type.rt_name;
|
let fields = Array.to_list (Array.mapi (fun i v ->
|
||||||
Array.iteri (fun i v ->
|
Printf.sprintf "%s=%s" r.r_type.rt_fields.(i) (inspect v)
|
||||||
Buffer.add_char buf ' ';
|
) r.r_fields) in
|
||||||
Buffer.add_string buf r.r_type.rt_fields.(i);
|
Printf.sprintf "<record:%s %s>" r.r_type.rt_name (String.concat " " fields)
|
||||||
Buffer.add_char buf '=';
|
| Parameter p -> Printf.sprintf "<parameter:%s>" p.pm_uid
|
||||||
inspect_into buf v) r.r_fields;
|
|
||||||
Buffer.add_char buf '>'
|
|
||||||
| Parameter p ->
|
|
||||||
Buffer.add_string buf "<parameter:"; Buffer.add_string buf p.pm_uid; Buffer.add_char buf '>'
|
|
||||||
| Vector arr ->
|
| Vector arr ->
|
||||||
Buffer.add_string buf "#(";
|
let elts = Array.to_list (Array.map inspect arr) in
|
||||||
Array.iteri (fun i v ->
|
Printf.sprintf "#(%s)" (String.concat " " elts)
|
||||||
if i > 0 then Buffer.add_char buf ' ';
|
| VmFrame f -> Printf.sprintf "<vm-frame:ip=%d base=%d>" f.vf_ip f.vf_base
|
||||||
inspect_into buf v) arr;
|
| VmMachine m -> Printf.sprintf "<vm-machine:sp=%d frames=%d>" m.vm_sp (List.length m.vm_frames)
|
||||||
Buffer.add_char buf ')'
|
| StringBuffer buf -> Printf.sprintf "<string-buffer:%d>" (Buffer.length buf)
|
||||||
| VmFrame f ->
|
| HashTable ht -> Printf.sprintf "<hash-table:%d>" (Hashtbl.length ht)
|
||||||
Buffer.add_string buf (Printf.sprintf "<vm-frame:ip=%d base=%d>" f.vf_ip f.vf_base)
|
|
||||||
| VmMachine m ->
|
|
||||||
Buffer.add_string buf (Printf.sprintf "<vm-machine:sp=%d frames=%d>" m.vm_sp (List.length m.vm_frames))
|
|
||||||
| StringBuffer b ->
|
|
||||||
Buffer.add_string buf (Printf.sprintf "<string-buffer:%d>" (Buffer.length b))
|
|
||||||
| HashTable ht ->
|
|
||||||
Buffer.add_string buf (Printf.sprintf "<hash-table:%d>" (Hashtbl.length ht))
|
|
||||||
| Char n ->
|
| Char n ->
|
||||||
Buffer.add_string buf "#\\";
|
let name = match n with
|
||||||
(match n with
|
| 32 -> "space" | 10 -> "newline" | 9 -> "tab"
|
||||||
| 32 -> Buffer.add_string buf "space"
|
| 13 -> "return" | 0 -> "nul" | 27 -> "escape"
|
||||||
| 10 -> Buffer.add_string buf "newline"
|
| 127 -> "delete" | 8 -> "backspace"
|
||||||
| 9 -> Buffer.add_string buf "tab"
|
| _ -> let buf = Buffer.create 1 in
|
||||||
| 13 -> Buffer.add_string buf "return"
|
Buffer.add_utf_8_uchar buf (Uchar.of_int n);
|
||||||
| 0 -> Buffer.add_string buf "nul"
|
Buffer.contents buf
|
||||||
| 27 -> Buffer.add_string buf "escape"
|
in "#\\" ^ name
|
||||||
| 127 -> Buffer.add_string buf "delete"
|
| Eof -> "#!eof"
|
||||||
| 8 -> Buffer.add_string buf "backspace"
|
|
||||||
| _ -> Buffer.add_utf_8_uchar buf (Uchar.of_int n))
|
|
||||||
| Eof -> Buffer.add_string buf "#!eof"
|
|
||||||
| Port { sp_kind = PortInput (_, pos); sp_closed } ->
|
| Port { sp_kind = PortInput (_, pos); sp_closed } ->
|
||||||
Buffer.add_string buf (Printf.sprintf "<input-port:pos=%d%s>" !pos (if sp_closed then ":closed" else ""))
|
Printf.sprintf "<input-port:pos=%d%s>" !pos (if sp_closed then ":closed" else "")
|
||||||
| Port { sp_kind = PortOutput b; sp_closed } ->
|
| Port { sp_kind = PortOutput buf; sp_closed } ->
|
||||||
Buffer.add_string buf (Printf.sprintf "<output-port:len=%d%s>" (Buffer.length b) (if sp_closed then ":closed" else ""))
|
Printf.sprintf "<output-port:len=%d%s>" (Buffer.length buf) (if sp_closed then ":closed" else "")
|
||||||
| Rational (n, d) ->
|
| Rational (n, d) -> Printf.sprintf "%d/%d" n d
|
||||||
Buffer.add_string buf (string_of_int n); Buffer.add_char buf '/';
|
| SxSet ht -> Printf.sprintf "<set:%d>" (Hashtbl.length ht)
|
||||||
Buffer.add_string buf (string_of_int d)
|
| SxRegexp (src, flags, _) -> Printf.sprintf "#/%s/%s" src flags
|
||||||
| SxSet ht ->
|
| SxBytevector b -> Printf.sprintf "#u8(%s)" (String.concat " " (List.init (Bytes.length b) (fun i -> string_of_int (Char.code (Bytes.get b i)))))
|
||||||
Buffer.add_string buf (Printf.sprintf "<set:%d>" (Hashtbl.length ht))
|
|
||||||
| SxRegexp (src, flags, _) ->
|
|
||||||
Buffer.add_string buf "#/"; Buffer.add_string buf src;
|
|
||||||
Buffer.add_char buf '/'; Buffer.add_string buf flags
|
|
||||||
| SxBytevector b ->
|
|
||||||
Buffer.add_string buf "#u8(";
|
|
||||||
let n = Bytes.length b in
|
|
||||||
for i = 0 to n - 1 do
|
|
||||||
if i > 0 then Buffer.add_char buf ' ';
|
|
||||||
Buffer.add_string buf (string_of_int (Char.code (Bytes.get b i)))
|
|
||||||
done;
|
|
||||||
Buffer.add_char buf ')'
|
|
||||||
| AdtValue a ->
|
|
||||||
Buffer.add_char buf '('; Buffer.add_string buf a.av_ctor;
|
|
||||||
Array.iter (fun v -> Buffer.add_char buf ' '; inspect_into buf v) a.av_fields;
|
|
||||||
Buffer.add_char buf ')'
|
|
||||||
|
|
||||||
let inspect v =
|
|
||||||
let buf = Buffer.create 64 in
|
|
||||||
inspect_into buf v;
|
|
||||||
Buffer.contents buf
|
|
||||||
|
|||||||
@@ -57,6 +57,9 @@ let () = Sx_types._convert_vm_suspension := (fun exn ->
|
|||||||
let jit_compile_ref : (lambda -> (string, value) Hashtbl.t -> vm_closure option) ref =
|
let jit_compile_ref : (lambda -> (string, value) Hashtbl.t -> vm_closure option) ref =
|
||||||
ref (fun _ _ -> None)
|
ref (fun _ _ -> None)
|
||||||
|
|
||||||
|
(* JIT threshold and counters live in Sx_types so primitives can read them
|
||||||
|
without creating a sx_primitives → sx_vm dependency cycle. *)
|
||||||
|
|
||||||
(** Sentinel closure indicating JIT compilation was attempted and failed.
|
(** Sentinel closure indicating JIT compilation was attempted and failed.
|
||||||
Prevents retrying compilation on every call. *)
|
Prevents retrying compilation on every call. *)
|
||||||
let jit_failed_sentinel = {
|
let jit_failed_sentinel = {
|
||||||
@@ -327,18 +330,7 @@ and call_closure_reuse cl args =
|
|||||||
vm.sp <- saved_sp;
|
vm.sp <- saved_sp;
|
||||||
raise e);
|
raise e);
|
||||||
vm.frames <- saved_frames;
|
vm.frames <- saved_frames;
|
||||||
(* Snapshot/restore sp around the popped result.
|
pop vm
|
||||||
OP_RETURN normally leaves sp = saved_sp + 1, but the bytecode-exhausted
|
|
||||||
path (or a callee that returns a closure whose own RETURN leaves extra
|
|
||||||
stack residue) can leave sp inconsistent. Read the result at the
|
|
||||||
expected slot and reset sp explicitly so the parent frame's
|
|
||||||
intermediate values are not corrupted. *)
|
|
||||||
let result =
|
|
||||||
if vm.sp > saved_sp then vm.stack.(vm.sp - 1)
|
|
||||||
else Nil
|
|
||||||
in
|
|
||||||
vm.sp <- saved_sp;
|
|
||||||
result
|
|
||||||
| None ->
|
| None ->
|
||||||
call_closure cl args cl.vm_env_ref
|
call_closure cl args cl.vm_env_ref
|
||||||
|
|
||||||
@@ -364,13 +356,29 @@ and vm_call vm f args =
|
|||||||
| None ->
|
| None ->
|
||||||
if l.l_name <> None
|
if l.l_name <> None
|
||||||
then begin
|
then begin
|
||||||
l.l_compiled <- Some jit_failed_sentinel;
|
l.l_call_count <- l.l_call_count + 1;
|
||||||
match !jit_compile_ref l vm.globals with
|
if l.l_call_count >= !Sx_types.jit_threshold && !Sx_types.jit_budget > 0 then begin
|
||||||
| Some cl ->
|
l.l_compiled <- Some jit_failed_sentinel;
|
||||||
l.l_compiled <- Some cl;
|
match !jit_compile_ref l vm.globals with
|
||||||
push_closure_frame vm cl args
|
| Some cl ->
|
||||||
| None ->
|
incr Sx_types.jit_compiled_count;
|
||||||
|
l.l_compiled <- Some cl;
|
||||||
|
(* Phase 2 LRU: track this compiled lambda; if cache exceeds budget,
|
||||||
|
evict the oldest by clearing its l_compiled slot. *)
|
||||||
|
Queue.add (l.l_uid, Lambda l) Sx_types.jit_cache_queue;
|
||||||
|
while Queue.length Sx_types.jit_cache_queue > !Sx_types.jit_budget do
|
||||||
|
(match Queue.pop Sx_types.jit_cache_queue with
|
||||||
|
| (_, Lambda ev_l) -> ev_l.l_compiled <- None; incr Sx_types.jit_evicted_count
|
||||||
|
| _ -> ())
|
||||||
|
done;
|
||||||
|
push_closure_frame vm cl args
|
||||||
|
| None ->
|
||||||
|
incr Sx_types.jit_skipped_count;
|
||||||
|
push vm (cek_call_or_suspend vm f (List args))
|
||||||
|
end else begin
|
||||||
|
incr Sx_types.jit_threshold_skipped_count;
|
||||||
push vm (cek_call_or_suspend vm f (List args))
|
push vm (cek_call_or_suspend vm f (List args))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
push vm (cek_call_or_suspend vm f (List args)))
|
push vm (cek_call_or_suspend vm f (List args)))
|
||||||
@@ -642,9 +650,7 @@ and run vm =
|
|||||||
(* Read upvalue descriptors from bytecode *)
|
(* Read upvalue descriptors from bytecode *)
|
||||||
let uv_count = match code_val with
|
let uv_count = match code_val with
|
||||||
| Dict d -> (match Hashtbl.find_opt d "upvalue-count" with
|
| Dict d -> (match Hashtbl.find_opt d "upvalue-count" with
|
||||||
| Some (Integer n) -> n
|
| Some (Number n) -> int_of_float n | _ -> 0)
|
||||||
| Some (Number n) -> int_of_float n
|
|
||||||
| _ -> 0)
|
|
||||||
| _ -> 0
|
| _ -> 0
|
||||||
in
|
in
|
||||||
let upvalues = Array.init uv_count (fun _ ->
|
let upvalues = Array.init uv_count (fun _ ->
|
||||||
@@ -744,57 +750,38 @@ and run vm =
|
|||||||
| 160 (* OP_ADD *) ->
|
| 160 (* OP_ADD *) ->
|
||||||
let b = pop vm and a = pop vm in
|
let b = pop vm and a = pop vm in
|
||||||
push vm (match a, b with
|
push vm (match a, b with
|
||||||
| Integer x, Integer y -> Integer (x + y)
|
|
||||||
| Number x, Number y -> Number (x +. y)
|
| Number x, Number y -> Number (x +. y)
|
||||||
| Integer x, Number y -> Number (float_of_int x +. y)
|
|
||||||
| Number x, Integer y -> Number (x +. float_of_int y)
|
|
||||||
| _ -> (Hashtbl.find Sx_primitives.primitives "+") [a; b])
|
| _ -> (Hashtbl.find Sx_primitives.primitives "+") [a; b])
|
||||||
| 161 (* OP_SUB *) ->
|
| 161 (* OP_SUB *) ->
|
||||||
let b = pop vm and a = pop vm in
|
let b = pop vm and a = pop vm in
|
||||||
push vm (match a, b with
|
push vm (match a, b with
|
||||||
| Integer x, Integer y -> Integer (x - y)
|
|
||||||
| Number x, Number y -> Number (x -. y)
|
| Number x, Number y -> Number (x -. y)
|
||||||
| Integer x, Number y -> Number (float_of_int x -. y)
|
|
||||||
| Number x, Integer y -> Number (x -. float_of_int y)
|
|
||||||
| _ -> (Hashtbl.find Sx_primitives.primitives "-") [a; b])
|
| _ -> (Hashtbl.find Sx_primitives.primitives "-") [a; b])
|
||||||
| 162 (* OP_MUL *) ->
|
| 162 (* OP_MUL *) ->
|
||||||
let b = pop vm and a = pop vm in
|
let b = pop vm and a = pop vm in
|
||||||
push vm (match a, b with
|
push vm (match a, b with
|
||||||
| Integer x, Integer y -> Integer (x * y)
|
|
||||||
| Number x, Number y -> Number (x *. y)
|
| Number x, Number y -> Number (x *. y)
|
||||||
| Integer x, Number y -> Number (float_of_int x *. y)
|
|
||||||
| Number x, Integer y -> Number (x *. float_of_int y)
|
|
||||||
| _ -> (Hashtbl.find Sx_primitives.primitives "*") [a; b])
|
| _ -> (Hashtbl.find Sx_primitives.primitives "*") [a; b])
|
||||||
| 163 (* OP_DIV *) ->
|
| 163 (* OP_DIV *) ->
|
||||||
let b = pop vm and a = pop vm in
|
let b = pop vm and a = pop vm in
|
||||||
push vm (match a, b with
|
push vm (match a, b with
|
||||||
| Integer x, Integer y when y <> 0 && x mod y = 0 -> Integer (x / y)
|
|
||||||
| Integer x, Integer y -> Number (float_of_int x /. float_of_int y)
|
|
||||||
| Number x, Number y -> Number (x /. y)
|
| Number x, Number y -> Number (x /. y)
|
||||||
| Integer x, Number y -> Number (float_of_int x /. y)
|
|
||||||
| Number x, Integer y -> Number (x /. float_of_int y)
|
|
||||||
| _ -> (Hashtbl.find Sx_primitives.primitives "/") [a; b])
|
| _ -> (Hashtbl.find Sx_primitives.primitives "/") [a; b])
|
||||||
| 164 (* OP_EQ *) ->
|
| 164 (* OP_EQ *) ->
|
||||||
let b = pop vm and a = pop vm in
|
let b = pop vm and a = pop vm in
|
||||||
push vm (Bool (Sx_runtime._fast_eq a b))
|
push vm ((Hashtbl.find Sx_primitives.primitives "=") [a; b])
|
||||||
| 165 (* OP_LT *) ->
|
| 165 (* OP_LT *) ->
|
||||||
let b = pop vm and a = pop vm in
|
let b = pop vm and a = pop vm in
|
||||||
push vm (match a, b with
|
push vm (match a, b with
|
||||||
| Integer x, Integer y -> Bool (x < y)
|
|
||||||
| Number x, Number y -> Bool (x < y)
|
| Number x, Number y -> Bool (x < y)
|
||||||
| Integer x, Number y -> Bool (float_of_int x < y)
|
|
||||||
| Number x, Integer y -> Bool (x < float_of_int y)
|
|
||||||
| String x, String y -> Bool (x < y)
|
| String x, String y -> Bool (x < y)
|
||||||
| _ -> Sx_runtime.prim_call "<" [a; b])
|
| _ -> (Hashtbl.find Sx_primitives.primitives "<") [a; b])
|
||||||
| 166 (* OP_GT *) ->
|
| 166 (* OP_GT *) ->
|
||||||
let b = pop vm and a = pop vm in
|
let b = pop vm and a = pop vm in
|
||||||
push vm (match a, b with
|
push vm (match a, b with
|
||||||
| Integer x, Integer y -> Bool (x > y)
|
|
||||||
| Number x, Number y -> Bool (x > y)
|
| Number x, Number y -> Bool (x > y)
|
||||||
| Integer x, Number y -> Bool (float_of_int x > y)
|
|
||||||
| Number x, Integer y -> Bool (x > float_of_int y)
|
|
||||||
| String x, String y -> Bool (x > y)
|
| String x, String y -> Bool (x > y)
|
||||||
| _ -> Sx_runtime.prim_call ">" [a; b])
|
| _ -> (Hashtbl.find Sx_primitives.primitives ">") [a; b])
|
||||||
| 167 (* OP_NOT *) ->
|
| 167 (* OP_NOT *) ->
|
||||||
let v = pop vm in
|
let v = pop vm in
|
||||||
push vm (Bool (not (sx_truthy v)))
|
push vm (Bool (not (sx_truthy v)))
|
||||||
@@ -917,17 +904,9 @@ let resume_vm vm result =
|
|||||||
let rec restore_reuse pending =
|
let rec restore_reuse pending =
|
||||||
match pending with
|
match pending with
|
||||||
| [] -> ()
|
| [] -> ()
|
||||||
| (saved_frames, saved_sp) :: rest ->
|
| (saved_frames, _saved_sp) :: rest ->
|
||||||
let callback_result = pop vm in
|
let callback_result = pop vm in
|
||||||
vm.frames <- saved_frames;
|
vm.frames <- saved_frames;
|
||||||
(* Restore sp to the value captured before the suspended callee was
|
|
||||||
pushed. The callee's locals/temps may still be on the stack above
|
|
||||||
saved_sp; without this reset, subsequent LOCAL_GET/SET in the
|
|
||||||
caller frame (e.g. letrec sibling bindings waiting on the call)
|
|
||||||
see stale callee data instead of their own slots. Mirrors the
|
|
||||||
OP_RETURN+sp-reset semantics that sync `call_closure_reuse`
|
|
||||||
relies on for clean caller-frame state. *)
|
|
||||||
if saved_sp < vm.sp then vm.sp <- saved_sp;
|
|
||||||
push vm callback_result;
|
push vm callback_result;
|
||||||
(try
|
(try
|
||||||
run vm;
|
run vm;
|
||||||
@@ -1309,9 +1288,7 @@ let trace_run src globals =
|
|||||||
let code_val2 = frame.closure.vm_code.vc_constants.(idx) in
|
let code_val2 = frame.closure.vm_code.vc_constants.(idx) in
|
||||||
let uv_count = match code_val2 with
|
let uv_count = match code_val2 with
|
||||||
| Dict d -> (match Hashtbl.find_opt d "upvalue-count" with
|
| Dict d -> (match Hashtbl.find_opt d "upvalue-count" with
|
||||||
| Some (Integer n) -> n
|
| Some (Number n) -> int_of_float n | _ -> 0)
|
||||||
| Some (Number n) -> int_of_float n
|
|
||||||
| _ -> 0)
|
|
||||||
| _ -> 0 in
|
| _ -> 0 in
|
||||||
let upvalues = Array.init uv_count (fun _ ->
|
let upvalues = Array.init uv_count (fun _ ->
|
||||||
let is_local = read_u8 frame in
|
let is_local = read_u8 frame in
|
||||||
@@ -1432,9 +1409,7 @@ let disassemble (code : vm_code) =
|
|||||||
if op = 51 && idx < Array.length consts then begin
|
if op = 51 && idx < Array.length consts then begin
|
||||||
let uv_count = match consts.(idx) with
|
let uv_count = match consts.(idx) with
|
||||||
| Dict d -> (match Hashtbl.find_opt d "upvalue-count" with
|
| Dict d -> (match Hashtbl.find_opt d "upvalue-count" with
|
||||||
| Some (Integer n) -> n
|
| Some (Number n) -> int_of_float n | _ -> 0)
|
||||||
| Some (Number n) -> int_of_float n
|
|
||||||
| _ -> 0)
|
|
||||||
| _ -> 0 in
|
| _ -> 0 in
|
||||||
ip := !ip + uv_count * 2
|
ip := !ip + uv_count * 2
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -270,9 +270,7 @@ let vm_create_closure vm_val frame_val code_val =
|
|||||||
let f = unwrap_frame frame_val in
|
let f = unwrap_frame frame_val in
|
||||||
let uv_count = match code_val with
|
let uv_count = match code_val with
|
||||||
| Dict d -> (match Hashtbl.find_opt d "upvalue-count" with
|
| Dict d -> (match Hashtbl.find_opt d "upvalue-count" with
|
||||||
| Some (Integer n) -> n
|
| Some (Number n) -> int_of_float n | _ -> 0)
|
||||||
| Some (Number n) -> int_of_float n
|
|
||||||
| _ -> 0)
|
|
||||||
| _ -> 0
|
| _ -> 0
|
||||||
in
|
in
|
||||||
let upvalues = Array.init uv_count (fun _ ->
|
let upvalues = Array.init uv_count (fun _ ->
|
||||||
|
|||||||
@@ -265,9 +265,7 @@ let vm_create_closure vm_val frame_val code_val =
|
|||||||
let f = unwrap_frame frame_val in
|
let f = unwrap_frame frame_val in
|
||||||
let uv_count = match code_val with
|
let uv_count = match code_val with
|
||||||
| Dict d -> (match Hashtbl.find_opt d "upvalue-count" with
|
| Dict d -> (match Hashtbl.find_opt d "upvalue-count" with
|
||||||
| Some (Integer n) -> n
|
| Some (Number n) -> int_of_float n | _ -> 0)
|
||||||
| Some (Number n) -> int_of_float n
|
|
||||||
| _ -> 0)
|
|
||||||
| _ -> 0
|
| _ -> 0
|
||||||
in
|
in
|
||||||
let upvalues = Array.init uv_count (fun _ ->
|
let upvalues = Array.init uv_count (fun _ ->
|
||||||
|
|||||||
@@ -1,116 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# lib/apl/conformance.sh — run APL test suites, emit scoreboard.json + scoreboard.md.
|
|
||||||
|
|
||||||
set -uo pipefail
|
|
||||||
cd "$(git rev-parse --show-toplevel)"
|
|
||||||
|
|
||||||
SX_SERVER="${SX_SERVER:-/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe}"
|
|
||||||
if [ ! -x "$SX_SERVER" ]; then
|
|
||||||
SX_SERVER="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
|
|
||||||
|
|
||||||
SUITES=(structural operators dfn tradfn valence programs system idioms eval-ops pipeline)
|
|
||||||
|
|
||||||
OUT_JSON="lib/apl/scoreboard.json"
|
|
||||||
OUT_MD="lib/apl/scoreboard.md"
|
|
||||||
|
|
||||||
run_suite() {
|
|
||||||
local suite=$1
|
|
||||||
local file="lib/apl/tests/${suite}.sx"
|
|
||||||
local TMP
|
|
||||||
TMP=$(mktemp)
|
|
||||||
cat > "$TMP" << EPOCHS
|
|
||||||
(epoch 1)
|
|
||||||
(load "spec/stdlib.sx")
|
|
||||||
(load "lib/r7rs.sx")
|
|
||||||
(load "lib/apl/runtime.sx")
|
|
||||||
(load "lib/apl/tokenizer.sx")
|
|
||||||
(load "lib/apl/parser.sx")
|
|
||||||
(load "lib/apl/transpile.sx")
|
|
||||||
(epoch 2)
|
|
||||||
(eval "(define apl-test-pass 0)")
|
|
||||||
(eval "(define apl-test-fail 0)")
|
|
||||||
(eval "(define apl-test (fn (name got expected) (if (= got expected) (set! apl-test-pass (+ apl-test-pass 1)) (set! apl-test-fail (+ apl-test-fail 1)))))")
|
|
||||||
(epoch 3)
|
|
||||||
(load "${file}")
|
|
||||||
(epoch 4)
|
|
||||||
(eval "(list apl-test-pass apl-test-fail)")
|
|
||||||
EPOCHS
|
|
||||||
|
|
||||||
local OUTPUT
|
|
||||||
OUTPUT=$(timeout 300 "$SX_SERVER" < "$TMP" 2>/dev/null)
|
|
||||||
rm -f "$TMP"
|
|
||||||
|
|
||||||
local LINE
|
|
||||||
LINE=$(echo "$OUTPUT" | awk '/^\(ok-len 4 / {getline; print; exit}')
|
|
||||||
if [ -z "$LINE" ]; then
|
|
||||||
LINE=$(echo "$OUTPUT" | grep -E '^\(ok 4 \([0-9]+ [0-9]+\)\)' | tail -1 \
|
|
||||||
| sed -E 's/^\(ok 4 //; s/\)$//')
|
|
||||||
fi
|
|
||||||
|
|
||||||
local P F
|
|
||||||
P=$(echo "$LINE" | sed -E 's/^\(([0-9]+) ([0-9]+)\).*/\1/')
|
|
||||||
F=$(echo "$LINE" | sed -E 's/^\(([0-9]+) ([0-9]+)\).*/\2/')
|
|
||||||
P=${P:-0}
|
|
||||||
F=${F:-0}
|
|
||||||
echo "${P} ${F}"
|
|
||||||
}
|
|
||||||
|
|
||||||
declare -A SUITE_PASS
|
|
||||||
declare -A SUITE_FAIL
|
|
||||||
TOTAL_PASS=0
|
|
||||||
TOTAL_FAIL=0
|
|
||||||
|
|
||||||
echo "Running APL conformance suite..." >&2
|
|
||||||
for s in "${SUITES[@]}"; do
|
|
||||||
read -r p f < <(run_suite "$s")
|
|
||||||
SUITE_PASS[$s]=$p
|
|
||||||
SUITE_FAIL[$s]=$f
|
|
||||||
TOTAL_PASS=$((TOTAL_PASS + p))
|
|
||||||
TOTAL_FAIL=$((TOTAL_FAIL + f))
|
|
||||||
printf " %-12s %d/%d\n" "$s" "$p" "$((p+f))" >&2
|
|
||||||
done
|
|
||||||
|
|
||||||
# scoreboard.json
|
|
||||||
{
|
|
||||||
printf '{\n'
|
|
||||||
printf ' "suites": {\n'
|
|
||||||
first=1
|
|
||||||
for s in "${SUITES[@]}"; do
|
|
||||||
if [ $first -eq 0 ]; then printf ',\n'; fi
|
|
||||||
printf ' "%s": {"pass": %d, "fail": %d}' "$s" "${SUITE_PASS[$s]}" "${SUITE_FAIL[$s]}"
|
|
||||||
first=0
|
|
||||||
done
|
|
||||||
printf '\n },\n'
|
|
||||||
printf ' "total_pass": %d,\n' "$TOTAL_PASS"
|
|
||||||
printf ' "total_fail": %d,\n' "$TOTAL_FAIL"
|
|
||||||
printf ' "total": %d\n' "$((TOTAL_PASS + TOTAL_FAIL))"
|
|
||||||
printf '}\n'
|
|
||||||
} > "$OUT_JSON"
|
|
||||||
|
|
||||||
# scoreboard.md
|
|
||||||
{
|
|
||||||
printf '# APL Conformance Scoreboard\n\n'
|
|
||||||
printf '_Generated by `lib/apl/conformance.sh`_\n\n'
|
|
||||||
printf '| Suite | Pass | Fail | Total |\n'
|
|
||||||
printf '|-------|-----:|-----:|------:|\n'
|
|
||||||
for s in "${SUITES[@]}"; do
|
|
||||||
p=${SUITE_PASS[$s]}
|
|
||||||
f=${SUITE_FAIL[$s]}
|
|
||||||
printf '| %s | %d | %d | %d |\n' "$s" "$p" "$f" "$((p+f))"
|
|
||||||
done
|
|
||||||
printf '| **Total** | **%d** | **%d** | **%d** |\n' "$TOTAL_PASS" "$TOTAL_FAIL" "$((TOTAL_PASS + TOTAL_FAIL))"
|
|
||||||
printf '\n'
|
|
||||||
printf '## Notes\n\n'
|
|
||||||
printf '%s\n' '- Suites use the standard `apl-test name got expected` framework loaded against `lib/apl/runtime.sx` + `lib/apl/transpile.sx`.'
|
|
||||||
printf '%s\n' '- `lib/apl/tests/parse.sx` and `lib/apl/tests/scalar.sx` use their own self-contained frameworks and are excluded from this scoreboard.'
|
|
||||||
} > "$OUT_MD"
|
|
||||||
|
|
||||||
echo "Wrote $OUT_JSON and $OUT_MD" >&2
|
|
||||||
echo "Total: $TOTAL_PASS pass, $TOTAL_FAIL fail" >&2
|
|
||||||
|
|
||||||
[ "$TOTAL_FAIL" -eq 0 ]
|
|
||||||
@@ -1,576 +0,0 @@
|
|||||||
; APL Parser — right-to-left expression parser
|
|
||||||
;
|
|
||||||
; Takes a token list (output of apl-tokenize) and produces an AST.
|
|
||||||
; APL evaluates right-to-left with no precedence among functions.
|
|
||||||
; Operators bind to the function immediately to their left in the source.
|
|
||||||
;
|
|
||||||
; AST node types:
|
|
||||||
; (:num n) number literal
|
|
||||||
; (:str s) string literal
|
|
||||||
; (:vec n1 n2 ...) strand (juxtaposed literals)
|
|
||||||
; (:name "x") name reference / alpha / omega
|
|
||||||
; (:assign "x" expr) assignment x←expr
|
|
||||||
; (:monad fn arg) monadic function call
|
|
||||||
; (:dyad fn left right) dyadic function call
|
|
||||||
; (:derived-fn op fn) derived function: f/ f¨ f⍨
|
|
||||||
; (:derived-fn2 "." f g) inner product: f.g
|
|
||||||
; (:outer "∘." fn) outer product: ∘.f
|
|
||||||
; (:fn-glyph "⍳") function reference
|
|
||||||
; (:fn-name "foo") named-function reference (dfn variable)
|
|
||||||
; (:dfn stmt...) {⍺+⍵} anonymous function
|
|
||||||
; (:guard cond expr) cond:expr guard inside dfn
|
|
||||||
; (:program stmt...) multi-statement sequence
|
|
||||||
|
|
||||||
; ============================================================
|
|
||||||
; Glyph classification sets
|
|
||||||
; ============================================================
|
|
||||||
|
|
||||||
(define apl-parse-op-glyphs
|
|
||||||
(list "/" "\\" "¨" "⍨" "∘" "." "⍣" "⍤" "⍥" "@"))
|
|
||||||
|
|
||||||
(define
|
|
||||||
apl-parse-fn-glyphs
|
|
||||||
(list
|
|
||||||
"+"
|
|
||||||
"-"
|
|
||||||
"×"
|
|
||||||
"÷"
|
|
||||||
"*"
|
|
||||||
"⍟"
|
|
||||||
"⌈"
|
|
||||||
"⌊"
|
|
||||||
"|"
|
|
||||||
"!"
|
|
||||||
"?"
|
|
||||||
"○"
|
|
||||||
"~"
|
|
||||||
"<"
|
|
||||||
"≤"
|
|
||||||
"="
|
|
||||||
"≥"
|
|
||||||
">"
|
|
||||||
"≠"
|
|
||||||
"≢"
|
|
||||||
"≡"
|
|
||||||
"∊"
|
|
||||||
"∧"
|
|
||||||
"∨"
|
|
||||||
"⍱"
|
|
||||||
"⍲"
|
|
||||||
","
|
|
||||||
"⍪"
|
|
||||||
"⍴"
|
|
||||||
"⌽"
|
|
||||||
"⊖"
|
|
||||||
"⍉"
|
|
||||||
"↑"
|
|
||||||
"↓"
|
|
||||||
"⊂"
|
|
||||||
"⊃"
|
|
||||||
"⊆"
|
|
||||||
"∪"
|
|
||||||
"∩"
|
|
||||||
"⍳"
|
|
||||||
"⍸"
|
|
||||||
"⌷"
|
|
||||||
"⍋"
|
|
||||||
"⍒"
|
|
||||||
"⊥"
|
|
||||||
"⊤"
|
|
||||||
"⊣"
|
|
||||||
"⊢"
|
|
||||||
"⍎"
|
|
||||||
"⍕"))
|
|
||||||
|
|
||||||
(define apl-quad-fn-names (list "⎕FMT"))
|
|
||||||
|
|
||||||
(define
|
|
||||||
apl-parse-op-glyph?
|
|
||||||
(fn (v) (some (fn (g) (= g v)) apl-parse-op-glyphs)))
|
|
||||||
|
|
||||||
; ============================================================
|
|
||||||
; Token accessors
|
|
||||||
; ============================================================
|
|
||||||
|
|
||||||
(define
|
|
||||||
apl-parse-fn-glyph?
|
|
||||||
(fn (v) (some (fn (g) (= g v)) apl-parse-fn-glyphs)))
|
|
||||||
|
|
||||||
(define tok-type (fn (tok) (get tok :type)))
|
|
||||||
|
|
||||||
(define tok-val (fn (tok) (get tok :value)))
|
|
||||||
|
|
||||||
(define
|
|
||||||
is-op-tok?
|
|
||||||
(fn
|
|
||||||
(tok)
|
|
||||||
(and (= (tok-type tok) :glyph) (apl-parse-op-glyph? (tok-val tok)))))
|
|
||||||
|
|
||||||
; ============================================================
|
|
||||||
; Collect trailing operators starting at index i
|
|
||||||
; Returns {:ops (op ...) :end new-i}
|
|
||||||
; ============================================================
|
|
||||||
|
|
||||||
(define
|
|
||||||
is-fn-tok?
|
|
||||||
(fn
|
|
||||||
(tok)
|
|
||||||
(or
|
|
||||||
(and (= (tok-type tok) :glyph) (apl-parse-fn-glyph? (tok-val tok)))
|
|
||||||
(and
|
|
||||||
(= (tok-type tok) :name)
|
|
||||||
(some (fn (q) (= q (tok-val tok))) apl-quad-fn-names)))))
|
|
||||||
|
|
||||||
(define collect-ops (fn (tokens i) (collect-ops-loop tokens i (list))))
|
|
||||||
|
|
||||||
; ============================================================
|
|
||||||
; Build a derived-fn node by chaining operators left-to-right
|
|
||||||
; (+/¨ → (:derived-fn "¨" (:derived-fn "/" (:fn-glyph "+"))))
|
|
||||||
; ============================================================
|
|
||||||
|
|
||||||
(define
|
|
||||||
collect-ops-loop
|
|
||||||
(fn
|
|
||||||
(tokens i acc)
|
|
||||||
(if
|
|
||||||
(>= i (len tokens))
|
|
||||||
{:end i :ops acc}
|
|
||||||
(let
|
|
||||||
((tok (nth tokens i)))
|
|
||||||
(if
|
|
||||||
(is-op-tok? tok)
|
|
||||||
(collect-ops-loop tokens (+ i 1) (append acc (tok-val tok)))
|
|
||||||
{:end i :ops acc})))))
|
|
||||||
|
|
||||||
; ============================================================
|
|
||||||
; Find matching close bracket/paren/brace
|
|
||||||
; Returns the index of the matching close token
|
|
||||||
; ============================================================
|
|
||||||
|
|
||||||
(define
|
|
||||||
build-derived-fn
|
|
||||||
(fn
|
|
||||||
(fn-node ops)
|
|
||||||
(if
|
|
||||||
(= (len ops) 0)
|
|
||||||
fn-node
|
|
||||||
(build-derived-fn (list :derived-fn (first ops) fn-node) (rest ops)))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
find-matching-close
|
|
||||||
(fn
|
|
||||||
(tokens start open-type close-type)
|
|
||||||
(find-matching-close-loop tokens start open-type close-type 1)))
|
|
||||||
|
|
||||||
; ============================================================
|
|
||||||
; Segment collection: scan tokens left-to-right, building
|
|
||||||
; a list of {:kind "val"/"fn" :node ast} segments.
|
|
||||||
; Operators following function glyphs are merged into
|
|
||||||
; derived-fn nodes during this pass.
|
|
||||||
; ============================================================
|
|
||||||
|
|
||||||
(define
|
|
||||||
find-matching-close-loop
|
|
||||||
(fn
|
|
||||||
(tokens i open-type close-type depth)
|
|
||||||
(if
|
|
||||||
(>= i (len tokens))
|
|
||||||
(len tokens)
|
|
||||||
(let
|
|
||||||
((tt (tok-type (nth tokens i))))
|
|
||||||
(cond
|
|
||||||
((= tt open-type)
|
|
||||||
(find-matching-close-loop
|
|
||||||
tokens
|
|
||||||
(+ i 1)
|
|
||||||
open-type
|
|
||||||
close-type
|
|
||||||
(+ depth 1)))
|
|
||||||
((= tt close-type)
|
|
||||||
(if
|
|
||||||
(= depth 1)
|
|
||||||
i
|
|
||||||
(find-matching-close-loop
|
|
||||||
tokens
|
|
||||||
(+ i 1)
|
|
||||||
open-type
|
|
||||||
close-type
|
|
||||||
(- depth 1))))
|
|
||||||
(true
|
|
||||||
(find-matching-close-loop
|
|
||||||
tokens
|
|
||||||
(+ i 1)
|
|
||||||
open-type
|
|
||||||
close-type
|
|
||||||
depth)))))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
collect-segments
|
|
||||||
(fn (tokens) (collect-segments-loop tokens 0 (list))))
|
|
||||||
|
|
||||||
; ============================================================
|
|
||||||
; Build tree from segment list
|
|
||||||
;
|
|
||||||
; The segments are in left-to-right order.
|
|
||||||
; APL evaluates right-to-left, so the LEFTMOST function is
|
|
||||||
; the outermost (last-evaluated) node.
|
|
||||||
;
|
|
||||||
; Patterns:
|
|
||||||
; [val] → val node
|
|
||||||
; [fn val ...] → (:monad fn (build-tree rest))
|
|
||||||
; [val fn val ...] → (:dyad fn val (build-tree rest))
|
|
||||||
; [val val ...] → (:vec val1 val2 ...) — strand
|
|
||||||
; ============================================================
|
|
||||||
|
|
||||||
; Find the index of the first function segment (returns -1 if none)
|
|
||||||
(define
|
|
||||||
collect-segments-loop
|
|
||||||
(fn
|
|
||||||
(tokens i acc)
|
|
||||||
(if
|
|
||||||
(>= i (len tokens))
|
|
||||||
acc
|
|
||||||
(let
|
|
||||||
((tok (nth tokens i)) (n (len tokens)))
|
|
||||||
(let
|
|
||||||
((tt (tok-type tok)) (tv (tok-val tok)))
|
|
||||||
(cond
|
|
||||||
((or (= tt :diamond) (= tt :newline) (= tt :semi))
|
|
||||||
(collect-segments-loop tokens (+ i 1) acc))
|
|
||||||
((= tt :num)
|
|
||||||
(collect-segments-loop tokens (+ i 1) (append acc {:kind "val" :node (list :num tv)})))
|
|
||||||
((= tt :str)
|
|
||||||
(collect-segments-loop tokens (+ i 1) (append acc {:kind "val" :node (list :str tv)})))
|
|
||||||
((= tt :name)
|
|
||||||
(if
|
|
||||||
(some (fn (q) (= q tv)) apl-quad-fn-names)
|
|
||||||
(let
|
|
||||||
((op-result (collect-ops tokens (+ i 1))))
|
|
||||||
(let
|
|
||||||
((ops (get op-result :ops)) (ni (get op-result :end)))
|
|
||||||
(let
|
|
||||||
((fn-node (build-derived-fn (list :fn-glyph tv) ops)))
|
|
||||||
(collect-segments-loop
|
|
||||||
tokens
|
|
||||||
ni
|
|
||||||
(append acc {:kind "fn" :node fn-node})))))
|
|
||||||
(let
|
|
||||||
((br (maybe-bracket (list :name tv) tokens (+ i 1))))
|
|
||||||
(collect-segments-loop
|
|
||||||
tokens
|
|
||||||
(nth br 1)
|
|
||||||
(append acc {:kind "val" :node (nth br 0)})))))
|
|
||||||
((= tt :lparen)
|
|
||||||
(let
|
|
||||||
((end (find-matching-close tokens (+ i 1) :lparen :rparen)))
|
|
||||||
(let
|
|
||||||
((inner-tokens (slice tokens (+ i 1) end))
|
|
||||||
(after (+ end 1)))
|
|
||||||
(let
|
|
||||||
((br (maybe-bracket (parse-apl-expr inner-tokens) tokens after)))
|
|
||||||
(collect-segments-loop
|
|
||||||
tokens
|
|
||||||
(nth br 1)
|
|
||||||
(append acc {:kind "val" :node (nth br 0)}))))))
|
|
||||||
((= tt :lbrace)
|
|
||||||
(let
|
|
||||||
((end (find-matching-close tokens (+ i 1) :lbrace :rbrace)))
|
|
||||||
(let
|
|
||||||
((inner-tokens (slice tokens (+ i 1) end))
|
|
||||||
(after (+ end 1)))
|
|
||||||
(collect-segments-loop tokens after (append acc {:kind "fn" :node (parse-dfn inner-tokens)})))))
|
|
||||||
((= tt :glyph)
|
|
||||||
(cond
|
|
||||||
((or (= tv "⍺") (= tv "⍵"))
|
|
||||||
(collect-segments-loop
|
|
||||||
tokens
|
|
||||||
(+ i 1)
|
|
||||||
(append acc {:kind "val" :node (list :name tv)})))
|
|
||||||
((= tv "∇")
|
|
||||||
(collect-segments-loop
|
|
||||||
tokens
|
|
||||||
(+ i 1)
|
|
||||||
(append acc {:kind "fn" :node (list :fn-glyph "∇")})))
|
|
||||||
((and (= tv "∘") (< (+ i 1) n) (= (tok-val (nth tokens (+ i 1))) "."))
|
|
||||||
(if
|
|
||||||
(and (< (+ i 2) n) (is-fn-tok? (nth tokens (+ i 2))))
|
|
||||||
(let
|
|
||||||
((fn-tv (tok-val (nth tokens (+ i 2)))))
|
|
||||||
(let
|
|
||||||
((op-result (collect-ops tokens (+ i 3))))
|
|
||||||
(let
|
|
||||||
((ops (get op-result :ops))
|
|
||||||
(ni (get op-result :end)))
|
|
||||||
(let
|
|
||||||
((fn-node (build-derived-fn (list :fn-glyph fn-tv) ops)))
|
|
||||||
(collect-segments-loop
|
|
||||||
tokens
|
|
||||||
ni
|
|
||||||
(append acc {:kind "fn" :node (list :outer "∘." fn-node)}))))))
|
|
||||||
(collect-segments-loop tokens (+ i 1) acc)))
|
|
||||||
((apl-parse-fn-glyph? tv)
|
|
||||||
(let
|
|
||||||
((op-result (collect-ops tokens (+ i 1))))
|
|
||||||
(let
|
|
||||||
((ops (get op-result :ops))
|
|
||||||
(ni (get op-result :end)))
|
|
||||||
(if
|
|
||||||
(and
|
|
||||||
(= (len ops) 1)
|
|
||||||
(= (first ops) ".")
|
|
||||||
(< ni n)
|
|
||||||
(is-fn-tok? (nth tokens ni)))
|
|
||||||
(let
|
|
||||||
((g-tv (tok-val (nth tokens ni))))
|
|
||||||
(let
|
|
||||||
((op-result2 (collect-ops tokens (+ ni 1))))
|
|
||||||
(let
|
|
||||||
((ops2 (get op-result2 :ops))
|
|
||||||
(ni2 (get op-result2 :end)))
|
|
||||||
(let
|
|
||||||
((g-node (build-derived-fn (list :fn-glyph g-tv) ops2)))
|
|
||||||
(collect-segments-loop
|
|
||||||
tokens
|
|
||||||
ni2
|
|
||||||
(append acc {:kind "fn" :node (list :derived-fn2 "." (list :fn-glyph tv) g-node)}))))))
|
|
||||||
(let
|
|
||||||
((fn-node (build-derived-fn (list :fn-glyph tv) ops)))
|
|
||||||
(collect-segments-loop
|
|
||||||
tokens
|
|
||||||
ni
|
|
||||||
(append acc {:kind "fn" :node fn-node})))))))
|
|
||||||
((apl-parse-op-glyph? tv)
|
|
||||||
(collect-segments-loop tokens (+ i 1) acc))
|
|
||||||
(true (collect-segments-loop tokens (+ i 1) acc))))
|
|
||||||
(true (collect-segments-loop tokens (+ i 1) acc))))))))
|
|
||||||
|
|
||||||
(define find-first-fn (fn (segs) (find-first-fn-loop segs 0)))
|
|
||||||
|
|
||||||
; Build an array node from 0..n value segments
|
|
||||||
; If n=1 → return that segment's node
|
|
||||||
; If n>1 → return (:vec node1 node2 ...)
|
|
||||||
(define
|
|
||||||
find-first-fn-loop
|
|
||||||
(fn
|
|
||||||
(segs i)
|
|
||||||
(if
|
|
||||||
(>= i (len segs))
|
|
||||||
-1
|
|
||||||
(if
|
|
||||||
(= (get (nth segs i) :kind) "fn")
|
|
||||||
i
|
|
||||||
(find-first-fn-loop segs (+ i 1))))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
segs-to-array
|
|
||||||
(fn
|
|
||||||
(segs)
|
|
||||||
(if
|
|
||||||
(= (len segs) 1)
|
|
||||||
(get (first segs) :node)
|
|
||||||
(cons :vec (map (fn (s) (get s :node)) segs)))))
|
|
||||||
|
|
||||||
|
|
||||||
; ============================================================
|
|
||||||
; Split token list on statement separators (diamond / newline)
|
|
||||||
; Only splits at depth 0 (ignores separators inside { } or ( ) )
|
|
||||||
; ============================================================
|
|
||||||
|
|
||||||
(define
|
|
||||||
build-tree
|
|
||||||
(fn
|
|
||||||
(segs)
|
|
||||||
(cond
|
|
||||||
((= (len segs) 0) nil)
|
|
||||||
((= (len segs) 1) (get (first segs) :node))
|
|
||||||
((every? (fn (s) (= (get s :kind) "val")) segs)
|
|
||||||
(segs-to-array segs))
|
|
||||||
(true
|
|
||||||
(let
|
|
||||||
((fn-idx (find-first-fn segs)))
|
|
||||||
(cond
|
|
||||||
((= fn-idx -1) (segs-to-array segs))
|
|
||||||
((= fn-idx 0)
|
|
||||||
(list
|
|
||||||
:monad (get (first segs) :node)
|
|
||||||
(build-tree (rest segs))))
|
|
||||||
(true
|
|
||||||
(let
|
|
||||||
((left-segs (slice segs 0 fn-idx))
|
|
||||||
(fn-seg (nth segs fn-idx))
|
|
||||||
(right-segs (slice segs (+ fn-idx 1))))
|
|
||||||
(list
|
|
||||||
:dyad (get fn-seg :node)
|
|
||||||
(segs-to-array left-segs)
|
|
||||||
(build-tree right-segs))))))))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
split-statements
|
|
||||||
(fn (tokens) (split-statements-loop tokens (list) (list) 0)))
|
|
||||||
|
|
||||||
; ============================================================
|
|
||||||
; Parse a dfn body (tokens between { and })
|
|
||||||
; Handles guard expressions: cond : expr
|
|
||||||
; ============================================================
|
|
||||||
|
|
||||||
(define
|
|
||||||
split-statements-loop
|
|
||||||
(fn
|
|
||||||
(tokens current-stmt acc depth)
|
|
||||||
(if
|
|
||||||
(= (len tokens) 0)
|
|
||||||
(if (> (len current-stmt) 0) (append acc (list current-stmt)) acc)
|
|
||||||
(let
|
|
||||||
((tok (first tokens))
|
|
||||||
(rest-toks (rest tokens))
|
|
||||||
(tt (tok-type (first tokens))))
|
|
||||||
(cond
|
|
||||||
((or (= tt :lparen) (= tt :lbrace) (= tt :lbracket))
|
|
||||||
(split-statements-loop
|
|
||||||
rest-toks
|
|
||||||
(append current-stmt tok)
|
|
||||||
acc
|
|
||||||
(+ depth 1)))
|
|
||||||
((or (= tt :rparen) (= tt :rbrace) (= tt :rbracket))
|
|
||||||
(split-statements-loop
|
|
||||||
rest-toks
|
|
||||||
(append current-stmt tok)
|
|
||||||
acc
|
|
||||||
(- depth 1)))
|
|
||||||
((and (> depth 0) (or (= tt :diamond) (= tt :newline)))
|
|
||||||
(split-statements-loop
|
|
||||||
rest-toks
|
|
||||||
(append current-stmt tok)
|
|
||||||
acc
|
|
||||||
depth))
|
|
||||||
((and (= depth 0) (or (= tt :diamond) (= tt :newline)))
|
|
||||||
(if
|
|
||||||
(> (len current-stmt) 0)
|
|
||||||
(split-statements-loop
|
|
||||||
rest-toks
|
|
||||||
(list)
|
|
||||||
(append acc (list current-stmt))
|
|
||||||
depth)
|
|
||||||
(split-statements-loop rest-toks (list) acc depth)))
|
|
||||||
(true
|
|
||||||
(split-statements-loop
|
|
||||||
rest-toks
|
|
||||||
(append current-stmt tok)
|
|
||||||
acc
|
|
||||||
depth)))))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
parse-dfn
|
|
||||||
(fn
|
|
||||||
(tokens)
|
|
||||||
(let
|
|
||||||
((stmt-groups (split-statements tokens)))
|
|
||||||
(let ((stmts (map parse-dfn-stmt stmt-groups))) (cons :dfn stmts)))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
parse-dfn-stmt
|
|
||||||
(fn
|
|
||||||
(tokens)
|
|
||||||
(let
|
|
||||||
((colon-idx (find-top-level-colon tokens 0)))
|
|
||||||
(if
|
|
||||||
(>= colon-idx 0)
|
|
||||||
(let
|
|
||||||
((cond-tokens (slice tokens 0 colon-idx))
|
|
||||||
(body-tokens (slice tokens (+ colon-idx 1))))
|
|
||||||
(list
|
|
||||||
:guard (parse-apl-expr cond-tokens)
|
|
||||||
(parse-apl-expr body-tokens)))
|
|
||||||
(parse-stmt tokens)))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
find-top-level-colon
|
|
||||||
(fn (tokens i) (find-top-level-colon-loop tokens i 0)))
|
|
||||||
|
|
||||||
; ============================================================
|
|
||||||
; Parse a single statement (assignment or expression)
|
|
||||||
; ============================================================
|
|
||||||
|
|
||||||
(define
|
|
||||||
find-top-level-colon-loop
|
|
||||||
(fn
|
|
||||||
(tokens i depth)
|
|
||||||
(if
|
|
||||||
(>= i (len tokens))
|
|
||||||
-1
|
|
||||||
(let
|
|
||||||
((tok (nth tokens i)) (tt (tok-type (nth tokens i))))
|
|
||||||
(cond
|
|
||||||
((or (= tt :lparen) (= tt :lbrace) (= tt :lbracket))
|
|
||||||
(find-top-level-colon-loop tokens (+ i 1) (+ depth 1)))
|
|
||||||
((or (= tt :rparen) (= tt :rbrace) (= tt :rbracket))
|
|
||||||
(find-top-level-colon-loop tokens (+ i 1) (- depth 1)))
|
|
||||||
((and (= tt :colon) (= depth 0)) i)
|
|
||||||
(true (find-top-level-colon-loop tokens (+ i 1) depth)))))))
|
|
||||||
|
|
||||||
; ============================================================
|
|
||||||
; Parse an expression from a flat token list
|
|
||||||
; ============================================================
|
|
||||||
|
|
||||||
(define
|
|
||||||
parse-stmt
|
|
||||||
(fn
|
|
||||||
(tokens)
|
|
||||||
(if
|
|
||||||
(and
|
|
||||||
(>= (len tokens) 2)
|
|
||||||
(= (tok-type (nth tokens 0)) :name)
|
|
||||||
(= (tok-type (nth tokens 1)) :assign))
|
|
||||||
(list
|
|
||||||
:assign (tok-val (nth tokens 0))
|
|
||||||
(parse-apl-expr (slice tokens 2)))
|
|
||||||
(parse-apl-expr tokens))))
|
|
||||||
|
|
||||||
; ============================================================
|
|
||||||
; Main entry point
|
|
||||||
; parse-apl: string → AST
|
|
||||||
; ============================================================
|
|
||||||
|
|
||||||
(define
|
|
||||||
parse-apl-expr
|
|
||||||
(fn
|
|
||||||
(tokens)
|
|
||||||
(let
|
|
||||||
((segs (collect-segments tokens)))
|
|
||||||
(if (= (len segs) 0) nil (build-tree segs)))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
parse-apl
|
|
||||||
(fn
|
|
||||||
(src)
|
|
||||||
(let
|
|
||||||
((tokens (apl-tokenize src)))
|
|
||||||
(let
|
|
||||||
((stmt-groups (split-statements tokens)))
|
|
||||||
(if
|
|
||||||
(= (len stmt-groups) 0)
|
|
||||||
nil
|
|
||||||
(if
|
|
||||||
(= (len stmt-groups) 1)
|
|
||||||
(parse-stmt (first stmt-groups))
|
|
||||||
(cons :program (map parse-stmt stmt-groups))))))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
maybe-bracket
|
|
||||||
(fn
|
|
||||||
(val-node tokens after)
|
|
||||||
(if
|
|
||||||
(and
|
|
||||||
(< after (len tokens))
|
|
||||||
(= (tok-type (nth tokens after)) :lbracket))
|
|
||||||
(let
|
|
||||||
((end (find-matching-close tokens (+ after 1) :lbracket :rbracket)))
|
|
||||||
(let
|
|
||||||
((inner-tokens (slice tokens (+ after 1) end))
|
|
||||||
(next-after (+ end 1)))
|
|
||||||
(let
|
|
||||||
((idx-expr (parse-apl-expr inner-tokens)))
|
|
||||||
(let
|
|
||||||
((indexed (list :dyad (list :fn-glyph "⌷") idx-expr val-node)))
|
|
||||||
(maybe-bracket indexed tokens next-after)))))
|
|
||||||
(list val-node after))))
|
|
||||||
1536
lib/apl/runtime.sx
1536
lib/apl/runtime.sx
File diff suppressed because it is too large
Load Diff
@@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"suites": {
|
|
||||||
"structural": {"pass": 94, "fail": 0},
|
|
||||||
"operators": {"pass": 117, "fail": 0},
|
|
||||||
"dfn": {"pass": 24, "fail": 0},
|
|
||||||
"tradfn": {"pass": 25, "fail": 0},
|
|
||||||
"valence": {"pass": 14, "fail": 0},
|
|
||||||
"programs": {"pass": 45, "fail": 0},
|
|
||||||
"system": {"pass": 13, "fail": 0},
|
|
||||||
"idioms": {"pass": 64, "fail": 0},
|
|
||||||
"eval-ops": {"pass": 14, "fail": 0},
|
|
||||||
"pipeline": {"pass": 40, "fail": 0}
|
|
||||||
},
|
|
||||||
"total_pass": 450,
|
|
||||||
"total_fail": 0,
|
|
||||||
"total": 450
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
# APL Conformance Scoreboard
|
|
||||||
|
|
||||||
_Generated by `lib/apl/conformance.sh`_
|
|
||||||
|
|
||||||
| Suite | Pass | Fail | Total |
|
|
||||||
|-------|-----:|-----:|------:|
|
|
||||||
| structural | 94 | 0 | 94 |
|
|
||||||
| operators | 117 | 0 | 117 |
|
|
||||||
| dfn | 24 | 0 | 24 |
|
|
||||||
| tradfn | 25 | 0 | 25 |
|
|
||||||
| valence | 14 | 0 | 14 |
|
|
||||||
| programs | 45 | 0 | 45 |
|
|
||||||
| system | 13 | 0 | 13 |
|
|
||||||
| idioms | 64 | 0 | 64 |
|
|
||||||
| eval-ops | 14 | 0 | 14 |
|
|
||||||
| pipeline | 40 | 0 | 40 |
|
|
||||||
| **Total** | **450** | **0** | **450** |
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
- Suites use the standard `apl-test name got expected` framework loaded against `lib/apl/runtime.sx` + `lib/apl/transpile.sx`.
|
|
||||||
- `lib/apl/tests/parse.sx` and `lib/apl/tests/scalar.sx` use their own self-contained frameworks and are excluded from this scoreboard.
|
|
||||||
@@ -4,9 +4,9 @@
|
|||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
cd "$(git rev-parse --show-toplevel)"
|
cd "$(git rev-parse --show-toplevel)"
|
||||||
|
|
||||||
SX_SERVER="${SX_SERVER:-/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe}"
|
SX_SERVER="${SX_SERVER:-hosts/ocaml/_build/default/bin/sx_server.exe}"
|
||||||
if [ ! -x "$SX_SERVER" ]; then
|
if [ ! -x "$SX_SERVER" ]; then
|
||||||
SX_SERVER="hosts/ocaml/_build/default/bin/sx_server.exe"
|
SX_SERVER="/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe"
|
||||||
fi
|
fi
|
||||||
if [ ! -x "$SX_SERVER" ]; then
|
if [ ! -x "$SX_SERVER" ]; then
|
||||||
echo "ERROR: sx_server.exe not found."
|
echo "ERROR: sx_server.exe not found."
|
||||||
@@ -18,37 +18,19 @@ TMPFILE=$(mktemp); trap "rm -f $TMPFILE" EXIT
|
|||||||
cat > "$TMPFILE" << 'EPOCHS'
|
cat > "$TMPFILE" << 'EPOCHS'
|
||||||
(epoch 1)
|
(epoch 1)
|
||||||
(load "spec/stdlib.sx")
|
(load "spec/stdlib.sx")
|
||||||
(load "lib/r7rs.sx")
|
|
||||||
(load "lib/apl/runtime.sx")
|
(load "lib/apl/runtime.sx")
|
||||||
(load "lib/apl/tokenizer.sx")
|
|
||||||
(load "lib/apl/parser.sx")
|
|
||||||
(load "lib/apl/transpile.sx")
|
|
||||||
(epoch 2)
|
(epoch 2)
|
||||||
(eval "(define apl-test-pass 0)")
|
(load "lib/apl/tests/runtime.sx")
|
||||||
(eval "(define apl-test-fail 0)")
|
|
||||||
(eval "(define apl-test-fails (list))")
|
|
||||||
(eval "(define apl-test (fn (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 {:name name :got got :expected expected})))))))")
|
|
||||||
(epoch 3)
|
(epoch 3)
|
||||||
(load "lib/apl/tests/structural.sx")
|
|
||||||
(load "lib/apl/tests/operators.sx")
|
|
||||||
(load "lib/apl/tests/dfn.sx")
|
|
||||||
(load "lib/apl/tests/tradfn.sx")
|
|
||||||
(load "lib/apl/tests/valence.sx")
|
|
||||||
(load "lib/apl/tests/programs.sx")
|
|
||||||
(load "lib/apl/tests/system.sx")
|
|
||||||
(load "lib/apl/tests/idioms.sx")
|
|
||||||
(load "lib/apl/tests/eval-ops.sx")
|
|
||||||
(load "lib/apl/tests/pipeline.sx")
|
|
||||||
(epoch 4)
|
|
||||||
(eval "(list apl-test-pass apl-test-fail)")
|
(eval "(list apl-test-pass apl-test-fail)")
|
||||||
EPOCHS
|
EPOCHS
|
||||||
|
|
||||||
OUTPUT=$(timeout 300 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
|
OUTPUT=$(timeout 60 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
|
||||||
|
|
||||||
LINE=$(echo "$OUTPUT" | awk '/^\(ok-len 4 / {getline; print; exit}')
|
LINE=$(echo "$OUTPUT" | awk '/^\(ok-len 3 / {getline; print; exit}')
|
||||||
if [ -z "$LINE" ]; then
|
if [ -z "$LINE" ]; then
|
||||||
LINE=$(echo "$OUTPUT" | grep -E '^\(ok 4 \([0-9]+ [0-9]+\)\)' | tail -1 \
|
LINE=$(echo "$OUTPUT" | grep -E '^\(ok 3 \([0-9]+ [0-9]+\)\)' | tail -1 \
|
||||||
| sed -E 's/^\(ok 4 //; s/\)$//')
|
| sed -E 's/^\(ok 3 //; s/\)$//')
|
||||||
fi
|
fi
|
||||||
if [ -z "$LINE" ]; then
|
if [ -z "$LINE" ]; then
|
||||||
echo "ERROR: could not extract summary"
|
echo "ERROR: could not extract summary"
|
||||||
|
|||||||
@@ -1,227 +0,0 @@
|
|||||||
; Tests for apl-eval-ast and apl-call-dfn (manual AST construction).
|
|
||||||
|
|
||||||
(define rv (fn (arr) (get arr :ravel)))
|
|
||||||
(define sh (fn (arr) (get arr :shape)))
|
|
||||||
|
|
||||||
(define mknum (fn (n) (list :num n)))
|
|
||||||
(define mkname (fn (s) (list :name s)))
|
|
||||||
(define mkfg (fn (g) (list :fn-glyph g)))
|
|
||||||
(define mkmon (fn (g a) (list :monad (mkfg g) a)))
|
|
||||||
(define mkdyd (fn (g l r) (list :dyad (mkfg g) l r)))
|
|
||||||
(define mkdfn1 (fn (body) (list :dfn body)))
|
|
||||||
(define mkprog (fn (stmts) (cons :program stmts)))
|
|
||||||
|
|
||||||
(define mkasg (fn (mkname expr) (list :assign mkname expr)))
|
|
||||||
|
|
||||||
(define mkgrd (fn (c e) (list :guard c e)))
|
|
||||||
|
|
||||||
(define mkdfn (fn (stmts) (cons :dfn stmts)))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"eval :num literal"
|
|
||||||
(rv (apl-eval-ast (mknum 42) {}))
|
|
||||||
(list 42))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"eval :num literal shape"
|
|
||||||
(sh (apl-eval-ast (mknum 42) {}))
|
|
||||||
(list))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"eval :dyad +"
|
|
||||||
(rv (apl-eval-ast (mkdyd "+" (mknum 2) (mknum 3)) {}))
|
|
||||||
(list 5))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"eval :dyad ×"
|
|
||||||
(rv (apl-eval-ast (mkdyd "×" (mknum 6) (mknum 7)) {}))
|
|
||||||
(list 42))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"eval :monad - (negate)"
|
|
||||||
(rv (apl-eval-ast (mkmon "-" (mknum 7)) {}))
|
|
||||||
(list -7))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"eval :monad ⌊ (floor)"
|
|
||||||
(rv (apl-eval-ast (mkmon "⌊" (mknum 3)) {}))
|
|
||||||
(list 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"eval :name ⍵ from env"
|
|
||||||
(rv (apl-eval-ast (mkname "⍵") {:omega (apl-scalar 99) :alpha nil}))
|
|
||||||
(list 99))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"eval :name ⍺ from env"
|
|
||||||
(rv (apl-eval-ast (mkname "⍺") {:omega nil :alpha (apl-scalar 7)}))
|
|
||||||
(list 7))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"dfn {⍵+1} called monadic"
|
|
||||||
(rv
|
|
||||||
(apl-call-dfn-m
|
|
||||||
(mkdfn1 (mkdyd "+" (mkname "⍵") (mknum 1)))
|
|
||||||
(apl-scalar 5)))
|
|
||||||
(list 6))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"dfn {⍺+⍵} called dyadic"
|
|
||||||
(rv
|
|
||||||
(apl-call-dfn
|
|
||||||
(mkdfn1 (mkdyd "+" (mkname "⍺") (mkname "⍵")))
|
|
||||||
(apl-scalar 4)
|
|
||||||
(apl-scalar 9)))
|
|
||||||
(list 13))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"dfn {⍺×⍵} dyadic on vectors"
|
|
||||||
(rv
|
|
||||||
(apl-call-dfn
|
|
||||||
(mkdfn1 (mkdyd "×" (mkname "⍺") (mkname "⍵")))
|
|
||||||
(make-array (list 3) (list 1 2 3))
|
|
||||||
(make-array (list 3) (list 10 20 30))))
|
|
||||||
(list 10 40 90))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"dfn {-⍵} monadic negate"
|
|
||||||
(rv
|
|
||||||
(apl-call-dfn-m
|
|
||||||
(mkdfn1 (mkmon "-" (mkname "⍵")))
|
|
||||||
(make-array (list 3) (list 1 2 3))))
|
|
||||||
(list -1 -2 -3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"dfn {⍺-⍵} dyadic subtract scalar"
|
|
||||||
(rv
|
|
||||||
(apl-call-dfn
|
|
||||||
(mkdfn1 (mkdyd "-" (mkname "⍺") (mkname "⍵")))
|
|
||||||
(apl-scalar 10)
|
|
||||||
(apl-scalar 3)))
|
|
||||||
(list 7))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"dfn {⌈⍺,⍵} not used (just verify : missing) — ceiling of right"
|
|
||||||
(rv
|
|
||||||
(apl-call-dfn-m (mkdfn1 (mkmon "⌈" (mkname "⍵"))) (apl-scalar 5)))
|
|
||||||
(list 5))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"dfn nested dyad"
|
|
||||||
(rv
|
|
||||||
(apl-call-dfn
|
|
||||||
(mkdfn1
|
|
||||||
(mkdyd "+" (mkname "⍺") (mkdyd "×" (mkname "⍵") (mknum 2))))
|
|
||||||
(apl-scalar 1)
|
|
||||||
(apl-scalar 3)))
|
|
||||||
(list 7))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"dfn local assign x←⍵+1; ⍺×x"
|
|
||||||
(rv
|
|
||||||
(apl-call-dfn
|
|
||||||
(mkdfn
|
|
||||||
(list
|
|
||||||
(mkasg "x" (mkdyd "+" (mkname "⍵") (mknum 1)))
|
|
||||||
(mkdyd "×" (mkname "⍺") (mkname "x"))))
|
|
||||||
(apl-scalar 3)
|
|
||||||
(apl-scalar 4)))
|
|
||||||
(list 15))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"dfn guard: 0=⍵:99; ⍵×2 (true branch)"
|
|
||||||
(rv
|
|
||||||
(apl-call-dfn-m
|
|
||||||
(mkdfn
|
|
||||||
(list
|
|
||||||
(mkgrd (mkdyd "=" (mknum 0) (mkname "⍵")) (mknum 99))
|
|
||||||
(mkdyd "×" (mkname "⍵") (mknum 2))))
|
|
||||||
(apl-scalar 0)))
|
|
||||||
(list 99))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"dfn guard: 0=⍵:99; ⍵×2 (false branch)"
|
|
||||||
(rv
|
|
||||||
(apl-call-dfn-m
|
|
||||||
(mkdfn
|
|
||||||
(list
|
|
||||||
(mkgrd (mkdyd "=" (mknum 0) (mkname "⍵")) (mknum 99))
|
|
||||||
(mkdyd "×" (mkname "⍵") (mknum 2))))
|
|
||||||
(apl-scalar 5)))
|
|
||||||
(list 10))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"dfn default ⍺←10 used (monadic call)"
|
|
||||||
(rv
|
|
||||||
(apl-call-dfn-m
|
|
||||||
(mkdfn
|
|
||||||
(list
|
|
||||||
(mkasg "⍺" (mknum 10))
|
|
||||||
(mkdyd "+" (mkname "⍺") (mkname "⍵"))))
|
|
||||||
(apl-scalar 5)))
|
|
||||||
(list 15))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"dfn default ⍺←10 ignored when ⍺ given (dyadic call)"
|
|
||||||
(rv
|
|
||||||
(apl-call-dfn
|
|
||||||
(mkdfn
|
|
||||||
(list
|
|
||||||
(mkasg "⍺" (mknum 10))
|
|
||||||
(mkdyd "+" (mkname "⍺") (mkname "⍵"))))
|
|
||||||
(apl-scalar 100)
|
|
||||||
(apl-scalar 5)))
|
|
||||||
(list 105))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"dfn ∇ recursion: factorial via guard"
|
|
||||||
(rv
|
|
||||||
(apl-call-dfn-m
|
|
||||||
(mkdfn
|
|
||||||
(list
|
|
||||||
(mkgrd (mkdyd "=" (mknum 0) (mkname "⍵")) (mknum 1))
|
|
||||||
(mkdyd
|
|
||||||
"×"
|
|
||||||
(mkname "⍵")
|
|
||||||
(mkmon "∇" (mkdyd "-" (mkname "⍵") (mknum 1))))))
|
|
||||||
(apl-scalar 5)))
|
|
||||||
(list 120))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"dfn ∇ recursion: 3 → 6 (factorial)"
|
|
||||||
(rv
|
|
||||||
(apl-call-dfn-m
|
|
||||||
(mkdfn
|
|
||||||
(list
|
|
||||||
(mkgrd (mkdyd "=" (mknum 0) (mkname "⍵")) (mknum 1))
|
|
||||||
(mkdyd
|
|
||||||
"×"
|
|
||||||
(mkname "⍵")
|
|
||||||
(mkmon "∇" (mkdyd "-" (mkname "⍵") (mknum 1))))))
|
|
||||||
(apl-scalar 3)))
|
|
||||||
(list 6))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"dfn local: x←⍵+10; y←x×2; y"
|
|
||||||
(rv
|
|
||||||
(apl-call-dfn-m
|
|
||||||
(mkdfn
|
|
||||||
(list
|
|
||||||
(mkasg "x" (mkdyd "+" (mkname "⍵") (mknum 10)))
|
|
||||||
(mkasg "y" (mkdyd "×" (mkname "x") (mknum 2)))
|
|
||||||
(mkname "y")))
|
|
||||||
(apl-scalar 5)))
|
|
||||||
(list 30))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"dfn first guard wins: many guards"
|
|
||||||
(rv
|
|
||||||
(apl-call-dfn-m
|
|
||||||
(mkdfn
|
|
||||||
(list
|
|
||||||
(mkgrd (mkdyd "=" (mknum 1) (mkname "⍵")) (mknum 100))
|
|
||||||
(mkgrd (mkdyd "=" (mknum 2) (mkname "⍵")) (mknum 200))
|
|
||||||
(mkgrd (mkdyd "=" (mknum 3) (mkname "⍵")) (mknum 300))
|
|
||||||
(mknum 0)))
|
|
||||||
(apl-scalar 2)))
|
|
||||||
(list 200))
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
; Tests for operator handling in apl-eval-ast (Phase 7).
|
|
||||||
; Manual AST construction; verifies :derived-fn / :outer / :derived-fn2
|
|
||||||
; route through apl-resolve-monadic / apl-resolve-dyadic correctly.
|
|
||||||
|
|
||||||
(define mkrv (fn (arr) (get arr :ravel)))
|
|
||||||
(define mksh (fn (arr) (get arr :shape)))
|
|
||||||
(define mknum (fn (n) (list :num n)))
|
|
||||||
(define mkfg (fn (g) (list :fn-glyph g)))
|
|
||||||
(define mkmon (fn (g a) (list :monad g a)))
|
|
||||||
(define mkdyd (fn (g l r) (list :dyad g l r)))
|
|
||||||
(define mkder (fn (op f) (list :derived-fn op f)))
|
|
||||||
(define mkdr2 (fn (op f g) (list :derived-fn2 op f g)))
|
|
||||||
(define mkout (fn (f) (list :outer "∘." f)))
|
|
||||||
|
|
||||||
; helper: literal vector AST via :vec (from list of values)
|
|
||||||
(define mkvec (fn (xs) (cons :vec (map (fn (n) (mknum n)) xs))))
|
|
||||||
|
|
||||||
; ---------- monadic operators ----------
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"eval-ast +/ ⍳5 → 15"
|
|
||||||
(mkrv
|
|
||||||
(apl-eval-ast
|
|
||||||
(mkmon (mkder "/" (mkfg "+")) (mkmon (mkfg "⍳") (mknum 5)))
|
|
||||||
{}))
|
|
||||||
(list 15))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"eval-ast ×/ ⍳5 → 120"
|
|
||||||
(mkrv
|
|
||||||
(apl-eval-ast
|
|
||||||
(mkmon (mkder "/" (mkfg "×")) (mkmon (mkfg "⍳") (mknum 5)))
|
|
||||||
{}))
|
|
||||||
(list 120))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"eval-ast ⌈/ — max reduce"
|
|
||||||
(mkrv
|
|
||||||
(apl-eval-ast
|
|
||||||
(mkmon (mkder "/" (mkfg "⌈")) (mkvec (list 3 1 4 1 5 9 2 6)))
|
|
||||||
{}))
|
|
||||||
(list 9))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"eval-ast +\\ scan"
|
|
||||||
(mkrv
|
|
||||||
(apl-eval-ast
|
|
||||||
(mkmon (mkder "\\" (mkfg "+")) (mkvec (list 1 2 3 4 5)))
|
|
||||||
{}))
|
|
||||||
(list 1 3 6 10 15))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"eval-ast +⌿ first-axis reduce on vector"
|
|
||||||
(mkrv
|
|
||||||
(apl-eval-ast
|
|
||||||
(mkmon (mkder "⌿" (mkfg "+")) (mkvec (list 1 2 3 4 5)))
|
|
||||||
{}))
|
|
||||||
(list 15))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"eval-ast -¨ each-negate"
|
|
||||||
(mkrv
|
|
||||||
(apl-eval-ast
|
|
||||||
(mkmon (mkder "¨" (mkfg "-")) (mkvec (list 1 2 3 4)))
|
|
||||||
{}))
|
|
||||||
(list -1 -2 -3 -4))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"eval-ast +⍨ commute (double via x+x)"
|
|
||||||
(mkrv
|
|
||||||
(apl-eval-ast (mkmon (mkder "⍨" (mkfg "+")) (mknum 7)) {}))
|
|
||||||
(list 14))
|
|
||||||
|
|
||||||
; ---------- dyadic operators ----------
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"eval-ast outer ∘.× — multiplication table"
|
|
||||||
(mkrv
|
|
||||||
(apl-eval-ast
|
|
||||||
(mkdyd
|
|
||||||
(mkout (mkfg "×"))
|
|
||||||
(mkvec (list 1 2 3))
|
|
||||||
(mkvec (list 1 2 3)))
|
|
||||||
{}))
|
|
||||||
(list 1 2 3 2 4 6 3 6 9))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"eval-ast outer ∘.× shape (3 3)"
|
|
||||||
(mksh
|
|
||||||
(apl-eval-ast
|
|
||||||
(mkdyd
|
|
||||||
(mkout (mkfg "×"))
|
|
||||||
(mkvec (list 1 2 3))
|
|
||||||
(mkvec (list 1 2 3)))
|
|
||||||
{}))
|
|
||||||
(list 3 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"eval-ast inner +.× — dot product"
|
|
||||||
(mkrv
|
|
||||||
(apl-eval-ast
|
|
||||||
(mkdyd
|
|
||||||
(mkdr2 "." (mkfg "+") (mkfg "×"))
|
|
||||||
(mkvec (list 1 2 3))
|
|
||||||
(mkvec (list 4 5 6)))
|
|
||||||
{}))
|
|
||||||
(list 32))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"eval-ast inner ∧.= equal vectors"
|
|
||||||
(mkrv
|
|
||||||
(apl-eval-ast
|
|
||||||
(mkdyd
|
|
||||||
(mkdr2 "." (mkfg "∧") (mkfg "="))
|
|
||||||
(mkvec (list 1 2 3))
|
|
||||||
(mkvec (list 1 2 3)))
|
|
||||||
{}))
|
|
||||||
(list 1))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"eval-ast each-dyadic +¨"
|
|
||||||
(mkrv
|
|
||||||
(apl-eval-ast
|
|
||||||
(mkdyd
|
|
||||||
(mkder "¨" (mkfg "+"))
|
|
||||||
(mkvec (list 1 2 3))
|
|
||||||
(mkvec (list 10 20 30)))
|
|
||||||
{}))
|
|
||||||
(list 11 22 33))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"eval-ast commute -⍨ (subtract swapped)"
|
|
||||||
(mkrv
|
|
||||||
(apl-eval-ast
|
|
||||||
(mkdyd (mkder "⍨" (mkfg "-")) (mknum 5) (mknum 3))
|
|
||||||
{}))
|
|
||||||
(list -2))
|
|
||||||
|
|
||||||
; ---------- nested operators ----------
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"eval-ast +/¨ — sum of each"
|
|
||||||
(mkrv
|
|
||||||
(apl-eval-ast
|
|
||||||
(mkmon (mkder "/" (mkfg "+")) (mkvec (list 10 20 30)))
|
|
||||||
{}))
|
|
||||||
(list 60))
|
|
||||||
@@ -1,359 +0,0 @@
|
|||||||
; APL idiom corpus — classic Roger Hui / Phil Last idioms expressed
|
|
||||||
; through our runtime primitives. Each test names the APL one-liner
|
|
||||||
; and verifies the equivalent runtime call.
|
|
||||||
|
|
||||||
(define mkrv (fn (arr) (get arr :ravel)))
|
|
||||||
(define mksh (fn (arr) (get arr :shape)))
|
|
||||||
|
|
||||||
; ---------- reductions ----------
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"+/⍵ — sum"
|
|
||||||
(mkrv (apl-reduce apl-add (make-array (list 5) (list 1 2 3 4 5))))
|
|
||||||
(list 15))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"(+/⍵)÷⍴⍵ — mean"
|
|
||||||
(mkrv
|
|
||||||
(apl-div
|
|
||||||
(apl-reduce apl-add (make-array (list 5) (list 1 2 3 4 5)))
|
|
||||||
(apl-scalar 5)))
|
|
||||||
(list 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"⌈/⍵ — max"
|
|
||||||
(mkrv (apl-reduce apl-max (make-array (list 6) (list 3 1 4 1 5 9))))
|
|
||||||
(list 9))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"⌊/⍵ — min"
|
|
||||||
(mkrv (apl-reduce apl-min (make-array (list 6) (list 3 1 4 1 5 9))))
|
|
||||||
(list 1))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"(⌈/⍵)-⌊/⍵ — range"
|
|
||||||
(mkrv
|
|
||||||
(apl-sub
|
|
||||||
(apl-reduce apl-max (make-array (list 6) (list 3 1 4 1 5 9)))
|
|
||||||
(apl-reduce apl-min (make-array (list 6) (list 3 1 4 1 5 9)))))
|
|
||||||
(list 8))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"×/⍵ — product"
|
|
||||||
(mkrv (apl-reduce apl-mul (make-array (list 4) (list 1 2 3 4))))
|
|
||||||
(list 24))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"+\\⍵ — running sum"
|
|
||||||
(mkrv (apl-scan apl-add (make-array (list 5) (list 1 2 3 4 5))))
|
|
||||||
(list 1 3 6 10 15))
|
|
||||||
|
|
||||||
; ---------- sort / order ----------
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"⍵[⍋⍵] — sort ascending"
|
|
||||||
(mkrv (apl-quicksort (make-array (list 5) (list 3 1 4 1 5))))
|
|
||||||
(list 1 1 3 4 5))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"⌽⍵ — reverse"
|
|
||||||
(mkrv (apl-reverse (make-array (list 5) (list 1 2 3 4 5))))
|
|
||||||
(list 5 4 3 2 1))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"⊃⌽⍵ — last element"
|
|
||||||
(mkrv
|
|
||||||
(apl-disclose (apl-reverse (make-array (list 4) (list 10 20 30 40)))))
|
|
||||||
(list 40))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"1↑⍵ — first element"
|
|
||||||
(mkrv
|
|
||||||
(apl-take (apl-scalar 1) (make-array (list 4) (list 10 20 30 40))))
|
|
||||||
(list 10))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"1↓⍵ — drop first"
|
|
||||||
(mkrv
|
|
||||||
(apl-drop (apl-scalar 1) (make-array (list 4) (list 10 20 30 40))))
|
|
||||||
(list 20 30 40))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"¯1↓⍵ — drop last"
|
|
||||||
(mkrv
|
|
||||||
(apl-drop (apl-scalar -1) (make-array (list 4) (list 10 20 30 40))))
|
|
||||||
(list 10 20 30))
|
|
||||||
|
|
||||||
; ---------- counts / membership ----------
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"≢⍵ — tally"
|
|
||||||
(mkrv (apl-tally (make-array (list 7) (list 9 8 7 6 5 4 3))))
|
|
||||||
(list 7))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"+/⍵=v — count occurrences of v"
|
|
||||||
(mkrv
|
|
||||||
(apl-reduce
|
|
||||||
apl-add
|
|
||||||
(apl-eq (make-array (list 7) (list 1 2 3 2 1 3 2)) (apl-scalar 2))))
|
|
||||||
(list 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"0=N|M — divisibility test"
|
|
||||||
(mkrv (apl-eq (apl-scalar 0) (apl-mod (apl-scalar 3) (apl-scalar 12))))
|
|
||||||
(list 1))
|
|
||||||
|
|
||||||
; ---------- shape constructors ----------
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"N⍴1 — vector of N ones"
|
|
||||||
(mkrv (apl-reshape (apl-scalar 5) (apl-scalar 1)))
|
|
||||||
(list 1 1 1 1 1))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"(N N)⍴0 — N×N zero matrix"
|
|
||||||
(mkrv (apl-reshape (make-array (list 2) (list 3 3)) (apl-scalar 0)))
|
|
||||||
(list 0 0 0 0 0 0 0 0 0))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"⍳∘.=⍳ — N×N identity matrix"
|
|
||||||
(mkrv
|
|
||||||
(apl-outer apl-eq (apl-iota (apl-scalar 3)) (apl-iota (apl-scalar 3))))
|
|
||||||
(list 1 0 0 0 1 0 0 0 1))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"⍳∘.×⍳ — multiplication table"
|
|
||||||
(mkrv
|
|
||||||
(apl-outer apl-mul (apl-iota (apl-scalar 3)) (apl-iota (apl-scalar 3))))
|
|
||||||
(list 1 2 3 2 4 6 3 6 9))
|
|
||||||
|
|
||||||
; ---------- numerical idioms ----------
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"+\\⍳N — triangular numbers"
|
|
||||||
(mkrv (apl-scan apl-add (apl-iota (apl-scalar 5))))
|
|
||||||
(list 1 3 6 10 15))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"+/⍳N=N×(N+1)÷2 — sum of 1..N"
|
|
||||||
(mkrv (apl-reduce apl-add (apl-iota (apl-scalar 10))))
|
|
||||||
(list 55))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"×/⍳N — factorial via iota"
|
|
||||||
(mkrv (apl-reduce apl-mul (apl-iota (apl-scalar 5))))
|
|
||||||
(list 120))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"2|⍵ — parity (1=odd)"
|
|
||||||
(mkrv (apl-mod (apl-scalar 2) (make-array (list 5) (list 1 2 3 4 5))))
|
|
||||||
(list 1 0 1 0 1))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"+/2|⍵ — count odd"
|
|
||||||
(mkrv
|
|
||||||
(apl-reduce
|
|
||||||
apl-add
|
|
||||||
(apl-mod (apl-scalar 2) (make-array (list 5) (list 1 2 3 4 5)))))
|
|
||||||
(list 3))
|
|
||||||
|
|
||||||
; ---------- boolean idioms ----------
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"∧/⍵ — all-true"
|
|
||||||
(mkrv (apl-reduce apl-and (make-array (list 4) (list 1 1 1 1))))
|
|
||||||
(list 1))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"∧/⍵ — all-true with zero is false"
|
|
||||||
(mkrv (apl-reduce apl-and (make-array (list 4) (list 1 1 0 1))))
|
|
||||||
(list 0))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"∨/⍵ — any-true"
|
|
||||||
(mkrv (apl-reduce apl-or (make-array (list 4) (list 0 0 1 0))))
|
|
||||||
(list 1))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"∨/⍵ — any-true all zero is false"
|
|
||||||
(mkrv (apl-reduce apl-or (make-array (list 4) (list 0 0 0 0))))
|
|
||||||
(list 0))
|
|
||||||
|
|
||||||
; ---------- selection / scaling ----------
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"⍵×⍵ — square each"
|
|
||||||
(mkrv
|
|
||||||
(apl-mul
|
|
||||||
(make-array (list 4) (list 1 2 3 4))
|
|
||||||
(make-array (list 4) (list 1 2 3 4))))
|
|
||||||
(list 1 4 9 16))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"+/⍵×⍵ — sum of squares"
|
|
||||||
(mkrv
|
|
||||||
(apl-reduce
|
|
||||||
apl-add
|
|
||||||
(apl-mul
|
|
||||||
(make-array (list 4) (list 1 2 3 4))
|
|
||||||
(make-array (list 4) (list 1 2 3 4)))))
|
|
||||||
(list 30))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"⍵-(+/⍵)÷⍴⍵ — mean-centered"
|
|
||||||
(mkrv
|
|
||||||
(apl-sub
|
|
||||||
(make-array (list 5) (list 2 4 6 8 10))
|
|
||||||
(apl-div
|
|
||||||
(apl-reduce apl-add (make-array (list 5) (list 2 4 6 8 10)))
|
|
||||||
(apl-scalar 5))))
|
|
||||||
(list -4 -2 0 2 4))
|
|
||||||
|
|
||||||
; ---------- shape / structure ----------
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
",⍵ — ravel"
|
|
||||||
(mkrv (apl-ravel (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list 1 2 3 4 5 6))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"⍴⍴⍵ — rank"
|
|
||||||
(mkrv
|
|
||||||
(apl-shape (apl-shape (make-array (list 2 3) (list 1 2 3 4 5 6)))))
|
|
||||||
(list 2))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"src: +/⍳N → triangular(N)"
|
|
||||||
(mkrv (apl-run "+/⍳100"))
|
|
||||||
(list 5050))
|
|
||||||
|
|
||||||
(apl-test "src: ×/⍳N → N!" (mkrv (apl-run "×/⍳6")) (list 720))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"src: ⌈/V — max"
|
|
||||||
(mkrv (apl-run "⌈/3 1 4 1 5 9 2 6"))
|
|
||||||
(list 9))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"src: ⌊/V — min"
|
|
||||||
(mkrv (apl-run "⌊/3 1 4 1 5 9 2 6"))
|
|
||||||
(list 1))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"src: range = (⌈/V) - ⌊/V"
|
|
||||||
(mkrv (apl-run "(⌈/3 1 4 1 5 9 2 6) - ⌊/3 1 4 1 5 9 2 6"))
|
|
||||||
(list 8))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"src: +\\V — running sum"
|
|
||||||
(mkrv (apl-run "+\\1 2 3 4 5"))
|
|
||||||
(list 1 3 6 10 15))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"src: ×\\V — running product"
|
|
||||||
(mkrv (apl-run "×\\1 2 3 4 5"))
|
|
||||||
(list 1 2 6 24 120))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"src: V × V — squares"
|
|
||||||
(mkrv (apl-run "(⍳5) × ⍳5"))
|
|
||||||
(list 1 4 9 16 25))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"src: +/V × V — sum of squares"
|
|
||||||
(mkrv (apl-run "+/(⍳5) × ⍳5"))
|
|
||||||
(list 55))
|
|
||||||
|
|
||||||
(apl-test "src: ∧/V — all-true" (mkrv (apl-run "∧/1 1 1 1")) (list 1))
|
|
||||||
|
|
||||||
(apl-test "src: ∨/V — any-true" (mkrv (apl-run "∨/0 0 1 0")) (list 1))
|
|
||||||
|
|
||||||
(apl-test "src: 0 = N|M — divides" (mkrv (apl-run "0 = 3 | 12")) (list 1))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"src: 2 | V — parity"
|
|
||||||
(mkrv (apl-run "2 | 1 2 3 4 5 6"))
|
|
||||||
(list 1 0 1 0 1 0))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"src: +/2|V — count odd"
|
|
||||||
(mkrv (apl-run "+/2 | 1 2 3 4 5 6"))
|
|
||||||
(list 3))
|
|
||||||
|
|
||||||
(apl-test "src: ⍴ V" (mkrv (apl-run "⍴ 1 2 3 4 5")) (list 5))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"src: ⍴⍴ M — rank"
|
|
||||||
(mkrv (apl-run "⍴ ⍴ (2 3) ⍴ ⍳6"))
|
|
||||||
(list 2))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"src: N⍴1 — vector of ones"
|
|
||||||
(mkrv (apl-run "5 ⍴ 1"))
|
|
||||||
(list 1 1 1 1 1))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"src: ⍳N ∘.= ⍳N — identity matrix"
|
|
||||||
(mkrv (apl-run "(⍳3) ∘.= ⍳3"))
|
|
||||||
(list 1 0 0 0 1 0 0 0 1))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"src: ⍳N ∘.× ⍳N — multiplication table"
|
|
||||||
(mkrv (apl-run "(⍳3) ∘.× ⍳3"))
|
|
||||||
(list 1 2 3 2 4 6 3 6 9))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"src: V +.× V — dot product"
|
|
||||||
(mkrv (apl-run "1 2 3 +.× 4 5 6"))
|
|
||||||
(list 32))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"src: ∧.= V — vectors equal?"
|
|
||||||
(mkrv (apl-run "1 2 3 ∧.= 1 2 3"))
|
|
||||||
(list 1))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"src: V[1] — first element"
|
|
||||||
(mkrv (apl-run "(10 20 30 40)[1]"))
|
|
||||||
(list 10))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"src: 1↑V — first via take"
|
|
||||||
(mkrv (apl-run "1 ↑ 10 20 30 40"))
|
|
||||||
(list 10))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"src: 1↓V — drop first"
|
|
||||||
(mkrv (apl-run "1 ↓ 10 20 30 40"))
|
|
||||||
(list 20 30 40))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"src: ¯1↓V — drop last"
|
|
||||||
(mkrv (apl-run "¯1 ↓ 10 20 30 40"))
|
|
||||||
(list 10 20 30))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"src: ⌽V — reverse"
|
|
||||||
(mkrv (apl-run "⌽ 1 2 3 4 5"))
|
|
||||||
(list 5 4 3 2 1))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"src: ≢V — tally"
|
|
||||||
(mkrv (apl-run "≢ 9 8 7 6 5 4 3 2 1"))
|
|
||||||
(list 9))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"src: ,M — ravel"
|
|
||||||
(mkrv (apl-run ", (2 3) ⍴ ⍳6"))
|
|
||||||
(list 1 2 3 4 5 6))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"src: A=V — count occurrences"
|
|
||||||
(mkrv (apl-run "+/2 = 1 2 3 2 1 3 2"))
|
|
||||||
(list 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"src: ⌈/(V × V) — max squared"
|
|
||||||
(mkrv (apl-run "⌈/(1 2 3 4 5) × 1 2 3 4 5"))
|
|
||||||
(list 25))
|
|
||||||
@@ -1,791 +0,0 @@
|
|||||||
(define rv (fn (arr) (get arr :ravel)))
|
|
||||||
(define sh (fn (arr) (get arr :shape)))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"reduce +/ vector"
|
|
||||||
(rv (apl-reduce apl-add (make-array (list 5) (list 1 2 3 4 5))))
|
|
||||||
(list 15))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"reduce x/ vector"
|
|
||||||
(rv (apl-reduce apl-mul (make-array (list 4) (list 1 2 3 4))))
|
|
||||||
(list 24))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"reduce max/ vector"
|
|
||||||
(rv (apl-reduce apl-max (make-array (list 5) (list 3 1 4 1 5))))
|
|
||||||
(list 5))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"reduce min/ vector"
|
|
||||||
(rv (apl-reduce apl-min (make-array (list 3) (list 3 1 4))))
|
|
||||||
(list 1))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"reduce and/ all true"
|
|
||||||
(rv (apl-reduce apl-and (make-array (list 3) (list 1 1 1))))
|
|
||||||
(list 1))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"reduce or/ with true"
|
|
||||||
(rv (apl-reduce apl-or (make-array (list 3) (list 0 0 1))))
|
|
||||||
(list 1))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"reduce +/ single element"
|
|
||||||
(rv (apl-reduce apl-add (make-array (list 1) (list 42))))
|
|
||||||
(list 42))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"reduce +/ scalar no-op"
|
|
||||||
(rv (apl-reduce apl-add (apl-scalar 7)))
|
|
||||||
(list 7))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"reduce +/ shape is scalar"
|
|
||||||
(sh (apl-reduce apl-add (make-array (list 4) (list 1 2 3 4))))
|
|
||||||
(list))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"reduce +/ matrix row sums shape"
|
|
||||||
(sh (apl-reduce apl-add (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list 2))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"reduce +/ matrix row sums values"
|
|
||||||
(rv (apl-reduce apl-add (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list 6 15))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"reduce max/ matrix row maxima"
|
|
||||||
(rv (apl-reduce apl-max (make-array (list 2 3) (list 3 1 4 1 5 9))))
|
|
||||||
(list 4 9))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"reduce-first +/ vector same as reduce"
|
|
||||||
(rv (apl-reduce-first apl-add (make-array (list 5) (list 1 2 3 4 5))))
|
|
||||||
(list 15))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"reduce-first +/ matrix col sums shape"
|
|
||||||
(sh
|
|
||||||
(apl-reduce-first apl-add (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"reduce-first +/ matrix col sums values"
|
|
||||||
(rv
|
|
||||||
(apl-reduce-first apl-add (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list 5 7 9))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"reduce-first max/ matrix col maxima"
|
|
||||||
(rv
|
|
||||||
(apl-reduce-first apl-max (make-array (list 3 2) (list 1 9 2 8 3 7))))
|
|
||||||
(list 3 9))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"scan +\\ vector"
|
|
||||||
(rv (apl-scan apl-add (make-array (list 5) (list 1 2 3 4 5))))
|
|
||||||
(list 1 3 6 10 15))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"scan x\\ vector cumulative product"
|
|
||||||
(rv (apl-scan apl-mul (make-array (list 5) (list 1 2 3 4 5))))
|
|
||||||
(list 1 2 6 24 120))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"scan max\\ vector running max"
|
|
||||||
(rv (apl-scan apl-max (make-array (list 5) (list 3 1 4 1 5))))
|
|
||||||
(list 3 3 4 4 5))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"scan min\\ vector running min"
|
|
||||||
(rv (apl-scan apl-min (make-array (list 5) (list 3 1 4 1 5))))
|
|
||||||
(list 3 1 1 1 1))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"scan +\\ single element"
|
|
||||||
(rv (apl-scan apl-add (make-array (list 1) (list 42))))
|
|
||||||
(list 42))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"scan +\\ scalar no-op"
|
|
||||||
(rv (apl-scan apl-add (apl-scalar 7)))
|
|
||||||
(list 7))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"scan +\\ vector preserves shape"
|
|
||||||
(sh (apl-scan apl-add (make-array (list 5) (list 1 2 3 4 5))))
|
|
||||||
(list 5))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"scan +\\ matrix preserves shape"
|
|
||||||
(sh (apl-scan apl-add (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list 2 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"scan +\\ matrix row-wise"
|
|
||||||
(rv (apl-scan apl-add (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list 1 3 6 4 9 15))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"scan max\\ matrix row-wise running max"
|
|
||||||
(rv (apl-scan apl-max (make-array (list 2 3) (list 3 1 4 1 5 9))))
|
|
||||||
(list 3 3 4 1 5 9))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"scan-first +\\ vector same as scan"
|
|
||||||
(rv (apl-scan-first apl-add (make-array (list 5) (list 1 2 3 4 5))))
|
|
||||||
(list 1 3 6 10 15))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"scan-first +\\ scalar no-op"
|
|
||||||
(rv (apl-scan-first apl-add (apl-scalar 9)))
|
|
||||||
(list 9))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"scan-first +\\ matrix preserves shape"
|
|
||||||
(sh (apl-scan-first apl-add (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list 2 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"scan-first +\\ matrix col-wise"
|
|
||||||
(rv (apl-scan-first apl-add (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list 1 2 3 5 7 9))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"scan-first max\\ matrix col-wise running max"
|
|
||||||
(rv (apl-scan-first apl-max (make-array (list 3 2) (list 3 1 4 1 5 9))))
|
|
||||||
(list 3 1 4 1 5 9))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"each negate vector"
|
|
||||||
(rv (apl-each apl-neg-m (make-array (list 3) (list 1 2 3))))
|
|
||||||
(list -1 -2 -3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"each negate vector preserves shape"
|
|
||||||
(sh (apl-each apl-neg-m (make-array (list 3) (list 1 2 3))))
|
|
||||||
(list 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"each reciprocal vector"
|
|
||||||
(rv (apl-each apl-recip (make-array (list 3) (list 1 2 4))))
|
|
||||||
(list 1 (/ 1 2) (/ 1 4)))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"each abs vector"
|
|
||||||
(rv (apl-each apl-abs (make-array (list 4) (list -1 2 -3 4))))
|
|
||||||
(list 1 2 3 4))
|
|
||||||
|
|
||||||
(apl-test "each scalar" (rv (apl-each apl-neg-m (apl-scalar 5))) (list -5))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"each scalar shape"
|
|
||||||
(sh (apl-each apl-neg-m (apl-scalar 5)))
|
|
||||||
(list))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"each negate matrix shape"
|
|
||||||
(sh (apl-each apl-neg-m (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list 2 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"each negate matrix values"
|
|
||||||
(rv (apl-each apl-neg-m (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list -1 -2 -3 -4 -5 -6))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"each-dyadic scalar+scalar"
|
|
||||||
(rv (apl-each-dyadic apl-add (apl-scalar 3) (apl-scalar 4)))
|
|
||||||
(list 7))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"each-dyadic scalar+vector"
|
|
||||||
(rv
|
|
||||||
(apl-each-dyadic
|
|
||||||
apl-add
|
|
||||||
(apl-scalar 10)
|
|
||||||
(make-array (list 3) (list 1 2 3))))
|
|
||||||
(list 11 12 13))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"each-dyadic vector+scalar"
|
|
||||||
(rv
|
|
||||||
(apl-each-dyadic
|
|
||||||
apl-add
|
|
||||||
(make-array (list 3) (list 1 2 3))
|
|
||||||
(apl-scalar 10)))
|
|
||||||
(list 11 12 13))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"each-dyadic vector+vector"
|
|
||||||
(rv
|
|
||||||
(apl-each-dyadic
|
|
||||||
apl-add
|
|
||||||
(make-array (list 3) (list 1 2 3))
|
|
||||||
(make-array (list 3) (list 10 20 30))))
|
|
||||||
(list 11 22 33))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"each-dyadic mul matrix+matrix shape"
|
|
||||||
(sh
|
|
||||||
(apl-each-dyadic
|
|
||||||
apl-mul
|
|
||||||
(make-array (list 2 2) (list 1 2 3 4))
|
|
||||||
(make-array (list 2 2) (list 5 6 7 8))))
|
|
||||||
(list 2 2))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"each-dyadic mul matrix+matrix values"
|
|
||||||
(rv
|
|
||||||
(apl-each-dyadic
|
|
||||||
apl-mul
|
|
||||||
(make-array (list 2 2) (list 1 2 3 4))
|
|
||||||
(make-array (list 2 2) (list 5 6 7 8))))
|
|
||||||
(list 5 12 21 32))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"outer product mult table values"
|
|
||||||
(rv
|
|
||||||
(apl-outer
|
|
||||||
apl-mul
|
|
||||||
(make-array (list 3) (list 1 2 3))
|
|
||||||
(make-array (list 3) (list 1 2 3))))
|
|
||||||
(list 1 2 3 2 4 6 3 6 9))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"outer product mult table shape"
|
|
||||||
(sh
|
|
||||||
(apl-outer
|
|
||||||
apl-mul
|
|
||||||
(make-array (list 3) (list 1 2 3))
|
|
||||||
(make-array (list 3) (list 1 2 3))))
|
|
||||||
(list 3 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"outer product add table values"
|
|
||||||
(rv
|
|
||||||
(apl-outer
|
|
||||||
apl-add
|
|
||||||
(make-array (list 2) (list 1 2))
|
|
||||||
(make-array (list 3) (list 10 20 30))))
|
|
||||||
(list 11 21 31 12 22 32))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"outer product add table shape"
|
|
||||||
(sh
|
|
||||||
(apl-outer
|
|
||||||
apl-add
|
|
||||||
(make-array (list 2) (list 1 2))
|
|
||||||
(make-array (list 3) (list 10 20 30))))
|
|
||||||
(list 2 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"outer product scalar+vector shape"
|
|
||||||
(sh
|
|
||||||
(apl-outer apl-mul (apl-scalar 5) (make-array (list 3) (list 1 2 3))))
|
|
||||||
(list 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"outer product scalar+vector values"
|
|
||||||
(rv
|
|
||||||
(apl-outer apl-mul (apl-scalar 5) (make-array (list 3) (list 1 2 3))))
|
|
||||||
(list 5 10 15))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"outer product vector+scalar shape"
|
|
||||||
(sh
|
|
||||||
(apl-outer apl-mul (make-array (list 3) (list 1 2 3)) (apl-scalar 10)))
|
|
||||||
(list 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"outer product scalar+scalar"
|
|
||||||
(rv (apl-outer apl-mul (apl-scalar 6) (apl-scalar 7)))
|
|
||||||
(list 42))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"outer product scalar+scalar shape"
|
|
||||||
(sh (apl-outer apl-mul (apl-scalar 6) (apl-scalar 7)))
|
|
||||||
(list))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"outer product equality identity matrix values"
|
|
||||||
(rv
|
|
||||||
(apl-outer
|
|
||||||
apl-eq
|
|
||||||
(make-array (list 3) (list 1 2 3))
|
|
||||||
(make-array (list 3) (list 1 2 3))))
|
|
||||||
(list 1 0 0 0 1 0 0 0 1))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"outer product matrix+vector rank doubling shape"
|
|
||||||
(sh
|
|
||||||
(apl-outer
|
|
||||||
apl-add
|
|
||||||
(make-array (list 2 2) (list 1 2 3 4))
|
|
||||||
(make-array (list 3) (list 10 20 30))))
|
|
||||||
(list 2 2 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"outer product matrix+vector rank doubling values"
|
|
||||||
(rv
|
|
||||||
(apl-outer
|
|
||||||
apl-add
|
|
||||||
(make-array (list 2 2) (list 1 2 3 4))
|
|
||||||
(make-array (list 3) (list 10 20 30))))
|
|
||||||
(list 11 21 31 12 22 32 13 23 33 14 24 34))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"inner +.× dot product"
|
|
||||||
(rv
|
|
||||||
(apl-inner
|
|
||||||
apl-add
|
|
||||||
apl-mul
|
|
||||||
(make-array (list 3) (list 1 2 3))
|
|
||||||
(make-array (list 3) (list 4 5 6))))
|
|
||||||
(list 32))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"inner +.× dot product shape is scalar"
|
|
||||||
(sh
|
|
||||||
(apl-inner
|
|
||||||
apl-add
|
|
||||||
apl-mul
|
|
||||||
(make-array (list 3) (list 1 2 3))
|
|
||||||
(make-array (list 3) (list 4 5 6))))
|
|
||||||
(list))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"inner +.× matrix multiply 2x3 * 3x2 shape"
|
|
||||||
(sh
|
|
||||||
(apl-inner
|
|
||||||
apl-add
|
|
||||||
apl-mul
|
|
||||||
(make-array (list 2 3) (list 1 2 3 4 5 6))
|
|
||||||
(make-array (list 3 2) (list 7 8 9 10 11 12))))
|
|
||||||
(list 2 2))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"inner +.× matrix multiply 2x3 * 3x2 values"
|
|
||||||
(rv
|
|
||||||
(apl-inner
|
|
||||||
apl-add
|
|
||||||
apl-mul
|
|
||||||
(make-array (list 2 3) (list 1 2 3 4 5 6))
|
|
||||||
(make-array (list 3 2) (list 7 8 9 10 11 12))))
|
|
||||||
(list 58 64 139 154))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"inner +.× identity matrix 2x2"
|
|
||||||
(rv
|
|
||||||
(apl-inner
|
|
||||||
apl-add
|
|
||||||
apl-mul
|
|
||||||
(make-array (list 2 2) (list 1 0 0 1))
|
|
||||||
(make-array (list 2 2) (list 5 6 7 8))))
|
|
||||||
(list 5 6 7 8))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"inner ∧.= equal vectors"
|
|
||||||
(rv
|
|
||||||
(apl-inner
|
|
||||||
apl-and
|
|
||||||
apl-eq
|
|
||||||
(make-array (list 3) (list 1 2 3))
|
|
||||||
(make-array (list 3) (list 1 2 3))))
|
|
||||||
(list 1))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"inner ∧.= unequal vectors"
|
|
||||||
(rv
|
|
||||||
(apl-inner
|
|
||||||
apl-and
|
|
||||||
apl-eq
|
|
||||||
(make-array (list 3) (list 1 2 3))
|
|
||||||
(make-array (list 3) (list 1 9 3))))
|
|
||||||
(list 0))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"inner +.× matrix * vector shape"
|
|
||||||
(sh
|
|
||||||
(apl-inner
|
|
||||||
apl-add
|
|
||||||
apl-mul
|
|
||||||
(make-array (list 2 3) (list 1 2 3 4 5 6))
|
|
||||||
(make-array (list 3) (list 7 8 9))))
|
|
||||||
(list 2))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"inner +.× matrix * vector values"
|
|
||||||
(rv
|
|
||||||
(apl-inner
|
|
||||||
apl-add
|
|
||||||
apl-mul
|
|
||||||
(make-array (list 2 3) (list 1 2 3 4 5 6))
|
|
||||||
(make-array (list 3) (list 7 8 9))))
|
|
||||||
(list 50 122))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"inner +.× vector * matrix shape"
|
|
||||||
(sh
|
|
||||||
(apl-inner
|
|
||||||
apl-add
|
|
||||||
apl-mul
|
|
||||||
(make-array (list 3) (list 1 2 3))
|
|
||||||
(make-array (list 3 2) (list 4 5 6 7 8 9))))
|
|
||||||
(list 2))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"inner +.× vector * matrix values"
|
|
||||||
(rv
|
|
||||||
(apl-inner
|
|
||||||
apl-add
|
|
||||||
apl-mul
|
|
||||||
(make-array (list 3) (list 1 2 3))
|
|
||||||
(make-array (list 3 2) (list 4 5 6 7 8 9))))
|
|
||||||
(list 40 46))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"inner +.× single-element vectors"
|
|
||||||
(rv
|
|
||||||
(apl-inner
|
|
||||||
apl-add
|
|
||||||
apl-mul
|
|
||||||
(make-array (list 1) (list 6))
|
|
||||||
(make-array (list 1) (list 7))))
|
|
||||||
(list 42))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"commute +⍨ scalar doubles"
|
|
||||||
(rv (apl-commute apl-add (apl-scalar 5)))
|
|
||||||
(list 10))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"commute ×⍨ vector squares"
|
|
||||||
(rv (apl-commute apl-mul (make-array (list 4) (list 1 2 3 4))))
|
|
||||||
(list 1 4 9 16))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"commute +⍨ vector doubles"
|
|
||||||
(rv (apl-commute apl-add (make-array (list 3) (list 1 2 3))))
|
|
||||||
(list 2 4 6))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"commute +⍨ shape preserved"
|
|
||||||
(sh (apl-commute apl-add (make-array (list 3) (list 1 2 3))))
|
|
||||||
(list 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"commute ×⍨ matrix shape preserved"
|
|
||||||
(sh (apl-commute apl-mul (make-array (list 2 2) (list 1 2 3 4))))
|
|
||||||
(list 2 2))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"commute-dyadic -⍨ swaps subtraction"
|
|
||||||
(rv (apl-commute-dyadic apl-sub (apl-scalar 5) (apl-scalar 3)))
|
|
||||||
(list -2))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"commute-dyadic ÷⍨ swaps division"
|
|
||||||
(rv (apl-commute-dyadic apl-div (apl-scalar 4) (apl-scalar 12)))
|
|
||||||
(list 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"commute-dyadic -⍨ on vectors"
|
|
||||||
(rv
|
|
||||||
(apl-commute-dyadic
|
|
||||||
apl-sub
|
|
||||||
(make-array (list 3) (list 10 20 30))
|
|
||||||
(make-array (list 3) (list 1 2 3))))
|
|
||||||
(list -9 -18 -27))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"commute-dyadic +⍨ commutative same result"
|
|
||||||
(rv
|
|
||||||
(apl-commute-dyadic
|
|
||||||
apl-add
|
|
||||||
(make-array (list 3) (list 1 2 3))
|
|
||||||
(make-array (list 3) (list 10 20 30))))
|
|
||||||
(list 11 22 33))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"commute-dyadic ×⍨ commutative same result"
|
|
||||||
(rv
|
|
||||||
(apl-commute-dyadic
|
|
||||||
apl-mul
|
|
||||||
(make-array (list 3) (list 2 3 4))
|
|
||||||
(make-array (list 3) (list 5 6 7))))
|
|
||||||
(list 10 18 28))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"compose -∘| scalar (negative abs)"
|
|
||||||
(rv (apl-compose apl-neg-m apl-abs (apl-scalar -7)))
|
|
||||||
(list -7))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"compose -∘| vector"
|
|
||||||
(rv
|
|
||||||
(apl-compose apl-neg-m apl-abs (make-array (list 4) (list -1 2 -3 4))))
|
|
||||||
(list -1 -2 -3 -4))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"compose ⌊∘- (floor of negate)"
|
|
||||||
(rv (apl-compose apl-floor apl-neg-m (make-array (list 3) (list 1 2 3))))
|
|
||||||
(list -1 -2 -3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"compose -∘| matrix shape preserved"
|
|
||||||
(sh
|
|
||||||
(apl-compose apl-neg-m apl-abs (make-array (list 2 2) (list -1 2 -3 4))))
|
|
||||||
(list 2 2))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"compose-dyadic +∘- equals subtract scalar"
|
|
||||||
(rv (apl-compose-dyadic apl-add apl-neg-m (apl-scalar 10) (apl-scalar 3)))
|
|
||||||
(list 7))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"compose-dyadic +∘- equals subtract vector"
|
|
||||||
(rv
|
|
||||||
(apl-compose-dyadic
|
|
||||||
apl-add
|
|
||||||
apl-neg-m
|
|
||||||
(make-array (list 3) (list 10 20 30))
|
|
||||||
(make-array (list 3) (list 1 2 3))))
|
|
||||||
(list 9 18 27))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"compose-dyadic -∘| (subtract abs)"
|
|
||||||
(rv (apl-compose-dyadic apl-sub apl-abs (apl-scalar 10) (apl-scalar -3)))
|
|
||||||
(list 7))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"compose-dyadic ×∘- (multiply by negative)"
|
|
||||||
(rv
|
|
||||||
(apl-compose-dyadic
|
|
||||||
apl-mul
|
|
||||||
apl-neg-m
|
|
||||||
(make-array (list 3) (list 2 3 4))
|
|
||||||
(make-array (list 3) (list 1 2 3))))
|
|
||||||
(list -2 -6 -12))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"compose-dyadic shape preserved"
|
|
||||||
(sh
|
|
||||||
(apl-compose-dyadic
|
|
||||||
apl-add
|
|
||||||
apl-neg-m
|
|
||||||
(make-array (list 2 3) (list 1 2 3 4 5 6))
|
|
||||||
(make-array (list 2 3) (list 1 1 1 1 1 1))))
|
|
||||||
(list 2 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"power n=0 identity"
|
|
||||||
(rv (apl-power (fn (a) (apl-add a (apl-scalar 1))) 0 (apl-scalar 5)))
|
|
||||||
(list 5))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"power increment by 3"
|
|
||||||
(rv (apl-power (fn (a) (apl-add a (apl-scalar 1))) 3 (apl-scalar 0)))
|
|
||||||
(list 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"power double 4 times = 16"
|
|
||||||
(rv (apl-power (fn (a) (apl-mul a (apl-scalar 2))) 4 (apl-scalar 1)))
|
|
||||||
(list 16))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"power on vector +5"
|
|
||||||
(rv
|
|
||||||
(apl-power
|
|
||||||
(fn (a) (apl-add a (apl-scalar 1)))
|
|
||||||
5
|
|
||||||
(make-array (list 3) (list 1 2 3))))
|
|
||||||
(list 6 7 8))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"power on vector preserves shape"
|
|
||||||
(sh
|
|
||||||
(apl-power
|
|
||||||
(fn (a) (apl-add a (apl-scalar 1)))
|
|
||||||
5
|
|
||||||
(make-array (list 3) (list 1 2 3))))
|
|
||||||
(list 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"power on matrix"
|
|
||||||
(rv
|
|
||||||
(apl-power
|
|
||||||
(fn (a) (apl-mul a (apl-scalar 3)))
|
|
||||||
2
|
|
||||||
(make-array (list 2 2) (list 1 2 3 4))))
|
|
||||||
(list 9 18 27 36))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"power-fixed identity stops immediately"
|
|
||||||
(rv (apl-power-fixed (fn (a) a) (make-array (list 3) (list 1 2 3))))
|
|
||||||
(list 1 2 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"power-fixed floor half scalar to 0"
|
|
||||||
(rv
|
|
||||||
(apl-power-fixed
|
|
||||||
(fn (a) (apl-floor (apl-div a (apl-scalar 2))))
|
|
||||||
(apl-scalar 100)))
|
|
||||||
(list 0))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"power-fixed shape preserved"
|
|
||||||
(sh
|
|
||||||
(apl-power-fixed (fn (a) a) (make-array (list 2 2) (list 1 2 3 4))))
|
|
||||||
(list 2 2))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"rank tally⍤1 row tallies"
|
|
||||||
(rv (apl-rank apl-tally 1 (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list 3 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"rank tally⍤1 row tallies shape"
|
|
||||||
(sh (apl-rank apl-tally 1 (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list 2))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"rank neg⍤0 vector scalar cells"
|
|
||||||
(rv (apl-rank apl-neg-m 0 (make-array (list 3) (list 1 2 3))))
|
|
||||||
(list -1 -2 -3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"rank neg⍤0 vector preserves shape"
|
|
||||||
(sh (apl-rank apl-neg-m 0 (make-array (list 3) (list 1 2 3))))
|
|
||||||
(list 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"rank neg⍤1 matrix per-row"
|
|
||||||
(rv (apl-rank apl-neg-m 1 (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list -1 -2 -3 -4 -5 -6))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"rank neg⍤1 matrix preserves shape"
|
|
||||||
(sh (apl-rank apl-neg-m 1 (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list 2 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"rank k>=rank fallthrough"
|
|
||||||
(rv (apl-rank apl-tally 5 (make-array (list 4) (list 1 2 3 4))))
|
|
||||||
(list 4))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"rank tally⍤2 whole matrix tally"
|
|
||||||
(rv
|
|
||||||
(apl-rank
|
|
||||||
apl-tally
|
|
||||||
2
|
|
||||||
(make-array (list 3 5) (list 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15))))
|
|
||||||
(list 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"rank reverse⍤1 matrix reverse rows"
|
|
||||||
(rv (apl-rank apl-reverse 1 (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list 3 2 1 6 5 4))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"rank tally⍤1 3x4 row tallies"
|
|
||||||
(rv
|
|
||||||
(apl-rank
|
|
||||||
apl-tally
|
|
||||||
1
|
|
||||||
(make-array (list 3 4) (list 1 2 3 4 5 6 7 8 9 10 11 12))))
|
|
||||||
(list 4 4 4))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"at-replace single index"
|
|
||||||
(rv
|
|
||||||
(apl-at-replace
|
|
||||||
(apl-scalar 99)
|
|
||||||
(make-array (list 1) (list 2))
|
|
||||||
(make-array (list 5) (list 1 2 3 4 5))))
|
|
||||||
(list 1 99 3 4 5))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"at-replace multiple indices vector vals"
|
|
||||||
(rv
|
|
||||||
(apl-at-replace
|
|
||||||
(make-array (list 2) (list 99 88))
|
|
||||||
(make-array (list 2) (list 2 4))
|
|
||||||
(make-array (list 5) (list 1 2 3 4 5))))
|
|
||||||
(list 1 99 3 88 5))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"at-replace scalar broadcast"
|
|
||||||
(rv
|
|
||||||
(apl-at-replace
|
|
||||||
(apl-scalar 0)
|
|
||||||
(make-array (list 3) (list 1 3 5))
|
|
||||||
(make-array (list 5) (list 10 20 30 40 50))))
|
|
||||||
(list 0 20 0 40 0))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"at-replace preserves shape"
|
|
||||||
(sh
|
|
||||||
(apl-at-replace
|
|
||||||
(apl-scalar 99)
|
|
||||||
(make-array (list 1) (list 2))
|
|
||||||
(make-array (list 5) (list 1 2 3 4 5))))
|
|
||||||
(list 5))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"at-replace last index"
|
|
||||||
(rv
|
|
||||||
(apl-at-replace
|
|
||||||
(apl-scalar 99)
|
|
||||||
(make-array (list 1) (list 5))
|
|
||||||
(make-array (list 5) (list 1 2 3 4 5))))
|
|
||||||
(list 1 2 3 4 99))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"at-replace on matrix linear-index"
|
|
||||||
(rv
|
|
||||||
(apl-at-replace
|
|
||||||
(apl-scalar 99)
|
|
||||||
(make-array (list 1) (list 3))
|
|
||||||
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list 1 2 99 4 5 6))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"at-apply negate at indices"
|
|
||||||
(rv
|
|
||||||
(apl-at-apply
|
|
||||||
apl-neg-m
|
|
||||||
(make-array (list 3) (list 1 3 5))
|
|
||||||
(make-array (list 5) (list 1 2 3 4 5))))
|
|
||||||
(list -1 2 -3 4 -5))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"at-apply double at index 1"
|
|
||||||
(rv
|
|
||||||
(apl-at-apply
|
|
||||||
(fn (a) (apl-mul a (apl-scalar 2)))
|
|
||||||
(make-array (list 1) (list 1))
|
|
||||||
(make-array (list 2) (list 5 10))))
|
|
||||||
(list 10 10))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"at-apply preserves shape"
|
|
||||||
(sh
|
|
||||||
(apl-at-apply
|
|
||||||
apl-neg-m
|
|
||||||
(make-array (list 2) (list 1 3))
|
|
||||||
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list 2 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"at-apply on matrix linear-index"
|
|
||||||
(rv
|
|
||||||
(apl-at-apply
|
|
||||||
apl-neg-m
|
|
||||||
(make-array (list 2) (list 1 6))
|
|
||||||
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list -1 2 3 4 5 -6))
|
|
||||||
@@ -1,340 +0,0 @@
|
|||||||
(define apl-test-count 0)
|
|
||||||
(define apl-test-pass 0)
|
|
||||||
(define apl-test-fails (list))
|
|
||||||
|
|
||||||
(define apl-test
|
|
||||||
(fn (name actual expected)
|
|
||||||
(begin
|
|
||||||
(set! apl-test-count (+ apl-test-count 1))
|
|
||||||
(if (= actual expected)
|
|
||||||
(set! apl-test-pass (+ apl-test-pass 1))
|
|
||||||
(append! apl-test-fails {:name name :actual actual :expected expected})))))
|
|
||||||
|
|
||||||
(define tok-types
|
|
||||||
(fn (src)
|
|
||||||
(map (fn (t) (get t :type)) (apl-tokenize src))))
|
|
||||||
|
|
||||||
(define tok-values
|
|
||||||
(fn (src)
|
|
||||||
(map (fn (t) (get t :value)) (apl-tokenize src))))
|
|
||||||
|
|
||||||
(define tok-count
|
|
||||||
(fn (src)
|
|
||||||
(len (apl-tokenize src))))
|
|
||||||
|
|
||||||
(define tok-type-at
|
|
||||||
(fn (src i)
|
|
||||||
(get (nth (apl-tokenize src) i) :type)))
|
|
||||||
|
|
||||||
(define tok-value-at
|
|
||||||
(fn (src i)
|
|
||||||
(get (nth (apl-tokenize src) i) :value)))
|
|
||||||
|
|
||||||
(apl-test "empty: no tokens" (tok-count "") 0)
|
|
||||||
(apl-test "empty: whitespace only" (tok-count " ") 0)
|
|
||||||
(apl-test "num: zero" (tok-values "0") (list 0))
|
|
||||||
(apl-test "num: positive" (tok-values "42") (list 42))
|
|
||||||
(apl-test "num: large" (tok-values "12345") (list 12345))
|
|
||||||
(apl-test "num: negative" (tok-values "¯5") (list -5))
|
|
||||||
(apl-test "num: negative zero" (tok-values "¯0") (list 0))
|
|
||||||
(apl-test "num: strand count" (tok-count "1 2 3") 3)
|
|
||||||
(apl-test "num: strand types" (tok-types "1 2 3") (list :num :num :num))
|
|
||||||
(apl-test "num: strand values" (tok-values "1 2 3") (list 1 2 3))
|
|
||||||
(apl-test "num: neg in strand" (tok-values "1 ¯2 3") (list 1 -2 3))
|
|
||||||
(apl-test "str: empty" (tok-values "''") (list ""))
|
|
||||||
(apl-test "str: single char" (tok-values "'a'") (list "a"))
|
|
||||||
(apl-test "str: word" (tok-values "'hello'") (list "hello"))
|
|
||||||
(apl-test "str: escaped quote" (tok-values "''''") (list "'"))
|
|
||||||
(apl-test "str: type" (tok-types "'abc'") (list :str))
|
|
||||||
(apl-test "name: simple" (tok-values "foo") (list "foo"))
|
|
||||||
(apl-test "name: type" (tok-types "foo") (list :name))
|
|
||||||
(apl-test "name: mixed case" (tok-values "MyVar") (list "MyVar"))
|
|
||||||
(apl-test "name: with digits" (tok-values "x1") (list "x1"))
|
|
||||||
(apl-test "name: system var" (tok-values "⎕IO") (list "⎕IO"))
|
|
||||||
(apl-test "name: system var type" (tok-types "⎕IO") (list :name))
|
|
||||||
(apl-test "glyph: plus" (tok-types "+") (list :glyph))
|
|
||||||
(apl-test "glyph: plus value" (tok-values "+") (list "+"))
|
|
||||||
(apl-test "glyph: iota" (tok-values "⍳") (list "⍳"))
|
|
||||||
(apl-test "glyph: reduce" (tok-values "+/") (list "+" "/"))
|
|
||||||
(apl-test "glyph: floor" (tok-values "⌊") (list "⌊"))
|
|
||||||
(apl-test "glyph: rho" (tok-values "⍴") (list "⍴"))
|
|
||||||
(apl-test "glyph: alpha omega" (tok-types "⍺ ⍵") (list :glyph :glyph))
|
|
||||||
(apl-test "punct: lparen" (tok-types "(") (list :lparen))
|
|
||||||
(apl-test "punct: rparen" (tok-types ")") (list :rparen))
|
|
||||||
(apl-test "punct: brackets" (tok-types "[42]") (list :lbracket :num :rbracket))
|
|
||||||
(apl-test "punct: braces" (tok-types "{}") (list :lbrace :rbrace))
|
|
||||||
(apl-test "punct: semi" (tok-types ";") (list :semi))
|
|
||||||
(apl-test "assign: arrow" (tok-types "x←1") (list :name :assign :num))
|
|
||||||
(apl-test "diamond: separator" (tok-types "1⋄2") (list :num :diamond :num))
|
|
||||||
(apl-test "newline: emitted" (tok-types "1\n2") (list :num :newline :num))
|
|
||||||
(apl-test "comment: skipped" (tok-count "⍝ ignore me") 0)
|
|
||||||
(apl-test "comment: rest ignored" (tok-count "1 ⍝ note") 1)
|
|
||||||
(apl-test "colon: bare" (tok-types ":") (list :colon))
|
|
||||||
(apl-test "keyword: If" (tok-values ":If") (list ":If"))
|
|
||||||
(apl-test "keyword: type" (tok-types ":While") (list :keyword))
|
|
||||||
(apl-test "keyword: EndFor" (tok-values ":EndFor") (list ":EndFor"))
|
|
||||||
(apl-test "expr: +/ ⍳ 5" (tok-types "+/ ⍳ 5") (list :glyph :glyph :glyph :num))
|
|
||||||
(apl-test "expr: x←42" (tok-count "x←42") 3)
|
|
||||||
(apl-test "expr: dfn body" (tok-types "{⍺+⍵}")
|
|
||||||
(list :lbrace :glyph :glyph :glyph :rbrace))
|
|
||||||
|
|
||||||
(define apl-tokenize-test-summary
|
|
||||||
(str "tokenizer " apl-test-pass "/" apl-test-count
|
|
||||||
(if (= (len apl-test-fails) 0) "" (str " FAILS: " apl-test-fails))))
|
|
||||||
|
|
||||||
; ===========================================================================
|
|
||||||
; Parser tests
|
|
||||||
; ===========================================================================
|
|
||||||
|
|
||||||
; Helper: parse an APL source string and return the AST
|
|
||||||
(define parse
|
|
||||||
(fn (src) (parse-apl src)))
|
|
||||||
|
|
||||||
; Helper: build an expected AST node using keyword-tagged lists
|
|
||||||
(define num-node (fn (n) (list :num n)))
|
|
||||||
(define str-node (fn (s) (list :str s)))
|
|
||||||
(define name-node (fn (n) (list :name n)))
|
|
||||||
(define fn-node (fn (g) (list :fn-glyph g)))
|
|
||||||
(define fn-nm (fn (n) (list :fn-name n)))
|
|
||||||
(define assign-node (fn (nm expr) (list :assign nm expr)))
|
|
||||||
(define monad-node (fn (f a) (list :monad f a)))
|
|
||||||
(define dyad-node (fn (f l r) (list :dyad f l r)))
|
|
||||||
(define derived-fn (fn (op f) (list :derived-fn op f)))
|
|
||||||
(define derived-fn2 (fn (op f g) (list :derived-fn2 op f g)))
|
|
||||||
(define outer-node (fn (f) (list :outer "∘." f)))
|
|
||||||
(define guard-node (fn (c e) (list :guard c e)))
|
|
||||||
|
|
||||||
; ---- numeric literals ----
|
|
||||||
|
|
||||||
(apl-test "parse: num literal"
|
|
||||||
(parse "42")
|
|
||||||
(num-node 42))
|
|
||||||
|
|
||||||
(apl-test "parse: negative num"
|
|
||||||
(parse "¯3")
|
|
||||||
(num-node -3))
|
|
||||||
|
|
||||||
(apl-test "parse: zero"
|
|
||||||
(parse "0")
|
|
||||||
(num-node 0))
|
|
||||||
|
|
||||||
; ---- string literals ----
|
|
||||||
|
|
||||||
(apl-test "parse: str literal"
|
|
||||||
(parse "'hello'")
|
|
||||||
(str-node "hello"))
|
|
||||||
|
|
||||||
(apl-test "parse: empty str"
|
|
||||||
(parse "''")
|
|
||||||
(str-node ""))
|
|
||||||
|
|
||||||
; ---- name reference ----
|
|
||||||
|
|
||||||
(apl-test "parse: name"
|
|
||||||
(parse "x")
|
|
||||||
(name-node "x"))
|
|
||||||
|
|
||||||
(apl-test "parse: system name"
|
|
||||||
(parse "⎕IO")
|
|
||||||
(name-node "⎕IO"))
|
|
||||||
|
|
||||||
; ---- strands (vec nodes) ----
|
|
||||||
|
|
||||||
(apl-test "parse: strand 3 nums"
|
|
||||||
(parse "1 2 3")
|
|
||||||
(list :vec (num-node 1) (num-node 2) (num-node 3)))
|
|
||||||
|
|
||||||
(apl-test "parse: strand 2 nums"
|
|
||||||
(parse "1 2")
|
|
||||||
(list :vec (num-node 1) (num-node 2)))
|
|
||||||
|
|
||||||
(apl-test "parse: strand with negatives"
|
|
||||||
(parse "1 ¯2 3")
|
|
||||||
(list :vec (num-node 1) (num-node -2) (num-node 3)))
|
|
||||||
|
|
||||||
; ---- assignment ----
|
|
||||||
|
|
||||||
(apl-test "parse: assignment"
|
|
||||||
(parse "x←42")
|
|
||||||
(assign-node "x" (num-node 42)))
|
|
||||||
|
|
||||||
(apl-test "parse: assignment with spaces"
|
|
||||||
(parse "x ← 42")
|
|
||||||
(assign-node "x" (num-node 42)))
|
|
||||||
|
|
||||||
(apl-test "parse: assignment of expr"
|
|
||||||
(parse "r←2+3")
|
|
||||||
(assign-node "r" (dyad-node (fn-node "+") (num-node 2) (num-node 3))))
|
|
||||||
|
|
||||||
; ---- monadic functions ----
|
|
||||||
|
|
||||||
(apl-test "parse: monadic iota"
|
|
||||||
(parse "⍳5")
|
|
||||||
(monad-node (fn-node "⍳") (num-node 5)))
|
|
||||||
|
|
||||||
(apl-test "parse: monadic iota with space"
|
|
||||||
(parse "⍳ 5")
|
|
||||||
(monad-node (fn-node "⍳") (num-node 5)))
|
|
||||||
|
|
||||||
(apl-test "parse: monadic negate"
|
|
||||||
(parse "-3")
|
|
||||||
(monad-node (fn-node "-") (num-node 3)))
|
|
||||||
|
|
||||||
(apl-test "parse: monadic floor"
|
|
||||||
(parse "⌊2")
|
|
||||||
(monad-node (fn-node "⌊") (num-node 2)))
|
|
||||||
|
|
||||||
(apl-test "parse: monadic of name"
|
|
||||||
(parse "⍴x")
|
|
||||||
(monad-node (fn-node "⍴") (name-node "x")))
|
|
||||||
|
|
||||||
; ---- dyadic functions ----
|
|
||||||
|
|
||||||
(apl-test "parse: dyadic plus"
|
|
||||||
(parse "2+3")
|
|
||||||
(dyad-node (fn-node "+") (num-node 2) (num-node 3)))
|
|
||||||
|
|
||||||
(apl-test "parse: dyadic times"
|
|
||||||
(parse "2×3")
|
|
||||||
(dyad-node (fn-node "×") (num-node 2) (num-node 3)))
|
|
||||||
|
|
||||||
(apl-test "parse: dyadic with names"
|
|
||||||
(parse "x+y")
|
|
||||||
(dyad-node (fn-node "+") (name-node "x") (name-node "y")))
|
|
||||||
|
|
||||||
; ---- right-to-left evaluation ----
|
|
||||||
|
|
||||||
(apl-test "parse: right-to-left 2×3+4"
|
|
||||||
(parse "2×3+4")
|
|
||||||
(dyad-node (fn-node "×") (num-node 2)
|
|
||||||
(dyad-node (fn-node "+") (num-node 3) (num-node 4))))
|
|
||||||
|
|
||||||
(apl-test "parse: right-to-left chain"
|
|
||||||
(parse "1+2×3-4")
|
|
||||||
(dyad-node (fn-node "+") (num-node 1)
|
|
||||||
(dyad-node (fn-node "×") (num-node 2)
|
|
||||||
(dyad-node (fn-node "-") (num-node 3) (num-node 4)))))
|
|
||||||
|
|
||||||
; ---- parenthesized subexpressions ----
|
|
||||||
|
|
||||||
(apl-test "parse: parens override order"
|
|
||||||
(parse "(2+3)×4")
|
|
||||||
(dyad-node (fn-node "×")
|
|
||||||
(dyad-node (fn-node "+") (num-node 2) (num-node 3))
|
|
||||||
(num-node 4)))
|
|
||||||
|
|
||||||
(apl-test "parse: nested parens"
|
|
||||||
(parse "((2+3))")
|
|
||||||
(dyad-node (fn-node "+") (num-node 2) (num-node 3)))
|
|
||||||
|
|
||||||
(apl-test "parse: paren in dyadic right"
|
|
||||||
(parse "2×(3+4)")
|
|
||||||
(dyad-node (fn-node "×") (num-node 2)
|
|
||||||
(dyad-node (fn-node "+") (num-node 3) (num-node 4))))
|
|
||||||
|
|
||||||
; ---- operators → derived functions ----
|
|
||||||
|
|
||||||
(apl-test "parse: reduce +"
|
|
||||||
(parse "+/x")
|
|
||||||
(monad-node (derived-fn "/" (fn-node "+")) (name-node "x")))
|
|
||||||
|
|
||||||
(apl-test "parse: reduce iota"
|
|
||||||
(parse "+/⍳5")
|
|
||||||
(monad-node (derived-fn "/" (fn-node "+"))
|
|
||||||
(monad-node (fn-node "⍳") (num-node 5))))
|
|
||||||
|
|
||||||
(apl-test "parse: scan"
|
|
||||||
(parse "+\\x")
|
|
||||||
(monad-node (derived-fn "\\" (fn-node "+")) (name-node "x")))
|
|
||||||
|
|
||||||
(apl-test "parse: each"
|
|
||||||
(parse "⍳¨x")
|
|
||||||
(monad-node (derived-fn "¨" (fn-node "⍳")) (name-node "x")))
|
|
||||||
|
|
||||||
(apl-test "parse: commute"
|
|
||||||
(parse "-⍨3")
|
|
||||||
(monad-node (derived-fn "⍨" (fn-node "-")) (num-node 3)))
|
|
||||||
|
|
||||||
(apl-test "parse: stacked ops"
|
|
||||||
(parse "+/¨x")
|
|
||||||
(monad-node (derived-fn "¨" (derived-fn "/" (fn-node "+"))) (name-node "x")))
|
|
||||||
|
|
||||||
; ---- outer product ----
|
|
||||||
|
|
||||||
(apl-test "parse: outer product monadic"
|
|
||||||
(parse "∘.×")
|
|
||||||
(outer-node (fn-node "×")))
|
|
||||||
|
|
||||||
(apl-test "parse: outer product dyadic names"
|
|
||||||
(parse "x ∘.× y")
|
|
||||||
(dyad-node (outer-node (fn-node "×")) (name-node "x") (name-node "y")))
|
|
||||||
|
|
||||||
(apl-test "parse: outer product dyadic strands"
|
|
||||||
(parse "1 2 3 ∘.× 4 5 6")
|
|
||||||
(dyad-node (outer-node (fn-node "×"))
|
|
||||||
(list :vec (num-node 1) (num-node 2) (num-node 3))
|
|
||||||
(list :vec (num-node 4) (num-node 5) (num-node 6))))
|
|
||||||
|
|
||||||
; ---- inner product ----
|
|
||||||
|
|
||||||
(apl-test "parse: inner product"
|
|
||||||
(parse "+.×")
|
|
||||||
(derived-fn2 "." (fn-node "+") (fn-node "×")))
|
|
||||||
|
|
||||||
(apl-test "parse: inner product applied"
|
|
||||||
(parse "a +.× b")
|
|
||||||
(dyad-node (derived-fn2 "." (fn-node "+") (fn-node "×"))
|
|
||||||
(name-node "a") (name-node "b")))
|
|
||||||
|
|
||||||
; ---- dfn (anonymous function) ----
|
|
||||||
|
|
||||||
(apl-test "parse: simple dfn"
|
|
||||||
(parse "{⍺+⍵}")
|
|
||||||
(list :dfn (dyad-node (fn-node "+") (name-node "⍺") (name-node "⍵"))))
|
|
||||||
|
|
||||||
(apl-test "parse: monadic dfn"
|
|
||||||
(parse "{⍵×2}")
|
|
||||||
(list :dfn (dyad-node (fn-node "×") (name-node "⍵") (num-node 2))))
|
|
||||||
|
|
||||||
(apl-test "parse: dfn self-ref"
|
|
||||||
(parse "{⍵≤1:1 ⋄ ⍵×∇ ⍵-1}")
|
|
||||||
(list :dfn
|
|
||||||
(guard-node (dyad-node (fn-node "≤") (name-node "⍵") (num-node 1)) (num-node 1))
|
|
||||||
(dyad-node (fn-node "×") (name-node "⍵")
|
|
||||||
(monad-node (fn-node "∇") (dyad-node (fn-node "-") (name-node "⍵") (num-node 1))))))
|
|
||||||
|
|
||||||
; ---- dfn applied ----
|
|
||||||
|
|
||||||
(apl-test "parse: dfn as function"
|
|
||||||
(parse "{⍺+⍵} 3")
|
|
||||||
(monad-node
|
|
||||||
(list :dfn (dyad-node (fn-node "+") (name-node "⍺") (name-node "⍵")))
|
|
||||||
(num-node 3)))
|
|
||||||
|
|
||||||
; ---- multi-statement ----
|
|
||||||
|
|
||||||
(apl-test "parse: diamond separator"
|
|
||||||
(let ((result (parse "x←1 ⋄ x+2")))
|
|
||||||
(= (first result) :program))
|
|
||||||
true)
|
|
||||||
|
|
||||||
(apl-test "parse: diamond first stmt"
|
|
||||||
(let ((result (parse "x←1 ⋄ x+2")))
|
|
||||||
(nth result 1))
|
|
||||||
(assign-node "x" (num-node 1)))
|
|
||||||
|
|
||||||
(apl-test "parse: diamond second stmt"
|
|
||||||
(let ((result (parse "x←1 ⋄ x+2")))
|
|
||||||
(nth result 2))
|
|
||||||
(dyad-node (fn-node "+") (name-node "x") (num-node 2)))
|
|
||||||
|
|
||||||
; ---- combined summary ----
|
|
||||||
|
|
||||||
(define apl-parse-test-count (- apl-test-count 46))
|
|
||||||
(define apl-parse-test-pass (- apl-test-pass 46))
|
|
||||||
|
|
||||||
(define apl-test-summary
|
|
||||||
(str
|
|
||||||
"tokenizer 46/46 | "
|
|
||||||
"parser " apl-parse-test-pass "/" apl-parse-test-count
|
|
||||||
(if (= (len apl-test-fails) 0) "" (str " FAILS: " apl-test-fails))))
|
|
||||||
@@ -1,180 +0,0 @@
|
|||||||
; End-to-end pipeline tests: source string → tokenize → parse → eval-ast → array.
|
|
||||||
; Verifies the full stack as a single function call (apl-run).
|
|
||||||
|
|
||||||
(define mkrv (fn (arr) (get arr :ravel)))
|
|
||||||
(define mksh (fn (arr) (get arr :shape)))
|
|
||||||
|
|
||||||
; ---------- scalars ----------
|
|
||||||
|
|
||||||
(apl-test "apl-run \"42\" → scalar 42" (mkrv (apl-run "42")) (list 42))
|
|
||||||
|
|
||||||
(apl-test "apl-run \"¯7\" → scalar -7" (mkrv (apl-run "¯7")) (list -7))
|
|
||||||
|
|
||||||
; ---------- strands ----------
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"apl-run \"1 2 3\" → vector"
|
|
||||||
(mkrv (apl-run "1 2 3"))
|
|
||||||
(list 1 2 3))
|
|
||||||
|
|
||||||
(apl-test "apl-run \"1 2 3\" shape" (mksh (apl-run "1 2 3")) (list 3))
|
|
||||||
|
|
||||||
; ---------- dyadic arithmetic ----------
|
|
||||||
|
|
||||||
(apl-test "apl-run \"2 + 3\" → 5" (mkrv (apl-run "2 + 3")) (list 5))
|
|
||||||
|
|
||||||
(apl-run "2 × 3 + 4") ; right-to-left
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"apl-run \"2 × 3 + 4\" → 14 (right-to-left)"
|
|
||||||
(mkrv (apl-run "2 × 3 + 4"))
|
|
||||||
(list 14))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"apl-run \"1 2 3 + 4 5 6\" → 5 7 9"
|
|
||||||
(mkrv (apl-run "1 2 3 + 4 5 6"))
|
|
||||||
(list 5 7 9))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"apl-run \"3 × 1 2 3 4\" → scalar broadcast"
|
|
||||||
(mkrv (apl-run "3 × 1 2 3 4"))
|
|
||||||
(list 3 6 9 12))
|
|
||||||
|
|
||||||
; ---------- monadic primitives ----------
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"apl-run \"⍳5\" → 1..5"
|
|
||||||
(mkrv (apl-run "⍳5"))
|
|
||||||
(list 1 2 3 4 5))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"apl-run \"-3\" → -3 (monadic negate)"
|
|
||||||
(mkrv (apl-run "-3"))
|
|
||||||
(list -3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"apl-run \"⌈/ 1 3 9 5 7\" → 9 (max-reduce)"
|
|
||||||
(mkrv (apl-run "⌈/ 1 3 9 5 7"))
|
|
||||||
(list 9))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"apl-run \"⌊/ 4 7 2 9 1 3\" → 1 (min-reduce)"
|
|
||||||
(mkrv (apl-run "⌊/ 4 7 2 9 1 3"))
|
|
||||||
(list 1))
|
|
||||||
|
|
||||||
; ---------- operators ----------
|
|
||||||
|
|
||||||
(apl-test "apl-run \"+/⍳5\" → 15" (mkrv (apl-run "+/⍳5")) (list 15))
|
|
||||||
|
|
||||||
(apl-test "apl-run \"×/⍳5\" → 120" (mkrv (apl-run "×/⍳5")) (list 120))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"apl-run \"⌈/3 1 4 1 5 9 2\" → 9"
|
|
||||||
(mkrv (apl-run "⌈/3 1 4 1 5 9 2"))
|
|
||||||
(list 9))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"apl-run \"+\\\\⍳5\" → triangular numbers"
|
|
||||||
(mkrv (apl-run "+\\⍳5"))
|
|
||||||
(list 1 3 6 10 15))
|
|
||||||
|
|
||||||
; ---------- outer / inner products ----------
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"apl-run \"1 2 3 ∘.× 1 2 3\" → mult table values"
|
|
||||||
(mkrv (apl-run "1 2 3 ∘.× 1 2 3"))
|
|
||||||
(list 1 2 3 2 4 6 3 6 9))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"apl-run \"1 2 3 +.× 4 5 6\" → dot product 32"
|
|
||||||
(mkrv (apl-run "1 2 3 +.× 4 5 6"))
|
|
||||||
(list 32))
|
|
||||||
|
|
||||||
; ---------- shape ----------
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"apl-run \"⍴ 1 2 3 4 5\" → 5"
|
|
||||||
(mkrv (apl-run "⍴ 1 2 3 4 5"))
|
|
||||||
(list 5))
|
|
||||||
|
|
||||||
(apl-test "apl-run \"⍴⍳10\" → 10" (mkrv (apl-run "⍴⍳10")) (list 10))
|
|
||||||
|
|
||||||
; ---------- comparison ----------
|
|
||||||
|
|
||||||
(apl-test "apl-run \"3 < 5\" → 1" (mkrv (apl-run "3 < 5")) (list 1))
|
|
||||||
|
|
||||||
(apl-test "apl-run \"5 = 5\" → 1" (mkrv (apl-run "5 = 5")) (list 1))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"apl-run \"1 2 3 = 1 0 3\" → 1 0 1"
|
|
||||||
(mkrv (apl-run "1 2 3 = 1 0 3"))
|
|
||||||
(list 1 0 1))
|
|
||||||
|
|
||||||
; ---------- famous one-liners ----------
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"apl-run \"+/(⍳10)\" → sum 1..10 = 55"
|
|
||||||
(mkrv (apl-run "+/(⍳10)"))
|
|
||||||
(list 55))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"apl-run \"×/⍳10\" → 10! = 3628800"
|
|
||||||
(mkrv (apl-run "×/⍳10"))
|
|
||||||
(list 3628800))
|
|
||||||
|
|
||||||
(apl-test "apl-run \"⎕IO\" → 1" (mkrv (apl-run "⎕IO")) (list 1))
|
|
||||||
|
|
||||||
(apl-test "apl-run \"⎕ML\" → 1" (mkrv (apl-run "⎕ML")) (list 1))
|
|
||||||
|
|
||||||
(apl-test "apl-run \"⎕FR\" → 1248" (mkrv (apl-run "⎕FR")) (list 1248))
|
|
||||||
|
|
||||||
(apl-test "apl-run \"⎕TS\" shape (7)" (mksh (apl-run "⎕TS")) (list 7))
|
|
||||||
|
|
||||||
(apl-test "apl-run \"⎕FMT 42\" → \"42\"" (apl-run "⎕FMT 42") "42")
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"apl-run \"⎕FMT 1 2 3\" → \"1 2 3\""
|
|
||||||
(apl-run "⎕FMT 1 2 3")
|
|
||||||
"1 2 3")
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"apl-run \"⎕FMT ⍳5\" → \"1 2 3 4 5\""
|
|
||||||
(apl-run "⎕FMT ⍳5")
|
|
||||||
"1 2 3 4 5")
|
|
||||||
|
|
||||||
(apl-test "apl-run \"⎕IO + 4\" → 5" (mkrv (apl-run "⎕IO + 4")) (list 5))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"apl-run \"(10 20 30 40 50)[3]\" → 30"
|
|
||||||
(mkrv (apl-run "(10 20 30 40 50)[3]"))
|
|
||||||
(list 30))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"apl-run \"(⍳10)[5]\" → 5"
|
|
||||||
(mkrv (apl-run "(⍳10)[5]"))
|
|
||||||
(list 5))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"apl-run \"A ← 100 200 300 ⋄ A[2]\" → 200"
|
|
||||||
(mkrv (apl-run "A ← 100 200 300 ⋄ A[2]"))
|
|
||||||
(list 200))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"apl-run \"V ← ⍳10 ⋄ V[3]\" → 3"
|
|
||||||
(mkrv (apl-run "V ← ⍳10 ⋄ V[3]"))
|
|
||||||
(list 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"apl-run \"(10 20 30)[1]\" → 10 (1-indexed)"
|
|
||||||
(mkrv (apl-run "(10 20 30)[1]"))
|
|
||||||
(list 10))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"apl-run \"V ← 10 20 30 40 50 ⋄ V[3] + 1\" → 31"
|
|
||||||
(mkrv (apl-run "V ← 10 20 30 40 50 ⋄ V[3] + 1"))
|
|
||||||
(list 31))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"apl-run \"(⍳5)[3] × 7\" → 21"
|
|
||||||
(mkrv (apl-run "(⍳5)[3] × 7"))
|
|
||||||
(list 21))
|
|
||||||
@@ -1,304 +0,0 @@
|
|||||||
; Tests for classic APL programs (lib/apl/tests/programs/*.apl).
|
|
||||||
; Programs are showcase APL source; runtime impl is in lib/apl/runtime.sx.
|
|
||||||
|
|
||||||
(define mkrv (fn (arr) (get arr :ravel)))
|
|
||||||
(define mksh (fn (arr) (get arr :shape)))
|
|
||||||
|
|
||||||
; ===== primes (Sieve of Eratosthenes) =====
|
|
||||||
|
|
||||||
(apl-test "primes 1 → empty" (mkrv (apl-primes 1)) (list))
|
|
||||||
|
|
||||||
(apl-test "primes 2 → just 2" (mkrv (apl-primes 2)) (list 2))
|
|
||||||
|
|
||||||
(apl-test "primes 10 → 2 3 5 7" (mkrv (apl-primes 10)) (list 2 3 5 7))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"primes 20 → 2 3 5 7 11 13 17 19"
|
|
||||||
(mkrv (apl-primes 20))
|
|
||||||
(list 2 3 5 7 11 13 17 19))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"primes 30"
|
|
||||||
(mkrv (apl-primes 30))
|
|
||||||
(list 2 3 5 7 11 13 17 19 23 29))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"primes 50"
|
|
||||||
(mkrv (apl-primes 50))
|
|
||||||
(list 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47))
|
|
||||||
|
|
||||||
(apl-test "primes 7 length" (first (mksh (apl-primes 7))) 4)
|
|
||||||
|
|
||||||
(apl-test "primes 100 has 25 primes" (first (mksh (apl-primes 100))) 25)
|
|
||||||
|
|
||||||
; ===== compress helper sanity =====
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"compress 1 0 1 0 1 / 10 20 30 40 50"
|
|
||||||
(mkrv
|
|
||||||
(apl-compress
|
|
||||||
(make-array (list 5) (list 1 0 1 0 1))
|
|
||||||
(make-array (list 5) (list 10 20 30 40 50))))
|
|
||||||
(list 10 30 50))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"compress all-zero mask → empty"
|
|
||||||
(mkrv
|
|
||||||
(apl-compress
|
|
||||||
(make-array (list 3) (list 0 0 0))
|
|
||||||
(make-array (list 3) (list 1 2 3))))
|
|
||||||
(list))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"compress all-one mask → full vector"
|
|
||||||
(mkrv
|
|
||||||
(apl-compress
|
|
||||||
(make-array (list 3) (list 1 1 1))
|
|
||||||
(make-array (list 3) (list 1 2 3))))
|
|
||||||
(list 1 2 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"life: empty 5x5 stays empty"
|
|
||||||
(mkrv
|
|
||||||
(apl-life-step
|
|
||||||
(make-array
|
|
||||||
(list 5 5)
|
|
||||||
(list 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0))))
|
|
||||||
(list 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"life: horizontal blinker → vertical blinker"
|
|
||||||
(mkrv
|
|
||||||
(apl-life-step
|
|
||||||
(make-array
|
|
||||||
(list 5 5)
|
|
||||||
(list 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0))))
|
|
||||||
(list 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"life: vertical blinker → horizontal blinker"
|
|
||||||
(mkrv
|
|
||||||
(apl-life-step
|
|
||||||
(make-array
|
|
||||||
(list 5 5)
|
|
||||||
(list 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0))))
|
|
||||||
(list 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"life: blinker has period 2"
|
|
||||||
(mkrv
|
|
||||||
(apl-life-step
|
|
||||||
(apl-life-step
|
|
||||||
(make-array
|
|
||||||
(list 5 5)
|
|
||||||
(list 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0)))))
|
|
||||||
(list 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"life: 2x2 block stable on 5x5"
|
|
||||||
(mkrv
|
|
||||||
(apl-life-step
|
|
||||||
(make-array
|
|
||||||
(list 5 5)
|
|
||||||
(list 0 0 0 0 0 0 1 1 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0))))
|
|
||||||
(list 0 0 0 0 0 0 1 1 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"life: shape preserved"
|
|
||||||
(mksh
|
|
||||||
(apl-life-step
|
|
||||||
(make-array
|
|
||||||
(list 5 5)
|
|
||||||
(list 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0))))
|
|
||||||
(list 5 5))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"life: glider on 6x6 advances"
|
|
||||||
(mkrv
|
|
||||||
(apl-life-step
|
|
||||||
(make-array
|
|
||||||
(list 6 6)
|
|
||||||
(list
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
1
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
1
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
1
|
|
||||||
1
|
|
||||||
1
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0))))
|
|
||||||
(list
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
1
|
|
||||||
0
|
|
||||||
1
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
1
|
|
||||||
1
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
1
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0
|
|
||||||
0))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"mandelbrot c=0 stays bounded"
|
|
||||||
(mkrv (apl-mandelbrot-1d (make-array (list 1) (list 0)) 100))
|
|
||||||
(list 100))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"mandelbrot c=-1 cycle bounded"
|
|
||||||
(mkrv (apl-mandelbrot-1d (make-array (list 1) (list -1)) 100))
|
|
||||||
(list 100))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"mandelbrot c=-2 boundary stays bounded"
|
|
||||||
(mkrv (apl-mandelbrot-1d (make-array (list 1) (list -2)) 100))
|
|
||||||
(list 100))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"mandelbrot c=0.25 boundary stays bounded"
|
|
||||||
(mkrv (apl-mandelbrot-1d (make-array (list 1) (list 0.25)) 100))
|
|
||||||
(list 100))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"mandelbrot c=1 escapes at iter 3"
|
|
||||||
(mkrv (apl-mandelbrot-1d (make-array (list 1) (list 1)) 100))
|
|
||||||
(list 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"mandelbrot c=0.5 escapes at iter 5"
|
|
||||||
(mkrv (apl-mandelbrot-1d (make-array (list 1) (list 0.5)) 100))
|
|
||||||
(list 5))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"mandelbrot batched grid (rank-polymorphic)"
|
|
||||||
(mkrv (apl-mandelbrot-1d (make-array (list 5) (list -2 -1 0 1 2)) 10))
|
|
||||||
(list 10 10 10 3 2))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"mandelbrot batched preserves shape"
|
|
||||||
(mksh (apl-mandelbrot-1d (make-array (list 5) (list -2 -1 0 1 2)) 10))
|
|
||||||
(list 5))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"mandelbrot c=-1.5 stays bounded"
|
|
||||||
(mkrv (apl-mandelbrot-1d (make-array (list 1) (list -1.5)) 100))
|
|
||||||
(list 100))
|
|
||||||
|
|
||||||
(apl-test "queens 1 → 1 solution" (mkrv (apl-queens 1)) (list 1))
|
|
||||||
|
|
||||||
(apl-test "queens 2 → 0 solutions" (mkrv (apl-queens 2)) (list 0))
|
|
||||||
|
|
||||||
(apl-test "queens 3 → 0 solutions" (mkrv (apl-queens 3)) (list 0))
|
|
||||||
|
|
||||||
(apl-test "queens 4 → 2 solutions" (mkrv (apl-queens 4)) (list 2))
|
|
||||||
|
|
||||||
(apl-test "queens 5 → 10 solutions" (mkrv (apl-queens 5)) (list 10))
|
|
||||||
|
|
||||||
(apl-test "queens 6 → 4 solutions" (mkrv (apl-queens 6)) (list 4))
|
|
||||||
|
|
||||||
(apl-test "queens 7 → 40 solutions" (mkrv (apl-queens 7)) (list 40))
|
|
||||||
|
|
||||||
(apl-test "permutations of 3 has 6" (len (apl-permutations 3)) 6)
|
|
||||||
|
|
||||||
(apl-test "permutations of 4 has 24" (len (apl-permutations 4)) 24)
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"quicksort empty"
|
|
||||||
(mkrv (apl-quicksort (make-array (list 0) (list))))
|
|
||||||
(list))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"quicksort single"
|
|
||||||
(mkrv (apl-quicksort (make-array (list 1) (list 42))))
|
|
||||||
(list 42))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"quicksort already sorted"
|
|
||||||
(mkrv (apl-quicksort (make-array (list 5) (list 1 2 3 4 5))))
|
|
||||||
(list 1 2 3 4 5))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"quicksort reverse sorted"
|
|
||||||
(mkrv (apl-quicksort (make-array (list 5) (list 5 4 3 2 1))))
|
|
||||||
(list 1 2 3 4 5))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"quicksort with duplicates"
|
|
||||||
(mkrv (apl-quicksort (make-array (list 7) (list 3 1 4 1 5 9 2))))
|
|
||||||
(list 1 1 2 3 4 5 9))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"quicksort all equal"
|
|
||||||
(mkrv (apl-quicksort (make-array (list 5) (list 7 7 7 7 7))))
|
|
||||||
(list 7 7 7 7 7))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"quicksort negatives"
|
|
||||||
(mkrv (apl-quicksort (make-array (list 5) (list -3 1 -1 2 0))))
|
|
||||||
(list -3 -1 0 1 2))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"quicksort 11-element pi"
|
|
||||||
(mkrv
|
|
||||||
(apl-quicksort (make-array (list 11) (list 3 1 4 1 5 9 2 6 5 3 5))))
|
|
||||||
(list 1 1 2 3 3 4 5 5 5 6 9))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"quicksort preserves length"
|
|
||||||
(first
|
|
||||||
(mksh (apl-quicksort (make-array (list 7) (list 3 1 4 1 5 9 2)))))
|
|
||||||
7)
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
⍝ Conway's Game of Life — toroidal one-liner
|
|
||||||
⍝
|
|
||||||
⍝ The classic Roger Hui formulation:
|
|
||||||
⍝ life ← {⊃1 ⍵ ∨.∧ 3 4 = +/ +/ ¯1 0 1 ∘.⊖ ¯1 0 1 ⌽¨ ⊂⍵}
|
|
||||||
⍝
|
|
||||||
⍝ Read right-to-left:
|
|
||||||
⍝ ⊂⍵ : enclose the board (so it's a single scalar item)
|
|
||||||
⍝ ¯1 0 1 ⌽¨ ⊂⍵ : produce 3 horizontally-shifted copies
|
|
||||||
⍝ ¯1 0 1 ∘.⊖ … : outer-product with vertical shifts → 3×3 = 9 shifts
|
|
||||||
⍝ +/ +/ … : sum the 9 boards element-wise → neighbor-count + self
|
|
||||||
⍝ 3 4 = … : boolean — count is exactly 3 or exactly 4
|
|
||||||
⍝ 1 ⍵ ∨.∧ … : "alive next" iff (count=3) or (alive AND count=4)
|
|
||||||
⍝ ⊃ … : disclose back to a 2D board
|
|
||||||
⍝
|
|
||||||
⍝ Rules in plain language:
|
|
||||||
⍝ - dead cell + 3 live neighbors → born
|
|
||||||
⍝ - live cell + 2 or 3 live neighbors → survives
|
|
||||||
⍝ - all else → dies
|
|
||||||
⍝
|
|
||||||
⍝ Toroidal: edges wrap (rotate is cyclic).
|
|
||||||
|
|
||||||
life ← {⊃1 ⍵ ∨.∧ 3 4 = +/ +/ ¯1 0 1 ∘.⊖ ¯1 0 1 ⌽¨ ⊂⍵}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
⍝ Mandelbrot — real-axis subset
|
|
||||||
⍝
|
|
||||||
⍝ For complex c, the Mandelbrot set is { c : |z_n| stays bounded } where
|
|
||||||
⍝ z_0 = 0, z_{n+1} = z_n² + c.
|
|
||||||
⍝ Restricting c (and z) to ℝ gives the segment c ∈ [-2, 1/4]
|
|
||||||
⍝ where the iteration stays bounded.
|
|
||||||
⍝
|
|
||||||
⍝ Rank-polymorphic batched-iteration form:
|
|
||||||
⍝ mandelbrot ← {⍵ ⍵⍵ ⍺⍺ +,(⍺⍺ × ⍺⍺) }
|
|
||||||
⍝
|
|
||||||
⍝ Pseudocode (as we don't have ⎕ system fns yet):
|
|
||||||
⍝ z ← 0×c ⍝ start at zero
|
|
||||||
⍝ alive ← 1+0×c ⍝ all "still in"
|
|
||||||
⍝ for k iterations:
|
|
||||||
⍝ alive ← alive ∧ 4 ≥ z×z ⍝ still bounded?
|
|
||||||
⍝ z ← alive × c + z×z ⍝ freeze escaped via mask
|
|
||||||
⍝ count ← count + alive ⍝ tally surviving iters
|
|
||||||
⍝
|
|
||||||
⍝ Examples (count after 100 iterations):
|
|
||||||
⍝ c=0 : 100 (z stays at 0)
|
|
||||||
⍝ c=-1 : 100 (cycles 0,-1,0,-1,...)
|
|
||||||
⍝ c=-2 : 100 (settles at 2 — boundary)
|
|
||||||
⍝ c=0.25 : 100 (boundary — converges to 0.5)
|
|
||||||
⍝ c=0.5 : 5 (escapes by iteration 6)
|
|
||||||
⍝ c=1 : 3 (escapes quickly)
|
|
||||||
⍝
|
|
||||||
⍝ Real-axis Mandelbrot set: bounded for c ∈ [-2, 0.25].
|
|
||||||
|
|
||||||
mandelbrot ← {z←alive←count←0×⍵ ⋄ {alive←alive∧4≥z×z ⋄ z←alive×⍵+z×z ⋄ count+←alive}⍣⍺⊢⍵}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
⍝ N-Queens — count solutions to placing N non-attacking queens on N×N
|
|
||||||
⍝
|
|
||||||
⍝ A solution is encoded as a permutation P of 1..N where P[i] is the
|
|
||||||
⍝ column of the queen in row i. Rows and columns are then automatically
|
|
||||||
⍝ unique (it's a permutation). We must additionally rule out queens
|
|
||||||
⍝ sharing a diagonal: |i-j| = |P[i]-P[j]| for any pair.
|
|
||||||
⍝
|
|
||||||
⍝ Backtracking via reduce — the classic Roger Hui style:
|
|
||||||
⍝ queens ← {≢{⍵,¨⍨↓(0=∊(¨⍳⍴⍵)≠.+|⍵)/⍳⍴⍵}/(⍳⍵)⍴⊂⍳⍵}
|
|
||||||
⍝
|
|
||||||
⍝ Plain reading:
|
|
||||||
⍝ permute 1..N, keep those where no two queens share a diagonal.
|
|
||||||
⍝
|
|
||||||
⍝ Known solution counts (OEIS A000170):
|
|
||||||
⍝ N 1 2 3 4 5 6 7 8 9 10
|
|
||||||
⍝ q(N) 1 0 0 2 10 4 40 92 352 724
|
|
||||||
|
|
||||||
queens ← {≢({(i j)←⍺⍵ ⋄ (|i-j)≠|(P[i])-(P[j])}⌿permutations ⍵)}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
⍝ Sieve of Eratosthenes — the classic APL one-liner
|
|
||||||
⍝ primes ← (2=+⌿0=A∘.|A)/A←⍳N
|
|
||||||
⍝
|
|
||||||
⍝ Read right-to-left:
|
|
||||||
⍝ A ← ⍳N : A is 1..N
|
|
||||||
⍝ A∘.|A : outer-product residue table — M[i,j] = A[j] mod A[i]
|
|
||||||
⍝ 0=... : boolean — true where A[i] divides A[j]
|
|
||||||
⍝ +⌿... : column sums — count of divisors per A[j]
|
|
||||||
⍝ 2=... : true for numbers with exactly 2 divisors (1 and self) → primes
|
|
||||||
⍝ .../A : compress — select A[j] where mask[j] is true
|
|
||||||
⍝
|
|
||||||
⍝ Examples:
|
|
||||||
⍝ primes 10 → 2 3 5 7
|
|
||||||
⍝ primes 30 → 2 3 5 7 11 13 17 19 23 29
|
|
||||||
|
|
||||||
primes ← {(2=+⌿0=⍵∘.|⍵)/⍵←⍳⍵}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
⍝ Quicksort — the classic Roger Hui one-liner
|
|
||||||
⍝
|
|
||||||
⍝ Q ← {1≥≢⍵:⍵ ⋄ (∇⍵⌿⍨⍵<p),(p=⍵)/⍵,∇⍵⌿⍨⍵>p←⍵⌷⍨?≢⍵}
|
|
||||||
⍝
|
|
||||||
⍝ Read right-to-left:
|
|
||||||
⍝ ?≢⍵ : pick a random index in 1..length
|
|
||||||
⍝ ⍵⌷⍨… : take that element as pivot p
|
|
||||||
⍝ ⍵>p : boolean — elements greater than pivot
|
|
||||||
⍝ ∇⍵⌿⍨… : recursively sort the > partition
|
|
||||||
⍝ (p=⍵)/⍵ : keep elements equal to pivot
|
|
||||||
⍝ ⍵<p : boolean — elements less than pivot
|
|
||||||
⍝ ∇⍵⌿⍨… : recursively sort the < partition
|
|
||||||
⍝ , : catenate ⟨less⟩ ⟨equal⟩ ⟨greater⟩
|
|
||||||
⍝ 1≥≢⍵:⍵ : guard — base case for length ≤ 1
|
|
||||||
⍝
|
|
||||||
⍝ Stability: not stable on duplicates (but eq-class is preserved as a block).
|
|
||||||
⍝ Worst case O(N²) on already-sorted input with deterministic pivot;
|
|
||||||
⍝ randomized pivot selection gives expected O(N log N).
|
|
||||||
⍝
|
|
||||||
⍝ Examples:
|
|
||||||
⍝ Q 3 1 4 1 5 9 2 6 5 3 5 → 1 1 2 3 3 4 5 5 5 6 9
|
|
||||||
⍝ Q ⍳0 → ⍬ (empty)
|
|
||||||
⍝ Q ,42 → 42
|
|
||||||
|
|
||||||
quicksort ← {1≥≢⍵:⍵ ⋄ p←⍵⌷⍨?≢⍵ ⋄ (∇⍵⌿⍨⍵<p),(p=⍵)/⍵,∇⍵⌿⍨⍵>p}
|
|
||||||
@@ -1,369 +0,0 @@
|
|||||||
; APL scalar primitives test suite
|
|
||||||
; Requires: lib/apl/runtime.sx
|
|
||||||
|
|
||||||
; ============================================================
|
|
||||||
; Test framework
|
|
||||||
; ============================================================
|
|
||||||
|
|
||||||
(define apl-rt-count 0)
|
|
||||||
(define apl-rt-pass 0)
|
|
||||||
(define apl-rt-fails (list))
|
|
||||||
|
|
||||||
; Element-wise list comparison (handles both List and ListRef)
|
|
||||||
(define
|
|
||||||
lists-eq
|
|
||||||
(fn
|
|
||||||
(a b)
|
|
||||||
(if
|
|
||||||
(and (= (len a) 0) (= (len b) 0))
|
|
||||||
true
|
|
||||||
(if
|
|
||||||
(not (= (len a) (len b)))
|
|
||||||
false
|
|
||||||
(if
|
|
||||||
(not (= (first a) (first b)))
|
|
||||||
false
|
|
||||||
(lists-eq (rest a) (rest b)))))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
apl-rt-test
|
|
||||||
(fn
|
|
||||||
(name actual expected)
|
|
||||||
(begin
|
|
||||||
(set! apl-rt-count (+ apl-rt-count 1))
|
|
||||||
(if
|
|
||||||
(equal? actual expected)
|
|
||||||
(set! apl-rt-pass (+ apl-rt-pass 1))
|
|
||||||
(append! apl-rt-fails {:actual actual :expected expected :name name})))))
|
|
||||||
|
|
||||||
; Test that a ravel equals a plain list (handles ListRef vs List)
|
|
||||||
(define
|
|
||||||
ravel-test
|
|
||||||
(fn
|
|
||||||
(name arr expected-list)
|
|
||||||
(begin
|
|
||||||
(set! apl-rt-count (+ apl-rt-count 1))
|
|
||||||
(let
|
|
||||||
((actual (get arr :ravel)))
|
|
||||||
(if
|
|
||||||
(lists-eq actual expected-list)
|
|
||||||
(set! apl-rt-pass (+ apl-rt-pass 1))
|
|
||||||
(append! apl-rt-fails {:actual actual :expected expected-list :name name}))))))
|
|
||||||
|
|
||||||
; Test a scalar ravel value (single-element list)
|
|
||||||
(define
|
|
||||||
scalar-test
|
|
||||||
(fn (name arr expected-val) (ravel-test name arr (list expected-val))))
|
|
||||||
|
|
||||||
; ============================================================
|
|
||||||
; Array constructor tests
|
|
||||||
; ============================================================
|
|
||||||
|
|
||||||
(apl-rt-test
|
|
||||||
"scalar: shape is empty list"
|
|
||||||
(get (apl-scalar 5) :shape)
|
|
||||||
(list))
|
|
||||||
|
|
||||||
(apl-rt-test
|
|
||||||
"scalar: ravel has one element"
|
|
||||||
(get (apl-scalar 5) :ravel)
|
|
||||||
(list 5))
|
|
||||||
|
|
||||||
(apl-rt-test "scalar: rank 0" (array-rank (apl-scalar 5)) 0)
|
|
||||||
|
|
||||||
(apl-rt-test "scalar? returns true for scalar" (scalar? (apl-scalar 5)) true)
|
|
||||||
|
|
||||||
(apl-rt-test "scalar: zero" (get (apl-scalar 0) :ravel) (list 0))
|
|
||||||
|
|
||||||
(apl-rt-test
|
|
||||||
"vector: shape is (3)"
|
|
||||||
(get (apl-vector (list 1 2 3)) :shape)
|
|
||||||
(list 3))
|
|
||||||
|
|
||||||
(apl-rt-test
|
|
||||||
"vector: ravel matches input"
|
|
||||||
(get (apl-vector (list 1 2 3)) :ravel)
|
|
||||||
(list 1 2 3))
|
|
||||||
|
|
||||||
(apl-rt-test "vector: rank 1" (array-rank (apl-vector (list 1 2 3))) 1)
|
|
||||||
|
|
||||||
(apl-rt-test
|
|
||||||
"scalar? returns false for vector"
|
|
||||||
(scalar? (apl-vector (list 1 2 3)))
|
|
||||||
false)
|
|
||||||
|
|
||||||
(apl-rt-test
|
|
||||||
"make-array: rank 2"
|
|
||||||
(array-rank (make-array (list 2 3) (list 1 2 3 4 5 6)))
|
|
||||||
2)
|
|
||||||
|
|
||||||
(apl-rt-test
|
|
||||||
"make-array: shape"
|
|
||||||
(get (make-array (list 2 3) (list 1 2 3 4 5 6)) :shape)
|
|
||||||
(list 2 3))
|
|
||||||
|
|
||||||
(apl-rt-test
|
|
||||||
"array-ref: first element"
|
|
||||||
(array-ref (apl-vector (list 10 20 30)) 0)
|
|
||||||
10)
|
|
||||||
|
|
||||||
(apl-rt-test
|
|
||||||
"array-ref: last element"
|
|
||||||
(array-ref (apl-vector (list 10 20 30)) 2)
|
|
||||||
30)
|
|
||||||
|
|
||||||
(apl-rt-test "enclose: wraps in rank-0" (scalar? (enclose 42)) true)
|
|
||||||
|
|
||||||
(apl-rt-test
|
|
||||||
"enclose: ravel contains value"
|
|
||||||
(get (enclose 42) :ravel)
|
|
||||||
(list 42))
|
|
||||||
|
|
||||||
(apl-rt-test "disclose: unwraps rank-0" (disclose (enclose 42)) 42)
|
|
||||||
|
|
||||||
; ============================================================
|
|
||||||
; Shape primitive tests
|
|
||||||
; ============================================================
|
|
||||||
|
|
||||||
(ravel-test "⍴ scalar: returns empty" (apl-shape (apl-scalar 5)) (list))
|
|
||||||
|
|
||||||
(ravel-test
|
|
||||||
"⍴ vector: returns (3)"
|
|
||||||
(apl-shape (apl-vector (list 1 2 3)))
|
|
||||||
(list 3))
|
|
||||||
|
|
||||||
(ravel-test
|
|
||||||
"⍴ matrix: returns (2 3)"
|
|
||||||
(apl-shape (make-array (list 2 3) (list 1 2 3 4 5 6)))
|
|
||||||
(list 2 3))
|
|
||||||
|
|
||||||
(ravel-test
|
|
||||||
", ravel scalar: vector of 1"
|
|
||||||
(apl-ravel (apl-scalar 5))
|
|
||||||
(list 5))
|
|
||||||
|
|
||||||
(apl-rt-test
|
|
||||||
", ravel vector: same elements"
|
|
||||||
(get (apl-ravel (apl-vector (list 1 2 3))) :ravel)
|
|
||||||
(list 1 2 3))
|
|
||||||
|
|
||||||
(apl-rt-test
|
|
||||||
", ravel matrix: all elements"
|
|
||||||
(get (apl-ravel (make-array (list 2 3) (list 1 2 3 4 5 6))) :ravel)
|
|
||||||
(list 1 2 3 4 5 6))
|
|
||||||
|
|
||||||
(scalar-test "≢ tally scalar: 1" (apl-tally (apl-scalar 5)) 1)
|
|
||||||
|
|
||||||
(scalar-test
|
|
||||||
"≢ tally vector: first dimension"
|
|
||||||
(apl-tally (apl-vector (list 1 2 3)))
|
|
||||||
3)
|
|
||||||
|
|
||||||
(scalar-test
|
|
||||||
"≢ tally matrix: first dimension"
|
|
||||||
(apl-tally (make-array (list 2 3) (list 1 2 3 4 5 6)))
|
|
||||||
2)
|
|
||||||
|
|
||||||
(scalar-test
|
|
||||||
"≡ depth flat vector: 0"
|
|
||||||
(apl-depth (apl-vector (list 1 2 3)))
|
|
||||||
0)
|
|
||||||
|
|
||||||
(scalar-test "≡ depth scalar: 0" (apl-depth (apl-scalar 5)) 0)
|
|
||||||
|
|
||||||
(scalar-test
|
|
||||||
"≡ depth nested (enclose in vector): 1"
|
|
||||||
(apl-depth (enclose (apl-vector (list 1 2 3))))
|
|
||||||
1)
|
|
||||||
|
|
||||||
; ============================================================
|
|
||||||
; ⍳ iota tests
|
|
||||||
; ============================================================
|
|
||||||
|
|
||||||
(apl-rt-test
|
|
||||||
"⍳5 shape is (5)"
|
|
||||||
(get (apl-iota (apl-scalar 5)) :shape)
|
|
||||||
(list 5))
|
|
||||||
|
|
||||||
(ravel-test "⍳5 ravel is 1..5" (apl-iota (apl-scalar 5)) (list 1 2 3 4 5))
|
|
||||||
|
|
||||||
(ravel-test "⍳1 ravel is (1)" (apl-iota (apl-scalar 1)) (list 1))
|
|
||||||
|
|
||||||
(ravel-test "⍳0 ravel is empty" (apl-iota (apl-scalar 0)) (list))
|
|
||||||
|
|
||||||
(apl-rt-test "apl-io is 1" apl-io 1)
|
|
||||||
|
|
||||||
; ============================================================
|
|
||||||
; Arithmetic broadcast tests
|
|
||||||
; ============================================================
|
|
||||||
|
|
||||||
(scalar-test
|
|
||||||
"+ scalar scalar: 3+4=7"
|
|
||||||
(apl-add (apl-scalar 3) (apl-scalar 4))
|
|
||||||
7)
|
|
||||||
|
|
||||||
(ravel-test
|
|
||||||
"+ vector scalar: +10"
|
|
||||||
(apl-add (apl-vector (list 1 2 3)) (apl-scalar 10))
|
|
||||||
(list 11 12 13))
|
|
||||||
|
|
||||||
(ravel-test
|
|
||||||
"+ scalar vector: 10+"
|
|
||||||
(apl-add (apl-scalar 10) (apl-vector (list 1 2 3)))
|
|
||||||
(list 11 12 13))
|
|
||||||
|
|
||||||
(ravel-test
|
|
||||||
"+ vector vector"
|
|
||||||
(apl-add (apl-vector (list 1 2 3)) (apl-vector (list 4 5 6)))
|
|
||||||
(list 5 7 9))
|
|
||||||
|
|
||||||
(scalar-test "- negate monadic" (apl-neg-m (apl-scalar 5)) -5)
|
|
||||||
|
|
||||||
(scalar-test "- dyadic 10-3=7" (apl-sub (apl-scalar 10) (apl-scalar 3)) 7)
|
|
||||||
|
|
||||||
(scalar-test "× signum positive" (apl-signum (apl-scalar 7)) 1)
|
|
||||||
|
|
||||||
(scalar-test "× signum negative" (apl-signum (apl-scalar -3)) -1)
|
|
||||||
|
|
||||||
(scalar-test "× signum zero" (apl-signum (apl-scalar 0)) 0)
|
|
||||||
|
|
||||||
(scalar-test "× dyadic 3×4=12" (apl-mul (apl-scalar 3) (apl-scalar 4)) 12)
|
|
||||||
|
|
||||||
(scalar-test "÷ reciprocal 1÷4=0.25" (apl-recip (apl-scalar 4)) 0.25)
|
|
||||||
|
|
||||||
(scalar-test
|
|
||||||
"÷ dyadic 10÷4=2.5"
|
|
||||||
(apl-div (apl-scalar 10) (apl-scalar 4))
|
|
||||||
2.5)
|
|
||||||
|
|
||||||
(scalar-test "⌈ ceiling 2.3→3" (apl-ceil (apl-scalar 2.3)) 3)
|
|
||||||
|
|
||||||
(scalar-test "⌈ max 3 5 → 5" (apl-max (apl-scalar 3) (apl-scalar 5)) 5)
|
|
||||||
|
|
||||||
(scalar-test "⌊ floor 2.7→2" (apl-floor (apl-scalar 2.7)) 2)
|
|
||||||
|
|
||||||
(scalar-test "⌊ min 3 5 → 3" (apl-min (apl-scalar 3) (apl-scalar 5)) 3)
|
|
||||||
|
|
||||||
(scalar-test "* exp monadic e^0=1" (apl-exp (apl-scalar 0)) 1)
|
|
||||||
|
|
||||||
(scalar-test
|
|
||||||
"* pow dyadic 2^10=1024"
|
|
||||||
(apl-pow (apl-scalar 2) (apl-scalar 10))
|
|
||||||
1024)
|
|
||||||
|
|
||||||
(scalar-test "⍟ ln 1=0" (apl-ln (apl-scalar 1)) 0)
|
|
||||||
|
|
||||||
(scalar-test "| abs positive" (apl-abs (apl-scalar 5)) 5)
|
|
||||||
|
|
||||||
(scalar-test "| abs negative" (apl-abs (apl-scalar -5)) 5)
|
|
||||||
|
|
||||||
(scalar-test "| mod 3|7=1" (apl-mod (apl-scalar 3) (apl-scalar 7)) 1)
|
|
||||||
|
|
||||||
(scalar-test "! factorial 5!=120" (apl-fact (apl-scalar 5)) 120)
|
|
||||||
|
|
||||||
(scalar-test "! factorial 0!=1" (apl-fact (apl-scalar 0)) 1)
|
|
||||||
|
|
||||||
(scalar-test
|
|
||||||
"! binomial 4 choose 2 = 6"
|
|
||||||
(apl-binomial (apl-scalar 4) (apl-scalar 2))
|
|
||||||
6)
|
|
||||||
|
|
||||||
(scalar-test "○ pi×0=0" (apl-pi-times (apl-scalar 0)) 0)
|
|
||||||
|
|
||||||
(scalar-test "○ trig sin(0)=0" (apl-trig (apl-scalar 1) (apl-scalar 0)) 0)
|
|
||||||
|
|
||||||
(scalar-test "○ trig cos(0)=1" (apl-trig (apl-scalar 2) (apl-scalar 0)) 1)
|
|
||||||
|
|
||||||
; ============================================================
|
|
||||||
; Comparison tests
|
|
||||||
; ============================================================
|
|
||||||
|
|
||||||
(scalar-test "< less: 3<5 → 1" (apl-lt (apl-scalar 3) (apl-scalar 5)) 1)
|
|
||||||
|
|
||||||
(scalar-test "< less: 5<3 → 0" (apl-lt (apl-scalar 5) (apl-scalar 3)) 0)
|
|
||||||
|
|
||||||
(scalar-test
|
|
||||||
"≤ le equal: 3≤3 → 1"
|
|
||||||
(apl-le (apl-scalar 3) (apl-scalar 3))
|
|
||||||
1)
|
|
||||||
|
|
||||||
(scalar-test "= eq: 5=5 → 1" (apl-eq (apl-scalar 5) (apl-scalar 5)) 1)
|
|
||||||
|
|
||||||
(scalar-test "= ne: 5=6 → 0" (apl-eq (apl-scalar 5) (apl-scalar 6)) 0)
|
|
||||||
|
|
||||||
(scalar-test "≥ ge: 5≥3 → 1" (apl-ge (apl-scalar 5) (apl-scalar 3)) 1)
|
|
||||||
|
|
||||||
(scalar-test "> gt: 5>3 → 1" (apl-gt (apl-scalar 5) (apl-scalar 3)) 1)
|
|
||||||
|
|
||||||
(scalar-test "≠ ne: 5≠3 → 1" (apl-ne (apl-scalar 5) (apl-scalar 3)) 1)
|
|
||||||
|
|
||||||
(ravel-test
|
|
||||||
"comparison vector broadcast: 1 2 3 < 2 → 1 0 0"
|
|
||||||
(apl-lt (apl-vector (list 1 2 3)) (apl-scalar 2))
|
|
||||||
(list 1 0 0))
|
|
||||||
|
|
||||||
; ============================================================
|
|
||||||
; Logical tests
|
|
||||||
; ============================================================
|
|
||||||
|
|
||||||
(scalar-test "~ not 0 → 1" (apl-not (apl-scalar 0)) 1)
|
|
||||||
|
|
||||||
(scalar-test "~ not 1 → 0" (apl-not (apl-scalar 1)) 0)
|
|
||||||
|
|
||||||
(ravel-test
|
|
||||||
"~ not vector: 1 0 1 0 → 0 1 0 1"
|
|
||||||
(apl-not (apl-vector (list 1 0 1 0)))
|
|
||||||
(list 0 1 0 1))
|
|
||||||
|
|
||||||
(scalar-test
|
|
||||||
"∧ and 1∧1 → 1"
|
|
||||||
(apl-and (apl-scalar 1) (apl-scalar 1))
|
|
||||||
1)
|
|
||||||
|
|
||||||
(scalar-test
|
|
||||||
"∧ and 1∧0 → 0"
|
|
||||||
(apl-and (apl-scalar 1) (apl-scalar 0))
|
|
||||||
0)
|
|
||||||
|
|
||||||
(scalar-test "∨ or 0∨1 → 1" (apl-or (apl-scalar 0) (apl-scalar 1)) 1)
|
|
||||||
|
|
||||||
(scalar-test "∨ or 0∨0 → 0" (apl-or (apl-scalar 0) (apl-scalar 0)) 0)
|
|
||||||
|
|
||||||
(scalar-test
|
|
||||||
"⍱ nor 0⍱0 → 1"
|
|
||||||
(apl-nor (apl-scalar 0) (apl-scalar 0))
|
|
||||||
1)
|
|
||||||
|
|
||||||
(scalar-test
|
|
||||||
"⍱ nor 1⍱0 → 0"
|
|
||||||
(apl-nor (apl-scalar 1) (apl-scalar 0))
|
|
||||||
0)
|
|
||||||
|
|
||||||
(scalar-test
|
|
||||||
"⍲ nand 1⍲1 → 0"
|
|
||||||
(apl-nand (apl-scalar 1) (apl-scalar 1))
|
|
||||||
0)
|
|
||||||
|
|
||||||
(scalar-test
|
|
||||||
"⍲ nand 1⍲0 → 1"
|
|
||||||
(apl-nand (apl-scalar 1) (apl-scalar 0))
|
|
||||||
1)
|
|
||||||
|
|
||||||
; ============================================================
|
|
||||||
; plus-m identity test
|
|
||||||
; ============================================================
|
|
||||||
|
|
||||||
(scalar-test "+ monadic identity: +5 → 5" (apl-plus-m (apl-scalar 5)) 5)
|
|
||||||
|
|
||||||
; ============================================================
|
|
||||||
; Summary
|
|
||||||
; ============================================================
|
|
||||||
|
|
||||||
(define
|
|
||||||
apl-scalar-summary
|
|
||||||
(str
|
|
||||||
"scalar "
|
|
||||||
apl-rt-pass
|
|
||||||
"/"
|
|
||||||
apl-rt-count
|
|
||||||
(if (= (len apl-rt-fails) 0) "" (str " FAILS: " apl-rt-fails))))
|
|
||||||
@@ -1,608 +0,0 @@
|
|||||||
;; lib/apl/tests/structural.sx — Phase 3: structural primitives
|
|
||||||
;; Tests for: apl-reshape, apl-ravel, apl-transpose, apl-transpose-dyadic
|
|
||||||
;; Loaded after runtime.sx; shares apl-test / apl-test-pass / apl-test-fail.
|
|
||||||
|
|
||||||
(define rv (fn (arr) (get arr :ravel)))
|
|
||||||
(define sh (fn (arr) (get arr :shape)))
|
|
||||||
|
|
||||||
;; ---------------------------------------------------------------------------
|
|
||||||
;; 1. Ravel (monadic ,)
|
|
||||||
;; ---------------------------------------------------------------------------
|
|
||||||
(apl-test "ravel scalar" (rv (apl-ravel (apl-scalar 5))) (list 5))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"ravel vector"
|
|
||||||
(rv (apl-ravel (make-array (list 3) (list 1 2 3))))
|
|
||||||
(list 1 2 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"ravel matrix"
|
|
||||||
(rv (apl-ravel (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list 1 2 3 4 5 6))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"ravel shape is rank-1"
|
|
||||||
(sh (apl-ravel (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list 6))
|
|
||||||
|
|
||||||
;; ---------------------------------------------------------------------------
|
|
||||||
;; 2. Reshape (dyadic ⍴)
|
|
||||||
;; ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"reshape 2x3 ravel"
|
|
||||||
(rv
|
|
||||||
(apl-reshape
|
|
||||||
(make-array (list 2) (list 2 3))
|
|
||||||
(make-array (list 6) (list 1 2 3 4 5 6))))
|
|
||||||
(list 1 2 3 4 5 6))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"reshape 2x3 shape"
|
|
||||||
(sh
|
|
||||||
(apl-reshape
|
|
||||||
(make-array (list 2) (list 2 3))
|
|
||||||
(make-array (list 6) (list 1 2 3 4 5 6))))
|
|
||||||
(list 2 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"reshape cycle 6 from 1 2"
|
|
||||||
(rv
|
|
||||||
(apl-reshape
|
|
||||||
(make-array (list 1) (list 6))
|
|
||||||
(make-array (list 2) (list 1 2))))
|
|
||||||
(list 1 2 1 2 1 2))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"reshape cycle 2x3 from 1 2"
|
|
||||||
(rv
|
|
||||||
(apl-reshape
|
|
||||||
(make-array (list 2) (list 2 3))
|
|
||||||
(make-array (list 2) (list 1 2))))
|
|
||||||
(list 1 2 1 2 1 2))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"reshape scalar fill"
|
|
||||||
(rv (apl-reshape (make-array (list 1) (list 4)) (apl-scalar 7)))
|
|
||||||
(list 7 7 7 7))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"reshape truncate"
|
|
||||||
(rv
|
|
||||||
(apl-reshape
|
|
||||||
(make-array (list 1) (list 3))
|
|
||||||
(make-array (list 6) (list 10 20 30 40 50 60))))
|
|
||||||
(list 10 20 30))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"reshape matrix to vector"
|
|
||||||
(sh
|
|
||||||
(apl-reshape
|
|
||||||
(make-array (list 1) (list 6))
|
|
||||||
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list 6))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"reshape 2x2x3"
|
|
||||||
(sh
|
|
||||||
(apl-reshape
|
|
||||||
(make-array (list 3) (list 2 2 3))
|
|
||||||
(make-array (list 12) (range 1 13))))
|
|
||||||
(list 2 2 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"reshape to empty"
|
|
||||||
(rv
|
|
||||||
(apl-reshape
|
|
||||||
(make-array (list 1) (list 0))
|
|
||||||
(make-array (list 3) (list 1 2 3))))
|
|
||||||
(list))
|
|
||||||
|
|
||||||
;; ---------------------------------------------------------------------------
|
|
||||||
;; 3. Monadic transpose (⍉)
|
|
||||||
;; ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"transpose scalar shape"
|
|
||||||
(sh (apl-transpose (apl-scalar 99)))
|
|
||||||
(list))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"transpose scalar ravel"
|
|
||||||
(rv (apl-transpose (apl-scalar 99)))
|
|
||||||
(list 99))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"transpose vector shape"
|
|
||||||
(sh (apl-transpose (make-array (list 3) (list 3 1 4))))
|
|
||||||
(list 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"transpose vector ravel"
|
|
||||||
(rv (apl-transpose (make-array (list 3) (list 3 1 4))))
|
|
||||||
(list 3 1 4))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"transpose 2x3 shape"
|
|
||||||
(sh (apl-transpose (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list 3 2))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"transpose 2x3 ravel"
|
|
||||||
(rv (apl-transpose (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list 1 4 2 5 3 6))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"transpose 3x3"
|
|
||||||
(rv (apl-transpose (make-array (list 3 3) (list 1 2 3 4 5 6 7 8 9))))
|
|
||||||
(list 1 4 7 2 5 8 3 6 9))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"transpose 1x4 shape"
|
|
||||||
(sh (apl-transpose (make-array (list 1 4) (list 1 2 3 4))))
|
|
||||||
(list 4 1))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"transpose twice identity"
|
|
||||||
(rv
|
|
||||||
(apl-transpose
|
|
||||||
(apl-transpose (make-array (list 2 3) (list 1 2 3 4 5 6)))))
|
|
||||||
(list 1 2 3 4 5 6))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"transpose 3d shape"
|
|
||||||
(sh (apl-transpose (make-array (list 2 3 4) (range 0 24))))
|
|
||||||
(list 4 3 2))
|
|
||||||
|
|
||||||
;; ---------------------------------------------------------------------------
|
|
||||||
;; 4. Dyadic transpose (perm⍉arr)
|
|
||||||
;; ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"dyadic-transpose identity"
|
|
||||||
(rv
|
|
||||||
(apl-transpose-dyadic
|
|
||||||
(make-array (list 2) (list 1 2))
|
|
||||||
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list 1 2 3 4 5 6))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"dyadic-transpose swap 2x3"
|
|
||||||
(rv
|
|
||||||
(apl-transpose-dyadic
|
|
||||||
(make-array (list 2) (list 2 1))
|
|
||||||
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list 1 4 2 5 3 6))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"dyadic-transpose swap shape"
|
|
||||||
(sh
|
|
||||||
(apl-transpose-dyadic
|
|
||||||
(make-array (list 2) (list 2 1))
|
|
||||||
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list 3 2))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"dyadic-transpose 3d shape"
|
|
||||||
(sh
|
|
||||||
(apl-transpose-dyadic
|
|
||||||
(make-array (list 3) (list 2 1 3))
|
|
||||||
(make-array (list 2 3 4) (range 0 24))))
|
|
||||||
(list 3 2 4))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"take 3 from front"
|
|
||||||
(rv (apl-take (apl-scalar 3) (make-array (list 5) (list 1 2 3 4 5))))
|
|
||||||
(list 1 2 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"take 0"
|
|
||||||
(rv (apl-take (apl-scalar 0) (make-array (list 5) (list 1 2 3 4 5))))
|
|
||||||
(list))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"take -2 from back"
|
|
||||||
(rv (apl-take (apl-scalar -2) (make-array (list 5) (list 1 2 3 4 5))))
|
|
||||||
(list 4 5))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"take over-take pads with 0"
|
|
||||||
(rv (apl-take (apl-scalar 7) (make-array (list 5) (list 1 2 3 4 5))))
|
|
||||||
(list 1 2 3 4 5 0 0))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"take matrix 1 row 2 cols shape"
|
|
||||||
(sh
|
|
||||||
(apl-take
|
|
||||||
(make-array (list 2) (list 1 2))
|
|
||||||
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list 1 2))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"take matrix 1 row 2 cols ravel"
|
|
||||||
(rv
|
|
||||||
(apl-take
|
|
||||||
(make-array (list 2) (list 1 2))
|
|
||||||
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list 1 2))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"take matrix negative row"
|
|
||||||
(rv
|
|
||||||
(apl-take
|
|
||||||
(make-array (list 2) (list -1 3))
|
|
||||||
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list 4 5 6))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"drop 2 from front"
|
|
||||||
(rv (apl-drop (apl-scalar 2) (make-array (list 5) (list 1 2 3 4 5))))
|
|
||||||
(list 3 4 5))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"drop -2 from back"
|
|
||||||
(rv (apl-drop (apl-scalar -2) (make-array (list 5) (list 1 2 3 4 5))))
|
|
||||||
(list 1 2 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"drop all"
|
|
||||||
(rv (apl-drop (apl-scalar 5) (make-array (list 5) (list 1 2 3 4 5))))
|
|
||||||
(list))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"drop 0"
|
|
||||||
(rv (apl-drop (apl-scalar 0) (make-array (list 5) (list 1 2 3 4 5))))
|
|
||||||
(list 1 2 3 4 5))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"drop matrix 1 row shape"
|
|
||||||
(sh
|
|
||||||
(apl-drop
|
|
||||||
(make-array (list 2) (list 1 0))
|
|
||||||
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list 1 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"drop matrix 1 row ravel"
|
|
||||||
(rv
|
|
||||||
(apl-drop
|
|
||||||
(make-array (list 2) (list 1 0))
|
|
||||||
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list 4 5 6))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"reverse vector"
|
|
||||||
(rv (apl-reverse (make-array (list 5) (list 1 2 3 4 5))))
|
|
||||||
(list 5 4 3 2 1))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"reverse scalar identity"
|
|
||||||
(rv (apl-reverse (apl-scalar 42)))
|
|
||||||
(list 42))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"reverse matrix last axis"
|
|
||||||
(rv (apl-reverse (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list 3 2 1 6 5 4))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"reverse-first matrix"
|
|
||||||
(rv (apl-reverse-first (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list 4 5 6 1 2 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"reverse-first vector identity"
|
|
||||||
(rv (apl-reverse-first (make-array (list 4) (list 1 2 3 4))))
|
|
||||||
(list 4 3 2 1))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"rotate vector left by 2"
|
|
||||||
(rv (apl-rotate (apl-scalar 2) (make-array (list 5) (list 1 2 3 4 5))))
|
|
||||||
(list 3 4 5 1 2))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"rotate vector right by 1 (negative)"
|
|
||||||
(rv (apl-rotate (apl-scalar -1) (make-array (list 5) (list 1 2 3 4 5))))
|
|
||||||
(list 5 1 2 3 4))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"rotate by 0 is identity"
|
|
||||||
(rv (apl-rotate (apl-scalar 0) (make-array (list 5) (list 1 2 3 4 5))))
|
|
||||||
(list 1 2 3 4 5))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"rotate matrix last axis"
|
|
||||||
(rv
|
|
||||||
(apl-rotate (apl-scalar 1) (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list 2 3 1 5 6 4))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"rotate-first matrix"
|
|
||||||
(rv
|
|
||||||
(apl-rotate-first
|
|
||||||
(apl-scalar 1)
|
|
||||||
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list 4 5 6 1 2 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"cat v,v ravel"
|
|
||||||
(rv
|
|
||||||
(apl-catenate
|
|
||||||
(make-array (list 3) (list 1 2 3))
|
|
||||||
(make-array (list 2) (list 4 5))))
|
|
||||||
(list 1 2 3 4 5))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"cat v,v shape"
|
|
||||||
(sh
|
|
||||||
(apl-catenate
|
|
||||||
(make-array (list 3) (list 1 2 3))
|
|
||||||
(make-array (list 2) (list 4 5))))
|
|
||||||
(list 5))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"cat scalar,v"
|
|
||||||
(rv (apl-catenate (apl-scalar 99) (make-array (list 3) (list 1 2 3))))
|
|
||||||
(list 99 1 2 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"cat v,scalar"
|
|
||||||
(rv (apl-catenate (make-array (list 3) (list 1 2 3)) (apl-scalar 99)))
|
|
||||||
(list 1 2 3 99))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"cat matrix last-axis shape"
|
|
||||||
(sh
|
|
||||||
(apl-catenate
|
|
||||||
(make-array (list 2 3) (list 1 2 3 4 5 6))
|
|
||||||
(make-array (list 2 2) (list 7 8 9 10))))
|
|
||||||
(list 2 5))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"cat matrix last-axis ravel"
|
|
||||||
(rv
|
|
||||||
(apl-catenate
|
|
||||||
(make-array (list 2 3) (list 1 2 3 4 5 6))
|
|
||||||
(make-array (list 2 2) (list 7 8 9 10))))
|
|
||||||
(list 1 2 3 7 8 4 5 6 9 10))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"cat-first v,v shape"
|
|
||||||
(sh
|
|
||||||
(apl-catenate-first
|
|
||||||
(make-array (list 3) (list 1 2 3))
|
|
||||||
(make-array (list 2) (list 4 5))))
|
|
||||||
(list 5))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"cat-first matrix shape"
|
|
||||||
(sh
|
|
||||||
(apl-catenate-first
|
|
||||||
(make-array (list 2 3) (list 1 2 3 4 5 6))
|
|
||||||
(make-array (list 3 3) (list 11 12 13 14 15 16 17 18 19))))
|
|
||||||
(list 5 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"cat-first matrix ravel"
|
|
||||||
(rv
|
|
||||||
(apl-catenate-first
|
|
||||||
(make-array (list 2 3) (list 1 2 3 4 5 6))
|
|
||||||
(make-array (list 3 3) (list 11 12 13 14 15 16 17 18 19))))
|
|
||||||
(list 1 2 3 4 5 6 11 12 13 14 15 16 17 18 19))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"squad scalar into vector"
|
|
||||||
(rv
|
|
||||||
(apl-squad (apl-scalar 2) (make-array (list 5) (list 10 20 30 40 50))))
|
|
||||||
(list 20))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"squad first element"
|
|
||||||
(rv (apl-squad (apl-scalar 1) (make-array (list 3) (list 10 20 30))))
|
|
||||||
(list 10))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"squad last element"
|
|
||||||
(rv
|
|
||||||
(apl-squad (apl-scalar 5) (make-array (list 5) (list 10 20 30 40 50))))
|
|
||||||
(list 50))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"squad fully specified matrix element"
|
|
||||||
(rv
|
|
||||||
(apl-squad
|
|
||||||
(make-array (list 2) (list 2 3))
|
|
||||||
(make-array (list 3 4) (list 1 2 3 4 5 6 7 8 9 10 11 12))))
|
|
||||||
(list 7))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"squad partial row of matrix shape"
|
|
||||||
(sh
|
|
||||||
(apl-squad
|
|
||||||
(apl-scalar 2)
|
|
||||||
(make-array (list 3 4) (list 1 2 3 4 5 6 7 8 9 10 11 12))))
|
|
||||||
(list 4))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"squad partial row of matrix ravel"
|
|
||||||
(rv
|
|
||||||
(apl-squad
|
|
||||||
(apl-scalar 2)
|
|
||||||
(make-array (list 3 4) (list 1 2 3 4 5 6 7 8 9 10 11 12))))
|
|
||||||
(list 5 6 7 8))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"squad partial 3d slice shape"
|
|
||||||
(sh (apl-squad (apl-scalar 1) (make-array (list 2 3 4) (range 1 25))))
|
|
||||||
(list 3 4))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"grade-up basic"
|
|
||||||
(rv (apl-grade-up (make-array (list 5) (list 3 1 4 1 5))))
|
|
||||||
(list 2 4 1 3 5))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"grade-up shape"
|
|
||||||
(sh (apl-grade-up (make-array (list 4) (list 4 1 3 2))))
|
|
||||||
(list 4))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"grade-up no duplicates"
|
|
||||||
(rv (apl-grade-up (make-array (list 4) (list 4 1 3 2))))
|
|
||||||
(list 2 4 3 1))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"grade-up already sorted"
|
|
||||||
(rv (apl-grade-up (make-array (list 3) (list 1 2 3))))
|
|
||||||
(list 1 2 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"grade-up reverse sorted"
|
|
||||||
(rv (apl-grade-up (make-array (list 3) (list 3 2 1))))
|
|
||||||
(list 3 2 1))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"grade-down basic"
|
|
||||||
(rv (apl-grade-down (make-array (list 5) (list 3 1 4 1 5))))
|
|
||||||
(list 5 3 1 2 4))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"grade-down no duplicates"
|
|
||||||
(rv (apl-grade-down (make-array (list 4) (list 4 1 3 2))))
|
|
||||||
(list 1 3 4 2))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"grade-up single element"
|
|
||||||
(rv (apl-grade-up (make-array (list 1) (list 42))))
|
|
||||||
(list 1))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"enclose shape is scalar"
|
|
||||||
(sh (apl-enclose (make-array (list 3) (list 1 2 3))))
|
|
||||||
(list))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"enclose ravel length is 1"
|
|
||||||
(len (rv (apl-enclose (make-array (list 3) (list 1 2 3)))))
|
|
||||||
1)
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"enclose inner ravel"
|
|
||||||
(rv (first (rv (apl-enclose (make-array (list 3) (list 1 2 3))))))
|
|
||||||
(list 1 2 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"disclose of enclose round-trips ravel"
|
|
||||||
(rv (apl-disclose (apl-enclose (make-array (list 3) (list 10 20 30)))))
|
|
||||||
(list 10 20 30))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"disclose of enclose round-trips shape"
|
|
||||||
(sh (apl-disclose (apl-enclose (make-array (list 3) (list 10 20 30)))))
|
|
||||||
(list 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"disclose scalar ravel"
|
|
||||||
(rv (apl-disclose (apl-scalar 42)))
|
|
||||||
(list 42))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"disclose vector ravel"
|
|
||||||
(rv (apl-disclose (make-array (list 3) (list 5 6 7))))
|
|
||||||
(list 5))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"disclose matrix returns first row"
|
|
||||||
(rv (apl-disclose (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
|
||||||
(list 1 2 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"member basic"
|
|
||||||
(rv
|
|
||||||
(apl-member
|
|
||||||
(make-array (list 3) (list 1 2 3))
|
|
||||||
(make-array (list 2) (list 2 3))))
|
|
||||||
(list 0 1 1))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"member all absent"
|
|
||||||
(rv
|
|
||||||
(apl-member
|
|
||||||
(make-array (list 3) (list 4 5 6))
|
|
||||||
(make-array (list 3) (list 1 2 3))))
|
|
||||||
(list 0 0 0))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"member scalar"
|
|
||||||
(rv (apl-member (apl-scalar 5) (make-array (list 3) (list 1 5 9))))
|
|
||||||
(list 1))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"member shape preserved"
|
|
||||||
(sh
|
|
||||||
(apl-member
|
|
||||||
(make-array (list 2 3) (list 1 2 3 4 5 6))
|
|
||||||
(make-array (list 3) (list 1 3 5))))
|
|
||||||
(list 2 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"member matrix ravel"
|
|
||||||
(rv
|
|
||||||
(apl-member
|
|
||||||
(make-array (list 2 3) (list 1 2 3 4 5 6))
|
|
||||||
(make-array (list 3) (list 1 3 5))))
|
|
||||||
(list 1 0 1 0 1 0))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"index-of basic"
|
|
||||||
(rv
|
|
||||||
(apl-index-of
|
|
||||||
(make-array (list 4) (list 10 20 30 40))
|
|
||||||
(make-array (list 3) (list 20 40 10))))
|
|
||||||
(list 2 4 1))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"index-of not-found"
|
|
||||||
(rv
|
|
||||||
(apl-index-of
|
|
||||||
(make-array (list 3) (list 1 2 3))
|
|
||||||
(make-array (list 2) (list 5 2))))
|
|
||||||
(list 4 2))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"index-of scalar right"
|
|
||||||
(rv
|
|
||||||
(apl-index-of (make-array (list 3) (list 10 20 30)) (apl-scalar 20)))
|
|
||||||
(list 2))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"without basic"
|
|
||||||
(rv
|
|
||||||
(apl-without
|
|
||||||
(make-array (list 5) (list 1 2 3 4 5))
|
|
||||||
(make-array (list 2) (list 2 4))))
|
|
||||||
(list 1 3 5))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"without shape"
|
|
||||||
(sh
|
|
||||||
(apl-without
|
|
||||||
(make-array (list 5) (list 1 2 3 4 5))
|
|
||||||
(make-array (list 2) (list 2 4))))
|
|
||||||
(list 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"without nothing removed"
|
|
||||||
(rv
|
|
||||||
(apl-without
|
|
||||||
(make-array (list 3) (list 1 2 3))
|
|
||||||
(make-array (list 3) (list 4 5 6))))
|
|
||||||
(list 1 2 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"without all removed"
|
|
||||||
(rv
|
|
||||||
(apl-without
|
|
||||||
(make-array (list 3) (list 1 2 3))
|
|
||||||
(make-array (list 3) (list 1 2 3))))
|
|
||||||
(list))
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
; Tests for APL ⎕ system functions.
|
|
||||||
|
|
||||||
(define mkrv (fn (arr) (get arr :ravel)))
|
|
||||||
(define mksh (fn (arr) (get arr :shape)))
|
|
||||||
|
|
||||||
(apl-test "⎕IO returns 1" (mkrv (apl-quad-io)) (list 1))
|
|
||||||
|
|
||||||
(apl-test "⎕ML returns 1" (mkrv (apl-quad-ml)) (list 1))
|
|
||||||
|
|
||||||
(apl-test "⎕FR returns 1248" (mkrv (apl-quad-fr)) (list 1248))
|
|
||||||
|
|
||||||
(apl-test "⎕TS shape is 7" (mksh (apl-quad-ts)) (list 7))
|
|
||||||
|
|
||||||
(apl-test "⎕TS year is 1970 default" (first (mkrv (apl-quad-ts))) 1970)
|
|
||||||
|
|
||||||
(apl-test "⎕FMT scalar 42" (apl-quad-fmt (apl-scalar 42)) "42")
|
|
||||||
|
|
||||||
(apl-test "⎕FMT scalar negative" (apl-quad-fmt (apl-scalar -7)) "-7")
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"⎕FMT empty vector"
|
|
||||||
(apl-quad-fmt (make-array (list 0) (list)))
|
|
||||||
"")
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"⎕FMT singleton vector"
|
|
||||||
(apl-quad-fmt (make-array (list 1) (list 42)))
|
|
||||||
"42")
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"⎕FMT vector"
|
|
||||||
(apl-quad-fmt (make-array (list 5) (list 1 2 3 4 5)))
|
|
||||||
"1 2 3 4 5")
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"⎕FMT matrix 2x3"
|
|
||||||
(apl-quad-fmt (make-array (list 2 3) (list 1 2 3 4 5 6)))
|
|
||||||
"1 2 3\n4 5 6\n")
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"⎕← (print) returns its arg"
|
|
||||||
(mkrv (apl-quad-print (apl-scalar 99)))
|
|
||||||
(list 99))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"⎕← preserves shape"
|
|
||||||
(mksh (apl-quad-print (make-array (list 3) (list 1 2 3))))
|
|
||||||
(list 3))
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
; Tests for apl-call-tradfn (manual structure construction).
|
|
||||||
|
|
||||||
(define mkrv (fn (arr) (get arr :ravel)))
|
|
||||||
(define mksh (fn (arr) (get arr :shape)))
|
|
||||||
(define mknum (fn (n) (list :num n)))
|
|
||||||
(define mknm (fn (s) (list :name s)))
|
|
||||||
(define mkfg (fn (g) (list :fn-glyph g)))
|
|
||||||
(define mkmon (fn (g a) (list :monad (mkfg g) a)))
|
|
||||||
(define mkdyd (fn (g l r) (list :dyad (mkfg g) l r)))
|
|
||||||
(define mkasg (fn (n e) (list :assign n e)))
|
|
||||||
(define mkbr (fn (e) (list :branch e)))
|
|
||||||
|
|
||||||
(define mkif (fn (c t e) (list :if c t e)))
|
|
||||||
|
|
||||||
(define mkwhile (fn (c b) (list :while c b)))
|
|
||||||
|
|
||||||
(define mkfor (fn (v i b) (list :for v i b)))
|
|
||||||
|
|
||||||
(define mksel (fn (v cs d) (list :select v cs d)))
|
|
||||||
|
|
||||||
(define mktrap (fn (codes t c) (list :trap codes t c)))
|
|
||||||
|
|
||||||
(define mkthr (fn (code msg) (list :throw code msg)))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"tradfn R←L+W simple add"
|
|
||||||
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkasg "R" (mkdyd "+" (mknm "L") (mknm "W")))) :alpha "L"} (apl-scalar 5) (apl-scalar 7)))
|
|
||||||
(list 12))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"tradfn R←L×W"
|
|
||||||
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkasg "R" (mkdyd "×" (mknm "L") (mknm "W")))) :alpha "L"} (apl-scalar 6) (apl-scalar 7)))
|
|
||||||
(list 42))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"tradfn monadic R←-W"
|
|
||||||
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkasg "R" (mkmon "-" (mknm "W")))) :alpha nil} nil (apl-scalar 9)))
|
|
||||||
(list -9))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"tradfn →0 exits early"
|
|
||||||
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkasg "R" (mknm "W")) (mkbr (mknum 0)) (mkasg "R" (mknum 999))) :alpha nil} nil (apl-scalar 7)))
|
|
||||||
(list 7))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"tradfn branch to line 3 skips line 2"
|
|
||||||
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkbr (mknum 3)) (mkasg "R" (mknum 999)) (mkasg "R" (mknum 42))) :alpha nil} nil (apl-scalar 0)))
|
|
||||||
(list 42))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"tradfn local var t←W+1; R←t×2"
|
|
||||||
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkasg "t" (mkdyd "+" (mknm "W") (mknum 1))) (mkasg "R" (mkdyd "×" (mknm "t") (mknum 2)))) :alpha nil} nil (apl-scalar 5)))
|
|
||||||
(list 12))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"tradfn vector args"
|
|
||||||
(mkrv
|
|
||||||
(apl-call-tradfn
|
|
||||||
{:result "R" :omega "W" :stmts (list (mkasg "R" (mkdyd "+" (mknm "L") (mknm "W")))) :alpha "L"}
|
|
||||||
(make-array (list 3) (list 1 2 3))
|
|
||||||
(make-array (list 3) (list 10 20 30))))
|
|
||||||
(list 11 22 33))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"tradfn unset result returns nil"
|
|
||||||
(apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkbr (mknum 0))) :alpha nil} nil (apl-scalar 5))
|
|
||||||
nil)
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"tradfn run-off end returns result"
|
|
||||||
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkasg "R" (mkdyd "×" (mknm "W") (mknum 3)))) :alpha nil} nil (apl-scalar 7)))
|
|
||||||
(list 21))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"tradfn loop sum 1+2+...+5 via branch"
|
|
||||||
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkasg "i" (mknum 1)) (mkasg "R" (mknum 0)) (mkasg "R" (mkdyd "+" (mknm "R") (mknm "i"))) (mkasg "i" (mkdyd "+" (mknm "i") (mknum 1))) (mkbr (mkdyd "×" (mkdyd "≤" (mknm "i") (mknm "W")) (mknum 3)))) :alpha nil} nil (apl-scalar 5)))
|
|
||||||
(list 15))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"tradfn :If true branch"
|
|
||||||
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkif (mkdyd ">" (mknm "W") (mknum 0)) (list (mkasg "R" (mknum 1))) (list (mkasg "R" (mknum 0))))) :alpha nil} nil (apl-scalar 5)))
|
|
||||||
(list 1))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"tradfn :If false branch"
|
|
||||||
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkif (mkdyd ">" (mknm "W") (mknum 100)) (list (mkasg "R" (mknum 1))) (list (mkasg "R" (mknum 0))))) :alpha nil} nil (apl-scalar 5)))
|
|
||||||
(list 0))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"tradfn :While sum 1..N"
|
|
||||||
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkasg "i" (mknum 1)) (mkasg "R" (mknum 0)) (mkwhile (mkdyd "≤" (mknm "i") (mknm "W")) (list (mkasg "R" (mkdyd "+" (mknm "R") (mknm "i"))) (mkasg "i" (mkdyd "+" (mknm "i") (mknum 1)))))) :alpha nil} nil (apl-scalar 10)))
|
|
||||||
(list 55))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"tradfn :For sum elements"
|
|
||||||
(mkrv
|
|
||||||
(apl-call-tradfn
|
|
||||||
{:result "R" :omega "W" :stmts (list (mkasg "R" (mknum 0)) (mkfor "x" (mknm "W") (list (mkasg "R" (mkdyd "+" (mknm "R") (mknm "x")))))) :alpha nil}
|
|
||||||
nil
|
|
||||||
(make-array (list 4) (list 10 20 30 40))))
|
|
||||||
(list 100))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"tradfn :For with empty vector"
|
|
||||||
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkasg "R" (mknum 99)) (mkfor "x" (mknm "W") (list (mkasg "R" (mkdyd "+" (mknm "R") (mknm "x")))))) :alpha nil} nil (make-array (list 0) (list))))
|
|
||||||
(list 99))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"tradfn :Select dispatch hit"
|
|
||||||
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mksel (mknm "W") (list (list (mknum 1) (mkasg "R" (mknum 100))) (list (mknum 2) (mkasg "R" (mknum 200))) (list (mknum 3) (mkasg "R" (mknum 300)))) (list (mkasg "R" (mknum 0))))) :alpha nil} nil (apl-scalar 2)))
|
|
||||||
(list 200))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"tradfn :Select default block"
|
|
||||||
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mksel (mknm "W") (list (list (mknum 1) (mkasg "R" (mknum 100))) (list (mknum 2) (mkasg "R" (mknum 200)))) (list (mkasg "R" (mknum -1))))) :alpha nil} nil (apl-scalar 99)))
|
|
||||||
(list -1))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"tradfn nested :If"
|
|
||||||
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkif (mkdyd ">" (mknm "W") (mknum 0)) (list (mkif (mkdyd ">" (mknm "W") (mknum 10)) (list (mkasg "R" (mknum 2))) (list (mkasg "R" (mknum 1))))) (list (mkasg "R" (mknum 0))))) :alpha nil} nil (apl-scalar 5)))
|
|
||||||
(list 1))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"tradfn :If assigns persist outside"
|
|
||||||
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkasg "R" (mknum 0)) (mkif (mkdyd ">" (mknm "W") (mknum 0)) (list (mkasg "R" (mknum 42))) (list)) (mkasg "R" (mkdyd "+" (mknm "R") (mknum 1)))) :alpha nil} nil (apl-scalar 5)))
|
|
||||||
(list 43))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"tradfn :For factorial 1..5"
|
|
||||||
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkasg "R" (mknum 1)) (mkfor "x" (mkmon "⍳" (mknm "W")) (list (mkasg "R" (mkdyd "×" (mknm "R") (mknm "x")))))) :alpha nil} nil (apl-scalar 5)))
|
|
||||||
(list 120))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"tradfn :Trap normal flow (no error)"
|
|
||||||
(mkrv (apl-call-tradfn {:result "R" :omega nil :stmts (list (mktrap (list 0) (list (mkasg "R" (mknum 99))) (list (mkasg "R" (mknum -1))))) :alpha nil} nil nil))
|
|
||||||
(list 99))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"tradfn :Trap catches matching code"
|
|
||||||
(mkrv (apl-call-tradfn {:result "R" :omega nil :stmts (list (mktrap (list 5) (list (mkthr 5 "boom")) (list (mkasg "R" (mknum 42))))) :alpha nil} nil nil))
|
|
||||||
(list 42))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"tradfn :Trap catch-all (code 0)"
|
|
||||||
(mkrv (apl-call-tradfn {:result "R" :omega nil :stmts (list (mktrap (list 0) (list (mkthr 99 "any")) (list (mkasg "R" (mknum 1))))) :alpha nil} nil nil))
|
|
||||||
(list 1))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"tradfn :Trap catches one of many codes"
|
|
||||||
(mkrv (apl-call-tradfn {:result "R" :omega nil :stmts (list (mktrap (list 1 2 3) (list (mkthr 2 "two")) (list (mkasg "R" (mknum 22))))) :alpha nil} nil nil))
|
|
||||||
(list 22))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"tradfn :Trap continues to next stmt after catch"
|
|
||||||
(mkrv (apl-call-tradfn {:result "R" :omega nil :stmts (list (mktrap (list 7) (list (mkthr 7 "c")) (list (mkasg "R" (mknum 10)))) (mkasg "R" (mkdyd "+" (mknm "R") (mknum 5)))) :alpha nil} nil nil))
|
|
||||||
(list 15))
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
; Tests for valence detection (apl-dfn-valence, apl-tradfn-valence)
|
|
||||||
; and unified dispatch (apl-call).
|
|
||||||
|
|
||||||
(define mkrv (fn (arr) (get arr :ravel)))
|
|
||||||
(define mknum (fn (n) (list :num n)))
|
|
||||||
(define mknm (fn (s) (list :name s)))
|
|
||||||
(define mkfg (fn (g) (list :fn-glyph g)))
|
|
||||||
(define mkmon (fn (g a) (list :monad (mkfg g) a)))
|
|
||||||
(define mkdyd (fn (g l r) (list :dyad (mkfg g) l r)))
|
|
||||||
(define mkasg (fn (n e) (list :assign n e)))
|
|
||||||
(define mkdfn (fn (stmts) (cons :dfn stmts)))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"dfn-valence niladic body=42"
|
|
||||||
(apl-dfn-valence (mkdfn (list (mknum 42))))
|
|
||||||
:niladic)
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"dfn-valence monadic body=⍵+1"
|
|
||||||
(apl-dfn-valence (mkdfn (list (mkdyd "+" (mknm "⍵") (mknum 1)))))
|
|
||||||
:monadic)
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"dfn-valence dyadic body=⍺+⍵"
|
|
||||||
(apl-dfn-valence (mkdfn (list (mkdyd "+" (mknm "⍺") (mknm "⍵")))))
|
|
||||||
:dyadic)
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"dfn-valence dyadic mentions ⍺ via local"
|
|
||||||
(apl-dfn-valence (mkdfn (list (mkasg "x" (mknm "⍺")) (mknm "x"))))
|
|
||||||
:dyadic)
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"dfn-valence dyadic deep nest"
|
|
||||||
(apl-dfn-valence
|
|
||||||
(mkdfn (list (mkmon "-" (mkdyd "×" (mknm "⍺") (mknm "⍵"))))))
|
|
||||||
:dyadic)
|
|
||||||
|
|
||||||
(apl-test "tradfn-valence niladic" (apl-tradfn-valence {:result "R" :omega nil :stmts (list) :alpha nil}) :niladic)
|
|
||||||
|
|
||||||
(apl-test "tradfn-valence monadic" (apl-tradfn-valence {:result "R" :omega "W" :stmts (list) :alpha nil}) :monadic)
|
|
||||||
|
|
||||||
(apl-test "tradfn-valence dyadic" (apl-tradfn-valence {:result "R" :omega "W" :stmts (list) :alpha "L"}) :dyadic)
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"apl-call dfn niladic"
|
|
||||||
(mkrv (apl-call (mkdfn (list (mknum 42))) nil nil))
|
|
||||||
(list 42))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"apl-call dfn monadic"
|
|
||||||
(mkrv
|
|
||||||
(apl-call
|
|
||||||
(mkdfn (list (mkdyd "+" (mknm "⍵") (mknum 1))))
|
|
||||||
nil
|
|
||||||
(apl-scalar 5)))
|
|
||||||
(list 6))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"apl-call dfn dyadic"
|
|
||||||
(mkrv
|
|
||||||
(apl-call
|
|
||||||
(mkdfn (list (mkdyd "+" (mknm "⍺") (mknm "⍵"))))
|
|
||||||
(apl-scalar 3)
|
|
||||||
(apl-scalar 4)))
|
|
||||||
(list 7))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"apl-call tradfn dyadic"
|
|
||||||
(mkrv (apl-call {:result "R" :omega "W" :stmts (list (mkasg "R" (mkdyd "×" (mknm "L") (mknm "W")))) :alpha "L"} (apl-scalar 6) (apl-scalar 7)))
|
|
||||||
(list 42))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"apl-call tradfn monadic"
|
|
||||||
(mkrv (apl-call {:result "R" :omega "W" :stmts (list (mkasg "R" (mkmon "-" (mknm "W")))) :alpha nil} nil (apl-scalar 9)))
|
|
||||||
(list -9))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"apl-call tradfn niladic returns nil result"
|
|
||||||
(apl-call {:result "R" :omega nil :stmts (list) :alpha nil} nil nil)
|
|
||||||
nil)
|
|
||||||
@@ -1,168 +0,0 @@
|
|||||||
(define apl-glyph-set
|
|
||||||
(list "+" "-" "×" "÷" "*" "⍟" "⌈" "⌊" "|" "!" "?" "○" "~" "<" "≤" "=" "≥" ">" "≠"
|
|
||||||
"≢" "≡" "∊" "∧" "∨" "⍱" "⍲" "," "⍪" "⍴" "⌽" "⊖" "⍉" "↑" "↓" "⊂" "⊃" "⊆"
|
|
||||||
"∪" "∩" "⍳" "⍸" "⌷" "⍋" "⍒" "⊥" "⊤" "⊣" "⊢" "⍎" "⍕"
|
|
||||||
"⍺" "⍵" "∇" "/" "\\" "¨" "⍨" "∘" "." "⍣" "⍤" "⍥" "@" "¯"))
|
|
||||||
|
|
||||||
(define apl-glyph?
|
|
||||||
(fn (ch)
|
|
||||||
(some (fn (g) (= g ch)) apl-glyph-set)))
|
|
||||||
|
|
||||||
(define apl-digit?
|
|
||||||
(fn (ch)
|
|
||||||
(and (string? ch) (>= ch "0") (<= ch "9"))))
|
|
||||||
|
|
||||||
(define apl-alpha?
|
|
||||||
(fn (ch)
|
|
||||||
(and (string? ch)
|
|
||||||
(or (and (>= ch "a") (<= ch "z"))
|
|
||||||
(and (>= ch "A") (<= ch "Z"))
|
|
||||||
(= ch "_")))))
|
|
||||||
|
|
||||||
(define apl-tokenize
|
|
||||||
(fn (source)
|
|
||||||
(let ((pos 0)
|
|
||||||
(src-len (len source))
|
|
||||||
(tokens (list)))
|
|
||||||
|
|
||||||
(define tok-push!
|
|
||||||
(fn (type value)
|
|
||||||
(append! tokens {:type type :value value})))
|
|
||||||
|
|
||||||
(define cur-sw?
|
|
||||||
(fn (ch)
|
|
||||||
(and (< pos src-len) (starts-with? (slice source pos) ch))))
|
|
||||||
|
|
||||||
(define cur-byte
|
|
||||||
(fn ()
|
|
||||||
(if (< pos src-len) (nth source pos) nil)))
|
|
||||||
|
|
||||||
(define advance!
|
|
||||||
(fn ()
|
|
||||||
(set! pos (+ pos 1))))
|
|
||||||
|
|
||||||
(define consume!
|
|
||||||
(fn (ch)
|
|
||||||
(set! pos (+ pos (len ch)))))
|
|
||||||
|
|
||||||
(define find-glyph
|
|
||||||
(fn ()
|
|
||||||
(let ((rem (slice source pos)))
|
|
||||||
(let ((matches (filter (fn (g) (starts-with? rem g)) apl-glyph-set)))
|
|
||||||
(if (> (len matches) 0) (first matches) nil)))))
|
|
||||||
|
|
||||||
(define read-digits!
|
|
||||||
(fn (acc)
|
|
||||||
(if (and (< pos src-len) (apl-digit? (cur-byte)))
|
|
||||||
(let ((ch (cur-byte)))
|
|
||||||
(begin
|
|
||||||
(advance!)
|
|
||||||
(read-digits! (str acc ch))))
|
|
||||||
acc)))
|
|
||||||
|
|
||||||
(define read-ident-cont!
|
|
||||||
(fn ()
|
|
||||||
(when (and (< pos src-len)
|
|
||||||
(let ((ch (cur-byte)))
|
|
||||||
(or (apl-alpha? ch) (apl-digit? ch))))
|
|
||||||
(begin
|
|
||||||
(advance!)
|
|
||||||
(read-ident-cont!)))))
|
|
||||||
|
|
||||||
(define read-string!
|
|
||||||
(fn (acc)
|
|
||||||
(cond
|
|
||||||
((>= pos src-len) acc)
|
|
||||||
((cur-sw? "'")
|
|
||||||
(if (and (< (+ pos 1) src-len) (cur-sw? "'"))
|
|
||||||
(begin
|
|
||||||
(advance!)
|
|
||||||
(advance!)
|
|
||||||
(read-string! (str acc "'")))
|
|
||||||
(begin (advance!) acc)))
|
|
||||||
(true
|
|
||||||
(let ((ch (cur-byte)))
|
|
||||||
(begin
|
|
||||||
(advance!)
|
|
||||||
(read-string! (str acc ch))))))))
|
|
||||||
|
|
||||||
(define skip-line!
|
|
||||||
(fn ()
|
|
||||||
(when (and (< pos src-len) (not (cur-sw? "\n")))
|
|
||||||
(begin
|
|
||||||
(advance!)
|
|
||||||
(skip-line!)))))
|
|
||||||
|
|
||||||
(define scan!
|
|
||||||
(fn ()
|
|
||||||
(when (< pos src-len)
|
|
||||||
(let ((ch (cur-byte)))
|
|
||||||
(cond
|
|
||||||
((or (= ch " ") (= ch "\t") (= ch "\r"))
|
|
||||||
(begin (advance!) (scan!)))
|
|
||||||
((= ch "\n")
|
|
||||||
(begin (advance!) (tok-push! :newline nil) (scan!)))
|
|
||||||
((cur-sw? "⍝")
|
|
||||||
(begin (skip-line!) (scan!)))
|
|
||||||
((cur-sw? "⋄")
|
|
||||||
(begin (consume! "⋄") (tok-push! :diamond nil) (scan!)))
|
|
||||||
((= ch "(")
|
|
||||||
(begin (advance!) (tok-push! :lparen nil) (scan!)))
|
|
||||||
((= ch ")")
|
|
||||||
(begin (advance!) (tok-push! :rparen nil) (scan!)))
|
|
||||||
((= ch "[")
|
|
||||||
(begin (advance!) (tok-push! :lbracket nil) (scan!)))
|
|
||||||
((= ch "]")
|
|
||||||
(begin (advance!) (tok-push! :rbracket nil) (scan!)))
|
|
||||||
((= ch "{")
|
|
||||||
(begin (advance!) (tok-push! :lbrace nil) (scan!)))
|
|
||||||
((= ch "}")
|
|
||||||
(begin (advance!) (tok-push! :rbrace nil) (scan!)))
|
|
||||||
((= ch ";")
|
|
||||||
(begin (advance!) (tok-push! :semi nil) (scan!)))
|
|
||||||
((cur-sw? "←")
|
|
||||||
(begin (consume! "←") (tok-push! :assign nil) (scan!)))
|
|
||||||
((= ch ":")
|
|
||||||
(let ((start pos))
|
|
||||||
(begin
|
|
||||||
(advance!)
|
|
||||||
(if (and (< pos src-len) (apl-alpha? (cur-byte)))
|
|
||||||
(begin
|
|
||||||
(read-ident-cont!)
|
|
||||||
(tok-push! :keyword (slice source start pos)))
|
|
||||||
(tok-push! :colon nil))
|
|
||||||
(scan!))))
|
|
||||||
((and (cur-sw? "¯")
|
|
||||||
(< (+ pos (len "¯")) src-len)
|
|
||||||
(apl-digit? (nth source (+ pos (len "¯")))))
|
|
||||||
(begin
|
|
||||||
(consume! "¯")
|
|
||||||
(let ((digits (read-digits! "")))
|
|
||||||
(tok-push! :num (- 0 (parse-int digits 0))))
|
|
||||||
(scan!)))
|
|
||||||
((apl-digit? ch)
|
|
||||||
(begin
|
|
||||||
(let ((digits (read-digits! "")))
|
|
||||||
(tok-push! :num (parse-int digits 0)))
|
|
||||||
(scan!)))
|
|
||||||
((= ch "'")
|
|
||||||
(begin
|
|
||||||
(advance!)
|
|
||||||
(let ((s (read-string! "")))
|
|
||||||
(tok-push! :str s))
|
|
||||||
(scan!)))
|
|
||||||
((or (apl-alpha? ch) (cur-sw? "⎕"))
|
|
||||||
(let ((start pos))
|
|
||||||
(begin
|
|
||||||
(if (cur-sw? "⎕") (consume! "⎕") (advance!))
|
|
||||||
(read-ident-cont!)
|
|
||||||
(tok-push! :name (slice source start pos))
|
|
||||||
(scan!))))
|
|
||||||
(true
|
|
||||||
(let ((g (find-glyph)))
|
|
||||||
(if g
|
|
||||||
(begin (consume! g) (tok-push! :glyph g) (scan!))
|
|
||||||
(begin (advance!) (scan!))))))))))
|
|
||||||
|
|
||||||
(scan!)
|
|
||||||
tokens)))
|
|
||||||
@@ -1,460 +0,0 @@
|
|||||||
; APL transpile / AST evaluator
|
|
||||||
;
|
|
||||||
; Walks parsed AST nodes and evaluates against the runtime.
|
|
||||||
; Entry points:
|
|
||||||
; apl-eval-ast : node × env → value
|
|
||||||
; apl-eval-stmts : stmt-list × env → value (handles guards, locals, ⍺← default)
|
|
||||||
; apl-call-dfn : dfn-ast × ⍺ × ⍵ → value (dyadic)
|
|
||||||
; apl-call-dfn-m : dfn-ast × ⍵ → value (monadic)
|
|
||||||
;
|
|
||||||
; Env is a dict; ⍺ stored under "alpha", ⍵ under "omega",
|
|
||||||
; the dfn-ast itself under "nabla" (for ∇ recursion),
|
|
||||||
; user names under their literal name.
|
|
||||||
|
|
||||||
(define
|
|
||||||
apl-monadic-fn
|
|
||||||
(fn
|
|
||||||
(g)
|
|
||||||
(cond
|
|
||||||
((= g "+") apl-plus-m)
|
|
||||||
((= g "-") apl-neg-m)
|
|
||||||
((= g "×") apl-signum)
|
|
||||||
((= g "÷") apl-recip)
|
|
||||||
((= g "⌈") apl-ceil)
|
|
||||||
((= g "⌊") apl-floor)
|
|
||||||
((= g "⍳") apl-iota)
|
|
||||||
((= g "|") apl-abs)
|
|
||||||
((= g "*") apl-exp)
|
|
||||||
((= g "⍟") apl-ln)
|
|
||||||
((= g "!") apl-fact)
|
|
||||||
((= g "○") apl-pi-times)
|
|
||||||
((= g "~") apl-not)
|
|
||||||
((= g "≢") apl-tally)
|
|
||||||
((= g "⍴") apl-shape)
|
|
||||||
((= g "≡") apl-depth)
|
|
||||||
((= g "⊂") apl-enclose)
|
|
||||||
((= g "⊃") apl-disclose)
|
|
||||||
((= g ",") apl-ravel)
|
|
||||||
((= g "⌽") apl-reverse)
|
|
||||||
((= g "⊖") apl-reverse-first)
|
|
||||||
((= g "⍋") apl-grade-up)
|
|
||||||
((= g "⍒") apl-grade-down)
|
|
||||||
((= g "⎕FMT") apl-quad-fmt)
|
|
||||||
(else (error "no monadic fn for glyph")))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
apl-dyadic-fn
|
|
||||||
(fn
|
|
||||||
(g)
|
|
||||||
(cond
|
|
||||||
((= g "+") apl-add)
|
|
||||||
((= g "-") apl-sub)
|
|
||||||
((= g "×") apl-mul)
|
|
||||||
((= g "÷") apl-div)
|
|
||||||
((= g "⌈") apl-max)
|
|
||||||
((= g "⌊") apl-min)
|
|
||||||
((= g "*") apl-pow)
|
|
||||||
((= g "⍟") apl-log)
|
|
||||||
((= g "|") apl-mod)
|
|
||||||
((= g "!") apl-binomial)
|
|
||||||
((= g "○") apl-trig)
|
|
||||||
((= g "<") apl-lt)
|
|
||||||
((= g "≤") apl-le)
|
|
||||||
((= g "=") apl-eq)
|
|
||||||
((= g "≥") apl-ge)
|
|
||||||
((= g ">") apl-gt)
|
|
||||||
((= g "≠") apl-ne)
|
|
||||||
((= g "∧") apl-and)
|
|
||||||
((= g "∨") apl-or)
|
|
||||||
((= g "⍱") apl-nor)
|
|
||||||
((= g "⍲") apl-nand)
|
|
||||||
((= g ",") apl-catenate)
|
|
||||||
((= g "⍪") apl-catenate-first)
|
|
||||||
((= g "⍴") apl-reshape)
|
|
||||||
((= g "↑") apl-take)
|
|
||||||
((= g "↓") apl-drop)
|
|
||||||
((= g "⌷") apl-squad)
|
|
||||||
((= g "⌽") apl-rotate)
|
|
||||||
((= g "⊖") apl-rotate-first)
|
|
||||||
((= g "∊") apl-member)
|
|
||||||
((= g "⍳") apl-index-of)
|
|
||||||
((= g "~") apl-without)
|
|
||||||
(else (error "no dyadic fn for glyph")))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
apl-truthy?
|
|
||||||
(fn
|
|
||||||
(v)
|
|
||||||
(let
|
|
||||||
((rv (get v :ravel)))
|
|
||||||
(if (and (= (len rv) 1) (= (first rv) 0)) false true))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
apl-eval-ast
|
|
||||||
(fn
|
|
||||||
(node env)
|
|
||||||
(let
|
|
||||||
((tag (first node)))
|
|
||||||
(cond
|
|
||||||
((= tag :num) (apl-scalar (nth node 1)))
|
|
||||||
((= tag :vec)
|
|
||||||
(let
|
|
||||||
((items (rest node)))
|
|
||||||
(let
|
|
||||||
((vals (map (fn (n) (apl-eval-ast n env)) items)))
|
|
||||||
(make-array
|
|
||||||
(list (len vals))
|
|
||||||
(map (fn (v) (first (get v :ravel))) vals)))))
|
|
||||||
((= tag :name)
|
|
||||||
(let
|
|
||||||
((nm (nth node 1)))
|
|
||||||
(cond
|
|
||||||
((= nm "⍺") (get env "alpha"))
|
|
||||||
((= nm "⍵") (get env "omega"))
|
|
||||||
((= nm "⎕IO") (apl-quad-io))
|
|
||||||
((= nm "⎕ML") (apl-quad-ml))
|
|
||||||
((= nm "⎕FR") (apl-quad-fr))
|
|
||||||
((= nm "⎕TS") (apl-quad-ts))
|
|
||||||
(else (get env nm)))))
|
|
||||||
((= tag :monad)
|
|
||||||
(let
|
|
||||||
((fn-node (nth node 1)) (arg (nth node 2)))
|
|
||||||
(if
|
|
||||||
(and (= (first fn-node) :fn-glyph) (= (nth fn-node 1) "∇"))
|
|
||||||
(apl-call-dfn-m (get env "nabla") (apl-eval-ast arg env))
|
|
||||||
((apl-resolve-monadic fn-node env) (apl-eval-ast arg env)))))
|
|
||||||
((= tag :dyad)
|
|
||||||
(let
|
|
||||||
((fn-node (nth node 1))
|
|
||||||
(lhs (nth node 2))
|
|
||||||
(rhs (nth node 3)))
|
|
||||||
(if
|
|
||||||
(and (= (first fn-node) :fn-glyph) (= (nth fn-node 1) "∇"))
|
|
||||||
(apl-call-dfn
|
|
||||||
(get env "nabla")
|
|
||||||
(apl-eval-ast lhs env)
|
|
||||||
(apl-eval-ast rhs env))
|
|
||||||
((apl-resolve-dyadic fn-node env)
|
|
||||||
(apl-eval-ast lhs env)
|
|
||||||
(apl-eval-ast rhs env)))))
|
|
||||||
((= tag :program) (apl-eval-stmts (rest node) env))
|
|
||||||
((= tag :dfn) node)
|
|
||||||
(else (error (list "apl-eval-ast: unknown node tag" tag node)))))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
apl-eval-stmts
|
|
||||||
(fn
|
|
||||||
(stmts env)
|
|
||||||
(if
|
|
||||||
(= (len stmts) 0)
|
|
||||||
nil
|
|
||||||
(let
|
|
||||||
((stmt (first stmts)) (more (rest stmts)))
|
|
||||||
(let
|
|
||||||
((tag (first stmt)))
|
|
||||||
(cond
|
|
||||||
((= tag :guard)
|
|
||||||
(let
|
|
||||||
((cond-val (apl-eval-ast (nth stmt 1) env)))
|
|
||||||
(if
|
|
||||||
(apl-truthy? cond-val)
|
|
||||||
(apl-eval-ast (nth stmt 2) env)
|
|
||||||
(apl-eval-stmts more env))))
|
|
||||||
((and (= tag :assign) (= (nth stmt 1) "⍺"))
|
|
||||||
(if
|
|
||||||
(get env "alpha")
|
|
||||||
(apl-eval-stmts more env)
|
|
||||||
(let
|
|
||||||
((v (apl-eval-ast (nth stmt 2) env)))
|
|
||||||
(apl-eval-stmts more (assoc env "alpha" v)))))
|
|
||||||
((= tag :assign)
|
|
||||||
(let
|
|
||||||
((v (apl-eval-ast (nth stmt 2) env)))
|
|
||||||
(apl-eval-stmts more (assoc env (nth stmt 1) v))))
|
|
||||||
((= (len more) 0) (apl-eval-ast stmt env))
|
|
||||||
(else (begin (apl-eval-ast stmt env) (apl-eval-stmts more env)))))))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
apl-call-dfn
|
|
||||||
(fn
|
|
||||||
(dfn-ast alpha omega)
|
|
||||||
(let
|
|
||||||
((stmts (rest dfn-ast)) (env {:omega omega :nabla dfn-ast :alpha alpha}))
|
|
||||||
(apl-eval-stmts stmts env))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
apl-call-dfn-m
|
|
||||||
(fn
|
|
||||||
(dfn-ast omega)
|
|
||||||
(let
|
|
||||||
((stmts (rest dfn-ast)) (env {:omega omega :nabla dfn-ast :alpha nil}))
|
|
||||||
(apl-eval-stmts stmts env))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
apl-tradfn-eval-block
|
|
||||||
(fn
|
|
||||||
(stmts env)
|
|
||||||
(if
|
|
||||||
(= (len stmts) 0)
|
|
||||||
env
|
|
||||||
(let
|
|
||||||
((stmt (first stmts)))
|
|
||||||
(apl-tradfn-eval-block (rest stmts) (apl-tradfn-eval-stmt stmt env))))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
apl-tradfn-eval-while
|
|
||||||
(fn
|
|
||||||
(cond-expr body env)
|
|
||||||
(let
|
|
||||||
((cond-val (apl-eval-ast cond-expr env)))
|
|
||||||
(if
|
|
||||||
(apl-truthy? cond-val)
|
|
||||||
(apl-tradfn-eval-while
|
|
||||||
cond-expr
|
|
||||||
body
|
|
||||||
(apl-tradfn-eval-block body env))
|
|
||||||
env))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
apl-tradfn-eval-for
|
|
||||||
(fn
|
|
||||||
(var-name items body env)
|
|
||||||
(if
|
|
||||||
(= (len items) 0)
|
|
||||||
env
|
|
||||||
(let
|
|
||||||
((env-with-var (assoc env var-name (apl-scalar (first items)))))
|
|
||||||
(apl-tradfn-eval-for
|
|
||||||
var-name
|
|
||||||
(rest items)
|
|
||||||
body
|
|
||||||
(apl-tradfn-eval-block body env-with-var))))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
apl-tradfn-eval-select
|
|
||||||
(fn
|
|
||||||
(val cases default-block env)
|
|
||||||
(if
|
|
||||||
(= (len cases) 0)
|
|
||||||
(apl-tradfn-eval-block default-block env)
|
|
||||||
(let
|
|
||||||
((c (first cases)))
|
|
||||||
(let
|
|
||||||
((case-val (apl-eval-ast (first c) env)))
|
|
||||||
(if
|
|
||||||
(= (first (get val :ravel)) (first (get case-val :ravel)))
|
|
||||||
(apl-tradfn-eval-block (rest c) env)
|
|
||||||
(apl-tradfn-eval-select val (rest cases) default-block env)))))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
apl-tradfn-eval-stmt
|
|
||||||
(fn
|
|
||||||
(stmt env)
|
|
||||||
(let
|
|
||||||
((tag (first stmt)))
|
|
||||||
(cond
|
|
||||||
((= tag :assign)
|
|
||||||
(assoc env (nth stmt 1) (apl-eval-ast (nth stmt 2) env)))
|
|
||||||
((= tag :if)
|
|
||||||
(let
|
|
||||||
((cond-val (apl-eval-ast (nth stmt 1) env)))
|
|
||||||
(if
|
|
||||||
(apl-truthy? cond-val)
|
|
||||||
(apl-tradfn-eval-block (nth stmt 2) env)
|
|
||||||
(apl-tradfn-eval-block (nth stmt 3) env))))
|
|
||||||
((= tag :while)
|
|
||||||
(apl-tradfn-eval-while (nth stmt 1) (nth stmt 2) env))
|
|
||||||
((= tag :for)
|
|
||||||
(let
|
|
||||||
((iter-val (apl-eval-ast (nth stmt 2) env)))
|
|
||||||
(apl-tradfn-eval-for
|
|
||||||
(nth stmt 1)
|
|
||||||
(get iter-val :ravel)
|
|
||||||
(nth stmt 3)
|
|
||||||
env)))
|
|
||||||
((= tag :select)
|
|
||||||
(let
|
|
||||||
((val (apl-eval-ast (nth stmt 1) env)))
|
|
||||||
(apl-tradfn-eval-select val (nth stmt 2) (nth stmt 3) env)))
|
|
||||||
((= tag :trap)
|
|
||||||
(let
|
|
||||||
((codes (nth stmt 1))
|
|
||||||
(try-block (nth stmt 2))
|
|
||||||
(catch-block (nth stmt 3)))
|
|
||||||
(guard
|
|
||||||
(e
|
|
||||||
((apl-trap-matches? codes e)
|
|
||||||
(apl-tradfn-eval-block catch-block env)))
|
|
||||||
(apl-tradfn-eval-block try-block env))))
|
|
||||||
((= tag :throw) (apl-throw (nth stmt 1) (nth stmt 2)))
|
|
||||||
(else (begin (apl-eval-ast stmt env) env))))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
apl-tradfn-loop
|
|
||||||
(fn
|
|
||||||
(stmts line env result-name)
|
|
||||||
(cond
|
|
||||||
((= line 0) (get env result-name))
|
|
||||||
((> line (len stmts)) (get env result-name))
|
|
||||||
(else
|
|
||||||
(let
|
|
||||||
((stmt (nth stmts (- line 1))))
|
|
||||||
(let
|
|
||||||
((tag (first stmt)))
|
|
||||||
(cond
|
|
||||||
((= tag :branch)
|
|
||||||
(let
|
|
||||||
((target (apl-eval-ast (nth stmt 1) env)))
|
|
||||||
(let
|
|
||||||
((target-num (first (get target :ravel))))
|
|
||||||
(apl-tradfn-loop stmts target-num env result-name))))
|
|
||||||
(else
|
|
||||||
(apl-tradfn-loop
|
|
||||||
stmts
|
|
||||||
(+ line 1)
|
|
||||||
(apl-tradfn-eval-stmt stmt env)
|
|
||||||
result-name)))))))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
apl-call-tradfn
|
|
||||||
(fn
|
|
||||||
(tradfn alpha omega)
|
|
||||||
(let
|
|
||||||
((stmts (get tradfn :stmts))
|
|
||||||
(result-name (get tradfn :result))
|
|
||||||
(alpha-name (get tradfn :alpha))
|
|
||||||
(omega-name (get tradfn :omega)))
|
|
||||||
(let
|
|
||||||
((env-a (if alpha-name (assoc {} alpha-name alpha) {})))
|
|
||||||
(let
|
|
||||||
((env-ao (if omega-name (assoc env-a omega-name omega) env-a)))
|
|
||||||
(apl-tradfn-loop stmts 1 env-ao result-name))))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
apl-ast-mentions-list?
|
|
||||||
(fn
|
|
||||||
(lst target)
|
|
||||||
(if
|
|
||||||
(= (len lst) 0)
|
|
||||||
false
|
|
||||||
(if
|
|
||||||
(apl-ast-mentions? (first lst) target)
|
|
||||||
true
|
|
||||||
(apl-ast-mentions-list? (rest lst) target)))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
apl-ast-mentions?
|
|
||||||
(fn
|
|
||||||
(node target)
|
|
||||||
(cond
|
|
||||||
((not (list? node)) false)
|
|
||||||
((= (len node) 0) false)
|
|
||||||
((and (= (first node) :name) (= (nth node 1) target)) true)
|
|
||||||
(else (apl-ast-mentions-list? (rest node) target)))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
apl-dfn-valence
|
|
||||||
(fn
|
|
||||||
(dfn-ast)
|
|
||||||
(let
|
|
||||||
((body (rest dfn-ast)))
|
|
||||||
(cond
|
|
||||||
((apl-ast-mentions-list? body "⍺") :dyadic)
|
|
||||||
((apl-ast-mentions-list? body "⍵") :monadic)
|
|
||||||
(else :niladic)))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
apl-tradfn-valence
|
|
||||||
(fn
|
|
||||||
(tradfn)
|
|
||||||
(cond
|
|
||||||
((get tradfn :alpha) :dyadic)
|
|
||||||
((get tradfn :omega) :monadic)
|
|
||||||
(else :niladic))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
apl-call
|
|
||||||
(fn
|
|
||||||
(f alpha omega)
|
|
||||||
(cond
|
|
||||||
((and (list? f) (> (len f) 0) (= (first f) :dfn))
|
|
||||||
(if alpha (apl-call-dfn f alpha omega) (apl-call-dfn-m f omega)))
|
|
||||||
((dict? f) (apl-call-tradfn f alpha omega))
|
|
||||||
(else (error "apl-call: not a function")))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
apl-resolve-monadic
|
|
||||||
(fn
|
|
||||||
(fn-node env)
|
|
||||||
(let
|
|
||||||
((tag (first fn-node)))
|
|
||||||
(cond
|
|
||||||
((= tag :fn-glyph) (apl-monadic-fn (nth fn-node 1)))
|
|
||||||
((= tag :derived-fn)
|
|
||||||
(let
|
|
||||||
((op (nth fn-node 1)) (inner (nth fn-node 2)))
|
|
||||||
(cond
|
|
||||||
((= op "/")
|
|
||||||
(let
|
|
||||||
((f (apl-resolve-dyadic inner env)))
|
|
||||||
(fn (arr) (apl-reduce f arr))))
|
|
||||||
((= op "⌿")
|
|
||||||
(let
|
|
||||||
((f (apl-resolve-dyadic inner env)))
|
|
||||||
(fn (arr) (apl-reduce-first f arr))))
|
|
||||||
((= op "\\")
|
|
||||||
(let
|
|
||||||
((f (apl-resolve-dyadic inner env)))
|
|
||||||
(fn (arr) (apl-scan f arr))))
|
|
||||||
((= op "⍀")
|
|
||||||
(let
|
|
||||||
((f (apl-resolve-dyadic inner env)))
|
|
||||||
(fn (arr) (apl-scan-first f arr))))
|
|
||||||
((= op "¨")
|
|
||||||
(let
|
|
||||||
((f (apl-resolve-monadic inner env)))
|
|
||||||
(fn (arr) (apl-each f arr))))
|
|
||||||
((= op "⍨")
|
|
||||||
(let
|
|
||||||
((f (apl-resolve-dyadic inner env)))
|
|
||||||
(fn (arr) (apl-commute f arr))))
|
|
||||||
(else (error "apl-resolve-monadic: unsupported op")))))
|
|
||||||
(else (error "apl-resolve-monadic: unknown fn-node tag"))))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
apl-resolve-dyadic
|
|
||||||
(fn
|
|
||||||
(fn-node env)
|
|
||||||
(let
|
|
||||||
((tag (first fn-node)))
|
|
||||||
(cond
|
|
||||||
((= tag :fn-glyph) (apl-dyadic-fn (nth fn-node 1)))
|
|
||||||
((= tag :derived-fn)
|
|
||||||
(let
|
|
||||||
((op (nth fn-node 1)) (inner (nth fn-node 2)))
|
|
||||||
(cond
|
|
||||||
((= op "¨")
|
|
||||||
(let
|
|
||||||
((f (apl-resolve-dyadic inner env)))
|
|
||||||
(fn (a b) (apl-each-dyadic f a b))))
|
|
||||||
((= op "⍨")
|
|
||||||
(let
|
|
||||||
((f (apl-resolve-dyadic inner env)))
|
|
||||||
(fn (a b) (apl-commute-dyadic f a b))))
|
|
||||||
(else (error "apl-resolve-dyadic: unsupported op")))))
|
|
||||||
((= tag :outer)
|
|
||||||
(let
|
|
||||||
((inner (nth fn-node 2)))
|
|
||||||
(let
|
|
||||||
((f (apl-resolve-dyadic inner env)))
|
|
||||||
(fn (a b) (apl-outer f a b)))))
|
|
||||||
((= tag :derived-fn2)
|
|
||||||
(let
|
|
||||||
((f-node (nth fn-node 2)) (g-node (nth fn-node 3)))
|
|
||||||
(let
|
|
||||||
((f (apl-resolve-dyadic f-node env))
|
|
||||||
(g (apl-resolve-dyadic g-node env)))
|
|
||||||
(fn (a b) (apl-inner f g a b)))))
|
|
||||||
(else (error "apl-resolve-dyadic: unknown fn-node tag"))))))
|
|
||||||
|
|
||||||
(define apl-run (fn (src) (apl-eval-ast (parse-apl src) {})))
|
|
||||||
@@ -30,7 +30,7 @@ run_suite() {
|
|||||||
local name="$1" load_files="$2" pass_var="$3" fail_var="$4" failures_var="$5"
|
local name="$1" load_files="$2" pass_var="$3" fail_var="$4" failures_var="$5"
|
||||||
local TMP; TMP=$(mktemp)
|
local TMP; TMP=$(mktemp)
|
||||||
{
|
{
|
||||||
printf '(epoch 1)\n(load "spec/stdlib.sx")\n(load "lib/guest/prefix.sx")\n'
|
printf '(epoch 1)\n(load "spec/stdlib.sx")\n'
|
||||||
local i=2
|
local i=2
|
||||||
for f in $load_files; do
|
for f in $load_files; do
|
||||||
printf '(epoch %d)\n(load "%s")\n' "$i" "$f"
|
printf '(epoch %d)\n(load "%s")\n' "$i" "$f"
|
||||||
|
|||||||
@@ -23,19 +23,13 @@
|
|||||||
(cl-numberp? x)
|
(cl-numberp? x)
|
||||||
(let ((t (type-of x))) (or (= t "number") (= t "rational"))))
|
(let ((t (type-of x))) (or (= t "number") (= t "rational"))))
|
||||||
|
|
||||||
(prefix-rename "cl-"
|
(define cl-integerp? integer?)
|
||||||
'(
|
(define cl-floatp? float?)
|
||||||
(integerp? integer?)
|
(define cl-rationalp? rational?)
|
||||||
(floatp? float?)
|
|
||||||
(rationalp? rational?)
|
|
||||||
))
|
|
||||||
|
|
||||||
(define (cl-realp? x) (or (integer? x) (float? x) (rational? x)))
|
(define (cl-realp? x) (or (integer? x) (float? x) (rational? x)))
|
||||||
|
|
||||||
(prefix-rename "cl-"
|
(define cl-characterp? char?)
|
||||||
'(
|
|
||||||
(characterp? char?)
|
|
||||||
))
|
|
||||||
(define cl-stringp? (fn (x) (= (type-of x) "string")))
|
(define cl-stringp? (fn (x) (= (type-of x) "string")))
|
||||||
(define cl-symbolp? (fn (x) (= (type-of x) "symbol")))
|
(define cl-symbolp? (fn (x) (= (type-of x) "symbol")))
|
||||||
(define cl-keywordp? (fn (x) (= (type-of x) "keyword")))
|
(define cl-keywordp? (fn (x) (= (type-of x) "keyword")))
|
||||||
@@ -50,11 +44,8 @@
|
|||||||
(= t "native-fn")
|
(= t "native-fn")
|
||||||
(= t "component"))))
|
(= t "component"))))
|
||||||
|
|
||||||
(prefix-rename "cl-"
|
(define cl-vectorp? vector?)
|
||||||
'(
|
(define cl-arrayp? vector?)
|
||||||
(vectorp? vector?)
|
|
||||||
(arrayp? vector?)
|
|
||||||
))
|
|
||||||
|
|
||||||
;; sx_server: (rest (list x)) returns () not nil — cl-empty? handles both
|
;; sx_server: (rest (list x)) returns () not nil — cl-empty? handles both
|
||||||
(define
|
(define
|
||||||
@@ -65,25 +56,19 @@
|
|||||||
;; 2. Arithmetic — thin aliases to spec primitives
|
;; 2. Arithmetic — thin aliases to spec primitives
|
||||||
;; ---------------------------------------------------------------------------
|
;; ---------------------------------------------------------------------------
|
||||||
|
|
||||||
(prefix-rename "cl-"
|
(define cl-mod modulo)
|
||||||
'(
|
(define cl-rem remainder)
|
||||||
(mod modulo)
|
(define cl-gcd gcd)
|
||||||
(rem remainder)
|
(define cl-lcm lcm)
|
||||||
gcd
|
(define cl-expt expt)
|
||||||
lcm
|
(define cl-floor floor)
|
||||||
expt
|
(define cl-ceiling ceil)
|
||||||
floor
|
(define cl-truncate truncate)
|
||||||
(ceiling ceil)
|
(define cl-round round)
|
||||||
truncate
|
|
||||||
round
|
|
||||||
))
|
|
||||||
(define cl-abs (fn (x) (if (< x 0) (- 0 x) x)))
|
(define cl-abs (fn (x) (if (< x 0) (- 0 x) x)))
|
||||||
(define cl-min (fn (a b) (if (< a b) a b)))
|
(define cl-min (fn (a b) (if (< a b) a b)))
|
||||||
(define cl-max (fn (a b) (if (> a b) a b)))
|
(define cl-max (fn (a b) (if (> a b) a b)))
|
||||||
(prefix-rename "cl-"
|
(define cl-quotient quotient)
|
||||||
'(
|
|
||||||
quotient
|
|
||||||
))
|
|
||||||
|
|
||||||
(define
|
(define
|
||||||
(cl-signum x)
|
(cl-signum x)
|
||||||
@@ -102,27 +87,21 @@
|
|||||||
;; 3. Character functions — alias spec char primitives + CL name mapping
|
;; 3. Character functions — alias spec char primitives + CL name mapping
|
||||||
;; ---------------------------------------------------------------------------
|
;; ---------------------------------------------------------------------------
|
||||||
|
|
||||||
(prefix-rename "cl-"
|
(define cl-char->integer char->integer)
|
||||||
'(
|
(define cl-integer->char integer->char)
|
||||||
char->integer
|
(define cl-char-upcase char-upcase)
|
||||||
integer->char
|
(define cl-char-downcase char-downcase)
|
||||||
char-upcase
|
(define cl-char-code char->integer)
|
||||||
char-downcase
|
(define cl-code-char integer->char)
|
||||||
(char-code char->integer)
|
|
||||||
(code-char integer->char)
|
|
||||||
))
|
|
||||||
|
|
||||||
(prefix-rename "cl-"
|
(define cl-char=? char=?)
|
||||||
'(
|
(define cl-char<? char<?)
|
||||||
char=?
|
(define cl-char>? char>?)
|
||||||
char<?
|
(define cl-char<=? char<=?)
|
||||||
char>?
|
(define cl-char>=? char>=?)
|
||||||
char<=?
|
(define cl-char-ci=? char-ci=?)
|
||||||
char>=?
|
(define cl-char-ci<? char-ci<?)
|
||||||
char-ci=?
|
(define cl-char-ci>? char-ci>?)
|
||||||
char-ci<?
|
|
||||||
char-ci>?
|
|
||||||
))
|
|
||||||
|
|
||||||
;; Inline predicates — char-alphabetic?/char-numeric? unreliable in sx_server
|
;; Inline predicates — char-alphabetic?/char-numeric? unreliable in sx_server
|
||||||
(define
|
(define
|
||||||
@@ -173,11 +152,8 @@
|
|||||||
(cl-format dest template &rest args)
|
(cl-format dest template &rest args)
|
||||||
(let ((s (apply format (cons template args)))) (if (= dest nil) s s)))
|
(let ((s (apply format (cons template args)))) (if (= dest nil) s s)))
|
||||||
|
|
||||||
(prefix-rename "cl-"
|
(define cl-write-to-string write-to-string)
|
||||||
'(
|
(define cl-princ-to-string display-to-string)
|
||||||
write-to-string
|
|
||||||
(princ-to-string display-to-string)
|
|
||||||
))
|
|
||||||
|
|
||||||
;; CL read-from-string: parse value from a string using SX port
|
;; CL read-from-string: parse value from a string using SX port
|
||||||
(define
|
(define
|
||||||
@@ -185,27 +161,18 @@
|
|||||||
(let ((p (open-input-string s))) (read p)))
|
(let ((p (open-input-string s))) (read p)))
|
||||||
|
|
||||||
;; String stream (output)
|
;; String stream (output)
|
||||||
(prefix-rename "cl-"
|
(define cl-make-string-output-stream open-output-string)
|
||||||
'(
|
(define cl-get-output-stream-string get-output-string)
|
||||||
(make-string-output-stream open-output-string)
|
|
||||||
(get-output-stream-string get-output-string)
|
|
||||||
))
|
|
||||||
|
|
||||||
;; String stream (input)
|
;; String stream (input)
|
||||||
(prefix-rename "cl-"
|
(define cl-make-string-input-stream open-input-string)
|
||||||
'(
|
|
||||||
(make-string-input-stream open-input-string)
|
|
||||||
))
|
|
||||||
|
|
||||||
;; ---------------------------------------------------------------------------
|
;; ---------------------------------------------------------------------------
|
||||||
;; 5. Gensym
|
;; 5. Gensym
|
||||||
;; ---------------------------------------------------------------------------
|
;; ---------------------------------------------------------------------------
|
||||||
|
|
||||||
(prefix-rename "cl-"
|
(define cl-gensym gensym)
|
||||||
'(
|
(define cl-gentemp gensym)
|
||||||
gensym
|
|
||||||
(gentemp gensym)
|
|
||||||
))
|
|
||||||
|
|
||||||
;; ---------------------------------------------------------------------------
|
;; ---------------------------------------------------------------------------
|
||||||
;; 6. Multiple values (CL: values / nth-value)
|
;; 6. Multiple values (CL: values / nth-value)
|
||||||
@@ -236,19 +203,16 @@
|
|||||||
;; 7. Sets (CL: adjoin / member / union / intersection / set-difference)
|
;; 7. Sets (CL: adjoin / member / union / intersection / set-difference)
|
||||||
;; ---------------------------------------------------------------------------
|
;; ---------------------------------------------------------------------------
|
||||||
|
|
||||||
(prefix-rename "cl-"
|
(define cl-make-set make-set)
|
||||||
'(
|
(define cl-set? set?)
|
||||||
make-set
|
(define cl-set-add set-add!)
|
||||||
set?
|
(define cl-set-memberp set-member?)
|
||||||
(set-add set-add!)
|
(define cl-set-remove set-remove!)
|
||||||
(set-memberp set-member?)
|
(define cl-set-union set-union)
|
||||||
(set-remove set-remove!)
|
(define cl-set-intersect set-intersection)
|
||||||
set-union
|
(define cl-set-difference set-difference)
|
||||||
(set-intersect set-intersection)
|
(define cl-list->set list->set)
|
||||||
set-difference
|
(define cl-set->list set->list)
|
||||||
list->set
|
|
||||||
set->list
|
|
||||||
))
|
|
||||||
|
|
||||||
;; CL: (member item list) — returns tail starting at item, or nil
|
;; CL: (member item list) — returns tail starting at item, or nil
|
||||||
(define
|
(define
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"generated": "2026-05-06T22:55:42Z",
|
"generated": "2026-05-05T12:35:09Z",
|
||||||
"total_pass": 518,
|
"total_pass": 518,
|
||||||
"total_fail": 0,
|
"total_fail": 0,
|
||||||
"suites": [
|
"suites": [
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Common Lisp on SX — Scoreboard
|
# Common Lisp on SX — Scoreboard
|
||||||
|
|
||||||
_Generated: 2026-05-06 22:55 UTC_
|
_Generated: 2026-05-05 12:35 UTC_
|
||||||
|
|
||||||
| Suite | Pass | Fail | Status |
|
| Suite | Pass | Fail | Status |
|
||||||
|-------|------|------|--------|
|
|-------|------|------|--------|
|
||||||
|
|||||||
@@ -1008,27 +1008,11 @@
|
|||||||
(let
|
(let
|
||||||
((name (symbol-name head))
|
((name (symbol-name head))
|
||||||
(argc (len args))
|
(argc (len args))
|
||||||
(specialized-op (cond
|
(name-idx (pool-add (get em "pool") name)))
|
||||||
(and (= argc 2) (= name "+")) 160
|
|
||||||
(and (= argc 2) (= name "-")) 161
|
|
||||||
(and (= argc 2) (= name "*")) 162
|
|
||||||
(and (= argc 2) (= name "/")) 163
|
|
||||||
(and (= argc 2) (= name "=")) 164
|
|
||||||
(and (= argc 2) (= name "<")) 165
|
|
||||||
(and (= argc 2) (= name ">")) 166
|
|
||||||
(and (= argc 2) (= name "cons")) 172
|
|
||||||
(and (= argc 1) (= name "not")) 167
|
|
||||||
(and (= argc 1) (= name "len")) 168
|
|
||||||
(and (= argc 1) (= name "first")) 169
|
|
||||||
(and (= argc 1) (= name "rest")) 170
|
|
||||||
:else nil)))
|
|
||||||
(for-each (fn (a) (compile-expr em a scope false)) args)
|
(for-each (fn (a) (compile-expr em a scope false)) args)
|
||||||
(if specialized-op
|
(emit-op em 52)
|
||||||
(emit-op em specialized-op)
|
(emit-u16 em name-idx)
|
||||||
(let ((name-idx (pool-add (get em "pool") name)))
|
(emit-byte em argc))
|
||||||
(emit-op em 52)
|
|
||||||
(emit-u16 em name-idx)
|
|
||||||
(emit-byte em argc))))
|
|
||||||
(do
|
(do
|
||||||
(compile-expr em head scope false)
|
(compile-expr em head scope false)
|
||||||
(for-each (fn (a) (compile-expr em a scope false)) args)
|
(for-each (fn (a) (compile-expr em a scope false)) args)
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"language": "erlang",
|
"language": "erlang",
|
||||||
"total_pass": 0,
|
"total_pass": 530,
|
||||||
"total": 0,
|
"total": 530,
|
||||||
"suites": [
|
"suites": [
|
||||||
{"name":"tokenize","pass":0,"total":0,"status":"ok"},
|
{"name":"tokenize","pass":62,"total":62,"status":"ok"},
|
||||||
{"name":"parse","pass":0,"total":0,"status":"ok"},
|
{"name":"parse","pass":52,"total":52,"status":"ok"},
|
||||||
{"name":"eval","pass":0,"total":0,"status":"ok"},
|
{"name":"eval","pass":346,"total":346,"status":"ok"},
|
||||||
{"name":"runtime","pass":0,"total":0,"status":"ok"},
|
{"name":"runtime","pass":39,"total":39,"status":"ok"},
|
||||||
{"name":"ring","pass":0,"total":0,"status":"ok"},
|
{"name":"ring","pass":4,"total":4,"status":"ok"},
|
||||||
{"name":"ping-pong","pass":0,"total":0,"status":"ok"},
|
{"name":"ping-pong","pass":4,"total":4,"status":"ok"},
|
||||||
{"name":"bank","pass":0,"total":0,"status":"ok"},
|
{"name":"bank","pass":8,"total":8,"status":"ok"},
|
||||||
{"name":"echo","pass":0,"total":0,"status":"ok"},
|
{"name":"echo","pass":7,"total":7,"status":"ok"},
|
||||||
{"name":"fib","pass":0,"total":0,"status":"ok"}
|
{"name":"fib","pass":8,"total":8,"status":"ok"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
# Erlang-on-SX Scoreboard
|
# Erlang-on-SX Scoreboard
|
||||||
|
|
||||||
**Total: 0 / 0 tests passing**
|
**Total: 530 / 530 tests passing**
|
||||||
|
|
||||||
| | Suite | Pass | Total |
|
| | Suite | Pass | Total |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| ✅ | tokenize | 0 | 0 |
|
| ✅ | tokenize | 62 | 62 |
|
||||||
| ✅ | parse | 0 | 0 |
|
| ✅ | parse | 52 | 52 |
|
||||||
| ✅ | eval | 0 | 0 |
|
| ✅ | eval | 346 | 346 |
|
||||||
| ✅ | runtime | 0 | 0 |
|
| ✅ | runtime | 39 | 39 |
|
||||||
| ✅ | ring | 0 | 0 |
|
| ✅ | ring | 4 | 4 |
|
||||||
| ✅ | ping-pong | 0 | 0 |
|
| ✅ | ping-pong | 4 | 4 |
|
||||||
| ✅ | bank | 0 | 0 |
|
| ✅ | bank | 8 | 8 |
|
||||||
| ✅ | echo | 0 | 0 |
|
| ✅ | echo | 7 | 7 |
|
||||||
| ✅ | fib | 0 | 0 |
|
| ✅ | fib | 8 | 8 |
|
||||||
|
|
||||||
|
|
||||||
Generated by `lib/erlang/conformance.sh`.
|
Generated by `lib/erlang/conformance.sh`.
|
||||||
|
|||||||
44
lib/fiber.sx
44
lib/fiber.sx
@@ -1,44 +0,0 @@
|
|||||||
; lib/fiber.sx — pure SX fiber library using call/cc
|
|
||||||
;
|
|
||||||
; A fiber is a cooperative coroutine with true suspension (no eager
|
|
||||||
; pre-execution). Each fiber is a dict {:resume fn :done? fn}.
|
|
||||||
;
|
|
||||||
; make-fiber body → fiber dict
|
|
||||||
; body = (fn (yield init-val) ...) — body receives yield + first resume val
|
|
||||||
; yield = (fn (val) ...) — suspends fiber, returns val to resumer
|
|
||||||
;
|
|
||||||
; fiber-resume f v → next yielded value, or nil when body returns
|
|
||||||
; fiber-done? f → true after body has returned
|
|
||||||
|
|
||||||
(define make-fiber
|
|
||||||
(fn (body)
|
|
||||||
(let
|
|
||||||
((resume-k nil)
|
|
||||||
(caller-k nil)
|
|
||||||
(done false))
|
|
||||||
(let
|
|
||||||
((yield
|
|
||||||
(fn (val)
|
|
||||||
(call/cc
|
|
||||||
(fn (k)
|
|
||||||
(set! resume-k k)
|
|
||||||
(caller-k val))))))
|
|
||||||
{:resume
|
|
||||||
(fn (val)
|
|
||||||
(if
|
|
||||||
done
|
|
||||||
nil
|
|
||||||
(call/cc
|
|
||||||
(fn (k)
|
|
||||||
(set! caller-k k)
|
|
||||||
(if
|
|
||||||
(nil? resume-k)
|
|
||||||
(begin
|
|
||||||
(body yield val)
|
|
||||||
(set! done true)
|
|
||||||
(k nil))
|
|
||||||
(resume-k val))))))
|
|
||||||
:done? (fn () done)}))))
|
|
||||||
|
|
||||||
(define fiber-resume (fn (f v) ((get f :resume) v)))
|
|
||||||
(define fiber-done? (fn (f) ((get f :done?))))
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
;; lib/guest/ast.sx — canonical AST node shapes.
|
|
||||||
;;
|
|
||||||
;; A guest's parser may emit its own AST in whatever shape is convenient
|
|
||||||
;; for that language's evaluator/transpiler. This file gives a SHARED
|
|
||||||
;; canonical shape that cross-language tools (formatters, highlighters,
|
|
||||||
;; debuggers) can target without per-language adapters.
|
|
||||||
;;
|
|
||||||
;; Each canonical node is a tagged list: (KIND ...payload).
|
|
||||||
;;
|
|
||||||
;; Constructors (return a canonical node):
|
|
||||||
;;
|
|
||||||
;; (ast-literal VALUE) — number / string / bool / nil
|
|
||||||
;; (ast-var NAME) — identifier reference
|
|
||||||
;; (ast-app FN ARGS) — function application
|
|
||||||
;; (ast-lambda PARAMS BODY) — anonymous function
|
|
||||||
;; (ast-let BINDINGS BODY) — local bindings
|
|
||||||
;; (ast-letrec BINDINGS BODY) — recursive local bindings
|
|
||||||
;; (ast-if TEST THEN ELSE) — conditional
|
|
||||||
;; (ast-match-clause PATTERN BODY) — one match arm
|
|
||||||
;; (ast-module NAME BODY) — module declaration
|
|
||||||
;; (ast-import NAME) — import directive
|
|
||||||
;;
|
|
||||||
;; Predicates: (ast-literal? X), (ast-var? X), …
|
|
||||||
;; Generic: (ast? X) — any canonical node
|
|
||||||
;; (ast-kind X) — :literal / :var / :app / …
|
|
||||||
;;
|
|
||||||
;; Accessors (one per payload field):
|
|
||||||
;; (ast-literal-value N)
|
|
||||||
;; (ast-var-name N)
|
|
||||||
;; (ast-app-fn N) (ast-app-args N)
|
|
||||||
;; (ast-lambda-params N) (ast-lambda-body N)
|
|
||||||
;; (ast-let-bindings N) (ast-let-body N)
|
|
||||||
;; (ast-letrec-bindings N) (ast-letrec-body N)
|
|
||||||
;; (ast-if-test N) (ast-if-then N) (ast-if-else N)
|
|
||||||
;; (ast-match-clause-pattern N)
|
|
||||||
;; (ast-match-clause-body N)
|
|
||||||
;; (ast-module-name N) (ast-module-body N)
|
|
||||||
;; (ast-import-name N)
|
|
||||||
|
|
||||||
(define ast-literal (fn (v) (list :literal v)))
|
|
||||||
(define ast-var (fn (n) (list :var n)))
|
|
||||||
(define ast-app (fn (f args) (list :app f args)))
|
|
||||||
(define ast-lambda (fn (ps body) (list :lambda ps body)))
|
|
||||||
(define ast-let (fn (bs body) (list :let bs body)))
|
|
||||||
(define ast-letrec (fn (bs body) (list :letrec bs body)))
|
|
||||||
(define ast-if (fn (t th el) (list :if t th el)))
|
|
||||||
(define ast-match-clause (fn (p body) (list :match-clause p body)))
|
|
||||||
(define ast-module (fn (n body) (list :module n body)))
|
|
||||||
(define ast-import (fn (n) (list :import n)))
|
|
||||||
|
|
||||||
(define ast-kind (fn (x) (if (and (list? x) (not (empty? x))) (first x) nil)))
|
|
||||||
|
|
||||||
(define
|
|
||||||
ast?
|
|
||||||
(fn (x)
|
|
||||||
(and (list? x)
|
|
||||||
(not (empty? x))
|
|
||||||
(let ((k (first x)))
|
|
||||||
(or (= k :literal) (= k :var) (= k :app)
|
|
||||||
(= k :lambda) (= k :let) (= k :letrec)
|
|
||||||
(= k :if) (= k :match-clause)
|
|
||||||
(= k :module) (= k :import))))))
|
|
||||||
|
|
||||||
(define ast-literal? (fn (x) (and (ast? x) (= (first x) :literal))))
|
|
||||||
(define ast-var? (fn (x) (and (ast? x) (= (first x) :var))))
|
|
||||||
(define ast-app? (fn (x) (and (ast? x) (= (first x) :app))))
|
|
||||||
(define ast-lambda? (fn (x) (and (ast? x) (= (first x) :lambda))))
|
|
||||||
(define ast-let? (fn (x) (and (ast? x) (= (first x) :let))))
|
|
||||||
(define ast-letrec? (fn (x) (and (ast? x) (= (first x) :letrec))))
|
|
||||||
(define ast-if? (fn (x) (and (ast? x) (= (first x) :if))))
|
|
||||||
(define ast-match-clause? (fn (x) (and (ast? x) (= (first x) :match-clause))))
|
|
||||||
(define ast-module? (fn (x) (and (ast? x) (= (first x) :module))))
|
|
||||||
(define ast-import? (fn (x) (and (ast? x) (= (first x) :import))))
|
|
||||||
|
|
||||||
(define ast-literal-value (fn (n) (nth n 1)))
|
|
||||||
(define ast-var-name (fn (n) (nth n 1)))
|
|
||||||
(define ast-app-fn (fn (n) (nth n 1)))
|
|
||||||
(define ast-app-args (fn (n) (nth n 2)))
|
|
||||||
(define ast-lambda-params (fn (n) (nth n 1)))
|
|
||||||
(define ast-lambda-body (fn (n) (nth n 2)))
|
|
||||||
(define ast-let-bindings (fn (n) (nth n 1)))
|
|
||||||
(define ast-let-body (fn (n) (nth n 2)))
|
|
||||||
(define ast-letrec-bindings (fn (n) (nth n 1)))
|
|
||||||
(define ast-letrec-body (fn (n) (nth n 2)))
|
|
||||||
(define ast-if-test (fn (n) (nth n 1)))
|
|
||||||
(define ast-if-then (fn (n) (nth n 2)))
|
|
||||||
(define ast-if-else (fn (n) (nth n 3)))
|
|
||||||
(define ast-match-clause-pattern (fn (n) (nth n 1)))
|
|
||||||
(define ast-match-clause-body (fn (n) (nth n 2)))
|
|
||||||
(define ast-module-name (fn (n) (nth n 1)))
|
|
||||||
(define ast-module-body (fn (n) (nth n 2)))
|
|
||||||
(define ast-import-name (fn (n) (nth n 1)))
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"lang": "apl",
|
|
||||||
"captured": "2026-05-06T22:01:00Z",
|
|
||||||
"suite_command": "bash lib/apl/test.sh",
|
|
||||||
"totals": {
|
|
||||||
"pass": 73,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 73
|
|
||||||
},
|
|
||||||
"suites": [
|
|
||||||
{
|
|
||||||
"name": "all",
|
|
||||||
"pass": 73,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 73
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
{
|
|
||||||
"lang": "common-lisp",
|
|
||||||
"captured": "2026-05-06T22:59:46Z",
|
|
||||||
"suite_command": "bash lib/common-lisp/conformance.sh",
|
|
||||||
"totals": {
|
|
||||||
"pass": 518,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 518
|
|
||||||
},
|
|
||||||
"suites": [
|
|
||||||
{
|
|
||||||
"name": "Phase 1: tokenizer/reader",
|
|
||||||
"pass": 79,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 79
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Phase 1: parser/lambda-lists",
|
|
||||||
"pass": 31,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 31
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Phase 2: evaluator",
|
|
||||||
"pass": 182,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 182
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Phase 3: condition system",
|
|
||||||
"pass": 59,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 59
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Phase 3: restart-demo",
|
|
||||||
"pass": 7,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 7
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Phase 3: parse-recover",
|
|
||||||
"pass": 6,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Phase 3: interactive-debugger",
|
|
||||||
"pass": 7,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 7
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Phase 4: CLOS",
|
|
||||||
"pass": 41,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 41
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Phase 4: geometry",
|
|
||||||
"pass": 12,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 12
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Phase 4: mop-trace",
|
|
||||||
"pass": 13,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 13
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Phase 5: macros+LOOP",
|
|
||||||
"pass": 27,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 27
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Phase 6: stdlib",
|
|
||||||
"pass": 54,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 54
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source_scoreboard": "lib/common-lisp/scoreboard.json",
|
|
||||||
"note": "Step 2: previous baseline (309) was lower because Phase 2 (evaluator, +182 tests) and Phase 6 (stdlib, +27 tests) results were under-counted by the original conformance.sh's parser. Re-running with prefix.sx loaded reveals true counts. No tests regressed."
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
{
|
|
||||||
"lang": "erlang",
|
|
||||||
"captured": "2026-05-06T22:01:00Z",
|
|
||||||
"suite_command": "bash lib/erlang/conformance.sh",
|
|
||||||
"totals": {
|
|
||||||
"pass": 0,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 0
|
|
||||||
},
|
|
||||||
"suites": [
|
|
||||||
{
|
|
||||||
"name": "tokenize",
|
|
||||||
"pass": 0,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "parse",
|
|
||||||
"pass": 0,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "eval",
|
|
||||||
"pass": 0,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "runtime",
|
|
||||||
"pass": 0,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ring",
|
|
||||||
"pass": 0,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ping-pong",
|
|
||||||
"pass": 0,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "bank",
|
|
||||||
"pass": 0,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "echo",
|
|
||||||
"pass": 0,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "fib",
|
|
||||||
"pass": 0,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source_scoreboard": "lib/erlang/scoreboard.json"
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"lang": "forth",
|
|
||||||
"captured": "2026-05-06T22:01:00Z",
|
|
||||||
"suite_command": "bash lib/forth/test.sh",
|
|
||||||
"totals": {
|
|
||||||
"pass": 64,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 64
|
|
||||||
},
|
|
||||||
"suites": [
|
|
||||||
{
|
|
||||||
"name": "all",
|
|
||||||
"pass": 64,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 64
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
{
|
|
||||||
"lang": "haskell",
|
|
||||||
"captured": "2026-05-06T22:46:16Z",
|
|
||||||
"suite_command": "bash lib/haskell/conformance.sh",
|
|
||||||
"totals": {
|
|
||||||
"pass": 156,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 156
|
|
||||||
},
|
|
||||||
"suites": [
|
|
||||||
{
|
|
||||||
"name": "fib",
|
|
||||||
"pass": 2,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "sieve",
|
|
||||||
"pass": 2,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "quicksort",
|
|
||||||
"pass": 5,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "nqueens",
|
|
||||||
"pass": 2,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "calculator",
|
|
||||||
"pass": 5,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "collatz",
|
|
||||||
"pass": 11,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "palindrome",
|
|
||||||
"pass": 8,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 8
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "maybe",
|
|
||||||
"pass": 12,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 12
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "fizzbuzz",
|
|
||||||
"pass": 12,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 12
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "anagram",
|
|
||||||
"pass": 9,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "roman",
|
|
||||||
"pass": 14,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 14
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "binary",
|
|
||||||
"pass": 12,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 12
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "either",
|
|
||||||
"pass": 12,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 12
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "primes",
|
|
||||||
"pass": 12,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 12
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "zipwith",
|
|
||||||
"pass": 9,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "matrix",
|
|
||||||
"pass": 8,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 8
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "wordcount",
|
|
||||||
"pass": 7,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 7
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "powers",
|
|
||||||
"pass": 14,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 14
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source_scoreboard": "lib/haskell/scoreboard.json",
|
|
||||||
"note": "Step 1: previous baseline (0/18) was an artefact of the old conformance.sh bug \u2014 its (ok-len 3 ...) grep never matched, defaulting every program to 0 pass / 1 fail. Shared driver in Step 1 reads counters correctly."
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
{
|
|
||||||
"lang": "js",
|
|
||||||
"captured": "2026-05-06T22:01:00Z",
|
|
||||||
"suite_command": "bash lib/js/conformance.sh",
|
|
||||||
"totals": {
|
|
||||||
"pass": 94,
|
|
||||||
"fail": 54,
|
|
||||||
"total": 148
|
|
||||||
},
|
|
||||||
"suites": [
|
|
||||||
{
|
|
||||||
"name": "test262-slice",
|
|
||||||
"pass": 94,
|
|
||||||
"fail": 54,
|
|
||||||
"total": 148,
|
|
||||||
"failing_tests": [
|
|
||||||
"arithmetic/bitnot",
|
|
||||||
"arithmetic/mixed_concat",
|
|
||||||
"async/await_promise_all",
|
|
||||||
"closures/sum_sq",
|
|
||||||
"coercion/implicit_str_add",
|
|
||||||
"collections/array_index",
|
|
||||||
"collections/array_nested",
|
|
||||||
"collections/string_index",
|
|
||||||
"functions/rest_param",
|
|
||||||
"loops/for_break",
|
|
||||||
"loops/for_continue",
|
|
||||||
"loops/nested_for",
|
|
||||||
"loops/while_basic",
|
|
||||||
"loops/while_break_infinite",
|
|
||||||
"objects/array_filter_reduce",
|
|
||||||
"objects/array_map",
|
|
||||||
"objects/array_method_chain",
|
|
||||||
"objects/array_mutate",
|
|
||||||
"objects/array_push_length",
|
|
||||||
"objects/arrow_lexical_this",
|
|
||||||
"objects/class_basic",
|
|
||||||
"objects/class_extend_chain",
|
|
||||||
"objects/class_inherit",
|
|
||||||
"objects/counter_closure",
|
|
||||||
"objects/in_operator",
|
|
||||||
"objects/instanceof",
|
|
||||||
"objects/method_this",
|
|
||||||
"objects/new_constructor",
|
|
||||||
"objects/object_mutate",
|
|
||||||
"objects/prototype_chain",
|
|
||||||
"objects/string_method",
|
|
||||||
"objects/string_slice",
|
|
||||||
"promises/executor_throws",
|
|
||||||
"promises/finally_passthrough",
|
|
||||||
"promises/microtask_ordering",
|
|
||||||
"promises/new_promise_reject",
|
|
||||||
"promises/new_promise_resolve",
|
|
||||||
"promises/promise_all",
|
|
||||||
"promises/promise_all_empty",
|
|
||||||
"promises/promise_all_nonpromise",
|
|
||||||
"promises/promise_all_reject",
|
|
||||||
"promises/promise_race",
|
|
||||||
"promises/promise_resolve_already_promise",
|
|
||||||
"promises/reject_catch",
|
|
||||||
"promises/resolve_adopts",
|
|
||||||
"promises/resolve_then",
|
|
||||||
"promises/then_chain",
|
|
||||||
"promises/then_throw_catch",
|
|
||||||
"statements/block_scope",
|
|
||||||
"statements/const_multi",
|
|
||||||
"statements/if_else_false",
|
|
||||||
"statements/if_else_true",
|
|
||||||
"statements/let_init",
|
|
||||||
"statements/var_decl"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source_scoreboard": "lib/js/conformance.sh-output"
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"lang": "lua",
|
|
||||||
"captured": "2026-05-06T22:01:00Z",
|
|
||||||
"suite_command": "bash lib/lua/test.sh",
|
|
||||||
"totals": {
|
|
||||||
"pass": 185,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 185
|
|
||||||
},
|
|
||||||
"suites": [
|
|
||||||
{
|
|
||||||
"name": "all",
|
|
||||||
"pass": 185,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 185
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,187 +0,0 @@
|
|||||||
{
|
|
||||||
"lang": "prolog",
|
|
||||||
"captured": "2026-05-06T22:01:00Z",
|
|
||||||
"suite_command": "bash lib/prolog/conformance.sh",
|
|
||||||
"totals": {
|
|
||||||
"pass": 590,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 590
|
|
||||||
},
|
|
||||||
"suites": [
|
|
||||||
{
|
|
||||||
"name": "parse",
|
|
||||||
"pass": 25,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 25
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "unify",
|
|
||||||
"pass": 47,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 47
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "clausedb",
|
|
||||||
"pass": 14,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 14
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "solve",
|
|
||||||
"pass": 62,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 62
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "operators",
|
|
||||||
"pass": 19,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 19
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "dynamic",
|
|
||||||
"pass": 11,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "findall",
|
|
||||||
"pass": 11,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "term_inspect",
|
|
||||||
"pass": 14,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 14
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "append",
|
|
||||||
"pass": 6,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "reverse",
|
|
||||||
"pass": 6,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "member",
|
|
||||||
"pass": 7,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 7
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "nqueens",
|
|
||||||
"pass": 6,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "family",
|
|
||||||
"pass": 10,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "atoms",
|
|
||||||
"pass": 34,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 34
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "query_api",
|
|
||||||
"pass": 16,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 16
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "iso_predicates",
|
|
||||||
"pass": 29,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 29
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "meta_predicates",
|
|
||||||
"pass": 25,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 25
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "list_predicates",
|
|
||||||
"pass": 33,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 33
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "meta_call",
|
|
||||||
"pass": 15,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 15
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "set_predicates",
|
|
||||||
"pass": 15,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 15
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "char_predicates",
|
|
||||||
"pass": 27,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 27
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "io_predicates",
|
|
||||||
"pass": 24,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 24
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "assert_rules",
|
|
||||||
"pass": 15,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 15
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "string_agg",
|
|
||||||
"pass": 25,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 25
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "advanced",
|
|
||||||
"pass": 21,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 21
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "compiler",
|
|
||||||
"pass": 17,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 17
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "cross_validate",
|
|
||||||
"pass": 17,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 17
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "integration",
|
|
||||||
"pass": 20,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 20
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "hs_bridge",
|
|
||||||
"pass": 19,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 19
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source_scoreboard": "lib/prolog/scoreboard.json"
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"lang": "ruby",
|
|
||||||
"captured": "2026-05-06T22:01:00Z",
|
|
||||||
"suite_command": "bash lib/ruby/test.sh",
|
|
||||||
"totals": {
|
|
||||||
"pass": 76,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 76
|
|
||||||
},
|
|
||||||
"suites": [
|
|
||||||
{
|
|
||||||
"name": "all",
|
|
||||||
"pass": 76,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 76
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"lang": "smalltalk",
|
|
||||||
"captured": "2026-05-06T22:01:00Z",
|
|
||||||
"suite_command": "bash lib/smalltalk/conformance.sh",
|
|
||||||
"totals": {
|
|
||||||
"pass": 625,
|
|
||||||
"fail": 4,
|
|
||||||
"total": 629
|
|
||||||
},
|
|
||||||
"suites": [
|
|
||||||
{
|
|
||||||
"name": "all",
|
|
||||||
"pass": 625,
|
|
||||||
"fail": 4,
|
|
||||||
"total": 629
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "classic-corpus",
|
|
||||||
"pass": 4,
|
|
||||||
"fail": 1,
|
|
||||||
"total": 5
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source_scoreboard": "lib/smalltalk/scoreboard.json"
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
{
|
|
||||||
"lang": "tcl",
|
|
||||||
"captured": "2026-05-06T22:01:00Z",
|
|
||||||
"suite_command": "bash lib/tcl/conformance.sh",
|
|
||||||
"totals": {
|
|
||||||
"pass": 3,
|
|
||||||
"fail": 1,
|
|
||||||
"total": 4
|
|
||||||
},
|
|
||||||
"suites": [
|
|
||||||
{
|
|
||||||
"name": "assert",
|
|
||||||
"pass": 1,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "event-loop",
|
|
||||||
"pass": 0,
|
|
||||||
"fail": 1,
|
|
||||||
"total": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "for-each-line",
|
|
||||||
"pass": 1,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "with-temp-var",
|
|
||||||
"pass": 1,
|
|
||||||
"fail": 0,
|
|
||||||
"total": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source_scoreboard": "lib/tcl/scoreboard.json"
|
|
||||||
}
|
|
||||||
@@ -1,221 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# lib/guest/conformance.sh — shared, config-driven conformance driver.
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# bash lib/guest/conformance.sh <conf-file>
|
|
||||||
#
|
|
||||||
# The conf file is a bash file that sets:
|
|
||||||
# LANG_NAME e.g. prolog
|
|
||||||
# PRELOADS=( ... ) .sx files to load before any suite (path from repo root)
|
|
||||||
# SUITES=( ... ) colon-separated entries; format depends on MODE
|
|
||||||
# MODE "dict" or "counters"
|
|
||||||
# COUNTERS_PASS (counters mode) global symbol for the pass counter
|
|
||||||
# COUNTERS_FAIL (counters mode) global symbol for the fail counter
|
|
||||||
# TIMEOUT_PER_SUITE (optional, counters mode) seconds per suite, default 120
|
|
||||||
# SCOREBOARD_DIR (optional) defaults to lib/$LANG_NAME
|
|
||||||
#
|
|
||||||
# It may override the bash functions emit_scoreboard_json / emit_scoreboard_md
|
|
||||||
# to produce the per-language scoreboard schema. Defaults are provided.
|
|
||||||
#
|
|
||||||
# Suite formats:
|
|
||||||
# MODE=dict — "name:test-file:(runner-fn)"
|
|
||||||
# The runner expression is evaluated and is expected to
|
|
||||||
# return a dict with :passed/:failed/:total.
|
|
||||||
# MODE=counters — "name:test-file"
|
|
||||||
# Each suite is run in a fresh sx_server session: preloads
|
|
||||||
# are loaded, then the test file, then counters are read.
|
|
||||||
# The suite is treated as starting from counters (0, 0).
|
|
||||||
#
|
|
||||||
# Output:
|
|
||||||
# Writes $SCOREBOARD_DIR/scoreboard.json and $SCOREBOARD_DIR/scoreboard.md.
|
|
||||||
# Exits 0 if every suite is green, 1 otherwise.
|
|
||||||
|
|
||||||
set -uo pipefail
|
|
||||||
cd "$(git rev-parse --show-toplevel)"
|
|
||||||
|
|
||||||
if [ "$#" -lt 1 ]; then
|
|
||||||
echo "usage: $0 <conf-file>" >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
CONF="$1"
|
|
||||||
if [ ! -f "$CONF" ]; then
|
|
||||||
echo "config not found: $CONF" >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Defaults — the conf file may override these.
|
|
||||||
LANG_NAME=
|
|
||||||
PRELOADS=()
|
|
||||||
SUITES=()
|
|
||||||
MODE=dict
|
|
||||||
COUNTERS_PASS=
|
|
||||||
COUNTERS_FAIL=
|
|
||||||
TIMEOUT_PER_SUITE=120
|
|
||||||
SCOREBOARD_DIR=
|
|
||||||
|
|
||||||
emit_scoreboard_json() {
|
|
||||||
# Generic schema. Per-lang configs override this for byte-equality with
|
|
||||||
# historical scoreboards.
|
|
||||||
local n=${#GC_NAMES[@]} i sep
|
|
||||||
printf '{\n'
|
|
||||||
printf ' "lang": "%s",\n' "$LANG_NAME"
|
|
||||||
printf ' "total_passed": %d,\n' "$GC_TOTAL_PASS"
|
|
||||||
printf ' "total_failed": %d,\n' "$GC_TOTAL_FAIL"
|
|
||||||
printf ' "total": %d,\n' "$GC_TOTAL"
|
|
||||||
printf ' "suites": ['
|
|
||||||
for ((i=0; i<n; i++)); do
|
|
||||||
sep=","; [ $i -eq $((n-1)) ] && sep=""
|
|
||||||
printf '\n {"name":"%s","passed":%d,"failed":%d,"total":%d}%s' \
|
|
||||||
"${GC_NAMES[$i]}" "${GC_PASS[$i]}" "${GC_FAIL[$i]}" "${GC_TOTAL_S[$i]}" "$sep"
|
|
||||||
done
|
|
||||||
printf '\n ],\n'
|
|
||||||
printf ' "generated": "%s"\n' "$(date -Iseconds 2>/dev/null || date)"
|
|
||||||
printf '}\n'
|
|
||||||
}
|
|
||||||
|
|
||||||
emit_scoreboard_md() {
|
|
||||||
local n=${#GC_NAMES[@]} i status
|
|
||||||
printf '# %s scoreboard\n\n' "$LANG_NAME"
|
|
||||||
printf '**%d / %d passing** (%d failure(s)).\n\n' "$GC_TOTAL_PASS" "$GC_TOTAL" "$GC_TOTAL_FAIL"
|
|
||||||
printf '| Suite | Passed | Total | Status |\n'
|
|
||||||
printf '|-------|--------|-------|--------|\n'
|
|
||||||
for ((i=0; i<n; i++)); do
|
|
||||||
status="ok"; [ "${GC_FAIL[$i]}" -gt 0 ] && status="FAIL"
|
|
||||||
printf '| %s | %d | %d | %s |\n' \
|
|
||||||
"${GC_NAMES[$i]}" "${GC_PASS[$i]}" "${GC_TOTAL_S[$i]}" "$status"
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
# shellcheck disable=SC1090
|
|
||||||
source "$CONF"
|
|
||||||
|
|
||||||
if [ -z "$LANG_NAME" ]; then
|
|
||||||
echo "LANG_NAME not set in $CONF" >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
SCOREBOARD_DIR="${SCOREBOARD_DIR:-lib/$LANG_NAME}"
|
|
||||||
|
|
||||||
SX="${SX_SERVER:-hosts/ocaml/_build/default/bin/sx_server.exe}"
|
|
||||||
if [ ! -x "$SX" ]; then
|
|
||||||
MAIN_ROOT=$(git worktree list 2>/dev/null | head -1 | awk '{print $1}')
|
|
||||||
if [ -n "${MAIN_ROOT:-}" ] && [ -x "$MAIN_ROOT/$SX" ]; then
|
|
||||||
SX="$MAIN_ROOT/$SX"
|
|
||||||
else
|
|
||||||
echo "ERROR: sx_server.exe not found (set SX_SERVER to override)." >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
GC_NAMES=()
|
|
||||||
GC_PASS=()
|
|
||||||
GC_FAIL=()
|
|
||||||
GC_TOTAL_S=()
|
|
||||||
|
|
||||||
parse_result_line() {
|
|
||||||
# Match a (gc-result "name" P F T) line.
|
|
||||||
local line="$1"
|
|
||||||
if [[ "$line" =~ ^\(gc-result\ \"([^\"]+)\"\ ([0-9]+)\ ([0-9]+)\ ([0-9]+)\)$ ]]; then
|
|
||||||
GC_NAMES+=("${BASH_REMATCH[1]}")
|
|
||||||
GC_PASS+=("${BASH_REMATCH[2]}")
|
|
||||||
GC_FAIL+=("${BASH_REMATCH[3]}")
|
|
||||||
GC_TOTAL_S+=("${BASH_REMATCH[4]}")
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
case "$MODE" in
|
|
||||||
dict)
|
|
||||||
SCRIPT='(epoch 1)
|
|
||||||
'
|
|
||||||
for f in "${PRELOADS[@]}"; do
|
|
||||||
SCRIPT+='(load "'"$f"'")
|
|
||||||
'
|
|
||||||
done
|
|
||||||
SCRIPT+='(load "lib/guest/conformance.sx")
|
|
||||||
'
|
|
||||||
for entry in "${SUITES[@]}"; do
|
|
||||||
IFS=: read -r _ file _ <<< "$entry"
|
|
||||||
SCRIPT+='(load "'"$file"'")
|
|
||||||
'
|
|
||||||
done
|
|
||||||
SCRIPT+='(epoch 2)
|
|
||||||
'
|
|
||||||
for entry in "${SUITES[@]}"; do
|
|
||||||
IFS=: read -r name _ runner <<< "$entry"
|
|
||||||
SCRIPT+='(eval "(gc-dict-result \"'"$name"'\" '"$runner"')")
|
|
||||||
'
|
|
||||||
done
|
|
||||||
OUTPUT=$(printf '%s' "$SCRIPT" | "$SX" 2>&1)
|
|
||||||
expected=${#SUITES[@]}
|
|
||||||
matched=0
|
|
||||||
while IFS= read -r line; do
|
|
||||||
if parse_result_line "$line"; then
|
|
||||||
matched=$((matched + 1))
|
|
||||||
fi
|
|
||||||
done <<< "$OUTPUT"
|
|
||||||
if [ "$matched" -ne "$expected" ]; then
|
|
||||||
echo "Expected $expected suite results, got $matched" >&2
|
|
||||||
echo "---- raw output ----" >&2
|
|
||||||
printf '%s\n' "$OUTPUT" >&2
|
|
||||||
exit 3
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
counters)
|
|
||||||
if [ -z "$COUNTERS_PASS" ] || [ -z "$COUNTERS_FAIL" ]; then
|
|
||||||
echo "MODE=counters requires COUNTERS_PASS and COUNTERS_FAIL in $CONF" >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
for entry in "${SUITES[@]}"; do
|
|
||||||
IFS=: read -r name file <<< "$entry"
|
|
||||||
TMPFILE=$(mktemp)
|
|
||||||
{
|
|
||||||
printf '(epoch 1)\n'
|
|
||||||
for f in "${PRELOADS[@]}"; do printf '(load "%s")\n' "$f"; done
|
|
||||||
printf '(load "lib/guest/conformance.sx")\n'
|
|
||||||
printf '(epoch 2)\n'
|
|
||||||
printf '(load "%s")\n' "$file"
|
|
||||||
printf '(epoch 3)\n'
|
|
||||||
printf '(eval "(gc-counters-result \\"%s\\" 0 0 %s %s)")\n' \
|
|
||||||
"$name" "$COUNTERS_PASS" "$COUNTERS_FAIL"
|
|
||||||
} > "$TMPFILE"
|
|
||||||
OUTPUT=$(timeout "$TIMEOUT_PER_SUITE" "$SX" < "$TMPFILE" 2>&1 || true)
|
|
||||||
rm -f "$TMPFILE"
|
|
||||||
result=$(printf '%s\n' "$OUTPUT" | grep -E '^\(gc-result ' | tail -1 || true)
|
|
||||||
if [ -n "$result" ] && parse_result_line "$result"; then
|
|
||||||
:
|
|
||||||
else
|
|
||||||
# Suite hung or crashed before emitting a result. Record 0/1 so it
|
|
||||||
# shows up as a failure rather than vanishing.
|
|
||||||
GC_NAMES+=("$name")
|
|
||||||
GC_PASS+=(0)
|
|
||||||
GC_FAIL+=(1)
|
|
||||||
GC_TOTAL_S+=(1)
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Unknown MODE=$MODE in $CONF (expected dict|counters)" >&2
|
|
||||||
exit 2
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
GC_TOTAL_PASS=0
|
|
||||||
GC_TOTAL_FAIL=0
|
|
||||||
GC_TOTAL=0
|
|
||||||
for ((i=0; i<${#GC_NAMES[@]}; i++)); do
|
|
||||||
GC_TOTAL_PASS=$((GC_TOTAL_PASS + GC_PASS[i]))
|
|
||||||
GC_TOTAL_FAIL=$((GC_TOTAL_FAIL + GC_FAIL[i]))
|
|
||||||
GC_TOTAL=$((GC_TOTAL + GC_TOTAL_S[i]))
|
|
||||||
done
|
|
||||||
|
|
||||||
mkdir -p "$SCOREBOARD_DIR"
|
|
||||||
emit_scoreboard_json > "$SCOREBOARD_DIR/scoreboard.json"
|
|
||||||
emit_scoreboard_md > "$SCOREBOARD_DIR/scoreboard.md"
|
|
||||||
|
|
||||||
if [ "$GC_TOTAL_FAIL" -gt 0 ]; then
|
|
||||||
echo "$GC_TOTAL_FAIL failure(s) across $GC_TOTAL tests" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "All $GC_TOTAL tests pass."
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
;; lib/guest/conformance.sx — shared helpers for the guest conformance driver.
|
|
||||||
;;
|
|
||||||
;; The bash driver lib/guest/conformance.sh loads this file and then for each
|
|
||||||
;; suite emits an (eval "...") form whose result is a tagged list:
|
|
||||||
;;
|
|
||||||
;; (gc-result NAME PASSED FAILED TOTAL)
|
|
||||||
;;
|
|
||||||
;; The driver greps these from sx_server's output and aggregates them.
|
|
||||||
;;
|
|
||||||
;; Two suite shapes are supported:
|
|
||||||
;;
|
|
||||||
;; :dict — runner expression returns a dict with :passed/:failed/:total.
|
|
||||||
;; (gc-dict-result "parse" (pl-parse-tests-run!))
|
|
||||||
;;
|
|
||||||
;; :counters — runner has no return value, mutates pass/fail global counters.
|
|
||||||
;; (gc-counters-result NAME P0 F0 PASS FAIL)
|
|
||||||
;; where P0/F0 are the counters captured BEFORE the suite ran
|
|
||||||
;; and PASS/FAIL are the counters AFTER.
|
|
||||||
|
|
||||||
(define
|
|
||||||
gc-dict-result
|
|
||||||
(fn
|
|
||||||
(name r)
|
|
||||||
(list
|
|
||||||
(quote gc-result)
|
|
||||||
name
|
|
||||||
(get r :passed)
|
|
||||||
(get r :failed)
|
|
||||||
(get r :total))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
gc-counters-result
|
|
||||||
(fn
|
|
||||||
(name p0 f0 p1 f1)
|
|
||||||
(list
|
|
||||||
(quote gc-result)
|
|
||||||
name
|
|
||||||
(- p1 p0)
|
|
||||||
(- f1 f0)
|
|
||||||
(- (+ p1 f1) (+ p0 f0)))))
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
;; lib/guest/lex.sx — character-class predicates and token primitives shared
|
|
||||||
;; across guest tokenisers.
|
|
||||||
;;
|
|
||||||
;; All predicates are nil-safe — they accept nil (end-of-input) and return
|
|
||||||
;; false. This matches the convention used by the existing per-language
|
|
||||||
;; tokenisers (cur returns nil at EOF).
|
|
||||||
;;
|
|
||||||
;; Char classes
|
|
||||||
;; ------------
|
|
||||||
;; lex-digit? — 0-9
|
|
||||||
;; lex-hex-digit? — 0-9, a-f, A-F
|
|
||||||
;; lex-alpha? — a-z, A-Z (alias: lex-letter?)
|
|
||||||
;; lex-alnum? — alpha or digit
|
|
||||||
;; lex-ident-start? — alpha or underscore
|
|
||||||
;; lex-ident-char? — ident-start or digit
|
|
||||||
;; lex-space? — " ", "\t", "\r" (no newline)
|
|
||||||
;; lex-whitespace? — " ", "\t", "\r", "\n" (includes newline)
|
|
||||||
;;
|
|
||||||
;; Token record
|
|
||||||
;; ------------
|
|
||||||
;; (lex-make-token TYPE VALUE POS) — {:type :value :pos}
|
|
||||||
;; (lex-make-token-spanning TYPE VALUE POS END)
|
|
||||||
;; — {:type :value :pos :end}
|
|
||||||
;; (lex-token-type TOK)
|
|
||||||
;; (lex-token-value TOK)
|
|
||||||
;; (lex-token-pos TOK)
|
|
||||||
|
|
||||||
(define lex-digit? (fn (c) (and (not (= c nil)) (>= c "0") (<= c "9"))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
lex-hex-digit?
|
|
||||||
(fn
|
|
||||||
(c)
|
|
||||||
(and
|
|
||||||
(not (= c nil))
|
|
||||||
(or
|
|
||||||
(lex-digit? c)
|
|
||||||
(and (>= c "a") (<= c "f"))
|
|
||||||
(and (>= c "A") (<= c "F"))))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
lex-alpha?
|
|
||||||
(fn
|
|
||||||
(c)
|
|
||||||
(and
|
|
||||||
(not (= c nil))
|
|
||||||
(or (and (>= c "a") (<= c "z")) (and (>= c "A") (<= c "Z"))))))
|
|
||||||
|
|
||||||
(define lex-letter? lex-alpha?)
|
|
||||||
|
|
||||||
(define lex-alnum? (fn (c) (or (lex-alpha? c) (lex-digit? c))))
|
|
||||||
|
|
||||||
(define lex-ident-start? (fn (c) (or (lex-alpha? c) (= c "_"))))
|
|
||||||
|
|
||||||
(define lex-ident-char? (fn (c) (or (lex-ident-start? c) (lex-digit? c))))
|
|
||||||
|
|
||||||
(define lex-space? (fn (c) (or (= c " ") (= c "\t") (= c "\r"))))
|
|
||||||
|
|
||||||
(define lex-whitespace? (fn (c) (or (lex-space? c) (= c "\n"))))
|
|
||||||
|
|
||||||
(define lex-make-token (fn (type value pos) {:pos pos :value value :type type}))
|
|
||||||
|
|
||||||
(define lex-make-token-spanning (fn (type value pos end) {:pos pos :end end :value value :type type}))
|
|
||||||
|
|
||||||
(define lex-token-type (fn (tok) (get tok :type)))
|
|
||||||
(define lex-token-value (fn (tok) (get tok :value)))
|
|
||||||
(define lex-token-pos (fn (tok) (get tok :pos)))
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
;; lib/guest/pratt.sx — operator-table format + lookup for Pratt-style
|
|
||||||
;; precedence climbing.
|
|
||||||
;;
|
|
||||||
;; The climbing loop stays per-language because the two canaries use
|
|
||||||
;; opposite conventions (Lua: higher prec = tighter; Prolog: lower prec =
|
|
||||||
;; tighter, with xfx/xfy/yfx assoc tags). Forcing a single loop adds
|
|
||||||
;; callback indirection that obscures more than it shares.
|
|
||||||
;;
|
|
||||||
;; What IS shared and gets extracted: the operator-table format and lookup.
|
|
||||||
;; "Grammar is a dict, not hardcoded cond."
|
|
||||||
;;
|
|
||||||
;; Entry shape: (NAME PREC ASSOC).
|
|
||||||
;; NAME — string, the operator's source token.
|
|
||||||
;; PREC — integer, in the host's own convention.
|
|
||||||
;; ASSOC — :left | :right | :none for languages with traditional
|
|
||||||
;; associativity, or "xfx" / "xfy" / "yfx" for Prolog-style.
|
|
||||||
|
|
||||||
(define
|
|
||||||
pratt-op-lookup
|
|
||||||
(fn (table name)
|
|
||||||
(cond
|
|
||||||
((empty? table) nil)
|
|
||||||
((= (first (first table)) name) (first table))
|
|
||||||
(:else (pratt-op-lookup (rest table) name)))))
|
|
||||||
|
|
||||||
(define pratt-op-name (fn (entry) (first entry)))
|
|
||||||
(define pratt-op-prec (fn (entry) (nth entry 1)))
|
|
||||||
(define pratt-op-assoc (fn (entry) (nth entry 2)))
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
;; lib/guest/prefix.sx — prefix-rename macro.
|
|
||||||
;;
|
|
||||||
;; A guest runtime often re-exports a stretch of host primitives under a
|
|
||||||
;; language-specific prefix. The prefix-rename macro replaces the repeated
|
|
||||||
;; (define lang-foo foo) boilerplate with a single declarative call.
|
|
||||||
;;
|
|
||||||
;; Two entry shapes are supported:
|
|
||||||
;;
|
|
||||||
;; (prefix-rename "cl-" '(gcd lcm expt floor truncate))
|
|
||||||
;; ;; expands to (begin (define cl-gcd gcd)
|
|
||||||
;; ;; (define cl-lcm lcm) ...)
|
|
||||||
;;
|
|
||||||
;; (prefix-rename "cl-"
|
|
||||||
;; '((mod modulo)
|
|
||||||
;; (arrayp? vector?)
|
|
||||||
;; (ceiling ceil)))
|
|
||||||
;; ;; expands to (begin (define cl-mod modulo)
|
|
||||||
;; ;; (define cl-arrayp? vector?)
|
|
||||||
;; ;; (define cl-ceiling ceil))
|
|
||||||
;;
|
|
||||||
;; Mixed lists are supported — bare symbols are same-name aliases, two-element
|
|
||||||
;; lists are (alias target) pairs.
|
|
||||||
|
|
||||||
(defmacro
|
|
||||||
prefix-rename
|
|
||||||
(prefix entries-q)
|
|
||||||
(let
|
|
||||||
((entries (nth entries-q 1)))
|
|
||||||
(cons
|
|
||||||
(quote begin)
|
|
||||||
(map
|
|
||||||
(fn
|
|
||||||
(entry)
|
|
||||||
(cond
|
|
||||||
((= (type-of entry) "symbol")
|
|
||||||
(list
|
|
||||||
(quote define)
|
|
||||||
(make-symbol (str prefix (symbol-name entry)))
|
|
||||||
entry))
|
|
||||||
((and (list? entry) (= (len entry) 2))
|
|
||||||
(list
|
|
||||||
(quote define)
|
|
||||||
(make-symbol (str prefix (symbol-name (first entry))))
|
|
||||||
(nth entry 1)))
|
|
||||||
(:else (error (str "prefix-rename: invalid entry " entry)))))
|
|
||||||
entries))))
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
;; lib/guest/tests/ast.sx — exercises every constructor / predicate /
|
|
||||||
;; accessor in lib/guest/ast.sx so future ports have a stable contract
|
|
||||||
;; to point at.
|
|
||||||
|
|
||||||
(define gast-test-pass 0)
|
|
||||||
(define gast-test-fail 0)
|
|
||||||
(define gast-test-fails (list))
|
|
||||||
|
|
||||||
(define
|
|
||||||
gast-test
|
|
||||||
(fn (name actual expected)
|
|
||||||
(if (= actual expected)
|
|
||||||
(set! gast-test-pass (+ gast-test-pass 1))
|
|
||||||
(begin
|
|
||||||
(set! gast-test-fail (+ gast-test-fail 1))
|
|
||||||
(append! gast-test-fails {:name name :expected expected :actual actual})))))
|
|
||||||
|
|
||||||
;; Constructors round-trip.
|
|
||||||
(gast-test "literal-int" (ast-literal-value (ast-literal 42)) 42)
|
|
||||||
(gast-test "literal-str" (ast-literal-value (ast-literal "hi")) "hi")
|
|
||||||
(gast-test "literal-bool" (ast-literal-value (ast-literal true)) true)
|
|
||||||
(gast-test "var-name" (ast-var-name (ast-var "x")) "x")
|
|
||||||
(gast-test "app-fn" (ast-app-fn (ast-app (ast-var "f") (list (ast-literal 1)))) (ast-var "f"))
|
|
||||||
(gast-test "app-args-len" (len (ast-app-args (ast-app (ast-var "f") (list (ast-literal 1))))) 1)
|
|
||||||
(gast-test "lambda-params" (ast-lambda-params (ast-lambda (list "x" "y") (ast-var "x"))) (list "x" "y"))
|
|
||||||
(gast-test "lambda-body" (ast-lambda-body (ast-lambda (list "x") (ast-var "x"))) (ast-var "x"))
|
|
||||||
(gast-test "let-bindings" (len (ast-let-bindings (ast-let (list {:name "x" :value (ast-literal 1)}) (ast-var "x")))) 1)
|
|
||||||
(gast-test "letrec-body" (ast-letrec-body (ast-letrec (list) (ast-literal 0))) (ast-literal 0))
|
|
||||||
(gast-test "if-test" (ast-if-test (ast-if (ast-literal true) (ast-literal 1) (ast-literal 0))) (ast-literal true))
|
|
||||||
(gast-test "if-then" (ast-if-then (ast-if (ast-literal true) (ast-literal 1) (ast-literal 0))) (ast-literal 1))
|
|
||||||
(gast-test "if-else" (ast-if-else (ast-if (ast-literal true) (ast-literal 1) (ast-literal 0))) (ast-literal 0))
|
|
||||||
(gast-test "match-pattern" (ast-match-clause-pattern (ast-match-clause "P" (ast-literal 1))) "P")
|
|
||||||
(gast-test "match-body" (ast-match-clause-body (ast-match-clause "P" (ast-literal 1))) (ast-literal 1))
|
|
||||||
(gast-test "module-name" (ast-module-name (ast-module "m" (list))) "m")
|
|
||||||
(gast-test "import-name" (ast-import-name (ast-import "lib/foo")) "lib/foo")
|
|
||||||
|
|
||||||
;; Predicates fire only on matching kinds.
|
|
||||||
(gast-test "is-literal" (ast-literal? (ast-literal 1)) true)
|
|
||||||
(gast-test "not-literal" (ast-literal? (ast-var "x")) false)
|
|
||||||
(gast-test "is-var" (ast-var? (ast-var "x")) true)
|
|
||||||
(gast-test "is-app" (ast-app? (ast-app (ast-var "f") (list))) true)
|
|
||||||
(gast-test "is-lambda" (ast-lambda? (ast-lambda (list) (ast-literal 0))) true)
|
|
||||||
(gast-test "is-let" (ast-let? (ast-let (list) (ast-literal 0))) true)
|
|
||||||
(gast-test "is-letrec" (ast-letrec? (ast-letrec (list) (ast-literal 0))) true)
|
|
||||||
(gast-test "is-if" (ast-if? (ast-if (ast-literal true) (ast-literal 1) (ast-literal 0))) true)
|
|
||||||
(gast-test "is-match" (ast-match-clause? (ast-match-clause "P" (ast-literal 1))) true)
|
|
||||||
(gast-test "is-module" (ast-module? (ast-module "m" (list))) true)
|
|
||||||
(gast-test "is-import" (ast-import? (ast-import "x")) true)
|
|
||||||
|
|
||||||
;; ast? recognises any canonical node.
|
|
||||||
(gast-test "ast?-literal" (ast? (ast-literal 0)) true)
|
|
||||||
(gast-test "ast?-foreign" (ast? (list "lua-num" 0)) false)
|
|
||||||
(gast-test "ast?-non-list" (ast? 42) false)
|
|
||||||
|
|
||||||
;; ast-kind dispatch.
|
|
||||||
(gast-test "kind-literal" (ast-kind (ast-literal 0)) :literal)
|
|
||||||
(gast-test "kind-import" (ast-kind (ast-import "x")) :import)
|
|
||||||
|
|
||||||
(define gast-tests-run!
|
|
||||||
(fn ()
|
|
||||||
{:passed gast-test-pass
|
|
||||||
:failed gast-test-fail
|
|
||||||
:total (+ gast-test-pass gast-test-fail)}))
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
# Haskell-on-SX conformance config — sourced by lib/guest/conformance.sh.
|
|
||||||
|
|
||||||
LANG_NAME=haskell
|
|
||||||
MODE=counters
|
|
||||||
COUNTERS_PASS=hk-test-pass
|
|
||||||
COUNTERS_FAIL=hk-test-fail
|
|
||||||
TIMEOUT_PER_SUITE=120
|
|
||||||
|
|
||||||
PRELOADS=(
|
|
||||||
lib/haskell/tokenizer.sx
|
|
||||||
lib/haskell/layout.sx
|
|
||||||
lib/haskell/parser.sx
|
|
||||||
lib/haskell/desugar.sx
|
|
||||||
lib/haskell/runtime.sx
|
|
||||||
lib/haskell/match.sx
|
|
||||||
lib/haskell/eval.sx
|
|
||||||
lib/haskell/testlib.sx
|
|
||||||
)
|
|
||||||
|
|
||||||
SUITES=(
|
|
||||||
"fib:lib/haskell/tests/program-fib.sx"
|
|
||||||
"sieve:lib/haskell/tests/program-sieve.sx"
|
|
||||||
"quicksort:lib/haskell/tests/program-quicksort.sx"
|
|
||||||
"nqueens:lib/haskell/tests/program-nqueens.sx"
|
|
||||||
"calculator:lib/haskell/tests/program-calculator.sx"
|
|
||||||
"collatz:lib/haskell/tests/program-collatz.sx"
|
|
||||||
"palindrome:lib/haskell/tests/program-palindrome.sx"
|
|
||||||
"maybe:lib/haskell/tests/program-maybe.sx"
|
|
||||||
"fizzbuzz:lib/haskell/tests/program-fizzbuzz.sx"
|
|
||||||
"anagram:lib/haskell/tests/program-anagram.sx"
|
|
||||||
"roman:lib/haskell/tests/program-roman.sx"
|
|
||||||
"binary:lib/haskell/tests/program-binary.sx"
|
|
||||||
"either:lib/haskell/tests/program-either.sx"
|
|
||||||
"primes:lib/haskell/tests/program-primes.sx"
|
|
||||||
"zipwith:lib/haskell/tests/program-zipwith.sx"
|
|
||||||
"matrix:lib/haskell/tests/program-matrix.sx"
|
|
||||||
"wordcount:lib/haskell/tests/program-wordcount.sx"
|
|
||||||
"powers:lib/haskell/tests/program-powers.sx"
|
|
||||||
)
|
|
||||||
|
|
||||||
emit_scoreboard_json() {
|
|
||||||
local n=${#GC_NAMES[@]} i sep date_only
|
|
||||||
date_only=$(date '+%Y-%m-%d')
|
|
||||||
printf '{\n'
|
|
||||||
printf ' "date": "%s",\n' "$date_only"
|
|
||||||
printf ' "total_pass": %d,\n' "$GC_TOTAL_PASS"
|
|
||||||
printf ' "total_fail": %d,\n' "$GC_TOTAL_FAIL"
|
|
||||||
printf ' "programs": {\n'
|
|
||||||
for ((i=0; i<n; i++)); do
|
|
||||||
sep=","; [ $i -eq $((n-1)) ] && sep=""
|
|
||||||
printf ' "%s": {"pass": %d, "fail": %d}%s\n' \
|
|
||||||
"${GC_NAMES[$i]}" "${GC_PASS[$i]}" "${GC_FAIL[$i]}" "$sep"
|
|
||||||
done
|
|
||||||
printf ' }\n'
|
|
||||||
printf '}\n'
|
|
||||||
}
|
|
||||||
|
|
||||||
emit_scoreboard_md() {
|
|
||||||
local n=${#GC_NAMES[@]}
|
|
||||||
local i status p f t prog_pass=0 prog_total=$n date_only
|
|
||||||
date_only=$(date '+%Y-%m-%d')
|
|
||||||
for ((i=0; i<n; i++)); do
|
|
||||||
[ "${GC_FAIL[$i]}" -eq 0 ] && prog_pass=$((prog_pass + 1))
|
|
||||||
done
|
|
||||||
printf '# Haskell-on-SX Scoreboard\n\n'
|
|
||||||
printf 'Updated %s · Phase 6 (prelude extras + 18 programs)\n\n' "$date_only"
|
|
||||||
printf '| Program | Tests | Status |\n'
|
|
||||||
printf '|---------|-------|--------|\n'
|
|
||||||
for ((i=0; i<n; i++)); do
|
|
||||||
p=${GC_PASS[$i]}; f=${GC_FAIL[$i]}; t=${GC_TOTAL_S[$i]}
|
|
||||||
[ "$f" -eq 0 ] && status="✓" || status="✗"
|
|
||||||
printf '| %s.hs | %d/%d | %s |\n' "${GC_NAMES[$i]}" "$p" "$t" "$status"
|
|
||||||
done
|
|
||||||
printf '| **Total** | **%d/%d** | **%d/%d programs** |\n' \
|
|
||||||
"$GC_TOTAL_PASS" "$GC_TOTAL" "$prog_pass" "$prog_total"
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,140 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Thin wrapper — see lib/guest/conformance.sh and lib/haskell/conformance.conf.
|
# lib/haskell/conformance.sh — run the classic-program test suites.
|
||||||
exec bash "$(dirname "$0")/../guest/conformance.sh" "$(dirname "$0")/conformance.conf" "$@"
|
# 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 collatz palindrome maybe fizzbuzz anagram roman binary either primes zipwith matrix wordcount powers)
|
||||||
|
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 6 (prelude extras + 18 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 ]
|
||||||
|
|||||||
@@ -7,22 +7,6 @@
|
|||||||
;; (hs-to-sx (hs-compile "on click add .active to me"))
|
;; (hs-to-sx (hs-compile "on click add .active to me"))
|
||||||
;; → (hs-on me "click" (fn (event) (dom-add-class me "active")))
|
;; → (hs-on me "click" (fn (event) (dom-add-class me "active")))
|
||||||
|
|
||||||
;; ── Compiler plugin registries ────────────────────────────────────
|
|
||||||
;; Plugins call (hs-register-command! "head" compile-fn) and
|
|
||||||
;; (hs-register-converter! "TypeName" convert-fn) at load time. Both
|
|
||||||
;; compile-fn and convert-fn receive a ctx dict (built per call inside
|
|
||||||
;; hs-to-sx) exposing :hs-to-sx for recursion plus the AST node fields
|
|
||||||
;; the dispatch needs. Compile-fn returns an SX expression.
|
|
||||||
(begin
|
|
||||||
(define _hs-command-registry {})
|
|
||||||
(define _hs-converter-registry {})
|
|
||||||
(define
|
|
||||||
hs-register-command!
|
|
||||||
(fn (name compile-fn) (dict-set! _hs-command-registry name compile-fn)))
|
|
||||||
(define
|
|
||||||
hs-register-converter!
|
|
||||||
(fn (name convert-fn) (dict-set! _hs-converter-registry name convert-fn))))
|
|
||||||
|
|
||||||
(define
|
(define
|
||||||
hs-to-sx
|
hs-to-sx
|
||||||
(let
|
(let
|
||||||
@@ -226,6 +210,28 @@
|
|||||||
value)
|
value)
|
||||||
(list (quote set!) (hs-to-sx target) value)))))))
|
(list (quote set!) (hs-to-sx target) value)))))))
|
||||||
(true (list (quote set!) (hs-to-sx target) value)))))))
|
(true (list (quote set!) (hs-to-sx target) value)))))))
|
||||||
|
;; Throttle/debounce extraction state — module-level so they don't get
|
||||||
|
;; redefined on every emit-on call (which was causing JIT churn). Set
|
||||||
|
;; via _strip-throttle-debounce at the start of each emit-on, used in
|
||||||
|
;; the handler-build step inside scan-on.
|
||||||
|
(define _throttle-ms nil)
|
||||||
|
(define _debounce-ms nil)
|
||||||
|
(define
|
||||||
|
_strip-throttle-debounce
|
||||||
|
(fn
|
||||||
|
(lst)
|
||||||
|
(cond
|
||||||
|
((<= (len lst) 1) lst)
|
||||||
|
((= (first lst) :throttle)
|
||||||
|
(do
|
||||||
|
(set! _throttle-ms (nth lst 1))
|
||||||
|
(_strip-throttle-debounce (rest (rest lst)))))
|
||||||
|
((= (first lst) :debounce)
|
||||||
|
(do
|
||||||
|
(set! _debounce-ms (nth lst 1))
|
||||||
|
(_strip-throttle-debounce (rest (rest lst)))))
|
||||||
|
(true
|
||||||
|
(cons (first lst) (_strip-throttle-debounce (rest lst)))))))
|
||||||
(define
|
(define
|
||||||
emit-on
|
emit-on
|
||||||
(fn
|
(fn
|
||||||
@@ -234,6 +240,8 @@
|
|||||||
((parts (rest ast)))
|
((parts (rest ast)))
|
||||||
(let
|
(let
|
||||||
((event-name (first parts)))
|
((event-name (first parts)))
|
||||||
|
(set! _throttle-ms nil)
|
||||||
|
(set! _debounce-ms nil)
|
||||||
(define
|
(define
|
||||||
scan-on
|
scan-on
|
||||||
(fn
|
(fn
|
||||||
@@ -266,6 +274,13 @@
|
|||||||
((wrapped-body (if catch-info (let ((var (make-symbol (nth catch-info 0))) (catch-body (hs-to-sx (nth catch-info 1)))) (if finally-info (list (quote let) (list (list (quote __hs-exc) nil) (list (quote __hs-reraise) false)) (list (quote do) (list (quote guard) (list var (list true (list (quote let) (list (list var (list (quote host-hs-normalize-exc) var))) (list (quote guard) (list (quote __inner-exc) (list true (list (quote do) (list (quote set!) (quote __hs-exc) (quote __inner-exc)) (list (quote set!) (quote __hs-reraise) true)))) catch-body)))) compiled-body) (hs-to-sx finally-info) (list (quote when) (quote __hs-reraise) (list (quote raise) (quote __hs-exc))))) (list (quote let) (list (list (quote __hs-exc) nil) (list (quote __hs-reraise) false)) (list (quote do) (list (quote guard) (list var (list true (list (quote let) (list (list var (list (quote host-hs-normalize-exc) var))) (list (quote guard) (list (quote __inner-exc) (list true (list (quote do) (list (quote set!) (quote __hs-exc) (quote __inner-exc)) (list (quote set!) (quote __hs-reraise) true)))) catch-body)))) compiled-body) (list (quote when) (quote __hs-reraise) (list (quote raise) (quote __hs-exc))))))) (if finally-info (list (quote do) compiled-body (hs-to-sx finally-info)) compiled-body))))
|
((wrapped-body (if catch-info (let ((var (make-symbol (nth catch-info 0))) (catch-body (hs-to-sx (nth catch-info 1)))) (if finally-info (list (quote let) (list (list (quote __hs-exc) nil) (list (quote __hs-reraise) false)) (list (quote do) (list (quote guard) (list var (list true (list (quote let) (list (list var (list (quote host-hs-normalize-exc) var))) (list (quote guard) (list (quote __inner-exc) (list true (list (quote do) (list (quote set!) (quote __hs-exc) (quote __inner-exc)) (list (quote set!) (quote __hs-reraise) true)))) catch-body)))) compiled-body) (hs-to-sx finally-info) (list (quote when) (quote __hs-reraise) (list (quote raise) (quote __hs-exc))))) (list (quote let) (list (list (quote __hs-exc) nil) (list (quote __hs-reraise) false)) (list (quote do) (list (quote guard) (list var (list true (list (quote let) (list (list var (list (quote host-hs-normalize-exc) var))) (list (quote guard) (list (quote __inner-exc) (list true (list (quote do) (list (quote set!) (quote __hs-exc) (quote __inner-exc)) (list (quote set!) (quote __hs-reraise) true)))) catch-body)))) compiled-body) (list (quote when) (quote __hs-reraise) (list (quote raise) (quote __hs-exc))))))) (if finally-info (list (quote do) compiled-body (hs-to-sx finally-info)) compiled-body))))
|
||||||
(let
|
(let
|
||||||
((handler (let ((uses-the-result? (fn (expr) (cond ((= expr (quote the-result)) true) ((list? expr) (some (fn (x) (uses-the-result? x)) expr)) (true false))))) (let ((base-handler (list (quote fn) (list (quote event)) (if (uses-the-result? wrapped-body) (list (quote let) (list (list (quote the-result) nil)) wrapped-body) wrapped-body)))) (if count-filter-info (let ((mn (get count-filter-info "min")) (mx (get count-filter-info "max"))) (list (quote let) (list (list (quote __hs-count) 0)) (list (quote fn) (list (quote event)) (list (quote begin) (list (quote set!) (quote __hs-count) (list (quote +) (quote __hs-count) 1)) (list (quote when) (if (= mx -1) (list (quote >=) (quote __hs-count) mn) (list (quote and) (list (quote >=) (quote __hs-count) mn) (list (quote <=) (quote __hs-count) mx))) (nth base-handler 2)))))) base-handler)))))
|
((handler (let ((uses-the-result? (fn (expr) (cond ((= expr (quote the-result)) true) ((list? expr) (some (fn (x) (uses-the-result? x)) expr)) (true false))))) (let ((base-handler (list (quote fn) (list (quote event)) (if (uses-the-result? wrapped-body) (list (quote let) (list (list (quote the-result) nil)) wrapped-body) wrapped-body)))) (if count-filter-info (let ((mn (get count-filter-info "min")) (mx (get count-filter-info "max"))) (list (quote let) (list (list (quote __hs-count) 0)) (list (quote fn) (list (quote event)) (list (quote begin) (list (quote set!) (quote __hs-count) (list (quote +) (quote __hs-count) 1)) (list (quote when) (if (= mx -1) (list (quote >=) (quote __hs-count) mn) (list (quote and) (list (quote >=) (quote __hs-count) mn) (list (quote <=) (quote __hs-count) mx))) (nth base-handler 2)))))) base-handler)))))
|
||||||
|
(let
|
||||||
|
((handler (cond
|
||||||
|
(_throttle-ms
|
||||||
|
(list (quote hs-throttle!) handler (hs-to-sx _throttle-ms)))
|
||||||
|
(_debounce-ms
|
||||||
|
(list (quote hs-debounce!) handler (hs-to-sx _debounce-ms)))
|
||||||
|
(true handler))))
|
||||||
(let
|
(let
|
||||||
((on-call (if every? (list (quote hs-on-every) target event-name handler) (list (quote hs-on) target event-name handler))))
|
((on-call (if every? (list (quote hs-on-every) target event-name handler) (list (quote hs-on) target event-name handler))))
|
||||||
(cond
|
(cond
|
||||||
@@ -325,7 +340,7 @@
|
|||||||
(first pair)
|
(first pair)
|
||||||
handler))
|
handler))
|
||||||
or-sources)))
|
or-sources)))
|
||||||
on-call)))))))))))))
|
on-call))))))))))))))
|
||||||
((= (first items) :from)
|
((= (first items) :from)
|
||||||
(scan-on
|
(scan-on
|
||||||
(rest (rest items))
|
(rest (rest items))
|
||||||
@@ -469,7 +484,7 @@
|
|||||||
count-filter-info
|
count-filter-info
|
||||||
elsewhere?
|
elsewhere?
|
||||||
or-sources)))))
|
or-sources)))))
|
||||||
(scan-on (rest parts) nil nil false nil nil nil nil nil false nil)))))
|
(scan-on (_strip-throttle-debounce (rest parts)) nil nil false nil nil nil nil nil false nil)))))
|
||||||
(define
|
(define
|
||||||
emit-send
|
emit-send
|
||||||
(fn
|
(fn
|
||||||
@@ -968,22 +983,6 @@
|
|||||||
(true
|
(true
|
||||||
(let
|
(let
|
||||||
((head (first ast)))
|
((head (first ast)))
|
||||||
(let
|
|
||||||
((reg-cmd-fn (dict-get _hs-command-registry (str head)))
|
|
||||||
(reg-conv-fn
|
|
||||||
(and
|
|
||||||
(= head (quote as))
|
|
||||||
(dict-get _hs-converter-registry (nth ast 2)))))
|
|
||||||
(cond
|
|
||||||
(reg-conv-fn
|
|
||||||
(reg-conv-fn
|
|
||||||
{:hs-to-sx hs-to-sx
|
|
||||||
:ast ast
|
|
||||||
:value-ast (nth ast 1)
|
|
||||||
:type-name (nth ast 2)}))
|
|
||||||
(reg-cmd-fn
|
|
||||||
(reg-cmd-fn {:hs-to-sx hs-to-sx :ast ast :head head}))
|
|
||||||
(true
|
|
||||||
(cond
|
(cond
|
||||||
((= head (quote __bind-from-detail__))
|
((= head (quote __bind-from-detail__))
|
||||||
(let
|
(let
|
||||||
@@ -2490,6 +2489,15 @@
|
|||||||
(quote fn)
|
(quote fn)
|
||||||
(list (quote it))
|
(list (quote it))
|
||||||
(hs-to-sx body))))
|
(hs-to-sx body))))
|
||||||
|
((and (list? expr) (= (first expr) (quote attr)))
|
||||||
|
(list
|
||||||
|
(quote hs-attr-watch!)
|
||||||
|
(hs-to-sx (nth expr 2))
|
||||||
|
(nth expr 1)
|
||||||
|
(list
|
||||||
|
(quote fn)
|
||||||
|
(list (quote it))
|
||||||
|
(hs-to-sx body))))
|
||||||
(true nil))))
|
(true nil))))
|
||||||
((= head (quote init))
|
((= head (quote init))
|
||||||
(list
|
(list
|
||||||
@@ -2699,7 +2707,7 @@
|
|||||||
(quote begin)
|
(quote begin)
|
||||||
(list (quote set!) (quote it) (quote __hs-js))
|
(list (quote set!) (quote it) (quote __hs-js))
|
||||||
(quote __hs-js))))))
|
(quote __hs-js))))))
|
||||||
(true ast))))))))))))
|
(true ast)))))))))
|
||||||
|
|
||||||
;; ── Convenience: source → SX ─────────────────────────────────
|
;; ── Convenience: source → SX ─────────────────────────────────
|
||||||
(define
|
(define
|
||||||
|
|||||||
@@ -3,17 +3,6 @@
|
|||||||
;; Input: list of {:type T :value V :pos P} tokens from hs-tokenize
|
;; Input: list of {:type T :value V :pos P} tokens from hs-tokenize
|
||||||
;; Output: SX AST forms that map to runtime primitives
|
;; Output: SX AST forms that map to runtime primitives
|
||||||
|
|
||||||
;; ── Feature plugin registry ───────────────────────────────────────
|
|
||||||
;; Plugins call (hs-register-feature! "name" parse-fn) at load time.
|
|
||||||
;; parse-fn is (fn (ctx) ...) where ctx is a dict exposing parser
|
|
||||||
;; helpers (:adv! :tp-val :tp-type :parse-cmd-list ...) and the
|
|
||||||
;; built-in parse-X-feat dispatch fns.
|
|
||||||
(begin
|
|
||||||
(define _hs-feature-registry {})
|
|
||||||
(define
|
|
||||||
hs-register-feature!
|
|
||||||
(fn (name parse-fn) (dict-set! _hs-feature-registry name parse-fn))))
|
|
||||||
|
|
||||||
;; ── Parser entry point ────────────────────────────────────────────
|
;; ── Parser entry point ────────────────────────────────────────────
|
||||||
(define
|
(define
|
||||||
hs-parse
|
hs-parse
|
||||||
@@ -1358,7 +1347,17 @@
|
|||||||
cls
|
cls
|
||||||
(first extra-classes)
|
(first extra-classes)
|
||||||
tgt))
|
tgt))
|
||||||
((match-kw "for")
|
((and
|
||||||
|
(= (tp-type) "keyword") (= (tp-val) "for")
|
||||||
|
;; Only consume 'for' as a duration clause if the next
|
||||||
|
;; token is NOT '<ident> in ...' — that pattern is a
|
||||||
|
;; for-in loop, not a toggle duration.
|
||||||
|
(not
|
||||||
|
(and
|
||||||
|
(> (len tokens) (+ p 2))
|
||||||
|
(= (get (nth tokens (+ p 1)) "type") "ident")
|
||||||
|
(= (get (nth tokens (+ p 2)) "value") "in")))
|
||||||
|
(do (adv!) true))
|
||||||
(let
|
(let
|
||||||
((dur (parse-expr)))
|
((dur (parse-expr)))
|
||||||
(list (quote toggle-class-for) cls tgt dur)))
|
(list (quote toggle-class-for) cls tgt dur)))
|
||||||
@@ -3090,7 +3089,17 @@
|
|||||||
(= (tp-val) "queue"))
|
(= (tp-val) "queue"))
|
||||||
(do (adv!) (adv!)))
|
(do (adv!) (adv!)))
|
||||||
(let
|
(let
|
||||||
((every? (match-kw "every")))
|
((every? (match-kw "every"))
|
||||||
|
(throttle-ms nil)
|
||||||
|
(debounce-ms nil))
|
||||||
|
;; 'throttled at <duration>' / 'debounced at <duration>'
|
||||||
|
;; — parsed as handler modifiers, captured as :throttle / :debounce parts.
|
||||||
|
(when (and (= (tp-type) "ident") (= (tp-val) "throttled"))
|
||||||
|
(adv!)
|
||||||
|
(when (match-kw "at") (set! throttle-ms (parse-expr))))
|
||||||
|
(when (and (= (tp-type) "ident") (= (tp-val) "debounced"))
|
||||||
|
(adv!)
|
||||||
|
(when (match-kw "at") (set! debounce-ms (parse-expr))))
|
||||||
(let
|
(let
|
||||||
((having (if (or h-margin h-threshold) (dict "margin" h-margin "threshold" h-threshold) nil)))
|
((having (if (or h-margin h-threshold) (dict "margin" h-margin "threshold" h-threshold) nil)))
|
||||||
(let
|
(let
|
||||||
@@ -3105,6 +3114,10 @@
|
|||||||
(match-kw "end")
|
(match-kw "end")
|
||||||
(let
|
(let
|
||||||
((parts (list (quote on) event-name)))
|
((parts (list (quote on) event-name)))
|
||||||
|
(let
|
||||||
|
((parts (if throttle-ms (append parts (list :throttle throttle-ms)) parts)))
|
||||||
|
(let
|
||||||
|
((parts (if debounce-ms (append parts (list :debounce debounce-ms)) parts)))
|
||||||
(let
|
(let
|
||||||
((parts (if every? (append parts (list :every true)) parts)))
|
((parts (if every? (append parts (list :every true)) parts)))
|
||||||
(let
|
(let
|
||||||
@@ -3127,7 +3140,7 @@
|
|||||||
((parts (if finally-clause (append parts (list :finally finally-clause)) parts)))
|
((parts (if finally-clause (append parts (list :finally finally-clause)) parts)))
|
||||||
(let
|
(let
|
||||||
((parts (append parts (list (if (> (len event-vars) 0) (cons (quote do) (append (map (fn (nm) (list (quote ref) nm)) event-vars) (if (and (list? body) (= (first body) (quote do))) (rest body) (list body)))) body)))))
|
((parts (append parts (list (if (> (len event-vars) 0) (cons (quote do) (append (map (fn (nm) (list (quote ref) nm)) event-vars) (if (and (list? body) (= (first body) (quote do))) (rest body) (list body)))) body)))))
|
||||||
parts))))))))))))))))))))))))))
|
parts))))))))))))))))))))))))))))
|
||||||
(define
|
(define
|
||||||
parse-init-feat
|
parse-init-feat
|
||||||
(fn
|
(fn
|
||||||
@@ -3177,6 +3190,7 @@
|
|||||||
(or
|
(or
|
||||||
(= (tp-type) "hat")
|
(= (tp-type) "hat")
|
||||||
(= (tp-type) "local")
|
(= (tp-type) "local")
|
||||||
|
(= (tp-type) "attr")
|
||||||
(and (= (tp-type) "keyword") (= (tp-val) "dom")))
|
(and (= (tp-type) "keyword") (= (tp-val) "dom")))
|
||||||
(let
|
(let
|
||||||
((expr (parse-expr)))
|
((expr (parse-expr)))
|
||||||
@@ -3242,24 +3256,6 @@
|
|||||||
(do
|
(do
|
||||||
(match-kw "end")
|
(match-kw "end")
|
||||||
(list (quote socket) name-path url timeout on-message))))))))))
|
(list (quote socket) name-path url timeout on-message))))))))))
|
||||||
(define
|
|
||||||
parse-feat-ctx
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
{:adv! adv!
|
|
||||||
:tp-val tp-val
|
|
||||||
:tp-type tp-type
|
|
||||||
:at-end? at-end?
|
|
||||||
:parse-cmd-list parse-cmd-list
|
|
||||||
:parse-expr parse-expr
|
|
||||||
:parse-on-feat parse-on-feat
|
|
||||||
:parse-init-feat parse-init-feat
|
|
||||||
:parse-def-feat parse-def-feat
|
|
||||||
:parse-behavior-feat parse-behavior-feat
|
|
||||||
:parse-live-feat parse-live-feat
|
|
||||||
:parse-when-feat parse-when-feat
|
|
||||||
:parse-bind-feat parse-bind-feat
|
|
||||||
:parse-socket-feat parse-socket-feat}))
|
|
||||||
(define
|
(define
|
||||||
parse-feat
|
parse-feat
|
||||||
(fn
|
(fn
|
||||||
@@ -3290,23 +3286,29 @@
|
|||||||
((unit (tp-val)))
|
((unit (tp-val)))
|
||||||
(do (adv!) (list (quote string-postfix) inner unit)))
|
(do (adv!) (list (quote string-postfix) inner unit)))
|
||||||
inner))))
|
inner))))
|
||||||
|
((= val "on") (do (adv!) (parse-on-feat)))
|
||||||
|
((= val "init") (do (adv!) (parse-init-feat)))
|
||||||
|
((= val "def") (do (adv!) (parse-def-feat)))
|
||||||
|
((= val "behavior") (do (adv!) (parse-behavior-feat)))
|
||||||
|
((= val "live") (do (adv!) (parse-live-feat)))
|
||||||
|
((= val "when") (do (adv!) (parse-when-feat)))
|
||||||
|
((= val "worker")
|
||||||
|
(error
|
||||||
|
"worker plugin is not installed — see https://hyperscript.org/features/worker"))
|
||||||
|
((= val "bind") (do (adv!) (parse-bind-feat)))
|
||||||
|
((= val "socket") (do (adv!) (parse-socket-feat)))
|
||||||
(true
|
(true
|
||||||
(let
|
(if
|
||||||
((reg-fn (dict-get _hs-feature-registry val)))
|
(= (tp-type) "keyword")
|
||||||
(if
|
(parse-cmd-list)
|
||||||
reg-fn
|
(let
|
||||||
(reg-fn (parse-feat-ctx))
|
((saved-p p))
|
||||||
(if
|
(let
|
||||||
(= (tp-type) "keyword")
|
((expr (guard (_e (true nil)) (parse-expr))))
|
||||||
(parse-cmd-list)
|
(if
|
||||||
(let
|
(and expr (at-end?))
|
||||||
((saved-p p))
|
expr
|
||||||
(let
|
(do (set! p saved-p) (parse-cmd-list)))))))))))
|
||||||
((expr (guard (_e (true nil)) (parse-expr))))
|
|
||||||
(if
|
|
||||||
(and expr (at-end?))
|
|
||||||
expr
|
|
||||||
(do (set! p saved-p) (parse-cmd-list)))))))))))))
|
|
||||||
(define
|
(define
|
||||||
coll-feats
|
coll-feats
|
||||||
(fn
|
(fn
|
||||||
@@ -3349,33 +3351,3 @@
|
|||||||
(let
|
(let
|
||||||
((result (hs-parse (hs-tokenize src) src)))
|
((result (hs-parse (hs-tokenize src) src)))
|
||||||
(do (set! hs-span-mode false) result)))))
|
(do (set! hs-span-mode false) result)))))
|
||||||
|
|
||||||
;; ── Built-in feature registrations ────────────────────────────────
|
|
||||||
;; These mirror the original parse-feat cond branches. Registering at
|
|
||||||
;; load time means plugins can override or extend; ctx exposes the
|
|
||||||
;; parser internals each fn needs.
|
|
||||||
(begin
|
|
||||||
(hs-register-feature!
|
|
||||||
"on"
|
|
||||||
(fn (ctx) (begin ((dict-get ctx :adv!)) ((dict-get ctx :parse-on-feat)))))
|
|
||||||
(hs-register-feature!
|
|
||||||
"init"
|
|
||||||
(fn (ctx) (begin ((dict-get ctx :adv!)) ((dict-get ctx :parse-init-feat)))))
|
|
||||||
(hs-register-feature!
|
|
||||||
"def"
|
|
||||||
(fn (ctx) (begin ((dict-get ctx :adv!)) ((dict-get ctx :parse-def-feat)))))
|
|
||||||
(hs-register-feature!
|
|
||||||
"behavior"
|
|
||||||
(fn (ctx) (begin ((dict-get ctx :adv!)) ((dict-get ctx :parse-behavior-feat)))))
|
|
||||||
(hs-register-feature!
|
|
||||||
"live"
|
|
||||||
(fn (ctx) (begin ((dict-get ctx :adv!)) ((dict-get ctx :parse-live-feat)))))
|
|
||||||
(hs-register-feature!
|
|
||||||
"when"
|
|
||||||
(fn (ctx) (begin ((dict-get ctx :adv!)) ((dict-get ctx :parse-when-feat)))))
|
|
||||||
(hs-register-feature!
|
|
||||||
"bind"
|
|
||||||
(fn (ctx) (begin ((dict-get ctx :adv!)) ((dict-get ctx :parse-bind-feat)))))
|
|
||||||
(hs-register-feature!
|
|
||||||
"socket"
|
|
||||||
(fn (ctx) (begin ((dict-get ctx :adv!)) ((dict-get ctx :parse-socket-feat))))))
|
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
;; lib/hyperscript/plugins/prolog.sx — Prolog plugin
|
|
||||||
;;
|
|
||||||
;; Provides the `prolog` HS-level function. Replaces the ad-hoc
|
|
||||||
;; hs-prolog-hook / hs-set-prolog-hook! slots that previously lived in
|
|
||||||
;; lib/hyperscript/runtime.sx (nodes 140–142 of the plugin design doc).
|
|
||||||
;;
|
|
||||||
;; Two-step wiring preserves the original API:
|
|
||||||
;; 1. lib/prolog/runtime.sx loaded → defines pl-query-one
|
|
||||||
;; 2. lib/prolog/hs-bridge.sx (or this file's auto-wire) calls
|
|
||||||
;; (hs-set-prolog-hook! (fn (db goal) (not (= nil (pl-query-one db goal)))))
|
|
||||||
;; If neither is loaded, calling (prolog db goal) raises a clear error.
|
|
||||||
|
|
||||||
(define hs-prolog-hook nil)
|
|
||||||
|
|
||||||
(define hs-set-prolog-hook! (fn (f) (set! hs-prolog-hook f)))
|
|
||||||
|
|
||||||
(define
|
|
||||||
prolog
|
|
||||||
(fn
|
|
||||||
(db goal)
|
|
||||||
(if
|
|
||||||
(nil? hs-prolog-hook)
|
|
||||||
(raise "prolog hook not installed")
|
|
||||||
(hs-prolog-hook db goal))))
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
;; lib/hyperscript/plugins/worker.sx — Worker plugin (stub)
|
|
||||||
;;
|
|
||||||
;; Phase 1 of the worker plugin: the registration formerly inlined in
|
|
||||||
;; lib/hyperscript/parser.sx (E39 stub) moves here. Behaviour is
|
|
||||||
;; identical — `worker MyWorker ...` raises a helpful error directing
|
|
||||||
;; users to the full plugin (not yet implemented).
|
|
||||||
;;
|
|
||||||
;; Phase 2 (future) replaces this stub with parse-worker-feat, a
|
|
||||||
;; compiler entry, hs-worker-define!, and the postMessage-based
|
|
||||||
;; method dispatch documented in plans/designs/hs-plugin-system.md §4a.
|
|
||||||
|
|
||||||
(define hs-worker-loaded? true)
|
|
||||||
|
|
||||||
(hs-register-feature!
|
|
||||||
"worker"
|
|
||||||
(fn
|
|
||||||
(ctx)
|
|
||||||
(error
|
|
||||||
"worker plugin is not installed — see https://hyperscript.org/features/worker")))
|
|
||||||
@@ -12,6 +12,29 @@
|
|||||||
|
|
||||||
;; Register an event listener. Returns unlisten function.
|
;; Register an event listener. Returns unlisten function.
|
||||||
;; (hs-on target event-name handler) → unlisten-fn
|
;; (hs-on target event-name handler) → unlisten-fn
|
||||||
|
(begin
|
||||||
|
(define _hs-config-log-all false)
|
||||||
|
(define _hs-log-captured (list))
|
||||||
|
(define
|
||||||
|
hs-set-log-all!
|
||||||
|
(fn (flag) (set! _hs-config-log-all (if flag true false))))
|
||||||
|
(define hs-get-log-captured (fn () _hs-log-captured))
|
||||||
|
(define
|
||||||
|
hs-clear-log-captured!
|
||||||
|
(fn () (begin (set! _hs-log-captured (list)) nil)))
|
||||||
|
(define
|
||||||
|
hs-log-event!
|
||||||
|
(fn
|
||||||
|
(msg)
|
||||||
|
(when
|
||||||
|
_hs-config-log-all
|
||||||
|
(begin
|
||||||
|
(set! _hs-log-captured (append _hs-log-captured (list msg)))
|
||||||
|
(host-call (host-global "console") "log" msg)
|
||||||
|
nil)))))
|
||||||
|
|
||||||
|
;; Run an initializer function immediately.
|
||||||
|
;; (hs-init thunk) — called at element boot time
|
||||||
(define
|
(define
|
||||||
hs-each
|
hs-each
|
||||||
(fn
|
(fn
|
||||||
@@ -22,17 +45,52 @@
|
|||||||
;; (hs-init thunk) — called at element boot time
|
;; (hs-init thunk) — called at element boot time
|
||||||
(define meta (host-new "Object"))
|
(define meta (host-new "Object"))
|
||||||
|
|
||||||
;; Run an initializer function immediately.
|
|
||||||
;; (hs-init thunk) — called at element boot time
|
|
||||||
(define
|
|
||||||
hs-on-every
|
|
||||||
(fn (target event-name handler) (dom-listen target event-name handler)))
|
|
||||||
|
|
||||||
;; ── Async / timing ──────────────────────────────────────────────
|
;; ── Async / timing ──────────────────────────────────────────────
|
||||||
|
|
||||||
;; Wait for a duration in milliseconds.
|
;; Wait for a duration in milliseconds.
|
||||||
;; In hyperscript, wait is async-transparent — execution pauses.
|
;; In hyperscript, wait is async-transparent — execution pauses.
|
||||||
;; Here we use perform/IO suspension for true pause semantics.
|
;; Here we use perform/IO suspension for true pause semantics.
|
||||||
|
(define
|
||||||
|
hs-on-every
|
||||||
|
(fn (target event-name handler) (dom-listen target event-name handler)))
|
||||||
|
|
||||||
|
;; Throttle: drops events that arrive within the window. First event fires
|
||||||
|
;; immediately; subsequent events within `ms` of the previous fire are dropped.
|
||||||
|
;; Returns a wrapped handler suitable for hs-on / hs-on-every.
|
||||||
|
(define
|
||||||
|
hs-throttle!
|
||||||
|
(fn
|
||||||
|
(handler ms)
|
||||||
|
(let
|
||||||
|
((__hs-last-fire 0))
|
||||||
|
(fn
|
||||||
|
(event)
|
||||||
|
(let
|
||||||
|
((__hs-now (host-call (host-global "Date") "now")))
|
||||||
|
(when
|
||||||
|
(>= (- __hs-now __hs-last-fire) ms)
|
||||||
|
(set! __hs-last-fire __hs-now)
|
||||||
|
(handler event)))))))
|
||||||
|
|
||||||
|
;; Debounce: waits until `ms` has elapsed since the last event before firing.
|
||||||
|
;; In our synchronous test mock no time passes, so the timer fires immediately
|
||||||
|
;; via setTimeout(_, 0); the wrapped handler still gets called once per burst.
|
||||||
|
(define
|
||||||
|
hs-debounce!
|
||||||
|
(fn
|
||||||
|
(handler ms)
|
||||||
|
(let
|
||||||
|
((__hs-timer nil))
|
||||||
|
(fn
|
||||||
|
(event)
|
||||||
|
(when __hs-timer (host-call (host-global "window") "clearTimeout" __hs-timer))
|
||||||
|
(set! __hs-timer
|
||||||
|
(host-call (host-global "window") "setTimeout"
|
||||||
|
(host-new-function (list "ev") "return arguments[0](arguments[1]);")
|
||||||
|
ms handler event))))))
|
||||||
|
|
||||||
|
;; Wait for a DOM event on a target.
|
||||||
|
;; (hs-wait-for target event-name) — suspends until event fires
|
||||||
(define
|
(define
|
||||||
_hs-on-caller
|
_hs-on-caller
|
||||||
(let
|
(let
|
||||||
@@ -45,8 +103,7 @@
|
|||||||
(host-set! _ctx "meta" _m)
|
(host-set! _ctx "meta" _m)
|
||||||
_ctx)))
|
_ctx)))
|
||||||
|
|
||||||
;; Wait for a DOM event on a target.
|
;; Wait for CSS transitions/animations to settle on an element.
|
||||||
;; (hs-wait-for target event-name) — suspends until event fires
|
|
||||||
(define
|
(define
|
||||||
hs-on
|
hs-on
|
||||||
(fn
|
(fn
|
||||||
@@ -66,14 +123,14 @@
|
|||||||
(append prev (list unlisten)))
|
(append prev (list unlisten)))
|
||||||
unlisten))))))
|
unlisten))))))
|
||||||
|
|
||||||
;; Wait for CSS transitions/animations to settle on an element.
|
;; ── Class manipulation ──────────────────────────────────────────
|
||||||
|
|
||||||
|
;; Toggle a single class on an element.
|
||||||
(define
|
(define
|
||||||
hs-on-every
|
hs-on-every
|
||||||
(fn (target event-name handler) (dom-listen target event-name handler)))
|
(fn (target event-name handler) (dom-listen target event-name handler)))
|
||||||
|
|
||||||
;; ── Class manipulation ──────────────────────────────────────────
|
;; Toggle between two classes — exactly one is active at a time.
|
||||||
|
|
||||||
;; Toggle a single class on an element.
|
|
||||||
(define
|
(define
|
||||||
hs-on-intersection-attach!
|
hs-on-intersection-attach!
|
||||||
(fn
|
(fn
|
||||||
@@ -89,7 +146,8 @@
|
|||||||
(host-call observer "observe" target)
|
(host-call observer "observe" target)
|
||||||
observer)))))
|
observer)))))
|
||||||
|
|
||||||
;; Toggle between two classes — exactly one is active at a time.
|
;; Take a class from siblings — add to target, remove from others.
|
||||||
|
;; (hs-take! target cls) — like radio button class behavior
|
||||||
(define
|
(define
|
||||||
hs-on-mutation-attach!
|
hs-on-mutation-attach!
|
||||||
(fn
|
(fn
|
||||||
@@ -110,19 +168,18 @@
|
|||||||
(host-call observer "observe" target opts)
|
(host-call observer "observe" target opts)
|
||||||
observer))))))
|
observer))))))
|
||||||
|
|
||||||
;; Take a class from siblings — add to target, remove from others.
|
|
||||||
;; (hs-take! target cls) — like radio button class behavior
|
|
||||||
(define hs-init (fn (thunk) (thunk)))
|
|
||||||
|
|
||||||
;; ── DOM insertion ───────────────────────────────────────────────
|
;; ── DOM insertion ───────────────────────────────────────────────
|
||||||
|
|
||||||
;; Put content at a position relative to a target.
|
;; Put content at a position relative to a target.
|
||||||
;; pos: "into" | "before" | "after"
|
;; pos: "into" | "before" | "after"
|
||||||
(define hs-wait (fn (ms) (perform (list (quote io-sleep) ms))))
|
(define hs-init (fn (thunk) (thunk)))
|
||||||
|
|
||||||
;; ── Navigation / traversal ──────────────────────────────────────
|
;; ── Navigation / traversal ──────────────────────────────────────
|
||||||
|
|
||||||
;; Navigate to a URL.
|
;; Navigate to a URL.
|
||||||
|
(define hs-wait (fn (ms) (perform (list (quote io-sleep) ms))))
|
||||||
|
|
||||||
|
;; Find next sibling matching a selector (or any sibling).
|
||||||
(begin
|
(begin
|
||||||
(define
|
(define
|
||||||
hs-wait-for
|
hs-wait-for
|
||||||
@@ -135,7 +192,7 @@
|
|||||||
(target event-name timeout-ms)
|
(target event-name timeout-ms)
|
||||||
(perform (list (quote io-wait-event) target event-name timeout-ms)))))
|
(perform (list (quote io-wait-event) target event-name timeout-ms)))))
|
||||||
|
|
||||||
;; Find next sibling matching a selector (or any sibling).
|
;; Find previous sibling matching a selector.
|
||||||
(define
|
(define
|
||||||
hs-settle
|
hs-settle
|
||||||
(fn
|
(fn
|
||||||
@@ -143,7 +200,7 @@
|
|||||||
(hs-null-raise! target)
|
(hs-null-raise! target)
|
||||||
(when (not (nil? target)) (perform (list (quote io-settle) target)))))
|
(when (not (nil? target)) (perform (list (quote io-settle) target)))))
|
||||||
|
|
||||||
;; Find previous sibling matching a selector.
|
;; First element matching selector within a scope.
|
||||||
(define
|
(define
|
||||||
hs-toggle-class!
|
hs-toggle-class!
|
||||||
(fn
|
(fn
|
||||||
@@ -153,7 +210,7 @@
|
|||||||
(not (nil? target))
|
(not (nil? target))
|
||||||
(host-call (host-get target "classList") "toggle" cls))))
|
(host-call (host-get target "classList") "toggle" cls))))
|
||||||
|
|
||||||
;; First element matching selector within a scope.
|
;; Last element matching selector.
|
||||||
(define
|
(define
|
||||||
hs-toggle-var-cycle!
|
hs-toggle-var-cycle!
|
||||||
(fn
|
(fn
|
||||||
@@ -175,7 +232,7 @@
|
|||||||
var-name
|
var-name
|
||||||
(if (= idx -1) (first values) (nth values (mod (+ idx 1) n))))))))
|
(if (= idx -1) (first values) (nth values (mod (+ idx 1) n))))))))
|
||||||
|
|
||||||
;; Last element matching selector.
|
;; First/last within a specific scope.
|
||||||
(define
|
(define
|
||||||
hs-toggle-between!
|
hs-toggle-between!
|
||||||
(fn
|
(fn
|
||||||
@@ -188,7 +245,6 @@
|
|||||||
(do (dom-remove-class target cls1) (dom-add-class target cls2))
|
(do (dom-remove-class target cls1) (dom-add-class target cls2))
|
||||||
(do (dom-remove-class target cls2) (dom-add-class target cls1))))))
|
(do (dom-remove-class target cls2) (dom-add-class target cls1))))))
|
||||||
|
|
||||||
;; First/last within a specific scope.
|
|
||||||
(define
|
(define
|
||||||
hs-toggle-style!
|
hs-toggle-style!
|
||||||
(fn
|
(fn
|
||||||
@@ -212,6 +268,9 @@
|
|||||||
(dom-set-style target prop "hidden")
|
(dom-set-style target prop "hidden")
|
||||||
(dom-set-style target prop "")))))))
|
(dom-set-style target prop "")))))))
|
||||||
|
|
||||||
|
;; ── Iteration ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
;; Repeat a thunk N times.
|
||||||
(define
|
(define
|
||||||
hs-toggle-style-between!
|
hs-toggle-style-between!
|
||||||
(fn
|
(fn
|
||||||
@@ -223,9 +282,7 @@
|
|||||||
(dom-set-style target prop val2)
|
(dom-set-style target prop val2)
|
||||||
(dom-set-style target prop val1)))))
|
(dom-set-style target prop val1)))))
|
||||||
|
|
||||||
;; ── Iteration ───────────────────────────────────────────────────
|
;; Repeat forever (until break — relies on exception/continuation).
|
||||||
|
|
||||||
;; Repeat a thunk N times.
|
|
||||||
(define
|
(define
|
||||||
hs-toggle-style-cycle!
|
hs-toggle-style-cycle!
|
||||||
(fn
|
(fn
|
||||||
@@ -246,7 +303,10 @@
|
|||||||
(true (find-next (rest remaining))))))
|
(true (find-next (rest remaining))))))
|
||||||
(dom-set-style target prop (find-next vals)))))
|
(dom-set-style target prop (find-next vals)))))
|
||||||
|
|
||||||
;; Repeat forever (until break — relies on exception/continuation).
|
;; ── Fetch ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
;; Fetch a URL, parse response according to format.
|
||||||
|
;; (hs-fetch url format) — format is "json" | "text" | "html"
|
||||||
(define
|
(define
|
||||||
hs-take!
|
hs-take!
|
||||||
(fn
|
(fn
|
||||||
@@ -269,8 +329,7 @@
|
|||||||
(when with-cls (dom-remove-class target with-cls))))
|
(when with-cls (dom-remove-class target with-cls))))
|
||||||
(let
|
(let
|
||||||
((attr-val (if (> (len extra) 0) (first extra) nil))
|
((attr-val (if (> (len extra) 0) (first extra) nil))
|
||||||
(with-val
|
(with-val (if (> (len extra) 1) (nth extra 1) nil)))
|
||||||
(if (> (len extra) 1) (nth extra 1) nil)))
|
|
||||||
(do
|
(do
|
||||||
(for-each
|
(for-each
|
||||||
(fn
|
(fn
|
||||||
@@ -287,10 +346,10 @@
|
|||||||
(dom-set-attr target name attr-val)
|
(dom-set-attr target name attr-val)
|
||||||
(dom-set-attr target name ""))))))))
|
(dom-set-attr target name ""))))))))
|
||||||
|
|
||||||
;; ── Fetch ───────────────────────────────────────────────────────
|
;; ── Type coercion ───────────────────────────────────────────────
|
||||||
|
|
||||||
;; Fetch a URL, parse response according to format.
|
;; Coerce a value to a type by name.
|
||||||
;; (hs-fetch url format) — format is "json" | "text" | "html"
|
;; (hs-coerce value type-name) — type-name is "Int", "Float", "String", etc.
|
||||||
(begin
|
(begin
|
||||||
(define
|
(define
|
||||||
hs-element?
|
hs-element?
|
||||||
@@ -447,10 +506,10 @@
|
|||||||
(dom-insert-adjacent-html target "beforeend" value)
|
(dom-insert-adjacent-html target "beforeend" value)
|
||||||
(hs-boot-subtree! target)))))))))))
|
(hs-boot-subtree! target)))))))))))
|
||||||
|
|
||||||
;; ── Type coercion ───────────────────────────────────────────────
|
;; ── Object creation ─────────────────────────────────────────────
|
||||||
|
|
||||||
;; Coerce a value to a type by name.
|
;; Make a new object of a given type.
|
||||||
;; (hs-coerce value type-name) — type-name is "Int", "Float", "String", etc.
|
;; (hs-make type-name) — creates empty object/collection
|
||||||
(define
|
(define
|
||||||
hs-add-to!
|
hs-add-to!
|
||||||
(fn
|
(fn
|
||||||
@@ -464,10 +523,11 @@
|
|||||||
((hs-is-set? target) (do (host-call target "add" value) target))
|
((hs-is-set? target) (do (host-call target "add" value) target))
|
||||||
(true (do (host-call target "push" value) target)))))
|
(true (do (host-call target "push" value) target)))))
|
||||||
|
|
||||||
;; ── Object creation ─────────────────────────────────────────────
|
;; ── Behavior installation ───────────────────────────────────────
|
||||||
|
|
||||||
;; Make a new object of a given type.
|
;; Install a behavior on an element.
|
||||||
;; (hs-make type-name) — creates empty object/collection
|
;; A behavior is a function that takes (me ...params) and sets up features.
|
||||||
|
;; (hs-install behavior-fn me ...args)
|
||||||
(define
|
(define
|
||||||
hs-remove-from!
|
hs-remove-from!
|
||||||
(fn
|
(fn
|
||||||
@@ -477,11 +537,10 @@
|
|||||||
((hs-is-set? target) (do (host-call target "delete" value) target))
|
((hs-is-set? target) (do (host-call target "delete" value) target))
|
||||||
(true (host-call target "splice" (host-call target "indexOf" value) 1)))))
|
(true (host-call target "splice" (host-call target "indexOf" value) 1)))))
|
||||||
|
|
||||||
;; ── Behavior installation ───────────────────────────────────────
|
;; ── Measurement ─────────────────────────────────────────────────
|
||||||
|
|
||||||
;; Install a behavior on an element.
|
;; Measure an element's bounding rect, store as local variables.
|
||||||
;; A behavior is a function that takes (me ...params) and sets up features.
|
;; Returns a dict with x, y, width, height, top, left, right, bottom.
|
||||||
;; (hs-install behavior-fn me ...args)
|
|
||||||
(define
|
(define
|
||||||
hs-splice-at!
|
hs-splice-at!
|
||||||
(fn
|
(fn
|
||||||
@@ -494,10 +553,7 @@
|
|||||||
((i (if (< idx 0) (+ n idx) idx)))
|
((i (if (< idx 0) (+ n idx) idx)))
|
||||||
(cond
|
(cond
|
||||||
((or (< i 0) (>= i n)) target)
|
((or (< i 0) (>= i n)) target)
|
||||||
(true
|
(true (concat (slice target 0 i) (slice target (+ i 1) n))))))
|
||||||
(concat
|
|
||||||
(slice target 0 i)
|
|
||||||
(slice target (+ i 1) n))))))
|
|
||||||
(do
|
(do
|
||||||
(when
|
(when
|
||||||
target
|
target
|
||||||
@@ -508,10 +564,10 @@
|
|||||||
(host-call target "splice" i 1))))
|
(host-call target "splice" i 1))))
|
||||||
target))))
|
target))))
|
||||||
|
|
||||||
;; ── Measurement ─────────────────────────────────────────────────
|
;; Return the current text selection as a string. In the browser this is
|
||||||
|
;; `window.getSelection().toString()`. In the mock test runner, a test
|
||||||
;; Measure an element's bounding rect, store as local variables.
|
;; setup stashes the desired selection text at `window.__test_selection`
|
||||||
;; Returns a dict with x, y, width, height, top, left, right, bottom.
|
;; and the fallback path returns that so tests can assert on the result.
|
||||||
(define
|
(define
|
||||||
hs-index
|
hs-index
|
||||||
(fn
|
(fn
|
||||||
@@ -523,10 +579,11 @@
|
|||||||
((string? obj) (nth obj key))
|
((string? obj) (nth obj key))
|
||||||
(true (host-get obj key)))))
|
(true (host-get obj key)))))
|
||||||
|
|
||||||
;; Return the current text selection as a string. In the browser this is
|
|
||||||
;; `window.getSelection().toString()`. In the mock test runner, a test
|
;; ── Transition ──────────────────────────────────────────────────
|
||||||
;; setup stashes the desired selection text at `window.__test_selection`
|
|
||||||
;; and the fallback path returns that so tests can assert on the result.
|
;; Transition a CSS property to a value, optionally with duration.
|
||||||
|
;; (hs-transition target prop value duration)
|
||||||
(define
|
(define
|
||||||
hs-put-at!
|
hs-put-at!
|
||||||
(fn
|
(fn
|
||||||
@@ -548,11 +605,6 @@
|
|||||||
((= pos "start") (host-call target "unshift" value)))
|
((= pos "start") (host-call target "unshift" value)))
|
||||||
target)))))))
|
target)))))))
|
||||||
|
|
||||||
|
|
||||||
;; ── Transition ──────────────────────────────────────────────────
|
|
||||||
|
|
||||||
;; Transition a CSS property to a value, optionally with duration.
|
|
||||||
;; (hs-transition target prop value duration)
|
|
||||||
(define
|
(define
|
||||||
hs-dict-without
|
hs-dict-without
|
||||||
(fn
|
(fn
|
||||||
@@ -589,6 +641,11 @@
|
|||||||
((w (host-global "window")))
|
((w (host-global "window")))
|
||||||
(if w (host-call w "prompt" msg) nil))))
|
(if w (host-call w "prompt" msg) nil))))
|
||||||
|
|
||||||
|
|
||||||
|
;; ── Transition ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
;; Transition a CSS property to a value, optionally with duration.
|
||||||
|
;; (hs-transition target prop value duration)
|
||||||
(define
|
(define
|
||||||
hs-answer
|
hs-answer
|
||||||
(fn
|
(fn
|
||||||
@@ -597,11 +654,6 @@
|
|||||||
((w (host-global "window")))
|
((w (host-global "window")))
|
||||||
(if w (if (host-call w "confirm" msg) yes-val no-val) no-val))))
|
(if w (if (host-call w "confirm" msg) yes-val no-val) no-val))))
|
||||||
|
|
||||||
|
|
||||||
;; ── Transition ──────────────────────────────────────────────────
|
|
||||||
|
|
||||||
;; Transition a CSS property to a value, optionally with duration.
|
|
||||||
;; (hs-transition target prop value duration)
|
|
||||||
(define
|
(define
|
||||||
hs-answer-alert
|
hs-answer-alert
|
||||||
(fn
|
(fn
|
||||||
@@ -662,6 +714,10 @@
|
|||||||
(if (nil? sel) "" (host-call sel "toString" (list))))
|
(if (nil? sel) "" (host-call sel "toString" (list))))
|
||||||
stash)))))
|
stash)))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(define
|
(define
|
||||||
hs-reset!
|
hs-reset!
|
||||||
(fn
|
(fn
|
||||||
@@ -708,10 +764,6 @@
|
|||||||
(when default-val (dom-set-prop target "value" default-val)))))
|
(when default-val (dom-set-prop target "value" default-val)))))
|
||||||
(true nil)))))))
|
(true nil)))))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(define
|
(define
|
||||||
hs-next
|
hs-next
|
||||||
(fn
|
(fn
|
||||||
@@ -730,7 +782,8 @@
|
|||||||
((dom-matches? el sel) el)
|
((dom-matches? el sel) el)
|
||||||
(true (find-next (dom-next-sibling el))))))
|
(true (find-next (dom-next-sibling el))))))
|
||||||
(find-next sibling)))))
|
(find-next sibling)))))
|
||||||
|
;; ── Sandbox/test runtime additions ──────────────────────────────
|
||||||
|
;; Property access — dot notation and .length
|
||||||
(define
|
(define
|
||||||
hs-previous
|
hs-previous
|
||||||
(fn
|
(fn
|
||||||
@@ -749,10 +802,9 @@
|
|||||||
((dom-matches? el sel) el)
|
((dom-matches? el sel) el)
|
||||||
(true (find-prev (dom-get-prop el "previousElementSibling"))))))
|
(true (find-prev (dom-get-prop el "previousElementSibling"))))))
|
||||||
(find-prev sibling)))))
|
(find-prev sibling)))))
|
||||||
;; ── Sandbox/test runtime additions ──────────────────────────────
|
|
||||||
;; Property access — dot notation and .length
|
|
||||||
(define _hs-last-query-sel nil)
|
|
||||||
;; DOM query stub — sandbox returns empty list
|
;; DOM query stub — sandbox returns empty list
|
||||||
|
(define _hs-last-query-sel nil)
|
||||||
|
;; Method dispatch — obj.method(args)
|
||||||
(define
|
(define
|
||||||
hs-null-raise!
|
hs-null-raise!
|
||||||
(fn
|
(fn
|
||||||
@@ -763,7 +815,9 @@
|
|||||||
((msg (str "'" (or (host-get (host-global "window") "_hs_last_query_sel") "target") "' is null")))
|
((msg (str "'" (or (host-get (host-global "window") "_hs_last_query_sel") "target") "' is null")))
|
||||||
(host-set! (host-global "window") "_hs_null_error" msg)
|
(host-set! (host-global "window") "_hs_null_error" msg)
|
||||||
(guard (_null-e (true nil)) (raise msg))))))
|
(guard (_null-e (true nil)) (raise msg))))))
|
||||||
;; Method dispatch — obj.method(args)
|
|
||||||
|
;; ── 0.9.90 features ─────────────────────────────────────────────
|
||||||
|
;; beep! — debug logging, returns value unchanged
|
||||||
(define
|
(define
|
||||||
hs-empty-raise!
|
hs-empty-raise!
|
||||||
(fn
|
(fn
|
||||||
@@ -777,9 +831,7 @@
|
|||||||
((msg (str "'" (or (host-get (host-global "window") "_hs_last_query_sel") "target") "' is null")))
|
((msg (str "'" (or (host-get (host-global "window") "_hs_last_query_sel") "target") "' is null")))
|
||||||
(host-set! (host-global "window") "_hs_null_error" msg)
|
(host-set! (host-global "window") "_hs_null_error" msg)
|
||||||
(guard (_null-e (true nil)) (raise msg))))))
|
(guard (_null-e (true nil)) (raise msg))))))
|
||||||
|
;; Property-based is — check obj.key truthiness
|
||||||
;; ── 0.9.90 features ─────────────────────────────────────────────
|
|
||||||
;; beep! — debug logging, returns value unchanged
|
|
||||||
(define
|
(define
|
||||||
hs-query-all-checked
|
hs-query-all-checked
|
||||||
(fn
|
(fn
|
||||||
@@ -787,14 +839,14 @@
|
|||||||
(let
|
(let
|
||||||
((result (hs-query-all sel)))
|
((result (hs-query-all sel)))
|
||||||
(do (hs-empty-raise! result) result))))
|
(do (hs-empty-raise! result) result))))
|
||||||
;; Property-based is — check obj.key truthiness
|
;; Array slicing (inclusive both ends)
|
||||||
(define
|
(define
|
||||||
hs-dispatch!
|
hs-dispatch!
|
||||||
(fn
|
(fn
|
||||||
(target event detail)
|
(target event detail)
|
||||||
(hs-null-raise! target)
|
(hs-null-raise! target)
|
||||||
(when (not (nil? target)) (dom-dispatch target event detail))))
|
(when (not (nil? target)) (dom-dispatch target event detail))))
|
||||||
;; Array slicing (inclusive both ends)
|
;; Collection: sorted by
|
||||||
(define
|
(define
|
||||||
hs-query-all
|
hs-query-all
|
||||||
(fn
|
(fn
|
||||||
@@ -802,7 +854,7 @@
|
|||||||
(do
|
(do
|
||||||
(host-set! (host-global "window") "_hs_last_query_sel" sel)
|
(host-set! (host-global "window") "_hs_last_query_sel" sel)
|
||||||
(dom-query-all (dom-document) sel))))
|
(dom-query-all (dom-document) sel))))
|
||||||
;; Collection: sorted by
|
;; Collection: sorted by descending
|
||||||
(define
|
(define
|
||||||
hs-query-all-in
|
hs-query-all-in
|
||||||
(fn
|
(fn
|
||||||
@@ -811,17 +863,17 @@
|
|||||||
(nil? target)
|
(nil? target)
|
||||||
(hs-query-all sel)
|
(hs-query-all sel)
|
||||||
(host-call target "querySelectorAll" sel))))
|
(host-call target "querySelectorAll" sel))))
|
||||||
;; Collection: sorted by descending
|
;; Collection: split by
|
||||||
(define
|
(define
|
||||||
hs-list-set
|
hs-list-set
|
||||||
(fn
|
(fn
|
||||||
(lst idx val)
|
(lst idx val)
|
||||||
(append (take lst idx) (cons val (drop lst (+ idx 1))))))
|
(append (take lst idx) (cons val (drop lst (+ idx 1))))))
|
||||||
;; Collection: split by
|
;; Collection: joined by
|
||||||
(define
|
(define
|
||||||
hs-to-number
|
hs-to-number
|
||||||
(fn (v) (if (number? v) v (or (parse-number (str v)) 0))))
|
(fn (v) (if (number? v) v (or (parse-number (str v)) 0))))
|
||||||
;; Collection: joined by
|
|
||||||
(define
|
(define
|
||||||
hs-query-first
|
hs-query-first
|
||||||
(fn
|
(fn
|
||||||
@@ -951,7 +1003,7 @@
|
|||||||
((= (str ex) "hs-continue") (do-loop (rest remaining)))
|
((= (str ex) "hs-continue") (do-loop (rest remaining)))
|
||||||
(true (raise ex))))))))
|
(true (raise ex))))))))
|
||||||
(do-loop items))))
|
(do-loop items))))
|
||||||
|
;; Collection: joined by
|
||||||
(begin
|
(begin
|
||||||
(define
|
(define
|
||||||
hs-append
|
hs-append
|
||||||
@@ -992,7 +1044,7 @@
|
|||||||
(host-get value "outerHTML")
|
(host-get value "outerHTML")
|
||||||
(str value))))
|
(str value))))
|
||||||
(true nil)))))
|
(true nil)))))
|
||||||
;; Collection: joined by
|
|
||||||
(define
|
(define
|
||||||
hs-sender
|
hs-sender
|
||||||
(fn
|
(fn
|
||||||
@@ -1084,6 +1136,7 @@
|
|||||||
(hs-host-to-sx (perform (list "io-parse-json" raw))))
|
(hs-host-to-sx (perform (list "io-parse-json" raw))))
|
||||||
((= fmt "number")
|
((= fmt "number")
|
||||||
(hs-to-number (perform (list "io-parse-text" raw))))
|
(hs-to-number (perform (list "io-parse-text" raw))))
|
||||||
|
((= fmt "html") (perform (list "io-parse-html" raw)))
|
||||||
(true (perform (list "io-parse-text" raw)))))))))
|
(true (perform (list "io-parse-text" raw)))))))))
|
||||||
|
|
||||||
(define hs-fetch (fn (url format) (hs-fetch-impl url format false)))
|
(define hs-fetch (fn (url format) (hs-fetch-impl url format false)))
|
||||||
@@ -1623,14 +1676,10 @@
|
|||||||
((ch (substring sel i (+ i 1))))
|
((ch (substring sel i (+ i 1))))
|
||||||
(cond
|
(cond
|
||||||
((= ch ".")
|
((= ch ".")
|
||||||
(do
|
(do (flush!) (set! mode "class") (walk (+ i 1))))
|
||||||
(flush!)
|
|
||||||
(set! mode "class")
|
|
||||||
(walk (+ i 1))))
|
|
||||||
((= ch "#")
|
((= ch "#")
|
||||||
(do (flush!) (set! mode "id") (walk (+ i 1))))
|
(do (flush!) (set! mode "id") (walk (+ i 1))))
|
||||||
(true
|
(true (do (set! cur (str cur ch)) (walk (+ i 1)))))))))
|
||||||
(do (set! cur (str cur ch)) (walk (+ i 1)))))))))
|
|
||||||
(walk 0)
|
(walk 0)
|
||||||
(flush!)
|
(flush!)
|
||||||
{:tag tag :classes classes :id id}))))
|
{:tag tag :classes classes :id id}))))
|
||||||
@@ -1724,11 +1773,11 @@
|
|||||||
(value type-name)
|
(value type-name)
|
||||||
(if (nil? value) false (hs-type-check value type-name))))
|
(if (nil? value) false (hs-type-check value type-name))))
|
||||||
|
|
||||||
|
|
||||||
(define
|
(define
|
||||||
hs-strict-eq
|
hs-strict-eq
|
||||||
(fn (a b) (and (= (type-of a) (type-of b)) (= a b))))
|
(fn (a b) (and (= (type-of a) (type-of b)) (= a b))))
|
||||||
|
|
||||||
|
|
||||||
(define
|
(define
|
||||||
hs-id=
|
hs-id=
|
||||||
(fn
|
(fn
|
||||||
@@ -1760,6 +1809,20 @@
|
|||||||
((nil? suffix) false)
|
((nil? suffix) false)
|
||||||
(true (ends-with? (str s) (str suffix))))))
|
(true (ends-with? (str s) (str suffix))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
hs-attr-watch!
|
||||||
|
(fn
|
||||||
|
(target attr-name handler)
|
||||||
|
(let
|
||||||
|
((mo-class (host-get (host-global "window") "MutationObserver")))
|
||||||
|
(when
|
||||||
|
mo-class
|
||||||
|
(let
|
||||||
|
((cb (fn (records observer) (for-each (fn (rec) (when (= (host-get rec "attributeName") attr-name) (handler (host-call target "getAttribute" attr-name)))) records))))
|
||||||
|
(let
|
||||||
|
((mo (host-new "MutationObserver" cb)))
|
||||||
|
(host-call mo "observe" target {:attributeFilter (list attr-name) :attributes true})))))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
hs-scoped-set!
|
hs-scoped-set!
|
||||||
(fn
|
(fn
|
||||||
@@ -1805,10 +1868,7 @@
|
|||||||
((and (dict? a) (dict? b))
|
((and (dict? a) (dict? b))
|
||||||
(let
|
(let
|
||||||
((pos (host-call a "compareDocumentPosition" b)))
|
((pos (host-call a "compareDocumentPosition" b)))
|
||||||
(if
|
(if (number? pos) (not (= 0 (mod (/ pos 4) 2))) false)))
|
||||||
(number? pos)
|
|
||||||
(not (= 0 (mod (/ pos 4) 2)))
|
|
||||||
false)))
|
|
||||||
(true (< (str a) (str b))))))
|
(true (< (str a) (str b))))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
@@ -1929,10 +1989,7 @@
|
|||||||
((and (dict? a) (dict? b))
|
((and (dict? a) (dict? b))
|
||||||
(let
|
(let
|
||||||
((pos (host-call a "compareDocumentPosition" b)))
|
((pos (host-call a "compareDocumentPosition" b)))
|
||||||
(if
|
(if (number? pos) (not (= 0 (mod (/ pos 4) 2))) false)))
|
||||||
(number? pos)
|
|
||||||
(not (= 0 (mod (/ pos 4) 2)))
|
|
||||||
false)))
|
|
||||||
(true (< (str a) (str b))))))
|
(true (< (str a) (str b))))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
@@ -1985,9 +2042,7 @@
|
|||||||
|
|
||||||
(define
|
(define
|
||||||
hs-morph-char
|
hs-morph-char
|
||||||
(fn
|
(fn (s p) (if (or (< p 0) (>= p (string-length s))) nil (nth s p))))
|
||||||
(s p)
|
|
||||||
(if (or (< p 0) (>= p (string-length s))) nil (nth s p))))
|
|
||||||
|
|
||||||
(define
|
(define
|
||||||
hs-morph-index-from
|
hs-morph-index-from
|
||||||
@@ -2015,10 +2070,7 @@
|
|||||||
(q)
|
(q)
|
||||||
(let
|
(let
|
||||||
((c (hs-morph-char s q)))
|
((c (hs-morph-char s q)))
|
||||||
(if
|
(if (and c (< (index-of stop c) 0)) (loop (+ q 1)) q))))
|
||||||
(and c (< (index-of stop c) 0))
|
|
||||||
(loop (+ q 1))
|
|
||||||
q))))
|
|
||||||
(let ((e (loop p))) (list (substring s p e) e))))
|
(let ((e (loop p))) (list (substring s p e) e))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
@@ -2060,9 +2112,7 @@
|
|||||||
(append
|
(append
|
||||||
acc
|
acc
|
||||||
(list
|
(list
|
||||||
(list
|
(list name (substring s (+ p4 1) close)))))))
|
||||||
name
|
|
||||||
(substring s (+ p4 1) close)))))))
|
|
||||||
((= c2 "'")
|
((= c2 "'")
|
||||||
(let
|
(let
|
||||||
((close (hs-morph-index-from s "'" (+ p4 1))))
|
((close (hs-morph-index-from s "'" (+ p4 1))))
|
||||||
@@ -2072,9 +2122,7 @@
|
|||||||
(append
|
(append
|
||||||
acc
|
acc
|
||||||
(list
|
(list
|
||||||
(list
|
(list name (substring s (+ p4 1) close)))))))
|
||||||
name
|
|
||||||
(substring s (+ p4 1) close)))))))
|
|
||||||
(true
|
(true
|
||||||
(let
|
(let
|
||||||
((r2 (hs-morph-read-until s p4 " \t\n/>")))
|
((r2 (hs-morph-read-until s p4 " \t\n/>")))
|
||||||
@@ -2158,9 +2206,7 @@
|
|||||||
(for-each
|
(for-each
|
||||||
(fn
|
(fn
|
||||||
(c)
|
(c)
|
||||||
(when
|
(when (> (string-length c) 0) (dom-add-class el c)))
|
||||||
(> (string-length c) 0)
|
|
||||||
(dom-add-class el c)))
|
|
||||||
(split v " ")))
|
(split v " ")))
|
||||||
((and keep-id (= n "id")) nil)
|
((and keep-id (= n "id")) nil)
|
||||||
(true (dom-set-attr el n v)))))
|
(true (dom-set-attr el n v)))))
|
||||||
@@ -2261,8 +2307,7 @@
|
|||||||
((parts (split resolved ":")))
|
((parts (split resolved ":")))
|
||||||
(let
|
(let
|
||||||
((prop (first parts))
|
((prop (first parts))
|
||||||
(val
|
(val (if (> (len parts) 1) (nth parts 1) nil)))
|
||||||
(if (> (len parts) 1) (nth parts 1) nil)))
|
|
||||||
(cond
|
(cond
|
||||||
((and (not (= prop "display")) (not (= prop "opacity")) (not (= prop "visibility")) (not (= prop "hidden")) (not (= prop "class-hidden")) (not (= prop "class-invisible")) (not (= prop "class-opacity")) (not (= prop "details")) (not (= prop "dialog")) (dict-has? _hs-hide-strategies prop))
|
((and (not (= prop "display")) (not (= prop "opacity")) (not (= prop "visibility")) (not (= prop "hidden")) (not (= prop "class-hidden")) (not (= prop "class-invisible")) (not (= prop "class-opacity")) (not (= prop "details")) (not (= prop "dialog")) (dict-has? _hs-hide-strategies prop))
|
||||||
(let
|
(let
|
||||||
@@ -2302,8 +2347,7 @@
|
|||||||
((parts (split resolved ":")))
|
((parts (split resolved ":")))
|
||||||
(let
|
(let
|
||||||
((prop (first parts))
|
((prop (first parts))
|
||||||
(val
|
(val (if (> (len parts) 1) (nth parts 1) nil)))
|
||||||
(if (> (len parts) 1) (nth parts 1) nil)))
|
|
||||||
(cond
|
(cond
|
||||||
((and (not (= prop "display")) (not (= prop "opacity")) (not (= prop "visibility")) (not (= prop "hidden")) (not (= prop "class-hidden")) (not (= prop "class-invisible")) (not (= prop "class-opacity")) (not (= prop "details")) (not (= prop "dialog")) (dict-has? _hs-hide-strategies prop))
|
((and (not (= prop "display")) (not (= prop "opacity")) (not (= prop "visibility")) (not (= prop "hidden")) (not (= prop "class-hidden")) (not (= prop "class-invisible")) (not (= prop "class-opacity")) (not (= prop "details")) (not (= prop "dialog")) (dict-has? _hs-hide-strategies prop))
|
||||||
(let
|
(let
|
||||||
@@ -2408,14 +2452,10 @@
|
|||||||
(if
|
(if
|
||||||
(= depth 1)
|
(= depth 1)
|
||||||
j
|
j
|
||||||
(find-close
|
(find-close (+ j 1) (- depth 1)))
|
||||||
(+ j 1)
|
|
||||||
(- depth 1)))
|
|
||||||
(if
|
(if
|
||||||
(= (nth raw j) "{")
|
(= (nth raw j) "{")
|
||||||
(find-close
|
(find-close (+ j 1) (+ depth 1))
|
||||||
(+ j 1)
|
|
||||||
(+ depth 1))
|
|
||||||
(find-close (+ j 1) depth))))))
|
(find-close (+ j 1) depth))))))
|
||||||
(let
|
(let
|
||||||
((close (find-close start 1)))
|
((close (find-close start 1)))
|
||||||
@@ -2526,10 +2566,7 @@
|
|||||||
(if
|
(if
|
||||||
(= (len lst) 0)
|
(= (len lst) 0)
|
||||||
-1
|
-1
|
||||||
(if
|
(if (= (first lst) item) i (idx-loop (rest lst) (+ i 1))))))
|
||||||
(= (first lst) item)
|
|
||||||
i
|
|
||||||
(idx-loop (rest lst) (+ i 1))))))
|
|
||||||
(idx-loop obj 0)))
|
(idx-loop obj 0)))
|
||||||
(true
|
(true
|
||||||
(let
|
(let
|
||||||
@@ -2621,8 +2658,7 @@
|
|||||||
(cond
|
(cond
|
||||||
((= end "hs-pick-end") n)
|
((= end "hs-pick-end") n)
|
||||||
((= end "hs-pick-start") 0)
|
((= end "hs-pick-start") 0)
|
||||||
((and (number? end) (< end 0))
|
((and (number? end) (< end 0)) (max 0 (+ n end)))
|
||||||
(max 0 (+ n end)))
|
|
||||||
(true end))))
|
(true end))))
|
||||||
(cond
|
(cond
|
||||||
((string? col) (slice col s e))
|
((string? col) (slice col s e))
|
||||||
@@ -2802,6 +2838,8 @@
|
|||||||
hs-sorted-by-desc
|
hs-sorted-by-desc
|
||||||
(fn (col key-fn) (reverse (hs-sorted-by col key-fn))))
|
(fn (col key-fn) (reverse (hs-sorted-by col key-fn))))
|
||||||
|
|
||||||
|
;; ── SourceInfo API ────────────────────────────────────────────────
|
||||||
|
|
||||||
(define
|
(define
|
||||||
hs-dom-has-var?
|
hs-dom-has-var?
|
||||||
(fn
|
(fn
|
||||||
@@ -2821,8 +2859,6 @@
|
|||||||
((store (host-get el "__hs_vars")))
|
((store (host-get el "__hs_vars")))
|
||||||
(if (nil? store) nil (host-get store name)))))
|
(if (nil? store) nil (host-get store name)))))
|
||||||
|
|
||||||
;; ── SourceInfo API ────────────────────────────────────────────────
|
|
||||||
|
|
||||||
(define
|
(define
|
||||||
hs-dom-set-var-raw!
|
hs-dom-set-var-raw!
|
||||||
(fn
|
(fn
|
||||||
@@ -2911,9 +2947,27 @@
|
|||||||
((nth entry 2) val)))
|
((nth entry 2) val)))
|
||||||
_hs-dom-watchers)))
|
_hs-dom-watchers)))
|
||||||
|
|
||||||
|
(define hs-prolog-hook nil)
|
||||||
|
|
||||||
|
(define hs-set-prolog-hook! (fn (f) (set! hs-prolog-hook f)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
prolog
|
||||||
|
(fn
|
||||||
|
(db goal)
|
||||||
|
(if
|
||||||
|
(nil? hs-prolog-hook)
|
||||||
|
(raise "prolog hook not installed")
|
||||||
|
(hs-prolog-hook db goal))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
hs-null-error!
|
hs-null-error!
|
||||||
(fn (selector) (raise (str "'" selector "' is null"))))
|
(fn
|
||||||
|
(selector)
|
||||||
|
(let
|
||||||
|
((msg (str "'" selector "' is null")))
|
||||||
|
(host-set! (host-global "window") "_hs_null_error" msg)
|
||||||
|
(guard (_null-e (true nil)) (raise msg)))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
hs-named-target
|
hs-named-target
|
||||||
@@ -2933,9 +2987,7 @@
|
|||||||
((results (hs-query-all selector)))
|
((results (hs-query-all selector)))
|
||||||
(if
|
(if
|
||||||
(and
|
(and
|
||||||
(or
|
(or (nil? results) (and (list? results) (= (len results) 0)))
|
||||||
(nil? results)
|
|
||||||
(and (list? results) (= (len results) 0)))
|
|
||||||
(string? selector)
|
(string? selector)
|
||||||
(> (len selector) 0)
|
(> (len selector) 0)
|
||||||
(= (substring selector 0 1) "#"))
|
(= (substring selector 0 1) "#"))
|
||||||
|
|||||||
@@ -8,17 +8,7 @@
|
|||||||
|
|
||||||
;; ── Token constructor ─────────────────────────────────────────────
|
;; ── Token constructor ─────────────────────────────────────────────
|
||||||
|
|
||||||
(define hs-make-token
|
(define hs-make-token (fn (type value pos) {:pos pos :value value :type type}))
|
||||||
(fn (type value pos &rest extras)
|
|
||||||
(let
|
|
||||||
((end-arg (if (>= (len extras) 1) (nth extras 0) nil))
|
|
||||||
(line-arg (if (>= (len extras) 2) (nth extras 1) nil)))
|
|
||||||
(let
|
|
||||||
((end (if (nil? end-arg)
|
|
||||||
(+ pos (if (nil? value) 0 (len (str value))))
|
|
||||||
end-arg))
|
|
||||||
(line (if (nil? line-arg) 1 line-arg)))
|
|
||||||
{:pos pos :end end :line line :value value :type type}))))
|
|
||||||
|
|
||||||
;; ── Character predicates ──────────────────────────────────────────
|
;; ── Character predicates ──────────────────────────────────────────
|
||||||
|
|
||||||
@@ -231,26 +221,14 @@
|
|||||||
(fn
|
(fn
|
||||||
(src)
|
(src)
|
||||||
(let
|
(let
|
||||||
((tokens (list)) (pos 0) (src-len (len src)) (current-line 1))
|
((tokens (list)) (pos 0) (src-len (len src)))
|
||||||
(define
|
(define
|
||||||
hs-peek
|
hs-peek
|
||||||
(fn
|
(fn
|
||||||
(offset)
|
(offset)
|
||||||
(if (< (+ pos offset) src-len) (nth src (+ pos offset)) nil)))
|
(if (< (+ pos offset) src-len) (nth src (+ pos offset)) nil)))
|
||||||
(define hs-cur (fn () (hs-peek 0)))
|
(define hs-cur (fn () (hs-peek 0)))
|
||||||
(define
|
(define hs-advance! (fn (n) (set! pos (+ pos n))))
|
||||||
hs-advance!
|
|
||||||
(fn (n)
|
|
||||||
(let ((new-pos (+ pos n)))
|
|
||||||
(define
|
|
||||||
count-nl!
|
|
||||||
(fn (i)
|
|
||||||
(when (< i new-pos)
|
|
||||||
(when (= (nth src i) "\n")
|
|
||||||
(set! current-line (+ current-line 1)))
|
|
||||||
(count-nl! (+ i 1)))))
|
|
||||||
(count-nl! pos)
|
|
||||||
(set! pos new-pos))))
|
|
||||||
(define
|
(define
|
||||||
skip-ws!
|
skip-ws!
|
||||||
(fn
|
(fn
|
||||||
@@ -524,14 +502,13 @@
|
|||||||
(fn
|
(fn
|
||||||
(type value start)
|
(type value start)
|
||||||
(let
|
(let
|
||||||
((end-pos
|
((tok (hs-make-token type value start))
|
||||||
(max pos (+ start (if (nil? value) 0 (len (str value))))))
|
(end-pos
|
||||||
(newlines-after-start
|
(max pos (+ start (if (nil? value) 0 (len (str value)))))))
|
||||||
(- (len (split (slice src start (max start pos)) "\n")) 1))
|
(do
|
||||||
(start-line (- current-line newlines-after-start)))
|
(dict-set! tok "end" end-pos)
|
||||||
(append!
|
(dict-set! tok "line" (len (split (slice src 0 start) "\n")))
|
||||||
tokens
|
(append! tokens tok)))))
|
||||||
(hs-make-token type value start end-pos start-line)))))
|
|
||||||
(define
|
(define
|
||||||
scan!
|
scan!
|
||||||
(fn
|
(fn
|
||||||
@@ -781,30 +758,11 @@
|
|||||||
(fn
|
(fn
|
||||||
(src)
|
(src)
|
||||||
(let
|
(let
|
||||||
((tokens (list)) (pos 0) (src-len (len src)) (current-line 1))
|
((tokens (list)) (pos 0) (src-len (len src)))
|
||||||
(define t-cur (fn () (if (< pos src-len) (nth src pos) nil)))
|
(define t-cur (fn () (if (< pos src-len) (nth src pos) nil)))
|
||||||
(define t-peek (fn (n) (if (< (+ pos n) src-len) (nth src (+ pos n)) nil)))
|
(define t-peek (fn (n) (if (< (+ pos n) src-len) (nth src (+ pos n)) nil)))
|
||||||
(define
|
(define t-advance! (fn (n) (set! pos (+ pos n))))
|
||||||
t-advance!
|
(define t-emit! (fn (type value) (append! tokens (hs-make-token type value pos))))
|
||||||
(fn (n)
|
|
||||||
(let ((new-pos (+ pos n)))
|
|
||||||
(define
|
|
||||||
t-count-nl!
|
|
||||||
(fn (i)
|
|
||||||
(when (< i new-pos)
|
|
||||||
(when (= (nth src i) "\n")
|
|
||||||
(set! current-line (+ current-line 1)))
|
|
||||||
(t-count-nl! (+ i 1)))))
|
|
||||||
(t-count-nl! pos)
|
|
||||||
(set! pos new-pos))))
|
|
||||||
(define
|
|
||||||
t-emit!
|
|
||||||
(fn (type value)
|
|
||||||
(let
|
|
||||||
((end-pos (+ pos (if (nil? value) 0 (len (str value))))))
|
|
||||||
(append!
|
|
||||||
tokens
|
|
||||||
(hs-make-token type value pos end-pos current-line)))))
|
|
||||||
(define
|
(define
|
||||||
scan-to-close!
|
scan-to-close!
|
||||||
(fn
|
(fn
|
||||||
@@ -855,4 +813,230 @@
|
|||||||
:else (do (t-advance! 1) (scan-template!)))))))
|
:else (do (t-advance! 1) (scan-template!)))))))
|
||||||
(scan-template!)
|
(scan-template!)
|
||||||
(t-emit! "eof" nil)
|
(t-emit! "eof" nil)
|
||||||
tokens)))
|
tokens)))
|
||||||
|
|
||||||
|
;; ── Stream wrapper for upstream-style stateful tokenizer API ───────────────
|
||||||
|
;;
|
||||||
|
;; Upstream _hyperscript exposes a Tokens object with cursor + follow-set
|
||||||
|
;; semantics on _hyperscript.internals.tokenizer. Our hs-tokenize returns a
|
||||||
|
;; flat list; the stream wrapper adds the stateful operations.
|
||||||
|
;;
|
||||||
|
;; Type names map ours → upstream's (e.g. "ident" → "IDENTIFIER").
|
||||||
|
|
||||||
|
(define
|
||||||
|
hs-stream-type-map
|
||||||
|
(fn
|
||||||
|
(t)
|
||||||
|
(cond
|
||||||
|
((= t "ident") "IDENTIFIER")
|
||||||
|
((= t "number") "NUMBER")
|
||||||
|
((= t "string") "STRING")
|
||||||
|
((= t "class") "CLASS_REF")
|
||||||
|
((= t "id") "ID_REF")
|
||||||
|
((= t "attr") "ATTRIBUTE_REF")
|
||||||
|
((= t "style") "STYLE_REF")
|
||||||
|
((= t "whitespace") "WHITESPACE")
|
||||||
|
((= t "op") "OPERATOR")
|
||||||
|
((= t "eof") "EOF")
|
||||||
|
(true (upcase t)))))
|
||||||
|
|
||||||
|
;; Create a stream from a source string.
|
||||||
|
;; Returns a dict — mutable via dict-set!.
|
||||||
|
(define
|
||||||
|
hs-stream
|
||||||
|
(fn
|
||||||
|
(src)
|
||||||
|
{:tokens (hs-tokenize src) :pos 0 :follows (list) :last-match nil :last-ws nil}))
|
||||||
|
|
||||||
|
;; Skip whitespace tokens, advancing pos to the next non-WS token.
|
||||||
|
;; Captures the last skipped whitespace value into :last-ws.
|
||||||
|
(define
|
||||||
|
hs-stream-skip-ws!
|
||||||
|
(fn
|
||||||
|
(s)
|
||||||
|
(let
|
||||||
|
((tokens (get s :tokens)))
|
||||||
|
(define
|
||||||
|
loop
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(let
|
||||||
|
((p (get s :pos)))
|
||||||
|
(when
|
||||||
|
(and (< p (len tokens))
|
||||||
|
(= (get (nth tokens p) :type) "whitespace"))
|
||||||
|
(do
|
||||||
|
(dict-set! s :last-ws (get (nth tokens p) :value))
|
||||||
|
(dict-set! s :pos (+ p 1))
|
||||||
|
(loop))))))
|
||||||
|
(loop))))
|
||||||
|
|
||||||
|
;; Current token (after skipping whitespace).
|
||||||
|
(define
|
||||||
|
hs-stream-current
|
||||||
|
(fn
|
||||||
|
(s)
|
||||||
|
(do
|
||||||
|
(hs-stream-skip-ws! s)
|
||||||
|
(let
|
||||||
|
((tokens (get s :tokens)) (p (get s :pos)))
|
||||||
|
(if (< p (len tokens)) (nth tokens p) nil)))))
|
||||||
|
|
||||||
|
;; Returns the current token if its value matches; advances and updates
|
||||||
|
;; :last-match. Returns nil otherwise (no advance).
|
||||||
|
;; Honors the follow set: tokens whose value is in :follows do NOT match.
|
||||||
|
(define
|
||||||
|
hs-stream-match
|
||||||
|
(fn
|
||||||
|
(s value)
|
||||||
|
(let
|
||||||
|
((cur (hs-stream-current s)))
|
||||||
|
(cond
|
||||||
|
((nil? cur) nil)
|
||||||
|
((some (fn (f) (= f value)) (get s :follows)) nil)
|
||||||
|
((= (get cur :value) value)
|
||||||
|
(do
|
||||||
|
(dict-set! s :pos (+ (get s :pos) 1))
|
||||||
|
(dict-set! s :last-match cur)
|
||||||
|
cur))
|
||||||
|
(true nil)))))
|
||||||
|
|
||||||
|
;; Match by upstream-style type name. Accepts any number of allowed types.
|
||||||
|
(define
|
||||||
|
hs-stream-match-type
|
||||||
|
(fn
|
||||||
|
(s &rest types)
|
||||||
|
(let
|
||||||
|
((cur (hs-stream-current s)))
|
||||||
|
(cond
|
||||||
|
((nil? cur) nil)
|
||||||
|
((some (fn (t) (= (hs-stream-type-map (get cur :type)) t)) types)
|
||||||
|
(do
|
||||||
|
(dict-set! s :pos (+ (get s :pos) 1))
|
||||||
|
(dict-set! s :last-match cur)
|
||||||
|
cur))
|
||||||
|
(true nil)))))
|
||||||
|
|
||||||
|
;; Match if value is one of the given names.
|
||||||
|
(define
|
||||||
|
hs-stream-match-any
|
||||||
|
(fn
|
||||||
|
(s &rest names)
|
||||||
|
(let
|
||||||
|
((cur (hs-stream-current s)))
|
||||||
|
(cond
|
||||||
|
((nil? cur) nil)
|
||||||
|
((some (fn (n) (= (get cur :value) n)) names)
|
||||||
|
(do
|
||||||
|
(dict-set! s :pos (+ (get s :pos) 1))
|
||||||
|
(dict-set! s :last-match cur)
|
||||||
|
cur))
|
||||||
|
(true nil)))))
|
||||||
|
|
||||||
|
;; Match an op token whose value is in the list.
|
||||||
|
(define
|
||||||
|
hs-stream-match-any-op
|
||||||
|
(fn
|
||||||
|
(s &rest ops)
|
||||||
|
(let
|
||||||
|
((cur (hs-stream-current s)))
|
||||||
|
(cond
|
||||||
|
((nil? cur) nil)
|
||||||
|
((and (= (get cur :type) "op")
|
||||||
|
(some (fn (o) (= (get cur :value) o)) ops))
|
||||||
|
(do
|
||||||
|
(dict-set! s :pos (+ (get s :pos) 1))
|
||||||
|
(dict-set! s :last-match cur)
|
||||||
|
cur))
|
||||||
|
(true nil)))))
|
||||||
|
|
||||||
|
;; Peek N non-WS tokens ahead. Returns the token if its value matches; nil otherwise.
|
||||||
|
(define
|
||||||
|
hs-stream-peek
|
||||||
|
(fn
|
||||||
|
(s value offset)
|
||||||
|
(let
|
||||||
|
((tokens (get s :tokens)))
|
||||||
|
(define
|
||||||
|
skip-n-non-ws
|
||||||
|
(fn
|
||||||
|
(p remaining)
|
||||||
|
(cond
|
||||||
|
((>= p (len tokens)) -1)
|
||||||
|
((= (get (nth tokens p) :type) "whitespace")
|
||||||
|
(skip-n-non-ws (+ p 1) remaining))
|
||||||
|
((= remaining 0) p)
|
||||||
|
(true (skip-n-non-ws (+ p 1) (- remaining 1))))))
|
||||||
|
(let
|
||||||
|
((p (skip-n-non-ws (get s :pos) offset)))
|
||||||
|
(if (and (>= p 0) (< p (len tokens))
|
||||||
|
(= (get (nth tokens p) :value) value))
|
||||||
|
(nth tokens p)
|
||||||
|
nil)))))
|
||||||
|
|
||||||
|
;; Consume tokens until one whose value matches the marker. Returns
|
||||||
|
;; the consumed list (excluding the marker). Marker becomes current.
|
||||||
|
(define
|
||||||
|
hs-stream-consume-until
|
||||||
|
(fn
|
||||||
|
(s marker)
|
||||||
|
(let
|
||||||
|
((tokens (get s :tokens)) (out (list)))
|
||||||
|
(define
|
||||||
|
loop
|
||||||
|
(fn
|
||||||
|
(acc)
|
||||||
|
(let
|
||||||
|
((p (get s :pos)))
|
||||||
|
(cond
|
||||||
|
((>= p (len tokens)) acc)
|
||||||
|
((= (get (nth tokens p) :value) marker) acc)
|
||||||
|
(true
|
||||||
|
(do
|
||||||
|
(dict-set! s :pos (+ p 1))
|
||||||
|
(loop (append acc (list (nth tokens p))))))))))
|
||||||
|
(loop out))))
|
||||||
|
|
||||||
|
;; Consume until the next whitespace token; returns the consumed list.
|
||||||
|
(define
|
||||||
|
hs-stream-consume-until-ws
|
||||||
|
(fn
|
||||||
|
(s)
|
||||||
|
(let
|
||||||
|
((tokens (get s :tokens)))
|
||||||
|
(define
|
||||||
|
loop
|
||||||
|
(fn
|
||||||
|
(acc)
|
||||||
|
(let
|
||||||
|
((p (get s :pos)))
|
||||||
|
(cond
|
||||||
|
((>= p (len tokens)) acc)
|
||||||
|
((= (get (nth tokens p) :type) "whitespace") acc)
|
||||||
|
(true
|
||||||
|
(do
|
||||||
|
(dict-set! s :pos (+ p 1))
|
||||||
|
(loop (append acc (list (nth tokens p))))))))))
|
||||||
|
(loop (list)))))
|
||||||
|
|
||||||
|
;; Follow-set management.
|
||||||
|
(define hs-stream-push-follow! (fn (s v) (dict-set! s :follows (cons v (get s :follows)))))
|
||||||
|
(define
|
||||||
|
hs-stream-pop-follow!
|
||||||
|
(fn (s) (let ((f (get s :follows))) (when (> (len f) 0) (dict-set! s :follows (rest f))))))
|
||||||
|
(define
|
||||||
|
hs-stream-push-follows!
|
||||||
|
(fn (s vs) (for-each (fn (v) (hs-stream-push-follow! s v)) vs)))
|
||||||
|
(define
|
||||||
|
hs-stream-pop-follows!
|
||||||
|
(fn (s n) (when (> n 0) (do (hs-stream-pop-follow! s) (hs-stream-pop-follows! s (- n 1))))))
|
||||||
|
(define
|
||||||
|
hs-stream-clear-follows!
|
||||||
|
(fn (s) (let ((saved (get s :follows))) (do (dict-set! s :follows (list)) saved))))
|
||||||
|
(define
|
||||||
|
hs-stream-restore-follows!
|
||||||
|
(fn (s saved) (dict-set! s :follows saved)))
|
||||||
|
|
||||||
|
;; Last-consumed token / whitespace.
|
||||||
|
(define hs-stream-last-match (fn (s) (get s :last-match)))
|
||||||
|
(define hs-stream-last-ws (fn (s) (get s :last-ws)))
|
||||||
89
lib/jit.sx
Normal file
89
lib/jit.sx
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
;; lib/jit.sx — SX-level convenience wrappers over the JIT cache control
|
||||||
|
;; primitives (jit-stats, jit-set-threshold!, jit-set-budget!, jit-reset-cache!,
|
||||||
|
;; jit-reset-counters!). Host-specific implementations live in
|
||||||
|
;; hosts/<host>/lib/sx_*.ml; the API surface is portable across hosts.
|
||||||
|
|
||||||
|
;; with-jit-threshold — temporarily set the JIT call-count threshold for
|
||||||
|
;; the duration of body, restoring the previous value on exit. Useful for
|
||||||
|
;; sections that want eager compilation (threshold=1) or want to skip JIT
|
||||||
|
;; entirely (threshold=999999) for diagnostic comparison.
|
||||||
|
(defmacro
|
||||||
|
with-jit-threshold
|
||||||
|
(n &rest body)
|
||||||
|
`(let
|
||||||
|
((__old (get (jit-stats) "threshold")))
|
||||||
|
(jit-set-threshold! ,n)
|
||||||
|
(let
|
||||||
|
((__r (do ,@body)))
|
||||||
|
(jit-set-threshold! __old)
|
||||||
|
__r)))
|
||||||
|
|
||||||
|
;; with-jit-budget — temporarily set the LRU cache budget. Setting to 0
|
||||||
|
;; disables JIT entirely (everything falls through to the interpreter);
|
||||||
|
;; large values are effectively unbounded.
|
||||||
|
(defmacro
|
||||||
|
with-jit-budget
|
||||||
|
(n &rest body)
|
||||||
|
`(let
|
||||||
|
((__old (get (jit-stats) "budget")))
|
||||||
|
(jit-set-budget! ,n)
|
||||||
|
(let
|
||||||
|
((__r (do ,@body)))
|
||||||
|
(jit-set-budget! __old)
|
||||||
|
__r)))
|
||||||
|
|
||||||
|
;; with-fresh-jit — clear the cache before body, run body, clear again
|
||||||
|
;; after. Use between sessions / request batches / test suites where you
|
||||||
|
;; want deterministic timing free of carryover.
|
||||||
|
(defmacro
|
||||||
|
with-fresh-jit
|
||||||
|
(&rest body)
|
||||||
|
`(let
|
||||||
|
((__r (do (jit-reset-cache!) ,@body)))
|
||||||
|
(jit-reset-cache!)
|
||||||
|
__r))
|
||||||
|
|
||||||
|
;; jit-report — human-readable summary of current JIT state. Returns a
|
||||||
|
;; string suitable for logging.
|
||||||
|
(define
|
||||||
|
jit-report
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(let
|
||||||
|
((s (jit-stats)))
|
||||||
|
(let
|
||||||
|
((compiled (get s "compiled"))
|
||||||
|
(skipped (get s "below-threshold"))
|
||||||
|
(failed (get s "compile-failed"))
|
||||||
|
(evicted (get s "evicted"))
|
||||||
|
(cache-size (get s "cache-size"))
|
||||||
|
(budget (get s "budget"))
|
||||||
|
(threshold (get s "threshold")))
|
||||||
|
(let
|
||||||
|
((total (+ compiled skipped failed)))
|
||||||
|
(str
|
||||||
|
"jit: " cache-size "/" budget " cached "
|
||||||
|
"(thr=" threshold ") · "
|
||||||
|
compiled " compiled, "
|
||||||
|
skipped " below-thr, "
|
||||||
|
failed " failed, "
|
||||||
|
evicted " evicted "
|
||||||
|
"(" (if (> total 0) (* 100 (/ compiled total)) 0) "% compile rate)"))))))
|
||||||
|
|
||||||
|
;; jit-disable! / jit-enable! — convenience helpers. Disabling sets budget
|
||||||
|
;; to 0 which causes the VM to skip JIT entirely on the next call. Enable
|
||||||
|
;; restores the budget to its previous value (or 5000 if no previous).
|
||||||
|
(define _jit-saved-budget (list 5000))
|
||||||
|
|
||||||
|
(define
|
||||||
|
jit-disable!
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(set! _jit-saved-budget (list (get (jit-stats) "budget")))
|
||||||
|
(jit-set-budget! 0)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
jit-enable!
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(jit-set-budget! (first _jit-saved-budget))))
|
||||||
@@ -3,33 +3,28 @@
|
|||||||
(define lua-tok-value (fn (t) (if (= t nil) nil (get t :value))))
|
(define lua-tok-value (fn (t) (if (= t nil) nil (get t :value))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
lua-op-table
|
lua-binop-prec
|
||||||
(list
|
(fn
|
||||||
(list "or" 1 :left)
|
(op)
|
||||||
(list "and" 2 :left)
|
(cond
|
||||||
(list "<" 3 :left)
|
((= op "or") 1)
|
||||||
(list ">" 3 :left)
|
((= op "and") 2)
|
||||||
(list "<=" 3 :left)
|
((= op "<") 3)
|
||||||
(list ">=" 3 :left)
|
((= op ">") 3)
|
||||||
(list "==" 3 :left)
|
((= op "<=") 3)
|
||||||
(list "~=" 3 :left)
|
((= op ">=") 3)
|
||||||
(list ".." 5 :right)
|
((= op "==") 3)
|
||||||
(list "+" 6 :left)
|
((= op "~=") 3)
|
||||||
(list "-" 6 :left)
|
((= op "..") 5)
|
||||||
(list "*" 7 :left)
|
((= op "+") 6)
|
||||||
(list "/" 7 :left)
|
((= op "-") 6)
|
||||||
(list "%" 7 :left)
|
((= op "*") 7)
|
||||||
(list "^" 10 :right)))
|
((= op "/") 7)
|
||||||
|
((= op "%") 7)
|
||||||
|
((= op "^") 10)
|
||||||
|
(else 0))))
|
||||||
|
|
||||||
(define lua-binop-prec
|
(define lua-binop-right? (fn (op) (or (= op "..") (= op "^"))))
|
||||||
(fn (op)
|
|
||||||
(let ((entry (pratt-op-lookup lua-op-table op)))
|
|
||||||
(if (= entry nil) 0 (pratt-op-prec entry)))))
|
|
||||||
|
|
||||||
(define lua-binop-right?
|
|
||||||
(fn (op)
|
|
||||||
(let ((entry (pratt-op-lookup lua-op-table op)))
|
|
||||||
(and (not (= entry nil)) (= (pratt-op-assoc entry) :right)))))
|
|
||||||
|
|
||||||
(define
|
(define
|
||||||
lua-parse
|
lua-parse
|
||||||
|
|||||||
@@ -28,9 +28,6 @@ trap "rm -f $TMPFILE" EXIT
|
|||||||
|
|
||||||
cat > "$TMPFILE" << 'EPOCHS'
|
cat > "$TMPFILE" << 'EPOCHS'
|
||||||
(epoch 1)
|
(epoch 1)
|
||||||
(load "lib/guest/lex.sx")
|
|
||||||
(load "lib/guest/prefix.sx")
|
|
||||||
(load "lib/guest/pratt.sx")
|
|
||||||
(load "lib/lua/tokenizer.sx")
|
(load "lib/lua/tokenizer.sx")
|
||||||
(epoch 2)
|
(epoch 2)
|
||||||
(load "lib/lua/parser.sx")
|
(load "lib/lua/parser.sx")
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
(prefix-rename "lua-"
|
(define lua-make-token (fn (type value pos) {:pos pos :value value :type type}))
|
||||||
'((make-token lex-make-token)
|
|
||||||
(digit? lex-digit?)
|
|
||||||
(hex-digit? lex-hex-digit?)
|
|
||||||
(letter? lex-alpha?)
|
|
||||||
(ident-start? lex-ident-start?)
|
|
||||||
(ident-char? lex-ident-char?)
|
|
||||||
(ws? lex-whitespace?)))
|
|
||||||
|
|
||||||
|
(define lua-digit? (fn (c) (and (not (= c nil)) (>= c "0") (<= c "9"))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
lua-hex-digit?
|
||||||
|
(fn
|
||||||
|
(c)
|
||||||
|
(and
|
||||||
|
(not (= c nil))
|
||||||
|
(or
|
||||||
|
(lua-digit? c)
|
||||||
|
(and (>= c "a") (<= c "f"))
|
||||||
|
(and (>= c "A") (<= c "F"))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
lua-letter?
|
||||||
|
(fn
|
||||||
|
(c)
|
||||||
|
(and
|
||||||
|
(not (= c nil))
|
||||||
|
(or (and (>= c "a") (<= c "z")) (and (>= c "A") (<= c "Z"))))))
|
||||||
|
|
||||||
|
(define lua-ident-start? (fn (c) (or (lua-letter? c) (= c "_"))))
|
||||||
|
|
||||||
|
(define lua-ident-char? (fn (c) (or (lua-ident-start? c) (lua-digit? c))))
|
||||||
|
|
||||||
|
(define lua-ws? (fn (c) (or (= c " ") (= c "\t") (= c "\n") (= c "\r"))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
lua-keywords
|
lua-keywords
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
# Prolog conformance config — sourced by lib/guest/conformance.sh.
|
|
||||||
|
|
||||||
LANG_NAME=prolog
|
|
||||||
MODE=dict
|
|
||||||
|
|
||||||
PRELOADS=(
|
|
||||||
lib/guest/pratt.sx
|
|
||||||
lib/prolog/tokenizer.sx
|
|
||||||
lib/prolog/parser.sx
|
|
||||||
lib/prolog/runtime.sx
|
|
||||||
lib/prolog/query.sx
|
|
||||||
lib/prolog/compiler.sx
|
|
||||||
lib/prolog/hs-bridge.sx
|
|
||||||
)
|
|
||||||
|
|
||||||
SUITES=(
|
|
||||||
"parse:lib/prolog/tests/parse.sx:(pl-parse-tests-run!)"
|
|
||||||
"unify:lib/prolog/tests/unify.sx:(pl-unify-tests-run!)"
|
|
||||||
"clausedb:lib/prolog/tests/clausedb.sx:(pl-clausedb-tests-run!)"
|
|
||||||
"solve:lib/prolog/tests/solve.sx:(pl-solve-tests-run!)"
|
|
||||||
"operators:lib/prolog/tests/operators.sx:(pl-operators-tests-run!)"
|
|
||||||
"dynamic:lib/prolog/tests/dynamic.sx:(pl-dynamic-tests-run!)"
|
|
||||||
"findall:lib/prolog/tests/findall.sx:(pl-findall-tests-run!)"
|
|
||||||
"term_inspect:lib/prolog/tests/term_inspect.sx:(pl-term-inspect-tests-run!)"
|
|
||||||
"append:lib/prolog/tests/programs/append.sx:(pl-append-tests-run!)"
|
|
||||||
"reverse:lib/prolog/tests/programs/reverse.sx:(pl-reverse-tests-run!)"
|
|
||||||
"member:lib/prolog/tests/programs/member.sx:(pl-member-tests-run!)"
|
|
||||||
"nqueens:lib/prolog/tests/programs/nqueens.sx:(pl-nqueens-tests-run!)"
|
|
||||||
"family:lib/prolog/tests/programs/family.sx:(pl-family-tests-run!)"
|
|
||||||
"atoms:lib/prolog/tests/atoms.sx:(pl-atom-tests-run!)"
|
|
||||||
"query_api:lib/prolog/tests/query_api.sx:(pl-query-api-tests-run!)"
|
|
||||||
"iso_predicates:lib/prolog/tests/iso_predicates.sx:(pl-iso-predicates-tests-run!)"
|
|
||||||
"meta_predicates:lib/prolog/tests/meta_predicates.sx:(pl-meta-predicates-tests-run!)"
|
|
||||||
"list_predicates:lib/prolog/tests/list_predicates.sx:(pl-list-predicates-tests-run!)"
|
|
||||||
"meta_call:lib/prolog/tests/meta_call.sx:(pl-meta-call-tests-run!)"
|
|
||||||
"set_predicates:lib/prolog/tests/set_predicates.sx:(pl-set-predicates-tests-run!)"
|
|
||||||
"char_predicates:lib/prolog/tests/char_predicates.sx:(pl-char-predicates-tests-run!)"
|
|
||||||
"io_predicates:lib/prolog/tests/io_predicates.sx:(pl-io-predicates-tests-run!)"
|
|
||||||
"assert_rules:lib/prolog/tests/assert_rules.sx:(pl-assert-rules-tests-run!)"
|
|
||||||
"string_agg:lib/prolog/tests/string_agg.sx:(pl-string-agg-tests-run!)"
|
|
||||||
"advanced:lib/prolog/tests/advanced.sx:(pl-advanced-tests-run!)"
|
|
||||||
"compiler:lib/prolog/tests/compiler.sx:(pl-compiler-tests-run!)"
|
|
||||||
"cross_validate:lib/prolog/tests/cross_validate.sx:(pl-cross-validate-tests-run!)"
|
|
||||||
"integration:lib/prolog/tests/integration.sx:(pl-integration-tests-run!)"
|
|
||||||
"hs_bridge:lib/prolog/tests/hs_bridge.sx:(pl-hs-bridge-tests-run!)"
|
|
||||||
)
|
|
||||||
|
|
||||||
emit_scoreboard_json() {
|
|
||||||
local n=${#GC_NAMES[@]} i sep
|
|
||||||
printf '{\n'
|
|
||||||
printf ' "total_passed": %d,\n' "$GC_TOTAL_PASS"
|
|
||||||
printf ' "total_failed": %d,\n' "$GC_TOTAL_FAIL"
|
|
||||||
printf ' "total": %d,\n' "$GC_TOTAL"
|
|
||||||
printf ' "suites": {'
|
|
||||||
for ((i=0; i<n; i++)); do
|
|
||||||
sep=","; [ $i -eq $((n-1)) ] && sep=""
|
|
||||||
printf '"%s":{"passed":%d,"total":%d,"failed":%d}%s' \
|
|
||||||
"${GC_NAMES[$i]}" "${GC_PASS[$i]}" "${GC_TOTAL_S[$i]}" "${GC_FAIL[$i]}" "$sep"
|
|
||||||
done
|
|
||||||
printf '},\n'
|
|
||||||
printf ' "generated": "%s"\n' "$(date -Iseconds 2>/dev/null || date)"
|
|
||||||
printf '}\n'
|
|
||||||
}
|
|
||||||
|
|
||||||
emit_scoreboard_md() {
|
|
||||||
local n=${#GC_NAMES[@]} i status when
|
|
||||||
when="$(date -Iseconds 2>/dev/null || date)"
|
|
||||||
printf '# Prolog scoreboard\n\n'
|
|
||||||
printf '**%d / %d passing** (%d failure(s)).\n' \
|
|
||||||
"$GC_TOTAL_PASS" "$GC_TOTAL" "$GC_TOTAL_FAIL"
|
|
||||||
printf 'Generated %s.\n\n' "$when"
|
|
||||||
printf '| Suite | Passed | Total | Status |\n'
|
|
||||||
printf '|-------|--------|-------|--------|\n'
|
|
||||||
for ((i=0; i<n; i++)); do
|
|
||||||
status="ok"; [ "${GC_FAIL[$i]}" -gt 0 ] && status="FAIL"
|
|
||||||
printf '| %s | %d | %d | %s |\n' \
|
|
||||||
"${GC_NAMES[$i]}" "${GC_PASS[$i]}" "${GC_TOTAL_S[$i]}" "$status"
|
|
||||||
done
|
|
||||||
printf '\nRun `bash lib/prolog/conformance.sh` to refresh. Override the binary\n'
|
|
||||||
printf 'with `SX_SERVER=path/to/sx_server.exe bash …`.\n'
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,129 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Thin wrapper — see lib/guest/conformance.sh and lib/prolog/conformance.conf.
|
# Run every Prolog test suite via sx_server and refresh scoreboard.{json,md}.
|
||||||
exec bash "$(dirname "$0")/../guest/conformance.sh" "$(dirname "$0")/conformance.conf" "$@"
|
# Exit 0 if all green, 1 if any failures.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
ROOT="$(cd "$HERE/../.." && pwd)"
|
||||||
|
SX="${SX_SERVER:-/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe}"
|
||||||
|
|
||||||
|
if [[ ! -x "$SX" ]]; then
|
||||||
|
echo "sx_server not found at $SX (set SX_SERVER env to override)" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$ROOT"
|
||||||
|
|
||||||
|
# name : test-file : runner-fn
|
||||||
|
SUITES=(
|
||||||
|
"parse:lib/prolog/tests/parse.sx:pl-parse-tests-run!"
|
||||||
|
"unify:lib/prolog/tests/unify.sx:pl-unify-tests-run!"
|
||||||
|
"clausedb:lib/prolog/tests/clausedb.sx:pl-clausedb-tests-run!"
|
||||||
|
"solve:lib/prolog/tests/solve.sx:pl-solve-tests-run!"
|
||||||
|
"operators:lib/prolog/tests/operators.sx:pl-operators-tests-run!"
|
||||||
|
"dynamic:lib/prolog/tests/dynamic.sx:pl-dynamic-tests-run!"
|
||||||
|
"findall:lib/prolog/tests/findall.sx:pl-findall-tests-run!"
|
||||||
|
"term_inspect:lib/prolog/tests/term_inspect.sx:pl-term-inspect-tests-run!"
|
||||||
|
"append:lib/prolog/tests/programs/append.sx:pl-append-tests-run!"
|
||||||
|
"reverse:lib/prolog/tests/programs/reverse.sx:pl-reverse-tests-run!"
|
||||||
|
"member:lib/prolog/tests/programs/member.sx:pl-member-tests-run!"
|
||||||
|
"nqueens:lib/prolog/tests/programs/nqueens.sx:pl-nqueens-tests-run!"
|
||||||
|
"family:lib/prolog/tests/programs/family.sx:pl-family-tests-run!"
|
||||||
|
"atoms:lib/prolog/tests/atoms.sx:pl-atom-tests-run!"
|
||||||
|
"query_api:lib/prolog/tests/query_api.sx:pl-query-api-tests-run!"
|
||||||
|
"iso_predicates:lib/prolog/tests/iso_predicates.sx:pl-iso-predicates-tests-run!"
|
||||||
|
"meta_predicates:lib/prolog/tests/meta_predicates.sx:pl-meta-predicates-tests-run!"
|
||||||
|
"list_predicates:lib/prolog/tests/list_predicates.sx:pl-list-predicates-tests-run!"
|
||||||
|
"meta_call:lib/prolog/tests/meta_call.sx:pl-meta-call-tests-run!"
|
||||||
|
"set_predicates:lib/prolog/tests/set_predicates.sx:pl-set-predicates-tests-run!"
|
||||||
|
"char_predicates:lib/prolog/tests/char_predicates.sx:pl-char-predicates-tests-run!"
|
||||||
|
"io_predicates:lib/prolog/tests/io_predicates.sx:pl-io-predicates-tests-run!"
|
||||||
|
"assert_rules:lib/prolog/tests/assert_rules.sx:pl-assert-rules-tests-run!"
|
||||||
|
"string_agg:lib/prolog/tests/string_agg.sx:pl-string-agg-tests-run!"
|
||||||
|
"advanced:lib/prolog/tests/advanced.sx:pl-advanced-tests-run!"
|
||||||
|
"compiler:lib/prolog/tests/compiler.sx:pl-compiler-tests-run!"
|
||||||
|
"cross_validate:lib/prolog/tests/cross_validate.sx:pl-cross-validate-tests-run!"
|
||||||
|
"integration:lib/prolog/tests/integration.sx:pl-integration-tests-run!"
|
||||||
|
"hs_bridge:lib/prolog/tests/hs_bridge.sx:pl-hs-bridge-tests-run!"
|
||||||
|
)
|
||||||
|
|
||||||
|
SCRIPT='(epoch 1)
|
||||||
|
(load "lib/prolog/tokenizer.sx")
|
||||||
|
(load "lib/prolog/parser.sx")
|
||||||
|
(load "lib/prolog/runtime.sx")
|
||||||
|
(load "lib/prolog/query.sx")
|
||||||
|
(load "lib/prolog/compiler.sx")
|
||||||
|
(load "lib/prolog/hs-bridge.sx")'
|
||||||
|
for entry in "${SUITES[@]}"; do
|
||||||
|
IFS=: read -r _ file _ <<< "$entry"
|
||||||
|
SCRIPT+=$'\n(load "'"$file"$'")'
|
||||||
|
done
|
||||||
|
for entry in "${SUITES[@]}"; do
|
||||||
|
IFS=: read -r _ _ fn <<< "$entry"
|
||||||
|
SCRIPT+=$'\n(eval "('"$fn"$')")'
|
||||||
|
done
|
||||||
|
|
||||||
|
OUTPUT="$(printf '%s\n' "$SCRIPT" | "$SX" 2>&1)"
|
||||||
|
|
||||||
|
mapfile -t LINES < <(printf '%s\n' "$OUTPUT" | grep -E '^\{:failed')
|
||||||
|
|
||||||
|
if [[ ${#LINES[@]} -ne ${#SUITES[@]} ]]; then
|
||||||
|
echo "Expected ${#SUITES[@]} suite results, got ${#LINES[@]}" >&2
|
||||||
|
echo "---- raw output ----" >&2
|
||||||
|
printf '%s\n' "$OUTPUT" >&2
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
|
||||||
|
TOTAL_PASS=0
|
||||||
|
TOTAL_FAIL=0
|
||||||
|
TOTAL=0
|
||||||
|
JSON_SUITES=""
|
||||||
|
MD_ROWS=""
|
||||||
|
|
||||||
|
for i in "${!SUITES[@]}"; do
|
||||||
|
IFS=: read -r name _ _ <<< "${SUITES[$i]}"
|
||||||
|
line="${LINES[$i]}"
|
||||||
|
passed=$(grep -oE ':passed [0-9]+' <<< "$line" | grep -oE '[0-9]+')
|
||||||
|
total=$(grep -oE ':total [0-9]+' <<< "$line" | grep -oE '[0-9]+')
|
||||||
|
failed=$(grep -oE ':failed [0-9]+' <<< "$line" | grep -oE '[0-9]+')
|
||||||
|
TOTAL_PASS=$((TOTAL_PASS + passed))
|
||||||
|
TOTAL_FAIL=$((TOTAL_FAIL + failed))
|
||||||
|
TOTAL=$((TOTAL + total))
|
||||||
|
status="ok"
|
||||||
|
[[ "$failed" -gt 0 ]] && status="FAIL"
|
||||||
|
[[ -n "$JSON_SUITES" ]] && JSON_SUITES+=","
|
||||||
|
JSON_SUITES+="\"$name\":{\"passed\":$passed,\"total\":$total,\"failed\":$failed}"
|
||||||
|
MD_ROWS+="| $name | $passed | $total | $status |"$'\n'
|
||||||
|
done
|
||||||
|
|
||||||
|
WHEN="$(date -Iseconds 2>/dev/null || date)"
|
||||||
|
|
||||||
|
cat > "$HERE/scoreboard.json" <<JSON
|
||||||
|
{
|
||||||
|
"total_passed": $TOTAL_PASS,
|
||||||
|
"total_failed": $TOTAL_FAIL,
|
||||||
|
"total": $TOTAL,
|
||||||
|
"suites": {$JSON_SUITES},
|
||||||
|
"generated": "$WHEN"
|
||||||
|
}
|
||||||
|
JSON
|
||||||
|
|
||||||
|
cat > "$HERE/scoreboard.md" <<MD
|
||||||
|
# Prolog scoreboard
|
||||||
|
|
||||||
|
**$TOTAL_PASS / $TOTAL passing** ($TOTAL_FAIL failure(s)).
|
||||||
|
Generated $WHEN.
|
||||||
|
|
||||||
|
| Suite | Passed | Total | Status |
|
||||||
|
|-------|--------|-------|--------|
|
||||||
|
$MD_ROWS
|
||||||
|
Run \`bash lib/prolog/conformance.sh\` to refresh. Override the binary
|
||||||
|
with \`SX_SERVER=path/to/sx_server.exe bash …\`.
|
||||||
|
MD
|
||||||
|
|
||||||
|
if [[ "$TOTAL_FAIL" -gt 0 ]]; then
|
||||||
|
echo "$TOTAL_FAIL failure(s) across $TOTAL tests" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "All $TOTAL tests pass."
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
;;
|
;;
|
||||||
;; 1. Hook style — for `prolog(db, "goal(args)")` call syntax in Hyperscript:
|
;; 1. Hook style — for `prolog(db, "goal(args)")` call syntax in Hyperscript:
|
||||||
;; (pl-install-hs-hook!) ;; call once at startup
|
;; (pl-install-hs-hook!) ;; call once at startup
|
||||||
;; Requires lib/hyperscript/plugins/prolog.sx (provides hs-set-prolog-hook!)
|
;; Requires lib/hyperscript/runtime.sx (provides hs-set-prolog-hook!)
|
||||||
;;
|
;;
|
||||||
;; 2. Factory style — for named conditions like `when allowed(user, action)`:
|
;; 2. Factory style — for named conditions like `when allowed(user, action)`:
|
||||||
;; (define allowed (pl-hs-predicate/2 pl-db "allowed"))
|
;; (define allowed (pl-hs-predicate/2 pl-db "allowed"))
|
||||||
|
|||||||
@@ -104,9 +104,18 @@
|
|||||||
(list ":-" 1200 "xfx")
|
(list ":-" 1200 "xfx")
|
||||||
(list "mod" 400 "yfx")))
|
(list "mod" 400 "yfx")))
|
||||||
|
|
||||||
(define pl-op-lookup (fn (name) (pratt-op-lookup pl-op-table name)))
|
(define
|
||||||
|
pl-op-find
|
||||||
|
(fn
|
||||||
|
(name table)
|
||||||
|
(cond
|
||||||
|
((empty? table) nil)
|
||||||
|
((= (first (first table)) name) (rest (first table)))
|
||||||
|
(true (pl-op-find name (rest table))))))
|
||||||
|
|
||||||
;; Token → entry (name prec type) for known infix ops, else nil.
|
(define pl-op-lookup (fn (name) (pl-op-find name pl-op-table)))
|
||||||
|
|
||||||
|
;; Token → (name prec type) for known infix ops, else nil.
|
||||||
(define
|
(define
|
||||||
pl-token-op
|
pl-token-op
|
||||||
(fn
|
(fn
|
||||||
@@ -114,8 +123,14 @@
|
|||||||
(let
|
(let
|
||||||
((ty (get t :type)) (vv (get t :value)))
|
((ty (get t :type)) (vv (get t :value)))
|
||||||
(cond
|
(cond
|
||||||
((and (= ty "punct") (= vv ",")) (pl-op-lookup ","))
|
((and (= ty "punct") (= vv ","))
|
||||||
((or (= ty "atom") (= ty "op")) (pl-op-lookup vv))
|
(let
|
||||||
|
((info (pl-op-lookup ",")))
|
||||||
|
(if (nil? info) nil (cons "," info))))
|
||||||
|
((or (= ty "atom") (= ty "op"))
|
||||||
|
(let
|
||||||
|
((info (pl-op-lookup vv)))
|
||||||
|
(if (nil? info) nil (cons vv info))))
|
||||||
(true nil)))))
|
(true nil)))))
|
||||||
|
|
||||||
;; ── Term parser ─────────────────────────────────────────────────────
|
;; ── Term parser ─────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -3,5 +3,5 @@
|
|||||||
"total_failed": 0,
|
"total_failed": 0,
|
||||||
"total": 590,
|
"total": 590,
|
||||||
"suites": {"parse":{"passed":25,"total":25,"failed":0},"unify":{"passed":47,"total":47,"failed":0},"clausedb":{"passed":14,"total":14,"failed":0},"solve":{"passed":62,"total":62,"failed":0},"operators":{"passed":19,"total":19,"failed":0},"dynamic":{"passed":11,"total":11,"failed":0},"findall":{"passed":11,"total":11,"failed":0},"term_inspect":{"passed":14,"total":14,"failed":0},"append":{"passed":6,"total":6,"failed":0},"reverse":{"passed":6,"total":6,"failed":0},"member":{"passed":7,"total":7,"failed":0},"nqueens":{"passed":6,"total":6,"failed":0},"family":{"passed":10,"total":10,"failed":0},"atoms":{"passed":34,"total":34,"failed":0},"query_api":{"passed":16,"total":16,"failed":0},"iso_predicates":{"passed":29,"total":29,"failed":0},"meta_predicates":{"passed":25,"total":25,"failed":0},"list_predicates":{"passed":33,"total":33,"failed":0},"meta_call":{"passed":15,"total":15,"failed":0},"set_predicates":{"passed":15,"total":15,"failed":0},"char_predicates":{"passed":27,"total":27,"failed":0},"io_predicates":{"passed":24,"total":24,"failed":0},"assert_rules":{"passed":15,"total":15,"failed":0},"string_agg":{"passed":25,"total":25,"failed":0},"advanced":{"passed":21,"total":21,"failed":0},"compiler":{"passed":17,"total":17,"failed":0},"cross_validate":{"passed":17,"total":17,"failed":0},"integration":{"passed":20,"total":20,"failed":0},"hs_bridge":{"passed":19,"total":19,"failed":0}},
|
"suites": {"parse":{"passed":25,"total":25,"failed":0},"unify":{"passed":47,"total":47,"failed":0},"clausedb":{"passed":14,"total":14,"failed":0},"solve":{"passed":62,"total":62,"failed":0},"operators":{"passed":19,"total":19,"failed":0},"dynamic":{"passed":11,"total":11,"failed":0},"findall":{"passed":11,"total":11,"failed":0},"term_inspect":{"passed":14,"total":14,"failed":0},"append":{"passed":6,"total":6,"failed":0},"reverse":{"passed":6,"total":6,"failed":0},"member":{"passed":7,"total":7,"failed":0},"nqueens":{"passed":6,"total":6,"failed":0},"family":{"passed":10,"total":10,"failed":0},"atoms":{"passed":34,"total":34,"failed":0},"query_api":{"passed":16,"total":16,"failed":0},"iso_predicates":{"passed":29,"total":29,"failed":0},"meta_predicates":{"passed":25,"total":25,"failed":0},"list_predicates":{"passed":33,"total":33,"failed":0},"meta_call":{"passed":15,"total":15,"failed":0},"set_predicates":{"passed":15,"total":15,"failed":0},"char_predicates":{"passed":27,"total":27,"failed":0},"io_predicates":{"passed":24,"total":24,"failed":0},"assert_rules":{"passed":15,"total":15,"failed":0},"string_agg":{"passed":25,"total":25,"failed":0},"advanced":{"passed":21,"total":21,"failed":0},"compiler":{"passed":17,"total":17,"failed":0},"cross_validate":{"passed":17,"total":17,"failed":0},"integration":{"passed":20,"total":20,"failed":0},"hs_bridge":{"passed":19,"total":19,"failed":0}},
|
||||||
"generated": "2026-05-07T17:35:23+00:00"
|
"generated": "2026-05-06T12:17:46+00:00"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Prolog scoreboard
|
# Prolog scoreboard
|
||||||
|
|
||||||
**590 / 590 passing** (0 failure(s)).
|
**590 / 590 passing** (0 failure(s)).
|
||||||
Generated 2026-05-07T17:35:23+00:00.
|
Generated 2026-05-06T12:17:46+00:00.
|
||||||
|
|
||||||
| Suite | Passed | Total | Status |
|
| Suite | Passed | Total | Status |
|
||||||
|-------|--------|-------|--------|
|
|-------|--------|-------|--------|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"date": "2026-05-06T21:06:00Z",
|
"date": "2026-04-25T16:05:32Z",
|
||||||
"programs": [
|
"programs": [
|
||||||
"eight-queens.st",
|
"eight-queens.st",
|
||||||
"fibonacci.st",
|
"fibonacci.st",
|
||||||
@@ -8,8 +8,8 @@
|
|||||||
"quicksort.st"
|
"quicksort.st"
|
||||||
],
|
],
|
||||||
"program_count": 5,
|
"program_count": 5,
|
||||||
"program_tests_passed": 4,
|
"program_tests_passed": 39,
|
||||||
"all_tests_passed": 625,
|
"all_tests_passed": 847,
|
||||||
"all_tests_total": 629,
|
"all_tests_total": 847,
|
||||||
"exit_code": 1
|
"exit_code": 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
# Smalltalk-on-SX Scoreboard
|
# Smalltalk-on-SX Scoreboard
|
||||||
|
|
||||||
_Last run: 2026-05-06T21:06:00Z_
|
_Last run: 2026-04-25T16:05:32Z_
|
||||||
|
|
||||||
## Totals
|
## Totals
|
||||||
|
|
||||||
| Suite | Passing |
|
| Suite | Passing |
|
||||||
|-------|---------|
|
|-------|---------|
|
||||||
| All Smalltalk-on-SX tests | **625 / 629** |
|
| All Smalltalk-on-SX tests | **847 / 847** |
|
||||||
| Classic-corpus tests (`tests/programs.sx`) | **4** |
|
| Classic-corpus tests (`tests/programs.sx`) | **39** |
|
||||||
|
|
||||||
## Classic-corpus programs (`lib/smalltalk/tests/programs/`)
|
## Classic-corpus programs (`lib/smalltalk/tests/programs/`)
|
||||||
|
|
||||||
@@ -22,6 +22,7 @@ _Last run: 2026-05-06T21:06:00Z_
|
|||||||
## Per-file test counts
|
## Per-file test counts
|
||||||
|
|
||||||
```
|
```
|
||||||
|
OK lib/smalltalk/tests/ansi.sx 62 passed
|
||||||
OK lib/smalltalk/tests/blocks.sx 19 passed
|
OK lib/smalltalk/tests/blocks.sx 19 passed
|
||||||
OK lib/smalltalk/tests/cannot_return.sx 5 passed
|
OK lib/smalltalk/tests/cannot_return.sx 5 passed
|
||||||
OK lib/smalltalk/tests/collections.sx 29 passed
|
OK lib/smalltalk/tests/collections.sx 29 passed
|
||||||
@@ -29,13 +30,16 @@ OK lib/smalltalk/tests/conditional.sx 25 passed
|
|||||||
OK lib/smalltalk/tests/dnu.sx 15 passed
|
OK lib/smalltalk/tests/dnu.sx 15 passed
|
||||||
OK lib/smalltalk/tests/eval.sx 68 passed
|
OK lib/smalltalk/tests/eval.sx 68 passed
|
||||||
OK lib/smalltalk/tests/exceptions.sx 15 passed
|
OK lib/smalltalk/tests/exceptions.sx 15 passed
|
||||||
|
OK lib/smalltalk/tests/hashed.sx 30 passed
|
||||||
OK lib/smalltalk/tests/inline_cache.sx 10 passed
|
OK lib/smalltalk/tests/inline_cache.sx 10 passed
|
||||||
OK lib/smalltalk/tests/intrinsics.sx 24 passed
|
OK lib/smalltalk/tests/intrinsics.sx 24 passed
|
||||||
OK lib/smalltalk/tests/nlr.sx 14 passed
|
OK lib/smalltalk/tests/nlr.sx 14 passed
|
||||||
OK lib/smalltalk/tests/numbers.sx 47 passed
|
OK lib/smalltalk/tests/numbers.sx 47 passed
|
||||||
OK lib/smalltalk/tests/parse_chunks.sx 21 passed
|
OK lib/smalltalk/tests/parse_chunks.sx 21 passed
|
||||||
OK lib/smalltalk/tests/parse.sx 47 passed
|
OK lib/smalltalk/tests/parse.sx 47 passed
|
||||||
|
OK lib/smalltalk/tests/pharo.sx 91 passed
|
||||||
OK lib/smalltalk/tests/printing.sx 19 passed
|
OK lib/smalltalk/tests/printing.sx 19 passed
|
||||||
|
OK lib/smalltalk/tests/programs.sx 39 passed
|
||||||
OK lib/smalltalk/tests/reflection.sx 77 passed
|
OK lib/smalltalk/tests/reflection.sx 77 passed
|
||||||
OK lib/smalltalk/tests/runtime.sx 64 passed
|
OK lib/smalltalk/tests/runtime.sx 64 passed
|
||||||
OK lib/smalltalk/tests/streams.sx 21 passed
|
OK lib/smalltalk/tests/streams.sx 21 passed
|
||||||
@@ -43,10 +47,6 @@ OK lib/smalltalk/tests/sunit.sx 19 passed
|
|||||||
OK lib/smalltalk/tests/super.sx 9 passed
|
OK lib/smalltalk/tests/super.sx 9 passed
|
||||||
OK lib/smalltalk/tests/tokenize.sx 63 passed
|
OK lib/smalltalk/tests/tokenize.sx 63 passed
|
||||||
OK lib/smalltalk/tests/while.sx 14 passed
|
OK lib/smalltalk/tests/while.sx 14 passed
|
||||||
X lib/smalltalk/tests/ansi.sx: could not extract summary
|
|
||||||
X lib/smalltalk/tests/hashed.sx: could not extract summary
|
|
||||||
X lib/smalltalk/tests/pharo.sx: could not extract summary
|
|
||||||
X lib/smalltalk/tests/programs.sx: could not extract summary
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|||||||
@@ -63,8 +63,6 @@ for tcl_file in "${TCL_FILES[@]}"; do
|
|||||||
# Build epoch input using quoted heredoc for static parts; helper path via variable
|
# Build epoch input using quoted heredoc for static parts; helper path via variable
|
||||||
cat > "$tmpfile" << EPOCHS
|
cat > "$tmpfile" << EPOCHS
|
||||||
(epoch 1)
|
(epoch 1)
|
||||||
(load "lib/guest/lex.sx")
|
|
||||||
(load "lib/guest/prefix.sx")
|
|
||||||
(load "lib/tcl/tokenizer.sx")
|
(load "lib/tcl/tokenizer.sx")
|
||||||
(epoch 2)
|
(epoch 2)
|
||||||
(load "lib/tcl/parser.sx")
|
(load "lib/tcl/parser.sx")
|
||||||
|
|||||||
2569
lib/tcl/runtime.sx
2569
lib/tcl/runtime.sx
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,9 @@
|
|||||||
{
|
{
|
||||||
"total": 4,
|
"total": 3,
|
||||||
"passed": 3,
|
"passed": 3,
|
||||||
"failed": 1,
|
"failed": 0,
|
||||||
"programs": {
|
"programs": {
|
||||||
"assert": {"status": "PASS", "expected": "10", "got": "10"},
|
"assert": {"status": "PASS", "expected": "10", "got": "10"},
|
||||||
"event-loop": {"status": "FAIL", "expected": "done", "got": ""},
|
|
||||||
"for-each-line": {"status": "PASS", "expected": "13", "got": "13"},
|
"for-each-line": {"status": "PASS", "expected": "13", "got": "13"},
|
||||||
"with-temp-var": {"status": "PASS", "expected": "100 999", "got": "100 999"}
|
"with-temp-var": {"status": "PASS", "expected": "100 999", "got": "100 999"}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,7 @@
|
|||||||
| Program | Status | Expected | Got |
|
| Program | Status | Expected | Got |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| assert | ✓ PASS | 10 | 10 |
|
| assert | ✓ PASS | 10 | 10 |
|
||||||
| event-loop | ✗ FAIL | done | |
|
|
||||||
| for-each-line | ✓ PASS | 13 | 13 |
|
| for-each-line | ✓ PASS | 13 | 13 |
|
||||||
| with-temp-var | ✓ PASS | 100 999 | 100 999 |
|
| with-temp-var | ✓ PASS | 100 999 | 100 999 |
|
||||||
|
|
||||||
**3/4 passing**
|
**3/3 passing**
|
||||||
|
|||||||
@@ -33,15 +33,12 @@ HELPER_EOF
|
|||||||
|
|
||||||
cat > "$TMPFILE" << EPOCHS
|
cat > "$TMPFILE" << EPOCHS
|
||||||
(epoch 1)
|
(epoch 1)
|
||||||
(load "lib/guest/lex.sx")
|
|
||||||
(load "lib/guest/prefix.sx")
|
|
||||||
(load "lib/tcl/tokenizer.sx")
|
(load "lib/tcl/tokenizer.sx")
|
||||||
(epoch 2)
|
(epoch 2)
|
||||||
(load "lib/tcl/parser.sx")
|
(load "lib/tcl/parser.sx")
|
||||||
(epoch 3)
|
(epoch 3)
|
||||||
(load "lib/tcl/tests/parse.sx")
|
(load "lib/tcl/tests/parse.sx")
|
||||||
(epoch 4)
|
(epoch 4)
|
||||||
(load "lib/fiber.sx")
|
|
||||||
(load "lib/tcl/runtime.sx")
|
(load "lib/tcl/runtime.sx")
|
||||||
(epoch 5)
|
(epoch 5)
|
||||||
(load "lib/tcl/tests/eval.sx")
|
(load "lib/tcl/tests/eval.sx")
|
||||||
@@ -59,7 +56,7 @@ cat > "$TMPFILE" << EPOCHS
|
|||||||
(eval "tcl-test-summary")
|
(eval "tcl-test-summary")
|
||||||
EPOCHS
|
EPOCHS
|
||||||
|
|
||||||
OUTPUT=$(timeout 2400 "$SX_SERVER" < "$TMPFILE" 2>&1)
|
OUTPUT=$(timeout 180 "$SX_SERVER" < "$TMPFILE" 2>&1)
|
||||||
[ "$VERBOSE" = "-v" ] && echo "$OUTPUT"
|
[ "$VERBOSE" = "-v" ] && echo "$OUTPUT"
|
||||||
|
|
||||||
# Extract summary line from epoch 11 output
|
# Extract summary line from epoch 11 output
|
||||||
|
|||||||
@@ -95,15 +95,15 @@
|
|||||||
(get (run "proc g {} { yield }\ncoroutine cg g\ncg") :result)
|
(get (run "proc g {} { yield }\ncoroutine cg g\ncg") :result)
|
||||||
"")
|
"")
|
||||||
|
|
||||||
; --- clock seconds ---
|
; --- clock seconds stub ---
|
||||||
(ok "clock-seconds"
|
(ok "clock-seconds"
|
||||||
(> (parse-int (get (run "clock seconds") :result)) 0)
|
(get (run "clock seconds") :result)
|
||||||
true)
|
"0")
|
||||||
|
|
||||||
; --- clock milliseconds ---
|
; --- clock milliseconds stub ---
|
||||||
(ok "clock-milliseconds"
|
(ok "clock-milliseconds"
|
||||||
(> (parse-int (get (run "clock milliseconds") :result)) 0)
|
(get (run "clock milliseconds") :result)
|
||||||
true)
|
"0")
|
||||||
|
|
||||||
; --- clock format stub ---
|
; --- clock format stub ---
|
||||||
(ok "clock-format"
|
(ok "clock-format"
|
||||||
@@ -124,7 +124,7 @@
|
|||||||
"file0")
|
"file0")
|
||||||
|
|
||||||
(ok "eof-returns-1"
|
(ok "eof-returns-1"
|
||||||
(get (run "set ch [open /dev/null r]\nread $ch\neof $ch") :result)
|
(get (run "set ch [open /dev/null r]\neof $ch") :result)
|
||||||
"1")
|
"1")
|
||||||
|
|
||||||
(dict
|
(dict
|
||||||
|
|||||||
@@ -329,54 +329,6 @@
|
|||||||
(run "proc with-temp-var {varname tempval body} {\n upvar 1 $varname v\n set saved $v\n set v $tempval\n uplevel 1 $body\n set v $saved\n}\nset x 100\nwith-temp-var x 999 {\n set captured $x\n}\nlist $x $captured")
|
(run "proc with-temp-var {varname tempval body} {\n upvar 1 $varname v\n set saved $v\n set v $tempval\n uplevel 1 $body\n set v $saved\n}\nset x 100\nwith-temp-var x 999 {\n set captured $x\n}\nlist $x $captured")
|
||||||
:result)
|
:result)
|
||||||
"100 999")
|
"100 999")
|
||||||
(ok
|
|
||||||
"array-set-get"
|
|
||||||
(get
|
|
||||||
(run "array set a {x 1 y 2 z 3}; array get a x")
|
|
||||||
:result)
|
|
||||||
"x 1")
|
|
||||||
(ok
|
|
||||||
"array-names"
|
|
||||||
(get
|
|
||||||
(run "array set a {p 10 q 20}; lsort [array names a]")
|
|
||||||
:result)
|
|
||||||
"p q")
|
|
||||||
(ok
|
|
||||||
"array-size"
|
|
||||||
(get
|
|
||||||
(run "array set a {x 1 y 2 z 3}; array size a")
|
|
||||||
:result)
|
|
||||||
"3")
|
|
||||||
(ok
|
|
||||||
"array-exists-true"
|
|
||||||
(get
|
|
||||||
(run "array set a {x 1}; array exists a")
|
|
||||||
:result)
|
|
||||||
"1")
|
|
||||||
(ok
|
|
||||||
"array-exists-false"
|
|
||||||
(get
|
|
||||||
(run "array exists nosucharray")
|
|
||||||
:result)
|
|
||||||
"0")
|
|
||||||
(ok
|
|
||||||
"array-unset-key"
|
|
||||||
(get
|
|
||||||
(run "array set a {x 1 y 2 z 3}; array unset a y; lsort [array names a]")
|
|
||||||
:result)
|
|
||||||
"x z")
|
|
||||||
(ok
|
|
||||||
"array-scalar-access"
|
|
||||||
(get
|
|
||||||
(run "set a(foo) hello; set a(bar) world; set a(foo)")
|
|
||||||
:result)
|
|
||||||
"hello")
|
|
||||||
(ok
|
|
||||||
"array-get-all"
|
|
||||||
(get
|
|
||||||
(run "set a(k) v; set pairs [array get a]; llength $pairs")
|
|
||||||
:result)
|
|
||||||
"2")
|
|
||||||
(dict
|
(dict
|
||||||
"passed"
|
"passed"
|
||||||
tcl-eval-pass
|
tcl-eval-pass
|
||||||
|
|||||||
@@ -29,391 +29,160 @@
|
|||||||
(define
|
(define
|
||||||
ok
|
ok
|
||||||
(fn (label actual expected) (tcl-idiom-assert label expected actual)))
|
(fn (label actual expected) (tcl-idiom-assert label expected actual)))
|
||||||
(ok
|
|
||||||
"idiom-lmap"
|
; 1. lmap idiom: accumulate mapped values with foreach+lappend
|
||||||
|
(ok "idiom-lmap"
|
||||||
(get
|
(get
|
||||||
(run
|
(run "set result {}\nforeach x {1 2 3} { lappend result [expr {$x * $x}] }\nset result")
|
||||||
"set result {}\nforeach x {1 2 3} { lappend result [expr {$x * $x}] }\nset result")
|
|
||||||
:result)
|
:result)
|
||||||
"1 4 9")
|
"1 4 9")
|
||||||
(ok
|
|
||||||
"idiom-flatten"
|
; 2. Recursive list flatten
|
||||||
|
(ok "idiom-flatten"
|
||||||
(get
|
(get
|
||||||
(run
|
(run
|
||||||
"proc flatten {lst} { set out {}\n foreach item $lst {\n if {[llength $item] > 1} {\n foreach sub [flatten $item] { lappend out $sub }\n } else {\n lappend out $item\n }\n }\n return $out\n}\nflatten {1 {2 3} {4 {5 6}}}")
|
"proc flatten {lst} { set out {}\n foreach item $lst {\n if {[llength $item] > 1} {\n foreach sub [flatten $item] { lappend out $sub }\n } else {\n lappend out $item\n }\n }\n return $out\n}\nflatten {1 {2 3} {4 {5 6}}}")
|
||||||
:result)
|
:result)
|
||||||
"1 2 3 4 5 6")
|
"1 2 3 4 5 6")
|
||||||
(ok
|
|
||||||
"idiom-string-builder"
|
; 3. String builder accumulator
|
||||||
|
(ok "idiom-string-builder"
|
||||||
(get
|
(get
|
||||||
(run
|
(run "set buf \"\"\nforeach w {Hello World Tcl} { append buf $w \" \" }\nstring trimright $buf")
|
||||||
"set buf \"\"\nforeach w {Hello World Tcl} { append buf $w \" \" }\nstring trimright $buf")
|
|
||||||
:result)
|
:result)
|
||||||
"Hello World Tcl")
|
"Hello World Tcl")
|
||||||
(ok
|
|
||||||
"idiom-default-param"
|
; 4. Default parameter via info exists
|
||||||
(get (run "if {![info exists x]} { set x 42 }\nset x") :result)
|
(ok "idiom-default-param"
|
||||||
|
(get
|
||||||
|
(run "if {![info exists x]} { set x 42 }\nset x")
|
||||||
|
:result)
|
||||||
"42")
|
"42")
|
||||||
(ok
|
|
||||||
"idiom-alist-lookup"
|
; 5. Association list lookup (parallel key/value lists)
|
||||||
|
(ok "idiom-alist-lookup"
|
||||||
(get
|
(get
|
||||||
(run
|
(run
|
||||||
"set keys {a b c}\nset vals {10 20 30}\nset idx [lsearch $keys b]\nlindex $vals $idx")
|
"set keys {a b c}\nset vals {10 20 30}\nset idx [lsearch $keys b]\nlindex $vals $idx")
|
||||||
:result)
|
:result)
|
||||||
"20")
|
"20")
|
||||||
(ok
|
|
||||||
"idiom-optional-args"
|
; 6. Proc with optional args via args
|
||||||
|
(ok "idiom-optional-args"
|
||||||
(get
|
(get
|
||||||
(run
|
(run
|
||||||
"proc greet {name args} {\n set greeting \"Hello\"\n if {[llength $args] > 0} { set greeting [lindex $args 0] }\n return \"$greeting $name\"\n}\ngreet World Hi")
|
"proc greet {name args} {\n set greeting \"Hello\"\n if {[llength $args] > 0} { set greeting [lindex $args 0] }\n return \"$greeting $name\"\n}\ngreet World Hi")
|
||||||
:result)
|
:result)
|
||||||
"Hi World")
|
"Hi World")
|
||||||
(ok
|
|
||||||
"idiom-dict-builder"
|
; 7. Builder pattern: dict create from args
|
||||||
|
(ok "idiom-dict-builder"
|
||||||
(get
|
(get
|
||||||
(run
|
(run
|
||||||
"proc build-dict {args} { dict create {*}$args }\ndict get [build-dict name Alice age 30] name")
|
"proc build-dict {args} { dict create {*}$args }\ndict get [build-dict name Alice age 30] name")
|
||||||
:result)
|
:result)
|
||||||
"Alice")
|
"Alice")
|
||||||
(ok
|
|
||||||
"idiom-loop-with-index"
|
; 8. Loop with index using array
|
||||||
|
(ok "idiom-loop-with-index"
|
||||||
(get
|
(get
|
||||||
(run "set i 0\nforeach x {a b c} { set arr($i) $x; incr i }\nset arr(1)")
|
(run
|
||||||
|
"set i 0\nforeach x {a b c} { set arr($i) $x; incr i }\nset arr(1)")
|
||||||
:result)
|
:result)
|
||||||
"b")
|
"b")
|
||||||
(ok
|
|
||||||
"idiom-string-reverse"
|
; 9. String reverse via split+lreverse+join
|
||||||
|
(ok "idiom-string-reverse"
|
||||||
(get
|
(get
|
||||||
(run
|
(run
|
||||||
"set s hello\nset chars [split $s \"\"]\nset rev [lreverse $chars]\njoin $rev \"\"")
|
"set s hello\nset chars [split $s \"\"]\nset rev [lreverse $chars]\njoin $rev \"\"")
|
||||||
:result)
|
:result)
|
||||||
"olleh")
|
"olleh")
|
||||||
(ok "idiom-number-format" (get (run "format \"%05d\" 42") :result) "00042")
|
|
||||||
(ok
|
; 10. Number to padded string
|
||||||
"idiom-dict-comprehension"
|
(ok "idiom-number-format"
|
||||||
|
(get (run "format \"%05d\" 42") :result)
|
||||||
|
"00042")
|
||||||
|
|
||||||
|
; 11. Dict comprehension pattern
|
||||||
|
(ok "idiom-dict-comprehension"
|
||||||
(get
|
(get
|
||||||
(run
|
(run
|
||||||
"set squares {}\nforeach n {1 2 3 4} { dict set squares $n [expr {$n * $n}] }\ndict get $squares 3")
|
"set squares {}\nforeach n {1 2 3 4} { dict set squares $n [expr {$n * $n}] }\ndict get $squares 3")
|
||||||
:result)
|
:result)
|
||||||
"9")
|
"9")
|
||||||
(ok
|
|
||||||
"idiom-stack"
|
; 12. Stack ADT using list: push/pop
|
||||||
|
(ok "idiom-stack"
|
||||||
(get
|
(get
|
||||||
(run
|
(run
|
||||||
"proc stack-push {stackvar val} { upvar $stackvar s; lappend s $val }\nproc stack-pop {stackvar} { upvar $stackvar s; set val [lindex $s end]; set s [lrange $s 0 end-1]; return $val }\nset stk {}\nstack-push stk 10\nstack-push stk 20\nstack-push stk 30\nstack-pop stk")
|
"proc stack-push {stackvar val} { upvar $stackvar s; lappend s $val }\nproc stack-pop {stackvar} { upvar $stackvar s; set val [lindex $s end]; set s [lrange $s 0 end-1]; return $val }\nset stk {}\nstack-push stk 10\nstack-push stk 20\nstack-push stk 30\nstack-pop stk")
|
||||||
:result)
|
:result)
|
||||||
"30")
|
"30")
|
||||||
(ok
|
|
||||||
"idiom-queue"
|
; 13. Queue ADT using list: enqueue/dequeue
|
||||||
|
(ok "idiom-queue"
|
||||||
(get
|
(get
|
||||||
(run
|
(run
|
||||||
"proc q-enq {qvar val} { upvar $qvar q; lappend q $val }\nproc q-deq {qvar} { upvar $qvar q; set val [lindex $q 0]; set q [lrange $q 1 end]; return $val }\nset q {}\nq-enq q alpha\nq-enq q beta\nq-enq q gamma\nq-deq q")
|
"proc q-enq {qvar val} { upvar $qvar q; lappend q $val }\nproc q-deq {qvar} { upvar $qvar q; set val [lindex $q 0]; set q [lrange $q 1 end]; return $val }\nset q {}\nq-enq q alpha\nq-enq q beta\nq-enq q gamma\nq-deq q")
|
||||||
:result)
|
:result)
|
||||||
"alpha")
|
"alpha")
|
||||||
(ok
|
|
||||||
"idiom-pipeline"
|
; 14. Pipeline via proc chaining
|
||||||
|
(ok "idiom-pipeline"
|
||||||
(get
|
(get
|
||||||
(run
|
(run
|
||||||
"proc double {x} { expr {$x * 2} }\nproc add1 {x} { expr {$x + 1} }\nproc pipeline {val procs} { foreach p $procs { set val [$p $val] }; return $val }\npipeline 5 {double add1 double}")
|
"proc double {x} { expr {$x * 2} }\nproc add1 {x} { expr {$x + 1} }\nproc pipeline {val procs} { foreach p $procs { set val [$p $val] }; return $val }\npipeline 5 {double add1 double}")
|
||||||
:result)
|
:result)
|
||||||
"22")
|
"22")
|
||||||
(ok
|
|
||||||
"idiom-memoize"
|
; 15. Memoize pattern using dict (simple cache, not recursive)
|
||||||
|
(ok "idiom-memoize"
|
||||||
(get
|
(get
|
||||||
(run
|
(run
|
||||||
"set cache {}\nproc cached-square {n} { global cache\n if {[dict exists $cache $n]} { return [dict get $cache $n] }\n set r [expr {$n * $n}]\n dict set cache $n $r\n return $r\n}\nset a [cached-square 7]\nset b [cached-square 7]\nset c [cached-square 8]\nexpr {$a == $b && $c == 64}")
|
"set cache {}\nproc cached-square {n} { global cache\n if {[dict exists $cache $n]} { return [dict get $cache $n] }\n set r [expr {$n * $n}]\n dict set cache $n $r\n return $r\n}\nset a [cached-square 7]\nset b [cached-square 7]\nset c [cached-square 8]\nexpr {$a == $b && $c == 64}")
|
||||||
:result)
|
:result)
|
||||||
"1")
|
"1")
|
||||||
(ok
|
|
||||||
"idiom-recursive-eval"
|
; 16. Simple expression evaluator in Tcl (recursive descent)
|
||||||
|
(ok "idiom-recursive-eval"
|
||||||
(get
|
(get
|
||||||
(run
|
(run
|
||||||
"proc calc {expr} { return [::tcl::mathop::+ 0 [expr $expr]] }\nexpr {3 + 4 * 2}")
|
"proc calc {expr} { return [::tcl::mathop::+ 0 [expr $expr]] }\nexpr {3 + 4 * 2}")
|
||||||
:result)
|
:result)
|
||||||
"11")
|
"11")
|
||||||
(ok
|
|
||||||
"idiom-dict-for"
|
; 17. Apply proc to each pair in a dict
|
||||||
|
(ok "idiom-dict-for"
|
||||||
(get
|
(get
|
||||||
(run
|
(run
|
||||||
"set d [dict create a 1 b 2 c 3]\nset total 0\ndict for {k v} $d { incr total $v }\nset total")
|
"set d [dict create a 1 b 2 c 3]\nset total 0\ndict for {k v} $d { incr total $v }\nset total")
|
||||||
:result)
|
:result)
|
||||||
"6")
|
"6")
|
||||||
(ok
|
|
||||||
"idiom-find-max"
|
; 18. Find max in list
|
||||||
|
(ok "idiom-find-max"
|
||||||
(get
|
(get
|
||||||
(run
|
(run
|
||||||
"proc list-max {lst} {\n set m [lindex $lst 0]\n foreach x $lst { if {$x > $m} { set m $x } }\n return $m\n}\nlist-max {3 1 4 1 5 9 2 6}")
|
"proc list-max {lst} {\n set m [lindex $lst 0]\n foreach x $lst { if {$x > $m} { set m $x } }\n return $m\n}\nlist-max {3 1 4 1 5 9 2 6}")
|
||||||
:result)
|
:result)
|
||||||
"9")
|
"9")
|
||||||
(ok
|
|
||||||
"idiom-filter-list"
|
; 19. Filter list by predicate
|
||||||
|
(ok "idiom-filter-list"
|
||||||
(get
|
(get
|
||||||
(run
|
(run
|
||||||
"proc list-filter {lst pred} {\n set out {}\n foreach x $lst { if {[$pred $x]} { lappend out $x } }\n return $out\n}\nproc is-even {n} { expr {$n % 2 == 0} }\nlist-filter {1 2 3 4 5 6} is-even")
|
"proc list-filter {lst pred} {\n set out {}\n foreach x $lst { if {[$pred $x]} { lappend out $x } }\n return $out\n}\nproc is-even {n} { expr {$n % 2 == 0} }\nlist-filter {1 2 3 4 5 6} is-even")
|
||||||
:result)
|
:result)
|
||||||
"2 4 6")
|
"2 4 6")
|
||||||
(ok
|
|
||||||
"idiom-zip"
|
; 20. Zip two lists
|
||||||
|
(ok "idiom-zip"
|
||||||
(get
|
(get
|
||||||
(run
|
(run
|
||||||
"proc zip {a b} {\n set out {}\n set n [llength $a]\n for {set i 0} {$i < $n} {incr i} {\n lappend out [lindex $a $i]\n lappend out [lindex $b $i]\n }\n return $out\n}\nzip {1 2 3} {a b c}")
|
"proc zip {a b} {\n set out {}\n set n [llength $a]\n for {set i 0} {$i < $n} {incr i} {\n lappend out [lindex $a $i]\n lappend out [lindex $b $i]\n }\n return $out\n}\nzip {1 2 3} {a b c}")
|
||||||
:result)
|
:result)
|
||||||
"1 a 2 b 3 c")
|
"1 a 2 b 3 c")
|
||||||
(ok
|
|
||||||
"env-lookup-basic"
|
|
||||||
(env-lookup (let ((x 42)) (current-env)) "x")
|
|
||||||
42)
|
|
||||||
(ok
|
|
||||||
"env-lookup-missing"
|
|
||||||
(env-lookup (let ((x 42)) (current-env)) "z")
|
|
||||||
nil)
|
|
||||||
(ok
|
|
||||||
"env-extend-lookup"
|
|
||||||
(let
|
|
||||||
((e (let ((x 5)) (current-env))))
|
|
||||||
(env-lookup (env-extend e "y" 10) "y"))
|
|
||||||
10)
|
|
||||||
(ok
|
|
||||||
"eval-in-env-parent"
|
|
||||||
(let
|
|
||||||
((x 5))
|
|
||||||
(eval-in-env (env-extend (current-env) "y" 10) (quote (+ x y))))
|
|
||||||
15)
|
|
||||||
(ok
|
|
||||||
"eval-in-env-multi"
|
|
||||||
(let
|
|
||||||
((base (current-env)))
|
|
||||||
(eval-in-env
|
|
||||||
(env-extend (env-extend base "a" 3) "b" 7)
|
|
||||||
(quote (* a b))))
|
|
||||||
21)
|
|
||||||
|
|
||||||
; 26-32. Phase 5 channels: write/read/seek/tell/eof/append/non-blocking
|
|
||||||
(ok "channel-write-read"
|
|
||||||
(get
|
|
||||||
(run
|
|
||||||
"set f /tmp/tcl-phase5-1.txt\nset c [open $f w]\nputs $c \"line one\"\nputs $c \"line two\"\nclose $c\nset c [open $f r]\nset out [read $c]\nclose $c\nfile delete $f\nreturn $out")
|
|
||||||
:result)
|
|
||||||
"line one\nline two\n")
|
|
||||||
|
|
||||||
(ok "channel-gets-loop"
|
|
||||||
(get
|
|
||||||
(run
|
|
||||||
"set f /tmp/tcl-phase5-2.txt\nset c [open $f w]\nputs $c apple\nputs $c banana\nputs $c cherry\nclose $c\nset c [open $f r]\nset out {}\nwhile {[gets $c line] >= 0} {lappend out $line}\nclose $c\nfile delete $f\nreturn $out")
|
|
||||||
:result)
|
|
||||||
"apple banana cherry")
|
|
||||||
|
|
||||||
(ok "channel-seek-tell"
|
|
||||||
(get
|
|
||||||
(run
|
|
||||||
"set f /tmp/tcl-phase5-3.txt\nset c [open $f w]\nputs -nonewline $c \"hello world\"\nclose $c\nset c [open $f r]\nseek $c 6\nset pos [tell $c]\nset rest [read $c]\nclose $c\nfile delete $f\nreturn \"$pos:$rest\"")
|
|
||||||
:result)
|
|
||||||
"6:world")
|
|
||||||
|
|
||||||
(ok "channel-eof-after-read"
|
|
||||||
(get
|
|
||||||
(run
|
|
||||||
"set f /tmp/tcl-phase5-4.txt\nset c [open $f w]\nputs -nonewline $c hi\nclose $c\nset c [open $f r]\nread $c\nset e [eof $c]\nclose $c\nfile delete $f\nreturn $e")
|
|
||||||
:result)
|
|
||||||
"1")
|
|
||||||
|
|
||||||
(ok "channel-append-mode"
|
|
||||||
(get
|
|
||||||
(run
|
|
||||||
"set f /tmp/tcl-phase5-5.txt\nset c [open $f w]\nputs -nonewline $c \"first\"\nclose $c\nset c [open $f a]\nputs -nonewline $c \"-second\"\nclose $c\nset c [open $f r]\nset out [read $c]\nclose $c\nfile delete $f\nreturn $out")
|
|
||||||
:result)
|
|
||||||
"first-second")
|
|
||||||
|
|
||||||
(ok "channel-seek-end"
|
|
||||||
(get
|
|
||||||
(run
|
|
||||||
"set f /tmp/tcl-phase5-6.txt\nset c [open $f w]\nputs -nonewline $c \"abcdefghij\"\nclose $c\nset c [open $f r]\nseek $c 0 end\nset pos [tell $c]\nclose $c\nfile delete $f\nreturn $pos")
|
|
||||||
:result)
|
|
||||||
"10")
|
|
||||||
|
|
||||||
(ok "channel-fconfigure-blocking"
|
|
||||||
(get
|
|
||||||
(run
|
|
||||||
"set f /tmp/tcl-phase5-7.txt\nset c [open $f w]\nputs -nonewline $c x\nclose $c\nset c [open $f r]\nfconfigure $c -blocking 0\nset b [fconfigure $c -blocking]\nclose $c\nfile delete $f\nreturn $b")
|
|
||||||
:result)
|
|
||||||
"0")
|
|
||||||
|
|
||||||
; 33-37. Phase 5b event loop: after / vwait / fileevent / update
|
|
||||||
(ok "after-vwait-timer"
|
|
||||||
(get
|
|
||||||
(run
|
|
||||||
"after 30 {set ::done fired}\nvwait ::done\nset ::done")
|
|
||||||
:result)
|
|
||||||
"fired")
|
|
||||||
|
|
||||||
(ok "after-multiple-timers-update"
|
|
||||||
(get
|
|
||||||
(run
|
|
||||||
"set ::n 0\nafter 0 {incr ::n}\nafter 0 {incr ::n}\nafter 0 {incr ::n}\nupdate\nset ::n")
|
|
||||||
:result)
|
|
||||||
"3")
|
|
||||||
|
|
||||||
(ok "fileevent-readable-fires"
|
|
||||||
(get
|
|
||||||
(run
|
|
||||||
"set f /tmp/tcl-phase5b-1.txt\nset c [open $f w]\nputs -nonewline $c hi\nclose $c\nset c [open $f r]\nfileevent $c readable {set ::ready 1; fileevent $::ch readable {}}\nset ::ch $c\nvwait ::ready\nclose $c\nfile delete $f\nset ::ready")
|
|
||||||
:result)
|
|
||||||
"1")
|
|
||||||
|
|
||||||
(ok "fileevent-query-script"
|
|
||||||
(get
|
|
||||||
(run
|
|
||||||
"set f /tmp/tcl-phase5b-2.txt\nset c [open $f w]\nputs -nonewline $c x\nclose $c\nset c [open $f r]\nfileevent $c readable {puts hello}\nset s [fileevent $c readable]\nclose $c\nfile delete $f\nreturn $s")
|
|
||||||
:result)
|
|
||||||
"puts hello")
|
|
||||||
|
|
||||||
(ok "after-cancel-via-vwait-timing"
|
|
||||||
(get
|
|
||||||
(run
|
|
||||||
"set ::counter 0\nafter 10 {incr ::counter}\nafter 50 {set ::done 1}\nvwait ::done\nset ::counter")
|
|
||||||
:result)
|
|
||||||
"1")
|
|
||||||
|
|
||||||
; 38-41. Phase 5c sockets: TCP client + server
|
|
||||||
(ok "socket-server-fires-callback"
|
|
||||||
(get
|
|
||||||
(run
|
|
||||||
"proc h {sock host port} { global got; set got hit; close $sock }\nset srv [socket -server h 18901]\nset cli [socket localhost 18901]\nvwait got\nclose $srv\nclose $cli\nset got")
|
|
||||||
:result)
|
|
||||||
"hit")
|
|
||||||
|
|
||||||
(ok "socket-client-server-roundtrip"
|
|
||||||
(get
|
|
||||||
(run
|
|
||||||
"proc h {sock host port} { global received; gets $sock line; set received $line; close $sock }\nset srv [socket -server h 18902]\nset cli [socket localhost 18902]\nputs $cli ping\nflush $cli\nvwait received\nclose $srv\nclose $cli\nset received")
|
|
||||||
:result)
|
|
||||||
"ping")
|
|
||||||
|
|
||||||
(ok "socket-server-peer-host"
|
|
||||||
(get
|
|
||||||
(run
|
|
||||||
"proc h {sock host port} { global peer; set peer $host; close $sock }\nset srv [socket -server h 18903]\nset cli [socket 127.0.0.1 18903]\nvwait peer\nclose $srv\nclose $cli\nset peer")
|
|
||||||
:result)
|
|
||||||
"127.0.0.1")
|
|
||||||
|
|
||||||
(ok "socket-multiple-connections"
|
|
||||||
(get
|
|
||||||
(run
|
|
||||||
"proc h {sock host port} { global count; incr count; close $sock }\nset count 0\nset srv [socket -server h 18904]\nset c1 [socket localhost 18904]\nset c2 [socket localhost 18904]\nset c3 [socket localhost 18904]\nwhile {$count < 3} { update; after 5 }\nclose $srv\nclose $c1\nclose $c2\nclose $c3\nset count")
|
|
||||||
:result)
|
|
||||||
"3")
|
|
||||||
|
|
||||||
; 42-49. Phase 5d file metadata + ops
|
|
||||||
(ok "file-isfile-true"
|
|
||||||
(get
|
|
||||||
(run
|
|
||||||
"set f /tmp/tcl-phase5d-1.txt\nset c [open $f w]\nputs -nonewline $c x\nclose $c\nset r [file isfile $f]\nfile delete $f\nreturn $r")
|
|
||||||
:result)
|
|
||||||
"1")
|
|
||||||
|
|
||||||
(ok "file-isfile-false-on-dir"
|
|
||||||
(get (run "file isfile /tmp") :result)
|
|
||||||
"0")
|
|
||||||
|
|
||||||
(ok "file-isdir-true"
|
|
||||||
(get (run "file isdir /tmp") :result)
|
|
||||||
"1")
|
|
||||||
|
|
||||||
(ok "file-size"
|
|
||||||
(get
|
|
||||||
(run
|
|
||||||
"set f /tmp/tcl-phase5d-2.txt\nset c [open $f w]\nputs -nonewline $c hello\nclose $c\nset s [file size $f]\nfile delete $f\nreturn $s")
|
|
||||||
:result)
|
|
||||||
"5")
|
|
||||||
|
|
||||||
(ok "file-readable-true"
|
|
||||||
(get (run "file readable /tmp") :result)
|
|
||||||
"1")
|
|
||||||
|
|
||||||
(ok "file-readable-missing"
|
|
||||||
(get (run "file readable /no/such/path/here") :result)
|
|
||||||
"0")
|
|
||||||
|
|
||||||
(ok "file-mkdir-then-isdir"
|
|
||||||
(get
|
|
||||||
(run
|
|
||||||
"set d /tmp/tcl-phase5d-mkdir/sub\nfile mkdir $d\nset r [file isdir $d]\nfile delete $d\nfile delete /tmp/tcl-phase5d-mkdir\nreturn $r")
|
|
||||||
:result)
|
|
||||||
"1")
|
|
||||||
|
|
||||||
(ok "file-copy-roundtrip"
|
|
||||||
(get
|
|
||||||
(run
|
|
||||||
"set s /tmp/tcl-phase5d-src.txt\nset d /tmp/tcl-phase5d-dst.txt\nset c [open $s w]\nputs -nonewline $c copydata\nclose $c\nfile copy $s $d\nset c [open $d r]\nset out [read $c]\nclose $c\nfile delete $s\nfile delete $d\nreturn $out")
|
|
||||||
:result)
|
|
||||||
"copydata")
|
|
||||||
|
|
||||||
(ok "file-rename-then-exists"
|
|
||||||
(get
|
|
||||||
(run
|
|
||||||
"set s /tmp/tcl-phase5d-r1.txt\nset d /tmp/tcl-phase5d-r2.txt\nset c [open $s w]\nputs -nonewline $c x\nclose $c\nfile rename $s $d\nset r [list [file exists $s] [file exists $d]]\nfile delete $d\nreturn $r")
|
|
||||||
:result)
|
|
||||||
"0 1")
|
|
||||||
|
|
||||||
(ok "file-mtime-positive"
|
|
||||||
(get
|
|
||||||
(run
|
|
||||||
"set f /tmp/tcl-phase5d-mt.txt\nset c [open $f w]\nputs -nonewline $c x\nclose $c\nset m [file mtime $f]\nfile delete $f\nexpr {$m > 0}")
|
|
||||||
:result)
|
|
||||||
"1")
|
|
||||||
|
|
||||||
; 52-56. Phase 5e clock format options + clock scan
|
|
||||||
(ok "clock-format-utc"
|
|
||||||
(get
|
|
||||||
(run "clock format 0 -format {%Y-%m-%d %H:%M:%S} -gmt 1")
|
|
||||||
:result)
|
|
||||||
"1970-01-01 00:00:00")
|
|
||||||
|
|
||||||
(ok "clock-format-fmt-default"
|
|
||||||
(get
|
|
||||||
(run "clock format 1710513000 -format {%Y-%m-%d} -gmt 1")
|
|
||||||
:result)
|
|
||||||
"2024-03-15")
|
|
||||||
|
|
||||||
(ok "clock-scan-roundtrip"
|
|
||||||
(get
|
|
||||||
(run "set t [clock scan {2024-06-15 12:00:00} -format {%Y-%m-%d %H:%M:%S} -gmt 1]\nclock format $t -format {%Y-%m-%d %H:%M:%S} -gmt 1")
|
|
||||||
:result)
|
|
||||||
"2024-06-15 12:00:00")
|
|
||||||
|
|
||||||
(ok "clock-scan-returns-int"
|
|
||||||
(get
|
|
||||||
(run "expr {[clock scan {1970-01-01 00:00:00} -format {%Y-%m-%d %H:%M:%S} -gmt 1] == 0}")
|
|
||||||
:result)
|
|
||||||
"1")
|
|
||||||
|
|
||||||
(ok "clock-format-percent-pct"
|
|
||||||
(get
|
|
||||||
(run "clock format 0 -format {%Y%%%m} -gmt 1")
|
|
||||||
:result)
|
|
||||||
"1970%01")
|
|
||||||
|
|
||||||
; 57-59. Phase 5f socket -async (non-blocking connect)
|
|
||||||
(ok "socket-async-completes-writable"
|
|
||||||
(get
|
|
||||||
(run
|
|
||||||
"proc h {sock host port} { close $sock }\nset srv [socket -server h 18930]\nset c [socket -async localhost 18930]\nset ready 0\nfileevent $c writable {global ready; set ready 1}\nvwait ready\nclose $c\nclose $srv\nset ready")
|
|
||||||
:result)
|
|
||||||
"1")
|
|
||||||
|
|
||||||
(ok "socket-async-then-write"
|
|
||||||
(get
|
|
||||||
(run
|
|
||||||
"proc accept {sock host port} { global accepted_sock; set accepted_sock $sock; fileevent $sock readable [list reader $sock] }\nproc reader {sock} { global received; gets $sock line; set received $line; close $sock }\nset srv [socket -server accept 18931]\nset c [socket -async localhost 18931]\nfileevent $c writable {global wready; set wready 1; fileevent $::ch writable {}}\nset ::ch $c\nvwait wready\nputs $c async-data\nflush $c\nvwait received\nclose $c\nclose $srv\nset received")
|
|
||||||
:result)
|
|
||||||
"async-data")
|
|
||||||
|
|
||||||
(ok "socket-async-no-error"
|
|
||||||
(get
|
|
||||||
(run
|
|
||||||
"proc h {sock host port} { close $sock }\nset srv [socket -server h 18932]\nset c [socket -async localhost 18932]\nset r 0\nfileevent $c writable {global r; set r 1}\nvwait r\nset err [fconfigure $c -error]\nclose $c\nclose $srv\nreturn $err")
|
|
||||||
:result)
|
|
||||||
"")
|
|
||||||
|
|
||||||
(dict
|
(dict
|
||||||
"passed"
|
"passed"
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user