Compare commits
56 Commits
loops/comm
...
dd47fa8a0b
| Author | SHA1 | Date | |
|---|---|---|---|
| dd47fa8a0b | |||
| fad44ca097 | |||
| 702e7c8eac | |||
| 73694a3a84 | |||
| b9b875f399 | |||
| f620be096b | |||
| 1b34d41b33 | |||
| fd32bcf547 | |||
| d170d5fbae | |||
| abc98b7665 | |||
| 77f20b713d | |||
| 0491f061c4 | |||
| 2a4a4531b9 | |||
| f89e50aa4d | |||
| e670e914e7 | |||
| bd0377b6a3 | |||
| 3ec52d4556 | |||
| fb18629916 | |||
| d8be6b8230 | |||
| e105edee01 | |||
| 27425a3173 | |||
| bac3471a1f | |||
| 68b0a279f8 | |||
| b1bed8e0e5 | |||
| 9560145228 | |||
| 9435fab790 | |||
| fc2baee9c7 | |||
| 12b02d5691 | |||
| 57516ce18e | |||
| 46741a9643 | |||
| 1d3a93b0ca | |||
| f0a4dfbea8 | |||
| 54d7fcf436 | |||
| d361d83402 | |||
| 0b0d704f1e | |||
| 5ea81fe4e0 | |||
| 781bd36eeb | |||
| 743e0bae87 | |||
| cf4d19fb94 | |||
| 24fde8aa2f | |||
| 582894121d | |||
| c6b7e19892 | |||
| 40439cf0e1 | |||
| 6dfef34a4b | |||
| 8c25527205 | |||
| a5947e1295 | |||
| 0934c4bd28 | |||
| e224fb2db0 | |||
| 43c13c4eb1 | |||
| 4815db461b | |||
| 3ab8474e78 | |||
| d925be4768 | |||
| 418a0dc120 | |||
| fe0fafe8e9 | |||
| 2b448d99bc | |||
| 8bfeff8623 |
File diff suppressed because it is too large
Load Diff
@@ -293,8 +293,6 @@ env["pop-suite"] = function() {
|
||||
return null;
|
||||
};
|
||||
|
||||
env["test-allowed?"] = function(name) { return true; };
|
||||
|
||||
// Load test framework
|
||||
const projectDir = path.join(__dirname, "..", "..");
|
||||
const specTests = path.join(projectDir, "spec", "tests");
|
||||
@@ -343,20 +341,6 @@ if (fs.existsSync(swapPath)) {
|
||||
}
|
||||
}
|
||||
|
||||
// Load spec library files (define-library modules imported by tests)
|
||||
for (const libFile of ["stdlib.sx", "signals.sx", "coroutines.sx"]) {
|
||||
const libPath = path.join(projectDir, "spec", libFile);
|
||||
if (fs.existsSync(libPath)) {
|
||||
const libSrc = fs.readFileSync(libPath, "utf8");
|
||||
const libExprs = Sx.parse(libSrc);
|
||||
for (const expr of libExprs) {
|
||||
try { Sx.eval(expr, env); } catch (e) {
|
||||
console.error(`Error loading spec/${libFile}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load tw system (needed by spec/tests/test-tw.sx)
|
||||
const twDir = path.join(projectDir, "shared", "sx", "templates");
|
||||
for (const twFile of ["tw-type.sx", "tw-layout.sx", "tw.sx"]) {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -37,10 +37,7 @@ let rec deep_equal a b =
|
||||
match a, b with
|
||||
| Nil, Nil -> true
|
||||
| Bool a, Bool b -> a = b
|
||||
| Integer a, Integer b -> a = b
|
||||
| Number a, Number b -> a = b
|
||||
| Integer a, Number b -> float_of_int a = b
|
||||
| Number a, Integer b -> a = float_of_int b
|
||||
| String a, String b -> a = b
|
||||
| Symbol a, Symbol b -> a = b
|
||||
| Keyword a, Keyword b -> a = b
|
||||
@@ -229,7 +226,7 @@ let make_test_env () =
|
||||
| [String s] ->
|
||||
let parsed = Sx_parser.parse_all s in
|
||||
(match parsed with
|
||||
| [List (Symbol "sxbc" :: (Number _ | Integer _) :: payload :: _)] -> payload
|
||||
| [List (Symbol "sxbc" :: Number _ :: payload :: _)] -> payload
|
||||
| _ -> raise (Eval_error "bytecode-deserialize: invalid sxbc format"))
|
||||
| _ -> raise (Eval_error "bytecode-deserialize: expected string"));
|
||||
|
||||
@@ -243,7 +240,7 @@ let make_test_env () =
|
||||
| [String s] ->
|
||||
let parsed = Sx_parser.parse_all s in
|
||||
(match parsed with
|
||||
| [List (Symbol "cek-state" :: (Number _ | Integer _) :: payload :: _)] -> payload
|
||||
| [List (Symbol "cek-state" :: Number _ :: payload :: _)] -> payload
|
||||
| _ -> raise (Eval_error "cek-deserialize: invalid cek-state format"))
|
||||
| _ -> raise (Eval_error "cek-deserialize: expected string"));
|
||||
|
||||
@@ -323,10 +320,7 @@ let make_test_env () =
|
||||
bind "identical?" (fun args ->
|
||||
match args with
|
||||
| [a; b] -> Bool (match a, b with
|
||||
| Integer x, Integer y -> x = y
|
||||
| Number x, Number y -> x = y
|
||||
| Integer x, Number y -> float_of_int x = y
|
||||
| Number x, Integer y -> x = float_of_int y
|
||||
| String x, String y -> x = y
|
||||
| Bool x, Bool y -> x = y
|
||||
| Nil, Nil -> true
|
||||
@@ -372,15 +366,11 @@ let make_test_env () =
|
||||
|
||||
bind "append!" (fun args ->
|
||||
match args with
|
||||
| [ListRef r; v; (Number n)] when int_of_float n = 0 ->
|
||||
| [ListRef r; v; Number n] when int_of_float n = 0 ->
|
||||
r := v :: !r; ListRef r (* prepend *)
|
||||
| [ListRef r; v; (Integer 0)] ->
|
||||
r := v :: !r; ListRef r (* prepend Integer index *)
|
||||
| [ListRef r; v] -> r := !r @ [v]; ListRef r (* append in place *)
|
||||
| [List items; v; (Number n)] when int_of_float n = 0 ->
|
||||
| [List items; v; Number n] when int_of_float n = 0 ->
|
||||
List (v :: items) (* immutable prepend *)
|
||||
| [List items; v; (Integer 0)] ->
|
||||
List (v :: items) (* immutable prepend Integer index *)
|
||||
| [List items; v] -> List (items @ [v]) (* immutable fallback *)
|
||||
| _ -> raise (Eval_error "append!: expected list and value"));
|
||||
|
||||
@@ -556,10 +546,7 @@ let make_test_env () =
|
||||
bind "batch-begin!" (fun _args -> Sx_ref.batch_begin_b ());
|
||||
bind "batch-end!" (fun _args -> Sx_ref.batch_end_b ());
|
||||
bind "now-ms" (fun _args -> Number 1000.0);
|
||||
bind "random-int" (fun args -> match args with
|
||||
| [Number lo; _] -> Number lo
|
||||
| [Integer lo; _] -> Integer lo
|
||||
| _ -> Integer 0);
|
||||
bind "random-int" (fun args -> match args with [Number lo; _] -> Number lo | _ -> Number 0.0);
|
||||
bind "try-rerender-page" (fun _args -> Nil);
|
||||
bind "collect!" (fun args ->
|
||||
match args with
|
||||
@@ -1120,47 +1107,6 @@ let make_test_env () =
|
||||
| _ :: _ -> String "confirmed"
|
||||
| _ -> Nil);
|
||||
|
||||
bind "values" (fun args ->
|
||||
match args with
|
||||
| [v] -> v
|
||||
| vs ->
|
||||
let d = Hashtbl.create 2 in
|
||||
Hashtbl.replace d "_values" (Bool true);
|
||||
Hashtbl.replace d "_list" (List vs);
|
||||
Dict d);
|
||||
|
||||
bind "call-with-values" (fun args ->
|
||||
match args with
|
||||
| [producer; consumer] ->
|
||||
let result = Sx_ref.cek_call producer (List []) in
|
||||
let spread = (match result with
|
||||
| Dict d when (match Hashtbl.find_opt d "_values" with Some (Bool true) -> true | _ -> false) ->
|
||||
(match Hashtbl.find_opt d "_list" with Some (List l) -> l | _ -> [result])
|
||||
| _ -> [result])
|
||||
in
|
||||
Sx_ref.cek_call consumer (List spread)
|
||||
| _ -> raise (Eval_error "call-with-values: expected 2 args"));
|
||||
|
||||
bind "promise?" (fun args ->
|
||||
match args with
|
||||
| [v] -> Bool (Sx_ref.is_promise v)
|
||||
| _ -> Bool false);
|
||||
|
||||
bind "make-promise" (fun args ->
|
||||
match args with
|
||||
| [v] ->
|
||||
let d = Hashtbl.create 4 in
|
||||
Hashtbl.replace d "_promise" (Bool true);
|
||||
Hashtbl.replace d "forced" (Bool true);
|
||||
Hashtbl.replace d "value" v;
|
||||
Dict d
|
||||
| _ -> Nil);
|
||||
|
||||
bind "force" (fun args ->
|
||||
match args with
|
||||
| [p] -> Sx_ref.force_promise p
|
||||
| _ -> Nil);
|
||||
|
||||
env
|
||||
|
||||
(* ====================================================================== *)
|
||||
@@ -1196,20 +1142,18 @@ let run_foundation_tests () =
|
||||
in
|
||||
|
||||
Printf.printf "Suite: parser\n";
|
||||
assert_eq "number" (Integer 42) (List.hd (parse_all "42"));
|
||||
assert_eq "number" (Number 42.0) (List.hd (parse_all "42"));
|
||||
assert_eq "string" (String "hello") (List.hd (parse_all "\"hello\""));
|
||||
assert_eq "bool true" (Bool true) (List.hd (parse_all "true"));
|
||||
assert_eq "nil" Nil (List.hd (parse_all "nil"));
|
||||
assert_eq "keyword" (Keyword "class") (List.hd (parse_all ":class"));
|
||||
assert_eq "symbol" (Symbol "foo") (List.hd (parse_all "foo"));
|
||||
assert_eq "list" (List [Symbol "+"; Integer 1; Integer 2]) (List.hd (parse_all "(+ 1 2)"));
|
||||
assert_eq "list" (List [Symbol "+"; Number 1.0; Number 2.0]) (List.hd (parse_all "(+ 1 2)"));
|
||||
(match List.hd (parse_all "(div :class \"card\" (p \"hi\"))") with
|
||||
| List [Symbol "div"; Keyword "class"; String "card"; List [Symbol "p"; String "hi"]] ->
|
||||
incr pass_count; Printf.printf " PASS: nested list\n"
|
||||
| v -> incr fail_count; Printf.printf " FAIL: nested list — got %s\n" (Sx_types.inspect v));
|
||||
(match List.hd (parse_all "'(1 2 3)") with
|
||||
| List [Symbol "quote"; List [Integer 1; Integer 2; Integer 3]] ->
|
||||
incr pass_count; Printf.printf " PASS: quote sugar\n"
|
||||
| List [Symbol "quote"; List [Number 1.0; Number 2.0; Number 3.0]] ->
|
||||
incr pass_count; Printf.printf " PASS: quote sugar\n"
|
||||
| v -> incr fail_count; Printf.printf " FAIL: quote sugar — got %s\n" (Sx_types.inspect v));
|
||||
@@ -1217,7 +1161,7 @@ let run_foundation_tests () =
|
||||
| Dict d when dict_has d "a" && dict_has d "b" ->
|
||||
incr pass_count; Printf.printf " PASS: dict literal\n"
|
||||
| v -> incr fail_count; Printf.printf " FAIL: dict literal — got %s\n" (Sx_types.inspect v));
|
||||
assert_eq "comment" (Integer 42) (List.hd (parse_all ";; comment\n42"));
|
||||
assert_eq "comment" (Number 42.0) (List.hd (parse_all ";; comment\n42"));
|
||||
assert_eq "string escape" (String "hello\nworld") (List.hd (parse_all "\"hello\\nworld\""));
|
||||
assert_eq "multiple exprs" (Number 2.0) (Number (float_of_int (List.length (parse_all "(1 2 3) (4 5)"))));
|
||||
|
||||
@@ -2034,10 +1978,6 @@ let run_spec_tests env test_files =
|
||||
(match Hashtbl.find_opt d "children" with
|
||||
| Some (List l) when i >= 0 && i < List.length l -> List.nth l i
|
||||
| _ -> (match Hashtbl.find_opt d (string_of_int i) with Some v -> v | None -> Nil))
|
||||
| [Dict d; Integer n] ->
|
||||
(match Hashtbl.find_opt d "children" with
|
||||
| Some (List l) when n >= 0 && n < List.length l -> List.nth l n
|
||||
| _ -> (match Hashtbl.find_opt d (string_of_int n) with Some v -> v | None -> Nil))
|
||||
| _ -> Nil);
|
||||
|
||||
(* Stringify a value for DOM string properties *)
|
||||
@@ -2112,8 +2052,8 @@ let run_spec_tests env test_files =
|
||||
Hashtbl.replace d "childNodes" (List [])
|
||||
| _ -> ());
|
||||
stored
|
||||
| [ListRef r; idx_v; value] when (match idx_v with Number _ | Integer _ -> true | _ -> false) ->
|
||||
let idx = match idx_v with Number n -> int_of_float n | Integer n -> n | _ -> 0 in
|
||||
| [ListRef r; Number n; value] ->
|
||||
let idx = int_of_float n in
|
||||
let lst = !r in
|
||||
if idx >= 0 && idx < List.length lst then
|
||||
r := List.mapi (fun i v -> if i = idx then value else v) lst
|
||||
@@ -2250,7 +2190,7 @@ let run_spec_tests env test_files =
|
||||
| [String name; value] ->
|
||||
let attrs = match Hashtbl.find_opt d "attributes" with Some (Dict a) -> a | _ ->
|
||||
let a = Hashtbl.create 4 in Hashtbl.replace d "attributes" (Dict a); a in
|
||||
let sv = match value with String s -> s | Integer n -> string_of_int n | Number n ->
|
||||
let sv = match value with String s -> s | Number n ->
|
||||
let i = int_of_float n in if float_of_int i = n then string_of_int i
|
||||
else string_of_float n | _ -> Sx_types.inspect value in
|
||||
Hashtbl.replace attrs name (String sv);
|
||||
@@ -2692,7 +2632,6 @@ let run_spec_tests env test_files =
|
||||
let rec json_of_value = function
|
||||
| Nil -> `Null
|
||||
| Bool b -> `Bool b
|
||||
| Integer n -> `Int n
|
||||
| Number n ->
|
||||
if Float.is_integer n && Float.abs n < 1e16
|
||||
then `Int (int_of_float n) else `Float n
|
||||
@@ -2708,8 +2647,8 @@ let run_spec_tests env test_files =
|
||||
let rec value_of_json = function
|
||||
| `Null -> Nil
|
||||
| `Bool b -> Bool b
|
||||
| `Int i -> Integer i
|
||||
| `Intlit s -> (try Integer (int_of_string s) with _ -> try Number (float_of_string s) with _ -> String s)
|
||||
| `Int i -> Number (float_of_int i)
|
||||
| `Intlit s -> (try Number (float_of_string s) with _ -> String s)
|
||||
| `Float f -> Number f
|
||||
| `String s -> String s
|
||||
| `List xs -> List (List.map value_of_json xs)
|
||||
@@ -2872,7 +2811,6 @@ let run_spec_tests env test_files =
|
||||
match sx_vm_execute with
|
||||
| Some fn -> Sx_ref.cek_call fn (List args)
|
||||
| None -> Nil)));
|
||||
load_module "stdlib.sx" spec_dir; (* pure SX stdlib: format etc. *)
|
||||
load_module "signals.sx" spec_dir; (* core reactive primitives *)
|
||||
load_module "signals.sx" web_dir; (* web extensions *)
|
||||
load_module "freeze.sx" lib_dir;
|
||||
|
||||
@@ -296,10 +296,6 @@ let read_blob () =
|
||||
(* consume trailing newline *)
|
||||
(try ignore (input_line stdin) with End_of_file -> ());
|
||||
data
|
||||
| [List [Symbol "blob"; Integer n]] ->
|
||||
let data = read_exact_bytes n in
|
||||
(try ignore (input_line stdin) with End_of_file -> ());
|
||||
data
|
||||
| _ -> raise (Eval_error ("read_blob: expected (blob N), got: " ^ line))
|
||||
|
||||
(** Batch IO mode — collect requests during aser-slot, resolve after. *)
|
||||
@@ -361,11 +357,6 @@ let rec read_io_response () =
|
||||
| [List (Symbol "io-response" :: Number n :: values)]
|
||||
when int_of_float n = !current_epoch ->
|
||||
(match values with [v] -> v | _ -> List values)
|
||||
| [List [Symbol "io-response"; Integer n; value]]
|
||||
when n = !current_epoch -> value
|
||||
| [List (Symbol "io-response" :: Integer n :: values)]
|
||||
when n = !current_epoch ->
|
||||
(match values with [v] -> v | _ -> List values)
|
||||
(* Legacy untagged: (io-response value) — accept for backwards compat *)
|
||||
| [List [Symbol "io-response"; value]] -> value
|
||||
| [List (Symbol "io-response" :: values)] ->
|
||||
@@ -405,12 +396,6 @@ let read_batched_io_response () =
|
||||
when int_of_float n = !current_epoch -> s
|
||||
| [List [Symbol "io-response"; Number n; v]]
|
||||
when int_of_float n = !current_epoch -> serialize_value v
|
||||
| [List [Symbol "io-response"; Integer n; String s]]
|
||||
when n = !current_epoch -> s
|
||||
| [List [Symbol "io-response"; Integer n; SxExpr s]]
|
||||
when n = !current_epoch -> s
|
||||
| [List [Symbol "io-response"; Integer n; v]]
|
||||
when n = !current_epoch -> serialize_value v
|
||||
(* Legacy untagged *)
|
||||
| [List [Symbol "io-response"; String s]]
|
||||
| [List [Symbol "io-response"; SxExpr s]] -> s
|
||||
@@ -974,7 +959,6 @@ let setup_io_bridges env =
|
||||
bind "sleep" (fun args -> io_request "sleep" args);
|
||||
bind "set-response-status" (fun args -> match args with
|
||||
| [Number n] -> _pending_response_status := int_of_float n; Nil
|
||||
| [Integer n] -> _pending_response_status := n; Nil
|
||||
| _ -> Nil);
|
||||
bind "set-response-header" (fun args -> io_request "set-response-header" args)
|
||||
|
||||
@@ -1377,7 +1361,6 @@ let rec dispatch env cmd =
|
||||
| Bool true -> "true"
|
||||
| Bool false -> "false"
|
||||
| Number n -> Sx_types.format_number n
|
||||
| Integer n -> string_of_int n
|
||||
| String s -> "\"" ^ escape_sx_string s ^ "\""
|
||||
| Symbol s -> s
|
||||
| Keyword k -> ":" ^ k
|
||||
@@ -1391,10 +1374,6 @@ let rec dispatch env cmd =
|
||||
| Island i -> "~" ^ i.i_name
|
||||
| SxExpr s -> s
|
||||
| RawHTML s -> "\"" ^ escape_sx_string s ^ "\""
|
||||
| Char n -> Sx_types.inspect (Char n)
|
||||
| Eof -> Sx_types.inspect Eof
|
||||
| Port _ -> Sx_types.inspect result
|
||||
| Rational (n, d) -> Printf.sprintf "%d/%d" n d
|
||||
| _ -> "nil"
|
||||
in
|
||||
send_ok_raw (raw_serialize result)
|
||||
@@ -4471,8 +4450,6 @@ let site_mode () =
|
||||
match exprs with
|
||||
| [List [Symbol "epoch"; Number n]] ->
|
||||
current_epoch := int_of_float n
|
||||
| [List [Symbol "epoch"; Integer n]] ->
|
||||
current_epoch := n
|
||||
(* render-page: full SSR pipeline — URL → complete HTML *)
|
||||
| [List [Symbol "render-page"; String path]] ->
|
||||
(try match http_render_page env path [] with
|
||||
@@ -4530,8 +4507,6 @@ let () =
|
||||
(* Epoch marker: (epoch N) — set current epoch, read next command *)
|
||||
| [List [Symbol "epoch"; Number n]] ->
|
||||
current_epoch := int_of_float n
|
||||
| [List [Symbol "epoch"; Integer n]] ->
|
||||
current_epoch := n
|
||||
| [cmd] -> dispatch env cmd
|
||||
| _ -> send_error ("Expected single command, got " ^ string_of_int (List.length exprs))
|
||||
end
|
||||
|
||||
@@ -47,9 +47,7 @@ open Sx_runtime
|
||||
let trampoline_fn : (value -> value) ref = ref (fun v -> v)
|
||||
let trampoline v = !trampoline_fn v
|
||||
|
||||
(* Step limit for timeout detection — set to 0 to disable *)
|
||||
let step_limit : int ref = ref 0
|
||||
let step_count : int ref = ref 0
|
||||
|
||||
|
||||
(* === Mutable globals — backing refs for transpiler's !_ref / _ref := === *)
|
||||
let _strict_ref = ref (Bool false)
|
||||
@@ -128,90 +126,6 @@ let enhance_error_with_trace msg =
|
||||
_last_error_kont_ref := Nil;
|
||||
msg ^ (format_comp_trace trace)
|
||||
|
||||
(* Hand-written sf_define_type — skipped from transpile because the spec uses
|
||||
&rest params and empty-dict literals that the transpiler can't emit cleanly.
|
||||
Implements: (define-type Name (Ctor1 f1 f2) (Ctor2 f3) ...)
|
||||
Creates constructor fns, Name?/Ctor? predicates, Ctor-field accessors,
|
||||
and records ctors in *adt-registry*. *)
|
||||
let sf_define_type args env_val =
|
||||
let items = (match args with List l -> l | _ -> []) in
|
||||
let type_sym = List.nth items 0 in
|
||||
let type_name = value_to_string type_sym in
|
||||
let ctor_specs = List.tl items in
|
||||
let env_has_v k = sx_truthy (env_has env_val (String k)) in
|
||||
let env_bind_v k v = ignore (env_bind env_val (String k) v) in
|
||||
let env_get_v k = env_get env_val (String k) in
|
||||
if not (env_has_v "*adt-registry*") then
|
||||
env_bind_v "*adt-registry*" (Dict (Hashtbl.create 8));
|
||||
let registry = env_get_v "*adt-registry*" in
|
||||
let ctor_names = List.map (fun spec ->
|
||||
(match spec with List (sym :: _) -> String (value_to_string sym) | _ -> Nil)
|
||||
) ctor_specs in
|
||||
(match registry with Dict d -> Hashtbl.replace d type_name (List ctor_names) | _ -> ());
|
||||
env_bind_v (type_name ^ "?")
|
||||
(NativeFn (type_name ^ "?", fun pargs ->
|
||||
(match pargs with
|
||||
| [v] ->
|
||||
(match v with
|
||||
| Dict d -> Bool (Hashtbl.mem d "_adt" &&
|
||||
(match Hashtbl.find_opt d "_type" with Some (String t) -> t = type_name | _ -> false))
|
||||
| _ -> Bool false)
|
||||
| _ -> Bool false)));
|
||||
List.iter (fun spec ->
|
||||
(match spec with
|
||||
| List (sym :: fields) ->
|
||||
let cn = value_to_string sym in
|
||||
let field_names = List.map value_to_string fields in
|
||||
let arity = List.length fields in
|
||||
env_bind_v cn
|
||||
(NativeFn (cn, fun ctor_args ->
|
||||
if List.length ctor_args <> arity then
|
||||
raise (Eval_error (Printf.sprintf "%s: expected %d args, got %d"
|
||||
cn arity (List.length ctor_args)))
|
||||
else begin
|
||||
let d = Hashtbl.create 4 in
|
||||
Hashtbl.replace d "_adt" (Bool true);
|
||||
Hashtbl.replace d "_type" (String type_name);
|
||||
Hashtbl.replace d "_ctor" (String cn);
|
||||
Hashtbl.replace d "_fields" (List ctor_args);
|
||||
Dict d
|
||||
end));
|
||||
env_bind_v (cn ^ "?")
|
||||
(NativeFn (cn ^ "?", fun pargs ->
|
||||
(match pargs with
|
||||
| [v] ->
|
||||
(match v with
|
||||
| Dict d -> Bool (Hashtbl.mem d "_adt" &&
|
||||
(match Hashtbl.find_opt d "_ctor" with Some (String c) -> c = cn | _ -> false))
|
||||
| _ -> Bool false)
|
||||
| _ -> Bool false)));
|
||||
List.iteri (fun idx fname ->
|
||||
env_bind_v (cn ^ "-" ^ fname)
|
||||
(NativeFn (cn ^ "-" ^ fname, fun pargs ->
|
||||
(match pargs with
|
||||
| [v] ->
|
||||
(match v with
|
||||
| Dict d ->
|
||||
(match Hashtbl.find_opt d "_fields" with
|
||||
| Some (List fs) ->
|
||||
if idx < List.length fs then List.nth fs idx
|
||||
else raise (Eval_error (cn ^ "-" ^ fname ^ ": index out of bounds"))
|
||||
| _ -> raise (Eval_error (cn ^ "-" ^ fname ^ ": not an ADT")))
|
||||
| _ -> raise (Eval_error (cn ^ "-" ^ fname ^ ": not a dict")))
|
||||
| _ -> raise (Eval_error (cn ^ "-" ^ fname ^ ": expected 1 arg")))))
|
||||
) field_names
|
||||
| _ -> ())
|
||||
) ctor_specs;
|
||||
Nil
|
||||
|
||||
(* Register define-type via custom_special_forms so the CEK dispatch finds it.
|
||||
The top-level (register-special-form! ...) in spec/evaluator.sx is not a
|
||||
define and therefore is not transpiled; we wire it up here instead. *)
|
||||
let () = ignore (register_special_form (String "define-type")
|
||||
(NativeFn ("define-type", fun call_args ->
|
||||
match call_args with
|
||||
| [args; env] -> sf_define_type args env
|
||||
| _ -> Nil)))
|
||||
|
||||
|
||||
"""
|
||||
@@ -257,10 +171,7 @@ def compile_spec_to_ml(spec_dir: str | None = None) -> str:
|
||||
"debug-log", "debug_log", "range", "chunk-every", "zip-pairs",
|
||||
"string-contains?", "starts-with?", "ends-with?",
|
||||
"string-replace", "trim", "split", "index-of",
|
||||
"pad-left", "pad-right", "char-at", "substring",
|
||||
# sf-define-type uses &rest + empty-dict literals that the transpiler
|
||||
# can't emit as valid OCaml; hand-written implementation in FIXUPS.
|
||||
"sf-define-type"}
|
||||
"pad-left", "pad-right", "char-at", "substring"}
|
||||
defines = [(n, e) for n, e in defines if n not in skip]
|
||||
|
||||
# Deduplicate — keep last definition for each name (CEK overrides tree-walk)
|
||||
|
||||
@@ -89,38 +89,10 @@ let read_symbol s =
|
||||
while s.pos < s.len && is_symbol_char s.src.[s.pos] do advance s done;
|
||||
String.sub s.src start (s.pos - start)
|
||||
|
||||
let gcd a b =
|
||||
let rec g a b = if b = 0 then a else g b (a mod b) in g (abs a) (abs b)
|
||||
|
||||
let make_rat n d =
|
||||
if d = 0 then raise (Parse_error "rational: division by zero");
|
||||
let sign = if d < 0 then -1 else 1 in
|
||||
let g = gcd (abs n) (abs d) in
|
||||
let rn = sign * n / g and rd = sign * d / g in
|
||||
if rd = 1 then Integer rn else Rational (rn, rd)
|
||||
|
||||
let try_number str =
|
||||
(* Integers (no '.' or 'e'/'E') → exact Integer; rationals N/D; floats → inexact Number *)
|
||||
let has_dec = String.contains str '.' in
|
||||
let has_exp = String.contains str 'e' || String.contains str 'E' in
|
||||
if has_dec || has_exp then
|
||||
match float_of_string_opt str with
|
||||
| Some n -> Some (Number n)
|
||||
| None -> None
|
||||
else
|
||||
match String.split_on_char '/' str with
|
||||
| [num_s; den_s] when num_s <> "" && den_s <> "" ->
|
||||
(match int_of_string_opt num_s, int_of_string_opt den_s with
|
||||
| Some n, Some d -> (try Some (make_rat n d) with _ -> None)
|
||||
| _ -> None)
|
||||
| _ ->
|
||||
match int_of_string_opt str with
|
||||
| Some n -> Some (Integer n)
|
||||
| None ->
|
||||
(* handles "nan", "inf", "-inf" *)
|
||||
match float_of_string_opt str with
|
||||
| Some n -> Some (Number n)
|
||||
| None -> None
|
||||
match float_of_string_opt str with
|
||||
| Some n -> Some (Number n)
|
||||
| None -> None
|
||||
|
||||
let rec read_value s : value =
|
||||
skip_whitespace_and_comments s;
|
||||
@@ -136,34 +108,6 @@ let rec read_value s : value =
|
||||
| '"' -> String (read_string s)
|
||||
| '\'' -> advance s; List [Symbol "quote"; read_value s]
|
||||
| '`' -> advance s; List [Symbol "quasiquote"; read_value s]
|
||||
| '#' when s.pos + 1 < s.len && s.src.[s.pos + 1] = '\\' ->
|
||||
(* Character literal: #\a, #\space, #\newline, etc. *)
|
||||
advance s; advance s;
|
||||
if at_end s then raise (Parse_error "Unexpected end of input after #\\");
|
||||
let char_start = s.pos in
|
||||
(* Read a name if starts with ident char, else single char *)
|
||||
if is_ident_start s.src.[s.pos] then begin
|
||||
while s.pos < s.len && is_ident_char s.src.[s.pos] do advance s done;
|
||||
let name = String.sub s.src char_start (s.pos - char_start) in
|
||||
let cp = match name with
|
||||
| "space" -> 32 | "newline" -> 10 | "tab" -> 9
|
||||
| "return" -> 13 | "nul" -> 0 | "null" -> 0
|
||||
| "escape" -> 27 | "delete" -> 127 | "backspace" -> 8
|
||||
| "altmode" -> 27 | "rubout" -> 127
|
||||
| _ -> Char.code name.[0] (* single letter like #\a *)
|
||||
in Char cp
|
||||
end else begin
|
||||
let c = s.src.[s.pos] in
|
||||
advance s;
|
||||
Char (Char.code c)
|
||||
end
|
||||
| '#' when s.pos + 1 < s.len &&
|
||||
(s.src.[s.pos + 1] = 't' || s.src.[s.pos + 1] = 'f') &&
|
||||
(s.pos + 2 >= s.len || not (is_ident_char s.src.[s.pos + 2])) ->
|
||||
(* #t / #f — boolean literals (R7RS shorthand) *)
|
||||
let b = s.src.[s.pos + 1] = 't' in
|
||||
advance s; advance s;
|
||||
Bool b
|
||||
| '#' when s.pos + 1 < s.len && s.src.[s.pos + 1] = ';' ->
|
||||
(* Datum comment: #; discards next expression *)
|
||||
advance s; advance s;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -46,7 +46,7 @@ let sx_call f args =
|
||||
!Sx_types._cek_eval_lambda_ref f args
|
||||
| Continuation (k, _) ->
|
||||
k (match args with x :: _ -> x | [] -> Nil)
|
||||
| CallccContinuation (_, _) ->
|
||||
| CallccContinuation _ ->
|
||||
raise (Eval_error "callcc continuations must be invoked through the CEK machine")
|
||||
| _ ->
|
||||
let nargs = List.length args in
|
||||
@@ -156,9 +156,6 @@ let get_val container key =
|
||||
| "extra" -> f.cf_extra | "extra2" -> f.cf_extra2
|
||||
| "subscribers" -> f.cf_results
|
||||
| "prev-tracking" -> f.cf_extra
|
||||
| "after-thunk" -> f.cf_f (* wind-after frame *)
|
||||
| "winders-len" -> f.cf_extra (* wind-after frame *)
|
||||
| "body-result" -> f.cf_name (* wind-return frame *)
|
||||
| _ -> Nil)
|
||||
| VmFrame f, String k ->
|
||||
(match k with
|
||||
@@ -211,8 +208,6 @@ let get_val container key =
|
||||
| Dict d, Keyword k -> dict_get d k
|
||||
| (List l | ListRef { contents = l }), Number n ->
|
||||
(try List.nth l (int_of_float n) with _ -> Nil)
|
||||
| (List l | ListRef { contents = l }), Integer n ->
|
||||
(try List.nth l n with _ -> Nil)
|
||||
| Nil, _ -> Nil (* nil.anything → nil *)
|
||||
| _, _ -> Nil (* type mismatch → nil (matches JS/Python behavior) *)
|
||||
|
||||
@@ -386,20 +381,15 @@ let continuation_data v = match v with
|
||||
| _ -> raise (Eval_error "not a continuation")
|
||||
|
||||
(* Callcc (undelimited) continuation support *)
|
||||
let callcc_continuation_p v = match v with CallccContinuation (_, _) -> Bool true | _ -> Bool false
|
||||
let callcc_continuation_p v = match v with CallccContinuation _ -> Bool true | _ -> Bool false
|
||||
|
||||
let make_callcc_continuation captured winders_len =
|
||||
let n = match winders_len with Number f -> int_of_float f | Integer n -> n | _ -> 0 in
|
||||
CallccContinuation (sx_to_list captured, n)
|
||||
let make_callcc_continuation captured =
|
||||
CallccContinuation (sx_to_list captured)
|
||||
|
||||
let callcc_continuation_data v = match v with
|
||||
| CallccContinuation (frames, _) -> List frames
|
||||
| CallccContinuation frames -> List frames
|
||||
| _ -> raise (Eval_error "not a callcc continuation")
|
||||
|
||||
let callcc_continuation_winders_len v = match v with
|
||||
| CallccContinuation (_, n) -> Number (float_of_int n)
|
||||
| _ -> Number 0.0
|
||||
|
||||
(* Dynamic wind — simplified for OCaml (no async) *)
|
||||
let host_error msg =
|
||||
raise (Eval_error (value_to_str msg))
|
||||
|
||||
@@ -43,10 +43,9 @@ type env = {
|
||||
|
||||
and value =
|
||||
| Nil
|
||||
| Bool of bool
|
||||
| Integer of int (** Exact integer — distinct from inexact float. *)
|
||||
| Number of float (** Inexact float. *)
|
||||
| String of string
|
||||
| Bool of bool
|
||||
| Number of float
|
||||
| String of string
|
||||
| Symbol of string
|
||||
| Keyword of string
|
||||
| List of value list
|
||||
@@ -57,7 +56,7 @@ and value =
|
||||
| Macro of macro
|
||||
| Thunk of value * env
|
||||
| Continuation of (value -> value) * dict option
|
||||
| CallccContinuation of value list * int (** Undelimited continuation — captured kont frames + winders depth at capture *)
|
||||
| CallccContinuation of value list (** Undelimited continuation — captured kont frames *)
|
||||
| NativeFn of string * (value list -> value)
|
||||
| Signal of signal
|
||||
| RawHTML of string
|
||||
@@ -73,25 +72,6 @@ and value =
|
||||
| Record of record (** R7RS record — opaque, generative, field-indexed. *)
|
||||
| Parameter of parameter (** R7RS parameter — dynamic binding via kont-stack provide frames. *)
|
||||
| Vector of value array (** R7RS vector — mutable fixed-size array. *)
|
||||
| StringBuffer of Buffer.t (** Mutable string buffer — O(1) amortized append. *)
|
||||
| HashTable of (value, value) Hashtbl.t (** Mutable hash table with arbitrary keys. *)
|
||||
| Char of int (** Unicode codepoint — R7RS char type. *)
|
||||
| Eof (** EOF sentinel — returned by read-char etc. at end of input. *)
|
||||
| Port of sx_port (** String port — input (string cursor) or output (buffer). *)
|
||||
| Rational of int * int (** Exact rational: numerator, denominator (reduced, denom>0). *)
|
||||
| SxSet of (string, value) Hashtbl.t (** Mutable set keyed by inspect(value). *)
|
||||
| SxRegexp of string * string * Re.re (** Regexp: source, flags, compiled. *)
|
||||
| SxBytevector of bytes (** Mutable bytevector — R7RS bytevector type. *)
|
||||
|
||||
(** String input port: source string + mutable cursor position. *)
|
||||
and sx_port_kind =
|
||||
| PortInput of string * int ref
|
||||
| PortOutput of Buffer.t
|
||||
|
||||
and sx_port = {
|
||||
mutable sp_closed : bool;
|
||||
sp_kind : sx_port_kind;
|
||||
}
|
||||
|
||||
(** CEK machine state — record instead of Dict for performance.
|
||||
5 fields × 55K steps/sec = 275K Hashtbl allocations/sec eliminated. *)
|
||||
@@ -412,7 +392,6 @@ let format_number n =
|
||||
|
||||
let value_to_string = function
|
||||
| String s -> s | Symbol s -> s | Keyword k -> k
|
||||
| Integer n -> string_of_int n
|
||||
| Number n -> format_number n
|
||||
| Bool true -> "true" | Bool false -> "false"
|
||||
| Nil -> "" | _ -> "<value>"
|
||||
@@ -482,7 +461,6 @@ let make_keyword name = Keyword (value_to_string name)
|
||||
let type_of = function
|
||||
| Nil -> "nil"
|
||||
| Bool _ -> "boolean"
|
||||
| Integer _ -> "number"
|
||||
| Number _ -> "number"
|
||||
| String _ -> "string"
|
||||
| Symbol _ -> "symbol"
|
||||
@@ -495,7 +473,7 @@ let type_of = function
|
||||
| Macro _ -> "macro"
|
||||
| Thunk _ -> "thunk"
|
||||
| Continuation (_, _) -> "continuation"
|
||||
| CallccContinuation (_, _) -> "continuation"
|
||||
| CallccContinuation _ -> "continuation"
|
||||
| NativeFn _ -> "function"
|
||||
| Signal _ -> "signal"
|
||||
| RawHTML _ -> "raw-html"
|
||||
@@ -510,16 +488,6 @@ let type_of = function
|
||||
| Record r -> r.r_type.rt_name
|
||||
| Parameter _ -> "parameter"
|
||||
| Vector _ -> "vector"
|
||||
| StringBuffer _ -> "string-buffer"
|
||||
| HashTable _ -> "hash-table"
|
||||
| Char _ -> "char"
|
||||
| Eof -> "eof-object"
|
||||
| Port { sp_kind = PortInput _; _ } -> "input-port"
|
||||
| Port { sp_kind = PortOutput _; _ } -> "output-port"
|
||||
| Rational _ -> "rational"
|
||||
| SxSet _ -> "set"
|
||||
| SxRegexp _ -> "regexp"
|
||||
| SxBytevector _ -> "bytevector"
|
||||
|
||||
let is_nil = function Nil -> true | _ -> false
|
||||
let is_lambda = function Lambda _ -> true | _ -> false
|
||||
@@ -535,7 +503,7 @@ let is_signal = function
|
||||
let is_record = function Record _ -> true | _ -> false
|
||||
|
||||
let is_callable = function
|
||||
| Lambda _ | NativeFn _ | Continuation (_, _) | CallccContinuation (_, _) | VmClosure _ -> true
|
||||
| Lambda _ | NativeFn _ | Continuation (_, _) | CallccContinuation _ | VmClosure _ -> true
|
||||
| _ -> false
|
||||
|
||||
|
||||
@@ -648,7 +616,6 @@ let thunk_env = function
|
||||
(** {1 Record operations} *)
|
||||
|
||||
let val_to_int = function
|
||||
| Integer n -> n
|
||||
| Number n -> int_of_float n
|
||||
| v -> raise (Eval_error ("Expected number, got " ^ type_of v))
|
||||
|
||||
@@ -810,7 +777,6 @@ let rec inspect = function
|
||||
| Nil -> "nil"
|
||||
| Bool true -> "true"
|
||||
| Bool false -> "false"
|
||||
| Integer n -> string_of_int n
|
||||
| Number n -> format_number n
|
||||
| String s ->
|
||||
let buf = Buffer.create (String.length s + 2) in
|
||||
@@ -844,7 +810,7 @@ let rec inspect = function
|
||||
Printf.sprintf "<%s(%s)>" tag (String.concat ", " m.m_params)
|
||||
| Thunk _ -> "<thunk>"
|
||||
| Continuation (_, _) -> "<continuation>"
|
||||
| CallccContinuation (_, _) -> "<callcc-continuation>"
|
||||
| CallccContinuation _ -> "<callcc-continuation>"
|
||||
| NativeFn (name, _) -> Printf.sprintf "<native:%s>" name
|
||||
| Signal _ -> "<signal>"
|
||||
| RawHTML s -> Printf.sprintf "\"<raw-html:%d>\"" (String.length s)
|
||||
@@ -865,23 +831,3 @@ let rec inspect = function
|
||||
Printf.sprintf "#(%s)" (String.concat " " elts)
|
||||
| VmFrame f -> Printf.sprintf "<vm-frame:ip=%d base=%d>" f.vf_ip f.vf_base
|
||||
| VmMachine m -> Printf.sprintf "<vm-machine:sp=%d frames=%d>" m.vm_sp (List.length m.vm_frames)
|
||||
| StringBuffer buf -> Printf.sprintf "<string-buffer:%d>" (Buffer.length buf)
|
||||
| HashTable ht -> Printf.sprintf "<hash-table:%d>" (Hashtbl.length ht)
|
||||
| Char n ->
|
||||
let name = match n with
|
||||
| 32 -> "space" | 10 -> "newline" | 9 -> "tab"
|
||||
| 13 -> "return" | 0 -> "nul" | 27 -> "escape"
|
||||
| 127 -> "delete" | 8 -> "backspace"
|
||||
| _ -> let buf = Buffer.create 1 in
|
||||
Buffer.add_utf_8_uchar buf (Uchar.of_int n);
|
||||
Buffer.contents buf
|
||||
in "#\\" ^ name
|
||||
| Eof -> "#!eof"
|
||||
| Port { sp_kind = PortInput (_, pos); sp_closed } ->
|
||||
Printf.sprintf "<input-port:pos=%d%s>" !pos (if sp_closed then ":closed" else "")
|
||||
| Port { sp_kind = PortOutput buf; sp_closed } ->
|
||||
Printf.sprintf "<output-port:len=%d%s>" (Buffer.length buf) (if sp_closed then ":closed" else "")
|
||||
| Rational (n, d) -> Printf.sprintf "%d/%d" n d
|
||||
| SxSet ht -> Printf.sprintf "<set:%d>" (Hashtbl.length ht)
|
||||
| SxRegexp (src, flags, _) -> Printf.sprintf "#/%s/%s" src flags
|
||||
| SxBytevector b -> Printf.sprintf "#u8(%s)" (String.concat " " (List.init (Bytes.length b) (fun i -> string_of_int (Char.code (Bytes.get b i)))))
|
||||
|
||||
@@ -185,8 +185,7 @@ let code_from_value v =
|
||||
| Some _ as r -> r | None -> Hashtbl.find_opt d k2 in
|
||||
let bc_list = match find2 "bytecode" "vc-bytecode" with
|
||||
| Some (List l | ListRef { contents = l }) ->
|
||||
Array.of_list (List.map (fun x -> match x with
|
||||
| Integer n -> n | Number n -> int_of_float n | _ -> 0) l)
|
||||
Array.of_list (List.map (fun x -> match x with Number n -> int_of_float n | _ -> 0) l)
|
||||
| _ -> [||]
|
||||
in
|
||||
let entries = match find2 "constants" "vc-constants" with
|
||||
@@ -199,10 +198,10 @@ let code_from_value v =
|
||||
| _ -> entry
|
||||
) entries in
|
||||
let arity = match find2 "arity" "vc-arity" with
|
||||
| Some (Integer n) -> n | Some (Number n) -> int_of_float n | _ -> 0
|
||||
| Some (Number n) -> int_of_float n | _ -> 0
|
||||
in
|
||||
let rest_arity = match find2 "rest-arity" "vc-rest-arity" with
|
||||
| Some (Integer n) -> n | Some (Number n) -> int_of_float n | _ -> -1
|
||||
| Some (Number n) -> int_of_float n | _ -> -1
|
||||
in
|
||||
(* Compute locals from bytecode: scan for highest LOCAL_GET/LOCAL_SET slot.
|
||||
The compiler's arity may undercount when nested lets add many locals. *)
|
||||
@@ -750,7 +749,10 @@ and run vm =
|
||||
| _ -> (Hashtbl.find Sx_primitives.primitives "/") [a; b])
|
||||
| 164 (* OP_EQ *) ->
|
||||
let b = pop vm and a = pop vm in
|
||||
push vm ((Hashtbl.find Sx_primitives.primitives "=") [a; b])
|
||||
let rec norm = function
|
||||
| ListRef { contents = l } -> List (List.map norm l)
|
||||
| List l -> List (List.map norm l) | v -> v in
|
||||
push vm (Bool (norm a = norm b))
|
||||
| 165 (* OP_LT *) ->
|
||||
let b = pop vm and a = pop vm in
|
||||
push vm (match a, b with
|
||||
@@ -769,10 +771,10 @@ and run vm =
|
||||
| 168 (* OP_LEN *) ->
|
||||
let v = pop vm in
|
||||
push vm (match v with
|
||||
| List l | ListRef { contents = l } -> Integer (List.length l)
|
||||
| String s -> Integer (String.length s)
|
||||
| Dict d -> Integer (Hashtbl.length d)
|
||||
| Nil -> Integer 0
|
||||
| List l | ListRef { contents = l } -> Number (float_of_int (List.length l))
|
||||
| String s -> Number (float_of_int (String.length s))
|
||||
| Dict d -> Number (float_of_int (Hashtbl.length d))
|
||||
| Nil -> Number 0.0
|
||||
| _ -> (Hashtbl.find Sx_primitives.primitives "len") [v])
|
||||
| 169 (* OP_FIRST *) ->
|
||||
let v = pop vm in
|
||||
|
||||
@@ -256,7 +256,6 @@
|
||||
"callcc-continuation?"
|
||||
"callcc-continuation-data"
|
||||
"make-callcc-continuation"
|
||||
"callcc-continuation-winders-len"
|
||||
"dynamic-wind-call"
|
||||
"strip-prefix"
|
||||
"component-set-param-types!"
|
||||
@@ -296,8 +295,7 @@
|
||||
"*bind-tracking*"
|
||||
"*provide-batch-depth*"
|
||||
"*provide-batch-queue*"
|
||||
"*provide-subscribers*"
|
||||
"*winders*"))
|
||||
"*provide-subscribers*"))
|
||||
|
||||
(define
|
||||
ml-is-mutable-global?
|
||||
@@ -535,13 +533,13 @@
|
||||
"; cf_env = "
|
||||
(ef "env")
|
||||
"; cf_name = "
|
||||
(if (= frame-type "if") (ef "else") (cond (some (fn (k) (= k "body-result")) items) (ef "body-result") :else (ef "name")))
|
||||
(if (= frame-type "if") (ef "else") (ef "name"))
|
||||
"; cf_body = "
|
||||
(if (= frame-type "if") (ef "then") (ef "body"))
|
||||
"; cf_remaining = "
|
||||
(ef "remaining")
|
||||
"; cf_f = "
|
||||
(cond (some (fn (k) (= k "after-thunk")) items) (ef "after-thunk") (some (fn (k) (= k "f")) items) (ef "f") :else "Nil")
|
||||
(ef "f")
|
||||
"; cf_args = "
|
||||
(cond
|
||||
(some (fn (k) (= k "evaled")) items)
|
||||
@@ -584,8 +582,6 @@
|
||||
(ef "prev-tracking")
|
||||
(some (fn (k) (= k "extra")) items)
|
||||
(ef "extra")
|
||||
(some (fn (k) (= k "winders-len")) items)
|
||||
(ef "winders-len")
|
||||
:else "Nil")
|
||||
"; cf_extra2 = "
|
||||
(cond
|
||||
|
||||
@@ -1,289 +0,0 @@
|
||||
;; lib/apl/runtime.sx — APL primitives on SX
|
||||
;;
|
||||
;; APL vectors are represented as SX lists (functional, immutable results).
|
||||
;; Operations are rank-polymorphic: scalar/vector arguments both accepted.
|
||||
;; Index origin: 1 (traditional APL).
|
||||
;;
|
||||
;; Primitives used:
|
||||
;; map (multi-arg, Phase 1)
|
||||
;; bitwise-and/or/xor/not/arithmetic-shift (Phase 7)
|
||||
;; make-set/set-member?/set-add!/set->list (Phase 18)
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 1. Core vector constructors
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
;; ⍳N — iota: generate integer vector 1, 2, ..., N
|
||||
(define
|
||||
(apl-iota n)
|
||||
(letrec
|
||||
((go (fn (i acc) (if (< i 1) acc (go (- i 1) (cons i acc))))))
|
||||
(go n (list))))
|
||||
|
||||
;; ⍴A — shape (length of a vector)
|
||||
(define (apl-rho v) (if (list? v) (len v) 1))
|
||||
|
||||
;; A[I] — 1-indexed access
|
||||
(define (apl-at v i) (nth v (- i 1)))
|
||||
|
||||
;; Scalar predicate
|
||||
(define (apl-scalar? v) (not (list? v)))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 2. Rank-polymorphic helpers
|
||||
;; dyadic: scalar/vector × scalar/vector → scalar/vector
|
||||
;; monadic: scalar/vector → scalar/vector
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(define
|
||||
(apl-dyadic op a b)
|
||||
(cond
|
||||
((and (list? a) (list? b)) (map op a b))
|
||||
((list? a) (map (fn (x) (op x b)) a))
|
||||
((list? b) (map (fn (y) (op a y)) b))
|
||||
(else (op a b))))
|
||||
|
||||
(define (apl-monadic op a) (if (list? a) (map op a) (op a)))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 3. Arithmetic (element-wise, rank-polymorphic)
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(define (apl-add a b) (apl-dyadic + a b))
|
||||
(define (apl-sub a b) (apl-dyadic - a b))
|
||||
(define (apl-mul a b) (apl-dyadic * a b))
|
||||
(define (apl-div a b) (apl-dyadic / a b))
|
||||
(define (apl-mod a b) (apl-dyadic modulo a b))
|
||||
(define (apl-pow a b) (apl-dyadic pow a b))
|
||||
(define (apl-max a b) (apl-dyadic (fn (x y) (if (> x y) x y)) a b))
|
||||
(define (apl-min a b) (apl-dyadic (fn (x y) (if (< x y) x y)) a b))
|
||||
|
||||
(define (apl-neg a) (apl-monadic (fn (x) (- 0 x)) a))
|
||||
(define (apl-abs a) (apl-monadic abs a))
|
||||
(define (apl-floor a) (apl-monadic floor a))
|
||||
(define (apl-ceil a) (apl-monadic ceil a))
|
||||
(define (apl-sqrt a) (apl-monadic sqrt a))
|
||||
(define (apl-exp a) (apl-monadic exp a))
|
||||
(define (apl-log a) (apl-monadic log a))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 4. Comparison (element-wise, returns 0/1 booleans)
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(define (apl-bool v) (if v 1 0))
|
||||
|
||||
(define (apl-eq a b) (apl-dyadic (fn (x y) (apl-bool (= x y))) a b))
|
||||
(define
|
||||
(apl-neq a b)
|
||||
(apl-dyadic (fn (x y) (apl-bool (not (= x y)))) a b))
|
||||
(define (apl-lt a b) (apl-dyadic (fn (x y) (apl-bool (< x y))) a b))
|
||||
(define (apl-le a b) (apl-dyadic (fn (x y) (apl-bool (<= x y))) a b))
|
||||
(define (apl-gt a b) (apl-dyadic (fn (x y) (apl-bool (> x y))) a b))
|
||||
(define (apl-ge a b) (apl-dyadic (fn (x y) (apl-bool (>= x y))) a b))
|
||||
|
||||
;; Boolean logic (0/1 vectors)
|
||||
(define
|
||||
(apl-and a b)
|
||||
(apl-dyadic
|
||||
(fn
|
||||
(x y)
|
||||
(if
|
||||
(and (not (= x 0)) (not (= y 0)))
|
||||
1
|
||||
0))
|
||||
a
|
||||
b))
|
||||
(define
|
||||
(apl-or a b)
|
||||
(apl-dyadic
|
||||
(fn
|
||||
(x y)
|
||||
(if
|
||||
(or (not (= x 0)) (not (= y 0)))
|
||||
1
|
||||
0))
|
||||
a
|
||||
b))
|
||||
(define
|
||||
(apl-not a)
|
||||
(apl-monadic (fn (x) (if (= x 0) 1 0)) a))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 5. Bitwise operations (element-wise)
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(define (apl-bitand a b) (apl-dyadic bitwise-and a b))
|
||||
(define (apl-bitor a b) (apl-dyadic bitwise-or a b))
|
||||
(define (apl-bitxor a b) (apl-dyadic bitwise-xor a b))
|
||||
(define (apl-bitnot a) (apl-monadic bitwise-not a))
|
||||
(define
|
||||
(apl-lshift a b)
|
||||
(apl-dyadic (fn (x n) (arithmetic-shift x n)) a b))
|
||||
(define
|
||||
(apl-rshift a b)
|
||||
(apl-dyadic (fn (x n) (arithmetic-shift x (- 0 n))) a b))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 6. Reduction (fold) and scan
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(define (apl-reduce-add v) (reduce + 0 v))
|
||||
(define (apl-reduce-mul v) (reduce * 1 v))
|
||||
(define
|
||||
(apl-reduce-max v)
|
||||
(reduce (fn (acc x) (if (> acc x) acc x)) (first v) (rest v)))
|
||||
(define
|
||||
(apl-reduce-min v)
|
||||
(reduce (fn (acc x) (if (< acc x) acc x)) (first v) (rest v)))
|
||||
(define
|
||||
(apl-reduce-and v)
|
||||
(reduce
|
||||
(fn
|
||||
(acc x)
|
||||
(if
|
||||
(and (not (= acc 0)) (not (= x 0)))
|
||||
1
|
||||
0))
|
||||
1
|
||||
v))
|
||||
(define
|
||||
(apl-reduce-or v)
|
||||
(reduce
|
||||
(fn
|
||||
(acc x)
|
||||
(if
|
||||
(or (not (= acc 0)) (not (= x 0)))
|
||||
1
|
||||
0))
|
||||
0
|
||||
v))
|
||||
|
||||
;; Scan: prefix reduction (yields a vector of running totals)
|
||||
(define
|
||||
(apl-scan op v)
|
||||
(if
|
||||
(= (len v) 0)
|
||||
(list)
|
||||
(letrec
|
||||
((go (fn (xs acc result) (if (= (len xs) 0) (reverse result) (let ((next (op acc (first xs)))) (go (rest xs) next (cons next result)))))))
|
||||
(go (rest v) (first v) (list (first v))))))
|
||||
|
||||
(define (apl-scan-add v) (apl-scan + v))
|
||||
(define (apl-scan-mul v) (apl-scan * v))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 7. Vector manipulation
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
;; ⌽A — reverse
|
||||
(define (apl-reverse v) (reverse v))
|
||||
|
||||
;; A,B — catenate
|
||||
(define
|
||||
(apl-cat a b)
|
||||
(cond
|
||||
((and (list? a) (list? b)) (append a b))
|
||||
((list? a) (append a (list b)))
|
||||
((list? b) (cons a b))
|
||||
(else (list a b))))
|
||||
|
||||
;; ↑N A — take first N elements (negative: take last N)
|
||||
(define
|
||||
(apl-take n v)
|
||||
(if
|
||||
(>= n 0)
|
||||
(letrec
|
||||
((go (fn (xs i) (if (or (= i 0) (= (len xs) 0)) (list) (cons (first xs) (go (rest xs) (- i 1)))))))
|
||||
(go v n))
|
||||
(apl-reverse (apl-take (- 0 n) (apl-reverse v)))))
|
||||
|
||||
;; ↓N A — drop first N elements
|
||||
(define
|
||||
(apl-drop n v)
|
||||
(if
|
||||
(>= n 0)
|
||||
(letrec
|
||||
((go (fn (xs i) (if (or (= i 0) (= (len xs) 0)) xs (go (rest xs) (- i 1))))))
|
||||
(go v n))
|
||||
(apl-reverse (apl-drop (- 0 n) (apl-reverse v)))))
|
||||
|
||||
;; Rotate left by n positions
|
||||
(define
|
||||
(apl-rotate n v)
|
||||
(let ((m (modulo n (len v)))) (append (apl-drop m v) (apl-take m v))))
|
||||
|
||||
;; Compression: A/B — select elements of B where A is 1
|
||||
(define
|
||||
(apl-compress mask v)
|
||||
(if
|
||||
(= (len mask) 0)
|
||||
(list)
|
||||
(let
|
||||
((rest-result (apl-compress (rest mask) (rest v))))
|
||||
(if
|
||||
(not (= (first mask) 0))
|
||||
(cons (first v) rest-result)
|
||||
rest-result))))
|
||||
|
||||
;; Indexing: A[B] — select elements at indices B (1-indexed)
|
||||
(define (apl-index v indices) (map (fn (i) (apl-at v i)) indices))
|
||||
|
||||
;; Grade up: indices that would sort the vector ascending
|
||||
(define
|
||||
(apl-grade-up v)
|
||||
(let
|
||||
((indexed (map (fn (x i) (list x i)) v (apl-iota (len v)))))
|
||||
(map (fn (p) (nth p 1)) (sort indexed))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 8. Set operations (∊ ∪ ∩ ~)
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
;; Membership ∊: for each element in A, is it in B? → 0/1 vector
|
||||
(define
|
||||
(apl-member a b)
|
||||
(let
|
||||
((bset (let ((s (make-set))) (for-each (fn (x) (set-add! s x)) b) s)))
|
||||
(if
|
||||
(list? a)
|
||||
(map (fn (x) (apl-bool (set-member? bset x))) a)
|
||||
(apl-bool (set-member? bset a)))))
|
||||
|
||||
;; Nub ∪A — unique elements, preserving order
|
||||
(define
|
||||
(apl-nub v)
|
||||
(let
|
||||
((seen (make-set)))
|
||||
(letrec
|
||||
((go (fn (xs acc) (if (= (len xs) 0) (reverse acc) (if (set-member? seen (first xs)) (go (rest xs) acc) (begin (set-add! seen (first xs)) (go (rest xs) (cons (first xs) acc))))))))
|
||||
(go v (list)))))
|
||||
|
||||
;; Union A∪B — nub of concatenation
|
||||
(define (apl-union a b) (apl-nub (apl-cat a b)))
|
||||
|
||||
;; Intersection A∩B
|
||||
(define
|
||||
(apl-intersect a b)
|
||||
(let
|
||||
((bset (let ((s (make-set))) (for-each (fn (x) (set-add! s x)) b) s)))
|
||||
(filter (fn (x) (set-member? bset x)) a)))
|
||||
|
||||
;; Without A~B
|
||||
(define
|
||||
(apl-without a b)
|
||||
(let
|
||||
((bset (let ((s (make-set))) (for-each (fn (x) (set-add! s x)) b) s)))
|
||||
(filter (fn (x) (not (set-member? bset x))) a)))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 9. Format (⍕) — APL-style display
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(define
|
||||
(apl-format v)
|
||||
(if
|
||||
(list? v)
|
||||
(letrec
|
||||
((go (fn (xs acc) (if (= (len xs) 0) acc (go (rest xs) (str acc (if (= acc "") "" " ") (str (first xs))))))))
|
||||
(go v ""))
|
||||
(str v)))
|
||||
@@ -1,51 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# lib/apl/test.sh — smoke-test the APL runtime layer.
|
||||
|
||||
set -uo pipefail
|
||||
cd "$(git rev-parse --show-toplevel)"
|
||||
|
||||
SX_SERVER="${SX_SERVER:-hosts/ocaml/_build/default/bin/sx_server.exe}"
|
||||
if [ ! -x "$SX_SERVER" ]; then
|
||||
SX_SERVER="/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe"
|
||||
fi
|
||||
if [ ! -x "$SX_SERVER" ]; then
|
||||
echo "ERROR: sx_server.exe not found."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TMPFILE=$(mktemp); trap "rm -f $TMPFILE" EXIT
|
||||
|
||||
cat > "$TMPFILE" << 'EPOCHS'
|
||||
(epoch 1)
|
||||
(load "spec/stdlib.sx")
|
||||
(load "lib/apl/runtime.sx")
|
||||
(epoch 2)
|
||||
(load "lib/apl/tests/runtime.sx")
|
||||
(epoch 3)
|
||||
(eval "(list apl-test-pass apl-test-fail)")
|
||||
EPOCHS
|
||||
|
||||
OUTPUT=$(timeout 60 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
|
||||
|
||||
LINE=$(echo "$OUTPUT" | awk '/^\(ok-len 3 / {getline; print; exit}')
|
||||
if [ -z "$LINE" ]; then
|
||||
LINE=$(echo "$OUTPUT" | grep -E '^\(ok 3 \([0-9]+ [0-9]+\)\)' | tail -1 \
|
||||
| sed -E 's/^\(ok 3 //; s/\)$//')
|
||||
fi
|
||||
if [ -z "$LINE" ]; then
|
||||
echo "ERROR: could not extract summary"
|
||||
echo "$OUTPUT" | tail -10
|
||||
exit 1
|
||||
fi
|
||||
|
||||
P=$(echo "$LINE" | sed -E 's/^\(([0-9]+) ([0-9]+)\).*/\1/')
|
||||
F=$(echo "$LINE" | sed -E 's/^\(([0-9]+) ([0-9]+)\).*/\2/')
|
||||
TOTAL=$((P + F))
|
||||
|
||||
if [ "$F" -eq 0 ]; then
|
||||
echo "ok $P/$TOTAL lib/apl tests passed"
|
||||
else
|
||||
echo "FAIL $P/$TOTAL passed, $F failed"
|
||||
fi
|
||||
|
||||
[ "$F" -eq 0 ]
|
||||
@@ -1,327 +0,0 @@
|
||||
;; lib/apl/tests/runtime.sx — Tests for lib/apl/runtime.sx
|
||||
|
||||
;; --- Test framework ---
|
||||
(define apl-test-pass 0)
|
||||
(define apl-test-fail 0)
|
||||
(define apl-test-fails (list))
|
||||
|
||||
(define
|
||||
(apl-test name got expected)
|
||||
(if
|
||||
(= got expected)
|
||||
(set! apl-test-pass (+ apl-test-pass 1))
|
||||
(begin
|
||||
(set! apl-test-fail (+ apl-test-fail 1))
|
||||
(set! apl-test-fails (append apl-test-fails (list {:got got :expected expected :name name}))))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 1. Core vector constructors
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(apl-test
|
||||
"iota 5"
|
||||
(apl-iota 5)
|
||||
(list 1 2 3 4 5))
|
||||
(apl-test "iota 1" (apl-iota 1) (list 1))
|
||||
(apl-test "iota 0" (apl-iota 0) (list))
|
||||
(apl-test
|
||||
"rho list"
|
||||
(apl-rho (list 1 2 3))
|
||||
3)
|
||||
(apl-test "rho scalar" (apl-rho 42) 1)
|
||||
(apl-test
|
||||
"at 1"
|
||||
(apl-at (list 10 20 30) 1)
|
||||
10)
|
||||
(apl-test
|
||||
"at 3"
|
||||
(apl-at (list 10 20 30) 3)
|
||||
30)
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 2. Arithmetic — element-wise and rank-polymorphic
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(apl-test
|
||||
"add v+v"
|
||||
(apl-add
|
||||
(list 1 2 3)
|
||||
(list 10 20 30))
|
||||
(list 11 22 33))
|
||||
(apl-test
|
||||
"add s+v"
|
||||
(apl-add 10 (list 1 2 3))
|
||||
(list 11 12 13))
|
||||
(apl-test
|
||||
"add v+s"
|
||||
(apl-add (list 1 2 3) 100)
|
||||
(list 101 102 103))
|
||||
(apl-test "add s+s" (apl-add 3 4) 7)
|
||||
(apl-test
|
||||
"sub v-v"
|
||||
(apl-sub
|
||||
(list 5 4 3)
|
||||
(list 1 2 3))
|
||||
(list 4 2 0))
|
||||
(apl-test
|
||||
"mul v*s"
|
||||
(apl-mul (list 1 2 3) 3)
|
||||
(list 3 6 9))
|
||||
(apl-test
|
||||
"neg -v"
|
||||
(apl-neg (list 1 -2 3))
|
||||
(list -1 2 -3))
|
||||
(apl-test
|
||||
"abs v"
|
||||
(apl-abs (list -1 2 -3))
|
||||
(list 1 2 3))
|
||||
(apl-test
|
||||
"floor v"
|
||||
(apl-floor (list 1.7 2.2 3.9))
|
||||
(list 1 2 3))
|
||||
(apl-test
|
||||
"ceil v"
|
||||
(apl-ceil (list 1.1 2.5 3))
|
||||
(list 2 3 3))
|
||||
(apl-test
|
||||
"max v v"
|
||||
(apl-max
|
||||
(list 1 5 3)
|
||||
(list 4 2 6))
|
||||
(list 4 5 6))
|
||||
(apl-test
|
||||
"min v v"
|
||||
(apl-min
|
||||
(list 1 5 3)
|
||||
(list 4 2 6))
|
||||
(list 1 2 3))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 3. Comparison (returns 0/1)
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(apl-test "eq 3 3" (apl-eq 3 3) 1)
|
||||
(apl-test "eq 3 4" (apl-eq 3 4) 0)
|
||||
(apl-test
|
||||
"gt v>s"
|
||||
(apl-gt (list 1 5 3 7) 4)
|
||||
(list 0 1 0 1))
|
||||
(apl-test
|
||||
"lt v<v"
|
||||
(apl-lt
|
||||
(list 1 2 3)
|
||||
(list 3 2 1))
|
||||
(list 1 0 0))
|
||||
(apl-test
|
||||
"le v<=s"
|
||||
(apl-le (list 3 4 5) 4)
|
||||
(list 1 1 0))
|
||||
(apl-test
|
||||
"ge v>=s"
|
||||
(apl-ge (list 3 4 5) 4)
|
||||
(list 0 1 1))
|
||||
(apl-test
|
||||
"neq v!=s"
|
||||
(apl-neq (list 1 2 3) 2)
|
||||
(list 1 0 1))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 4. Boolean logic (0/1 values)
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(apl-test "and 1 1" (apl-and 1 1) 1)
|
||||
(apl-test "and 1 0" (apl-and 1 0) 0)
|
||||
(apl-test "or 0 1" (apl-or 0 1) 1)
|
||||
(apl-test "or 0 0" (apl-or 0 0) 0)
|
||||
(apl-test "not 0" (apl-not 0) 1)
|
||||
(apl-test "not 1" (apl-not 1) 0)
|
||||
(apl-test
|
||||
"not vec"
|
||||
(apl-not (list 1 0 1 0))
|
||||
(list 0 1 0 1))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 5. Bitwise operations
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(apl-test "bitand s" (apl-bitand 5 3) 1)
|
||||
(apl-test "bitor s" (apl-bitor 5 3) 7)
|
||||
(apl-test "bitxor s" (apl-bitxor 5 3) 6)
|
||||
(apl-test "bitnot 0" (apl-bitnot 0) -1)
|
||||
(apl-test "lshift 1 4" (apl-lshift 1 4) 16)
|
||||
(apl-test "rshift 16 2" (apl-rshift 16 2) 4)
|
||||
(apl-test
|
||||
"bitand vec"
|
||||
(apl-bitand (list 5 6) (list 3 7))
|
||||
(list 1 6))
|
||||
(apl-test
|
||||
"bitor vec"
|
||||
(apl-bitor (list 5 6) (list 3 7))
|
||||
(list 7 7))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 6. Reduction and scan
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(apl-test
|
||||
"reduce-add"
|
||||
(apl-reduce-add
|
||||
(list 1 2 3 4 5))
|
||||
15)
|
||||
(apl-test
|
||||
"reduce-mul"
|
||||
(apl-reduce-mul (list 1 2 3 4))
|
||||
24)
|
||||
(apl-test
|
||||
"reduce-max"
|
||||
(apl-reduce-max
|
||||
(list 3 1 4 1 5))
|
||||
5)
|
||||
(apl-test
|
||||
"reduce-min"
|
||||
(apl-reduce-min
|
||||
(list 3 1 4 1 5))
|
||||
1)
|
||||
(apl-test
|
||||
"reduce-and"
|
||||
(apl-reduce-and (list 1 1 1))
|
||||
1)
|
||||
(apl-test
|
||||
"reduce-and0"
|
||||
(apl-reduce-and (list 1 0 1))
|
||||
0)
|
||||
(apl-test
|
||||
"reduce-or"
|
||||
(apl-reduce-or (list 0 1 0))
|
||||
1)
|
||||
(apl-test
|
||||
"scan-add"
|
||||
(apl-scan-add (list 1 2 3 4))
|
||||
(list 1 3 6 10))
|
||||
(apl-test
|
||||
"scan-mul"
|
||||
(apl-scan-mul (list 1 2 3 4))
|
||||
(list 1 2 6 24))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 7. Vector manipulation
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(apl-test
|
||||
"reverse"
|
||||
(apl-reverse (list 1 2 3 4))
|
||||
(list 4 3 2 1))
|
||||
(apl-test
|
||||
"cat v v"
|
||||
(apl-cat (list 1 2) (list 3 4))
|
||||
(list 1 2 3 4))
|
||||
(apl-test
|
||||
"cat v s"
|
||||
(apl-cat (list 1 2) 3)
|
||||
(list 1 2 3))
|
||||
(apl-test
|
||||
"cat s v"
|
||||
(apl-cat 1 (list 2 3))
|
||||
(list 1 2 3))
|
||||
(apl-test
|
||||
"cat s s"
|
||||
(apl-cat 1 2)
|
||||
(list 1 2))
|
||||
(apl-test
|
||||
"take 3"
|
||||
(apl-take
|
||||
3
|
||||
(list 10 20 30 40 50))
|
||||
(list 10 20 30))
|
||||
(apl-test
|
||||
"take 0"
|
||||
(apl-take 0 (list 1 2 3))
|
||||
(list))
|
||||
(apl-test
|
||||
"take neg"
|
||||
(apl-take -2 (list 10 20 30))
|
||||
(list 20 30))
|
||||
(apl-test
|
||||
"drop 2"
|
||||
(apl-drop 2 (list 10 20 30 40))
|
||||
(list 30 40))
|
||||
(apl-test
|
||||
"drop neg"
|
||||
(apl-drop -1 (list 10 20 30))
|
||||
(list 10 20))
|
||||
(apl-test
|
||||
"rotate 2"
|
||||
(apl-rotate
|
||||
2
|
||||
(list 1 2 3 4 5))
|
||||
(list 3 4 5 1 2))
|
||||
(apl-test
|
||||
"compress"
|
||||
(apl-compress
|
||||
(list 1 0 1 0)
|
||||
(list 10 20 30 40))
|
||||
(list 10 30))
|
||||
(apl-test
|
||||
"index"
|
||||
(apl-index
|
||||
(list 10 20 30 40)
|
||||
(list 2 4))
|
||||
(list 20 40))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 8. Set operations
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(apl-test
|
||||
"member yes"
|
||||
(apl-member
|
||||
(list 1 2 5)
|
||||
(list 2 4 6))
|
||||
(list 0 1 0))
|
||||
(apl-test
|
||||
"member s"
|
||||
(apl-member 2 (list 1 2 3))
|
||||
1)
|
||||
(apl-test
|
||||
"member no"
|
||||
(apl-member 9 (list 1 2 3))
|
||||
0)
|
||||
(apl-test
|
||||
"nub"
|
||||
(apl-nub (list 1 2 1 3 2))
|
||||
(list 1 2 3))
|
||||
(apl-test
|
||||
"union"
|
||||
(apl-union
|
||||
(list 1 2 3)
|
||||
(list 2 3 4))
|
||||
(list 1 2 3 4))
|
||||
(apl-test
|
||||
"intersect"
|
||||
(apl-intersect
|
||||
(list 1 2 3 4)
|
||||
(list 2 4 6))
|
||||
(list 2 4))
|
||||
(apl-test
|
||||
"without"
|
||||
(apl-without
|
||||
(list 1 2 3 4)
|
||||
(list 2 4))
|
||||
(list 1 3))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 9. Format
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(apl-test
|
||||
"format vec"
|
||||
(apl-format (list 1 2 3))
|
||||
"1 2 3")
|
||||
(apl-test "format scalar" (apl-format 42) "42")
|
||||
(apl-test "format empty" (apl-format (list)) "")
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Summary
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(list apl-test-pass apl-test-fail)
|
||||
@@ -1,500 +0,0 @@
|
||||
;; lib/common-lisp/clos.sx — CLOS: classes, instances, generic functions
|
||||
;;
|
||||
;; Class records: {:clos-type "class" :name "NAME" :slots {...} :parents [...] :methods [...]}
|
||||
;; Instance: {:clos-type "instance" :class "NAME" :slots {slot: val ...}}
|
||||
;; Method: {:qualifiers [...] :specializers [...] :fn (fn (args next-fn) ...)}
|
||||
;;
|
||||
;; SX primitive notes:
|
||||
;; dict->list: use (map (fn (k) (list k (get d k))) (keys d))
|
||||
;; dict-set (pure): use assoc
|
||||
;; fn?/callable?: use callable?
|
||||
|
||||
;; ── dict helpers ───────────────────────────────────────────────────────────
|
||||
|
||||
(define
|
||||
clos-dict->list
|
||||
(fn (d) (map (fn (k) (list k (get d k))) (keys d))))
|
||||
|
||||
;; ── class registry ─────────────────────────────────────────────────────────
|
||||
|
||||
(define
|
||||
clos-class-registry
|
||||
(dict
|
||||
"t"
|
||||
{:parents (list) :clos-type "class" :slots (dict) :methods (list) :name "t"}
|
||||
"null"
|
||||
{:parents (list "t") :clos-type "class" :slots (dict) :methods (list) :name "null"}
|
||||
"integer"
|
||||
{:parents (list "t") :clos-type "class" :slots (dict) :methods (list) :name "integer"}
|
||||
"float"
|
||||
{:parents (list "t") :clos-type "class" :slots (dict) :methods (list) :name "float"}
|
||||
"string"
|
||||
{:parents (list "t") :clos-type "class" :slots (dict) :methods (list) :name "string"}
|
||||
"symbol"
|
||||
{:parents (list "t") :clos-type "class" :slots (dict) :methods (list) :name "symbol"}
|
||||
"cons"
|
||||
{:parents (list "t") :clos-type "class" :slots (dict) :methods (list) :name "cons"}
|
||||
"list"
|
||||
{:parents (list "t") :clos-type "class" :slots (dict) :methods (list) :name "list"}))
|
||||
|
||||
;; ── clos-generic-registry ─────────────────────────────────────────────────
|
||||
|
||||
(define clos-generic-registry (dict))
|
||||
|
||||
;; ── class-of ──────────────────────────────────────────────────────────────
|
||||
|
||||
(define
|
||||
clos-class-of
|
||||
(fn
|
||||
(x)
|
||||
(cond
|
||||
((nil? x) "null")
|
||||
((integer? x) "integer")
|
||||
((float? x) "float")
|
||||
((string? x) "string")
|
||||
((symbol? x) "symbol")
|
||||
((and (list? x) (> (len x) 0)) "cons")
|
||||
((and (list? x) (= (len x) 0)) "null")
|
||||
((and (dict? x) (= (get x "clos-type") "instance")) (get x "class"))
|
||||
(:else "t"))))
|
||||
|
||||
;; ── subclass-of? ──────────────────────────────────────────────────────────
|
||||
;;
|
||||
;; Captures clos-class-registry at define time to avoid free-variable issues.
|
||||
|
||||
(define
|
||||
clos-subclass-of?
|
||||
(let
|
||||
((registry clos-class-registry))
|
||||
(fn
|
||||
(class-name super-name)
|
||||
(if
|
||||
(= class-name super-name)
|
||||
true
|
||||
(let
|
||||
((rec (get registry class-name)))
|
||||
(if
|
||||
(nil? rec)
|
||||
false
|
||||
(some
|
||||
(fn (p) (clos-subclass-of? p super-name))
|
||||
(get rec "parents"))))))))
|
||||
|
||||
;; ── instance-of? ──────────────────────────────────────────────────────────
|
||||
|
||||
(define
|
||||
clos-instance-of?
|
||||
(fn (obj class-name) (clos-subclass-of? (clos-class-of obj) class-name)))
|
||||
|
||||
;; ── defclass ──────────────────────────────────────────────────────────────
|
||||
;;
|
||||
;; slot-specs: list of dicts with keys: name initarg initform accessor reader writer
|
||||
;; Each missing key defaults to nil.
|
||||
|
||||
(define clos-slot-spec (fn (spec) (if (string? spec) {:initform nil :initarg nil :reader nil :writer nil :accessor nil :name spec} spec)))
|
||||
|
||||
(define
|
||||
clos-defclass
|
||||
(fn
|
||||
(name parents slot-specs)
|
||||
(let
|
||||
((slots (dict)))
|
||||
(for-each
|
||||
(fn
|
||||
(pname)
|
||||
(let
|
||||
((prec (get clos-class-registry pname)))
|
||||
(when
|
||||
(not (nil? prec))
|
||||
(for-each
|
||||
(fn
|
||||
(k)
|
||||
(when
|
||||
(nil? (get slots k))
|
||||
(dict-set! slots k (get (get prec "slots") k))))
|
||||
(keys (get prec "slots"))))))
|
||||
parents)
|
||||
(for-each
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((spec (clos-slot-spec s)))
|
||||
(dict-set! slots (get spec "name") spec)))
|
||||
slot-specs)
|
||||
(let
|
||||
((class-rec {:parents parents :clos-type "class" :slots slots :methods (list) :name name}))
|
||||
(dict-set! clos-class-registry name class-rec)
|
||||
(clos-install-accessors-for name slots)
|
||||
name))))
|
||||
|
||||
;; ── accessor installation (forward-declared, defined after defmethod) ──────
|
||||
|
||||
(define
|
||||
clos-install-accessors-for
|
||||
(fn
|
||||
(class-name slots)
|
||||
(for-each
|
||||
(fn
|
||||
(k)
|
||||
(let
|
||||
((spec (get slots k)))
|
||||
(let
|
||||
((reader (get spec "reader")))
|
||||
(when
|
||||
(not (nil? reader))
|
||||
(clos-add-reader-method reader class-name k)))
|
||||
(let
|
||||
((accessor (get spec "accessor")))
|
||||
(when
|
||||
(not (nil? accessor))
|
||||
(clos-add-reader-method accessor class-name k)))))
|
||||
(keys slots))))
|
||||
|
||||
;; placeholder — real impl filled in after defmethod is defined
|
||||
(define clos-add-reader-method (fn (method-name class-name slot-name) nil))
|
||||
|
||||
;; ── make-instance ─────────────────────────────────────────────────────────
|
||||
|
||||
(define
|
||||
clos-make-instance
|
||||
(fn
|
||||
(class-name &rest initargs)
|
||||
(let
|
||||
((class-rec (get clos-class-registry class-name)))
|
||||
(if
|
||||
(nil? class-rec)
|
||||
(error (str "No class named: " class-name))
|
||||
(let
|
||||
((slots (dict)))
|
||||
(for-each
|
||||
(fn
|
||||
(k)
|
||||
(let
|
||||
((spec (get (get class-rec "slots") k)))
|
||||
(let
|
||||
((initform (get spec "initform")))
|
||||
(when
|
||||
(not (nil? initform))
|
||||
(dict-set!
|
||||
slots
|
||||
k
|
||||
(if (callable? initform) (initform) initform))))))
|
||||
(keys (get class-rec "slots")))
|
||||
(define
|
||||
apply-args
|
||||
(fn
|
||||
(args)
|
||||
(when
|
||||
(>= (len args) 2)
|
||||
(let
|
||||
((key (str (first args))) (val (first (rest args))))
|
||||
(let
|
||||
((skey (if (= (slice key 0 1) ":") (slice key 1 (len key)) key)))
|
||||
(let
|
||||
((matched false))
|
||||
(for-each
|
||||
(fn
|
||||
(sk)
|
||||
(let
|
||||
((spec (get (get class-rec "slots") sk)))
|
||||
(let
|
||||
((ia (get spec "initarg")))
|
||||
(when
|
||||
(or
|
||||
(= ia key)
|
||||
(= ia (str ":" skey))
|
||||
(= sk skey))
|
||||
(dict-set! slots sk val)
|
||||
(set! matched true)))))
|
||||
(keys (get class-rec "slots")))))
|
||||
(apply-args (rest (rest args)))))))
|
||||
(apply-args initargs)
|
||||
{:clos-type "instance" :slots slots :class class-name})))))
|
||||
|
||||
;; ── slot-value ────────────────────────────────────────────────────────────
|
||||
|
||||
(define
|
||||
clos-slot-value
|
||||
(fn
|
||||
(instance slot-name)
|
||||
(if
|
||||
(and (dict? instance) (= (get instance "clos-type") "instance"))
|
||||
(get (get instance "slots") slot-name)
|
||||
(error (str "Not a CLOS instance: " (inspect instance))))))
|
||||
|
||||
(define
|
||||
clos-set-slot-value!
|
||||
(fn
|
||||
(instance slot-name value)
|
||||
(if
|
||||
(and (dict? instance) (= (get instance "clos-type") "instance"))
|
||||
(dict-set! (get instance "slots") slot-name value)
|
||||
(error (str "Not a CLOS instance: " (inspect instance))))))
|
||||
|
||||
(define
|
||||
clos-slot-boundp
|
||||
(fn
|
||||
(instance slot-name)
|
||||
(and
|
||||
(dict? instance)
|
||||
(= (get instance "clos-type") "instance")
|
||||
(not (nil? (get (get instance "slots") slot-name))))))
|
||||
|
||||
;; ── find-class / change-class ─────────────────────────────────────────────
|
||||
|
||||
(define clos-find-class (fn (name) (get clos-class-registry name)))
|
||||
|
||||
(define
|
||||
clos-change-class!
|
||||
(fn
|
||||
(instance new-class-name)
|
||||
(if
|
||||
(and (dict? instance) (= (get instance "clos-type") "instance"))
|
||||
(dict-set! instance "class" new-class-name)
|
||||
(error (str "Not a CLOS instance: " (inspect instance))))))
|
||||
|
||||
;; ── defgeneric ────────────────────────────────────────────────────────────
|
||||
|
||||
(define
|
||||
clos-defgeneric
|
||||
(fn
|
||||
(name options)
|
||||
(let
|
||||
((combination (or (get options "method-combination") "standard")))
|
||||
(when
|
||||
(nil? (get clos-generic-registry name))
|
||||
(dict-set! clos-generic-registry name {:methods (list) :combination combination :name name}))
|
||||
name)))
|
||||
|
||||
;; ── defmethod ─────────────────────────────────────────────────────────────
|
||||
;;
|
||||
;; method-fn: (fn (args next-fn) body)
|
||||
;; args = list of all call arguments
|
||||
;; next-fn = (fn () next-method-result) or nil
|
||||
|
||||
(define
|
||||
clos-defmethod
|
||||
(fn
|
||||
(generic-name qualifiers specializers method-fn)
|
||||
(when
|
||||
(nil? (get clos-generic-registry generic-name))
|
||||
(clos-defgeneric generic-name {}))
|
||||
(let
|
||||
((grec (get clos-generic-registry generic-name))
|
||||
(new-method {:fn method-fn :qualifiers qualifiers :specializers specializers}))
|
||||
(let
|
||||
((kept (filter (fn (m) (not (and (= (get m "qualifiers") qualifiers) (= (get m "specializers") specializers)))) (get grec "methods"))))
|
||||
(dict-set!
|
||||
clos-generic-registry
|
||||
generic-name
|
||||
(assoc grec "methods" (append kept (list new-method))))
|
||||
generic-name))))
|
||||
|
||||
;; Now install the real accessor-method installer
|
||||
(set!
|
||||
clos-add-reader-method
|
||||
(fn
|
||||
(method-name class-name slot-name)
|
||||
(clos-defmethod
|
||||
method-name
|
||||
(list)
|
||||
(list class-name)
|
||||
(fn (args next-fn) (clos-slot-value (first args) slot-name)))))
|
||||
|
||||
;; ── method specificity ─────────────────────────────────────────────────────
|
||||
|
||||
(define
|
||||
clos-method-matches?
|
||||
(fn
|
||||
(method args)
|
||||
(let
|
||||
((specs (get method "specializers")))
|
||||
(if
|
||||
(> (len specs) (len args))
|
||||
false
|
||||
(define
|
||||
check-all
|
||||
(fn
|
||||
(i)
|
||||
(if
|
||||
(>= i (len specs))
|
||||
true
|
||||
(let
|
||||
((spec (nth specs i)) (arg (nth args i)))
|
||||
(if
|
||||
(= spec "t")
|
||||
(check-all (+ i 1))
|
||||
(if
|
||||
(clos-instance-of? arg spec)
|
||||
(check-all (+ i 1))
|
||||
false))))))
|
||||
(check-all 0)))))
|
||||
|
||||
;; Precedence distance: how far class-name is from spec-name up the hierarchy.
|
||||
(define
|
||||
clos-specificity
|
||||
(let
|
||||
((registry clos-class-registry))
|
||||
(fn
|
||||
(class-name spec-name)
|
||||
(define
|
||||
walk
|
||||
(fn
|
||||
(cn depth)
|
||||
(if
|
||||
(= cn spec-name)
|
||||
depth
|
||||
(let
|
||||
((rec (get registry cn)))
|
||||
(if
|
||||
(nil? rec)
|
||||
nil
|
||||
(let
|
||||
((results (map (fn (p) (walk p (+ depth 1))) (get rec "parents"))))
|
||||
(let
|
||||
((non-nil (filter (fn (x) (not (nil? x))) results)))
|
||||
(if
|
||||
(empty? non-nil)
|
||||
nil
|
||||
(reduce
|
||||
(fn (a b) (if (< a b) a b))
|
||||
(first non-nil)
|
||||
(rest non-nil))))))))))
|
||||
(walk class-name 0))))
|
||||
|
||||
(define
|
||||
clos-method-more-specific?
|
||||
(fn
|
||||
(m1 m2 args)
|
||||
(let
|
||||
((s1 (get m1 "specializers")) (s2 (get m2 "specializers")))
|
||||
(define
|
||||
cmp
|
||||
(fn
|
||||
(i)
|
||||
(if
|
||||
(>= i (len s1))
|
||||
false
|
||||
(let
|
||||
((c1 (clos-specificity (clos-class-of (nth args i)) (nth s1 i)))
|
||||
(c2
|
||||
(clos-specificity (clos-class-of (nth args i)) (nth s2 i))))
|
||||
(cond
|
||||
((and (nil? c1) (nil? c2)) (cmp (+ i 1)))
|
||||
((nil? c1) false)
|
||||
((nil? c2) true)
|
||||
((< c1 c2) true)
|
||||
((> c1 c2) false)
|
||||
(:else (cmp (+ i 1))))))))
|
||||
(cmp 0))))
|
||||
|
||||
(define
|
||||
clos-sort-methods
|
||||
(fn
|
||||
(methods args)
|
||||
(define
|
||||
insert
|
||||
(fn
|
||||
(m sorted)
|
||||
(if
|
||||
(empty? sorted)
|
||||
(list m)
|
||||
(if
|
||||
(clos-method-more-specific? m (first sorted) args)
|
||||
(cons m sorted)
|
||||
(cons (first sorted) (insert m (rest sorted)))))))
|
||||
(reduce (fn (acc m) (insert m acc)) (list) methods)))
|
||||
|
||||
;; ── call-generic (standard method combination) ─────────────────────────────
|
||||
|
||||
(define
|
||||
clos-call-generic
|
||||
(fn
|
||||
(generic-name args)
|
||||
(let
|
||||
((grec (get clos-generic-registry generic-name)))
|
||||
(if
|
||||
(nil? grec)
|
||||
(error (str "No generic function: " generic-name))
|
||||
(let
|
||||
((applicable (filter (fn (m) (clos-method-matches? m args)) (get grec "methods"))))
|
||||
(if
|
||||
(empty? applicable)
|
||||
(error
|
||||
(str
|
||||
"No applicable method for "
|
||||
generic-name
|
||||
" with classes "
|
||||
(inspect (map clos-class-of args))))
|
||||
(let
|
||||
((primary (filter (fn (m) (empty? (get m "qualifiers"))) applicable))
|
||||
(before
|
||||
(filter
|
||||
(fn (m) (= (get m "qualifiers") (list "before")))
|
||||
applicable))
|
||||
(after
|
||||
(filter
|
||||
(fn (m) (= (get m "qualifiers") (list "after")))
|
||||
applicable))
|
||||
(around
|
||||
(filter
|
||||
(fn (m) (= (get m "qualifiers") (list "around")))
|
||||
applicable)))
|
||||
(let
|
||||
((sp (clos-sort-methods primary args))
|
||||
(sb (clos-sort-methods before args))
|
||||
(sa (clos-sort-methods after args))
|
||||
(sw (clos-sort-methods around args)))
|
||||
(define
|
||||
make-primary-chain
|
||||
(fn
|
||||
(methods)
|
||||
(if
|
||||
(empty? methods)
|
||||
(fn
|
||||
()
|
||||
(error (str "No next primary method: " generic-name)))
|
||||
(fn
|
||||
()
|
||||
((get (first methods) "fn")
|
||||
args
|
||||
(make-primary-chain (rest methods)))))))
|
||||
(define
|
||||
make-around-chain
|
||||
(fn
|
||||
(around-methods inner-thunk)
|
||||
(if
|
||||
(empty? around-methods)
|
||||
inner-thunk
|
||||
(fn
|
||||
()
|
||||
((get (first around-methods) "fn")
|
||||
args
|
||||
(make-around-chain
|
||||
(rest around-methods)
|
||||
inner-thunk))))))
|
||||
(for-each (fn (m) ((get m "fn") args (fn () nil))) sb)
|
||||
(let
|
||||
((primary-thunk (make-primary-chain sp)))
|
||||
(let
|
||||
((result (if (empty? sw) (primary-thunk) ((make-around-chain sw primary-thunk)))))
|
||||
(for-each
|
||||
(fn (m) ((get m "fn") args (fn () nil)))
|
||||
(reverse sa))
|
||||
result))))))))))
|
||||
|
||||
;; ── call-next-method / next-method-p ──────────────────────────────────────
|
||||
|
||||
(define clos-call-next-method (fn (next-fn) (next-fn)))
|
||||
|
||||
(define clos-next-method-p (fn (next-fn) (not (nil? next-fn))))
|
||||
|
||||
;; ── with-slots ────────────────────────────────────────────────────────────
|
||||
|
||||
(define
|
||||
clos-with-slots
|
||||
(fn
|
||||
(instance slot-names body-fn)
|
||||
(let
|
||||
((vals (map (fn (s) (clos-slot-value instance s)) slot-names)))
|
||||
(apply body-fn vals))))
|
||||
@@ -1,161 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# lib/common-lisp/conformance.sh — CL-on-SX conformance test runner
|
||||
#
|
||||
# Runs all Common Lisp test suites and writes scoreboard.json + scoreboard.md.
|
||||
#
|
||||
# Usage:
|
||||
# bash lib/common-lisp/conformance.sh
|
||||
# bash lib/common-lisp/conformance.sh -v
|
||||
|
||||
set -uo pipefail
|
||||
cd "$(git rev-parse --show-toplevel)"
|
||||
|
||||
SX_SERVER="${SX_SERVER:-hosts/ocaml/_build/default/bin/sx_server.exe}"
|
||||
if [ ! -x "$SX_SERVER" ]; then
|
||||
SX_SERVER="/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe"
|
||||
fi
|
||||
if [ ! -x "$SX_SERVER" ]; then
|
||||
echo "ERROR: sx_server.exe not found."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERBOSE="${1:-}"
|
||||
TOTAL_PASS=0; TOTAL_FAIL=0
|
||||
SUITE_NAMES=()
|
||||
SUITE_PASS=()
|
||||
SUITE_FAIL=()
|
||||
|
||||
# run_suite NAME "file1 file2 ..." PASS_VAR FAIL_VAR FAILURES_VAR
|
||||
run_suite() {
|
||||
local name="$1" load_files="$2" pass_var="$3" fail_var="$4" failures_var="$5"
|
||||
local TMP; TMP=$(mktemp)
|
||||
{
|
||||
printf '(epoch 1)\n(load "spec/stdlib.sx")\n'
|
||||
local i=2
|
||||
for f in $load_files; do
|
||||
printf '(epoch %d)\n(load "%s")\n' "$i" "$f"
|
||||
i=$((i+1))
|
||||
done
|
||||
printf '(epoch 100)\n(eval "%s")\n' "$pass_var"
|
||||
printf '(epoch 101)\n(eval "%s")\n' "$fail_var"
|
||||
} > "$TMP"
|
||||
local OUT; OUT=$(timeout 30 "$SX_SERVER" < "$TMP" 2>/dev/null)
|
||||
rm -f "$TMP"
|
||||
local P F
|
||||
P=$(echo "$OUT" | grep -A1 "^(ok-len 100 " | tail -1 | tr -d ' ()' || true)
|
||||
F=$(echo "$OUT" | grep -A1 "^(ok-len 101 " | tail -1 | tr -d ' ()' || true)
|
||||
# Also try plain (ok 100 N) format
|
||||
[ -z "$P" ] && P=$(echo "$OUT" | grep "^(ok 100 " | awk '{print $3}' | tr -d ')' || true)
|
||||
[ -z "$F" ] && F=$(echo "$OUT" | grep "^(ok 101 " | awk '{print $3}' | tr -d ')' || true)
|
||||
[ -z "$P" ] && P=0; [ -z "$F" ] && F=0
|
||||
SUITE_NAMES+=("$name")
|
||||
SUITE_PASS+=("$P")
|
||||
SUITE_FAIL+=("$F")
|
||||
TOTAL_PASS=$((TOTAL_PASS + P))
|
||||
TOTAL_FAIL=$((TOTAL_FAIL + F))
|
||||
if [ "$F" = "0" ] && [ "${P:-0}" -gt 0 ] 2>/dev/null; then
|
||||
echo " PASS $name ($P tests)"
|
||||
else
|
||||
echo " FAIL $name ($P passed, $F failed)"
|
||||
fi
|
||||
}
|
||||
|
||||
echo "=== Common Lisp on SX — Conformance Run ==="
|
||||
echo ""
|
||||
|
||||
run_suite "Phase 1: tokenizer/reader" \
|
||||
"lib/common-lisp/reader.sx lib/common-lisp/tests/read.sx" \
|
||||
"cl-test-pass" "cl-test-fail" "cl-test-fails"
|
||||
|
||||
run_suite "Phase 1: parser/lambda-lists" \
|
||||
"lib/common-lisp/reader.sx lib/common-lisp/parser.sx lib/common-lisp/tests/lambda.sx" \
|
||||
"cl-test-pass" "cl-test-fail" "cl-test-fails"
|
||||
|
||||
run_suite "Phase 2: evaluator" \
|
||||
"lib/common-lisp/reader.sx lib/common-lisp/parser.sx lib/common-lisp/eval.sx lib/common-lisp/tests/eval.sx" \
|
||||
"cl-test-pass" "cl-test-fail" "cl-test-fails"
|
||||
|
||||
run_suite "Phase 3: condition system" \
|
||||
"lib/common-lisp/runtime.sx lib/common-lisp/tests/conditions.sx" \
|
||||
"passed" "failed" "failures"
|
||||
|
||||
run_suite "Phase 3: restart-demo" \
|
||||
"lib/common-lisp/runtime.sx lib/common-lisp/tests/programs/restart-demo.sx" \
|
||||
"demo-passed" "demo-failed" "demo-failures"
|
||||
|
||||
run_suite "Phase 3: parse-recover" \
|
||||
"lib/common-lisp/runtime.sx lib/common-lisp/tests/programs/parse-recover.sx" \
|
||||
"parse-passed" "parse-failed" "parse-failures"
|
||||
|
||||
run_suite "Phase 3: interactive-debugger" \
|
||||
"lib/common-lisp/runtime.sx lib/common-lisp/tests/programs/interactive-debugger.sx" \
|
||||
"debugger-passed" "debugger-failed" "debugger-failures"
|
||||
|
||||
run_suite "Phase 4: CLOS" \
|
||||
"lib/common-lisp/runtime.sx lib/common-lisp/clos.sx lib/common-lisp/tests/clos.sx" \
|
||||
"passed" "failed" "failures"
|
||||
|
||||
run_suite "Phase 4: geometry" \
|
||||
"lib/common-lisp/runtime.sx lib/common-lisp/clos.sx lib/common-lisp/tests/programs/geometry.sx" \
|
||||
"geo-passed" "geo-failed" "geo-failures"
|
||||
|
||||
run_suite "Phase 4: mop-trace" \
|
||||
"lib/common-lisp/runtime.sx lib/common-lisp/clos.sx lib/common-lisp/tests/programs/mop-trace.sx" \
|
||||
"mop-passed" "mop-failed" "mop-failures"
|
||||
|
||||
run_suite "Phase 5: macros+LOOP" \
|
||||
"lib/common-lisp/reader.sx lib/common-lisp/parser.sx lib/common-lisp/eval.sx lib/common-lisp/loop.sx lib/common-lisp/tests/macros.sx" \
|
||||
"macro-passed" "macro-failed" "macro-failures"
|
||||
|
||||
run_suite "Phase 6: stdlib" \
|
||||
"lib/common-lisp/reader.sx lib/common-lisp/parser.sx lib/common-lisp/eval.sx lib/common-lisp/tests/stdlib.sx" \
|
||||
"stdlib-passed" "stdlib-failed" "stdlib-failures"
|
||||
|
||||
echo ""
|
||||
echo "=== Total: $TOTAL_PASS passed, $TOTAL_FAIL failed ==="
|
||||
|
||||
# ── write scoreboard.json ─────────────────────────────────────────────────
|
||||
|
||||
SCORE_DIR="lib/common-lisp"
|
||||
JSON="$SCORE_DIR/scoreboard.json"
|
||||
{
|
||||
printf '{\n'
|
||||
printf ' "generated": "%s",\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
printf ' "total_pass": %d,\n' "$TOTAL_PASS"
|
||||
printf ' "total_fail": %d,\n' "$TOTAL_FAIL"
|
||||
printf ' "suites": [\n'
|
||||
first=true
|
||||
for i in "${!SUITE_NAMES[@]}"; do
|
||||
if [ "$first" = "true" ]; then first=false; else printf ',\n'; fi
|
||||
printf ' {"name": "%s", "pass": %d, "fail": %d}' \
|
||||
"${SUITE_NAMES[$i]}" "${SUITE_PASS[$i]}" "${SUITE_FAIL[$i]}"
|
||||
done
|
||||
printf '\n ]\n'
|
||||
printf '}\n'
|
||||
} > "$JSON"
|
||||
|
||||
# ── write scoreboard.md ───────────────────────────────────────────────────
|
||||
|
||||
MD="$SCORE_DIR/scoreboard.md"
|
||||
{
|
||||
printf '# Common Lisp on SX — Scoreboard\n\n'
|
||||
printf '_Generated: %s_\n\n' "$(date -u '+%Y-%m-%d %H:%M UTC')"
|
||||
printf '| Suite | Pass | Fail | Status |\n'
|
||||
printf '|-------|------|------|--------|\n'
|
||||
for i in "${!SUITE_NAMES[@]}"; do
|
||||
p="${SUITE_PASS[$i]}" f="${SUITE_FAIL[$i]}"
|
||||
status=""
|
||||
if [ "$f" = "0" ] && [ "${p:-0}" -gt 0 ] 2>/dev/null; then
|
||||
status="pass"
|
||||
else
|
||||
status="FAIL"
|
||||
fi
|
||||
printf '| %s | %s | %s | %s |\n' "${SUITE_NAMES[$i]}" "$p" "$f" "$status"
|
||||
done
|
||||
printf '\n**Total: %d passed, %d failed**\n' "$TOTAL_PASS" "$TOTAL_FAIL"
|
||||
} > "$MD"
|
||||
|
||||
echo ""
|
||||
echo "Scoreboard written to $JSON and $MD"
|
||||
|
||||
[ "$TOTAL_FAIL" -eq 0 ]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,623 +0,0 @@
|
||||
;; lib/common-lisp/loop.sx — The LOOP macro for CL-on-SX
|
||||
;;
|
||||
;; Supported clauses:
|
||||
;; for VAR in LIST — iterate over list
|
||||
;; for VAR across VECTOR — alias for 'in'
|
||||
;; for VAR from N — numeric iteration (to/upto/below/downto/above/by)
|
||||
;; for VAR = EXPR [then EXPR] — general iteration
|
||||
;; while COND — stop when false
|
||||
;; until COND — stop when true
|
||||
;; repeat N — repeat N times
|
||||
;; collect EXPR [into VAR]
|
||||
;; append EXPR [into VAR]
|
||||
;; nconc EXPR [into VAR]
|
||||
;; sum EXPR [into VAR]
|
||||
;; count EXPR [into VAR]
|
||||
;; maximize EXPR [into VAR]
|
||||
;; minimize EXPR [into VAR]
|
||||
;; do FORM...
|
||||
;; when/if COND clause...
|
||||
;; unless COND clause...
|
||||
;; finally FORM...
|
||||
;; always COND
|
||||
;; never COND
|
||||
;; thereis COND
|
||||
;; named BLOCK-NAME
|
||||
;;
|
||||
;; Depends on: lib/common-lisp/runtime.sx, lib/common-lisp/eval.sx already loaded.
|
||||
;; Uses defmacro in the CL evaluator.
|
||||
|
||||
;; ── LOOP expansion driver ─────────────────────────────────────────────────
|
||||
|
||||
;; cl-loop-parse: analyse the flat LOOP clause list and build a Lisp form.
|
||||
;; Returns a (block NAME (let (...) (tagbody ...))) form.
|
||||
(define
|
||||
cl-loop-parse
|
||||
(fn
|
||||
(clauses)
|
||||
(define block-name nil)
|
||||
(define with-bindings (list))
|
||||
(define for-bindings (list))
|
||||
(define test-forms (list))
|
||||
(define repeat-var nil)
|
||||
(define repeat-count nil)
|
||||
(define body-forms (list))
|
||||
(define accum-vars (dict))
|
||||
(define accum-clauses (dict))
|
||||
(define result-var nil)
|
||||
(define finally-forms (list))
|
||||
(define return-expr nil)
|
||||
(define termination nil)
|
||||
(define idx 0)
|
||||
(define (lp-peek) (if (< idx (len clauses)) (nth clauses idx) nil))
|
||||
(define
|
||||
(next!)
|
||||
(let ((v (lp-peek))) (do (set! idx (+ idx 1)) v)))
|
||||
(define
|
||||
(skip-if pred)
|
||||
(if (and (not (nil? (lp-peek))) (pred (lp-peek))) (next!) nil))
|
||||
(define (upcase-str s) (if (string? s) (upcase s) s))
|
||||
(define (kw? s k) (= (upcase-str s) k))
|
||||
(define
|
||||
(make-accum-var!)
|
||||
(if
|
||||
(nil? result-var)
|
||||
(do (set! result-var "#LOOP-RESULT") result-var)
|
||||
result-var))
|
||||
(define
|
||||
(add-accum! type expr into-var)
|
||||
(let
|
||||
((v (if (nil? into-var) (make-accum-var!) into-var)))
|
||||
(if
|
||||
(not (has-key? accum-vars v))
|
||||
(do
|
||||
(set!
|
||||
accum-vars
|
||||
(assoc
|
||||
accum-vars
|
||||
v
|
||||
(cond
|
||||
((= type ":sum") 0)
|
||||
((= type ":count") 0)
|
||||
((= type ":maximize") nil)
|
||||
((= type ":minimize") nil)
|
||||
(:else (list)))))
|
||||
(set! accum-clauses (assoc accum-clauses v type))))
|
||||
(let
|
||||
((update (cond ((= type ":collect") (list "SETQ" v (list "APPEND" v (list "LIST" expr)))) ((= type ":append") (list "SETQ" v (list "APPEND" v expr))) ((= type ":nconc") (list "SETQ" v (list "NCONC" v expr))) ((= type ":sum") (list "SETQ" v (list "+" v expr))) ((= type ":count") (list "SETQ" v (list "+" v (list "IF" expr 1 0)))) ((= type ":maximize") (list "SETQ" v (list "IF" (list "OR" (list "NULL" v) (list ">" expr v)) expr v))) ((= type ":minimize") (list "SETQ" v (list "IF" (list "OR" (list "NULL" v) (list "<" expr v)) expr v))) (:else (list "SETQ" v (list "APPEND" v (list "LIST" expr)))))))
|
||||
(set! body-forms (append body-forms (list update))))))
|
||||
(define
|
||||
(parse-clause!)
|
||||
(let
|
||||
((tok (lp-peek)))
|
||||
(if
|
||||
(nil? tok)
|
||||
nil
|
||||
(do
|
||||
(let
|
||||
((u (upcase-str tok)))
|
||||
(cond
|
||||
((= u "NAMED")
|
||||
(do (next!) (set! block-name (next!)) (parse-clause!)))
|
||||
((= u "WITH")
|
||||
(do
|
||||
(next!)
|
||||
(let
|
||||
((var (next!)))
|
||||
(skip-if (fn (s) (kw? s "=")))
|
||||
(let
|
||||
((init (next!)))
|
||||
(set!
|
||||
with-bindings
|
||||
(append with-bindings (list (list var init))))
|
||||
(parse-clause!)))))
|
||||
((= u "FOR")
|
||||
(do
|
||||
(next!)
|
||||
(let
|
||||
((var (next!)))
|
||||
(let
|
||||
((kw2 (upcase-str (lp-peek))))
|
||||
(cond
|
||||
((or (= kw2 "IN") (= kw2 "ACROSS"))
|
||||
(do
|
||||
(next!)
|
||||
(let
|
||||
((lst-expr (next!))
|
||||
(tail-var (str "#TAIL-" var)))
|
||||
(set!
|
||||
for-bindings
|
||||
(append for-bindings (list {:list lst-expr :tail tail-var :type ":list" :var var})))
|
||||
(parse-clause!))))
|
||||
((= kw2 "=")
|
||||
(do
|
||||
(next!)
|
||||
(let
|
||||
((init-expr (next!)))
|
||||
(let
|
||||
((then-expr (if (kw? (lp-peek) "THEN") (do (next!) (next!)) init-expr)))
|
||||
(set!
|
||||
for-bindings
|
||||
(append for-bindings (list {:type ":general" :then then-expr :init init-expr :var var})))
|
||||
(parse-clause!)))))
|
||||
((or (= kw2 "FROM") (= kw2 "DOWNFROM") (= kw2 "UPFROM"))
|
||||
(do
|
||||
(next!)
|
||||
(let
|
||||
((from-expr (next!))
|
||||
(dir (if (= kw2 "DOWNFROM") ":down" ":up"))
|
||||
(limit-expr nil)
|
||||
(limit-type nil)
|
||||
(step-expr 1))
|
||||
(let
|
||||
((lkw (upcase-str (lp-peek))))
|
||||
(when
|
||||
(or
|
||||
(= lkw "TO")
|
||||
(= lkw "UPTO")
|
||||
(= lkw "BELOW")
|
||||
(= lkw "DOWNTO")
|
||||
(= lkw "ABOVE"))
|
||||
(do
|
||||
(next!)
|
||||
(set! limit-type lkw)
|
||||
(set! limit-expr (next!)))))
|
||||
(when
|
||||
(kw? (lp-peek) "BY")
|
||||
(do (next!) (set! step-expr (next!))))
|
||||
(set!
|
||||
for-bindings
|
||||
(append for-bindings (list {:dir dir :step step-expr :from from-expr :type ":numeric" :limit-type limit-type :var var :limit limit-expr})))
|
||||
(parse-clause!))))
|
||||
((or (= kw2 "TO") (= kw2 "UPTO") (= kw2 "BELOW"))
|
||||
(do
|
||||
(next!)
|
||||
(let
|
||||
((limit-expr (next!))
|
||||
(step-expr 1))
|
||||
(when
|
||||
(kw? (lp-peek) "BY")
|
||||
(do (next!) (set! step-expr (next!))))
|
||||
(set!
|
||||
for-bindings
|
||||
(append for-bindings (list {:dir ":up" :step step-expr :from 0 :type ":numeric" :limit-type kw2 :var var :limit limit-expr})))
|
||||
(parse-clause!))))
|
||||
(:else (do (parse-clause!))))))))
|
||||
((= u "WHILE")
|
||||
(do
|
||||
(next!)
|
||||
(set! test-forms (append test-forms (list {:expr (next!) :type ":while"})))
|
||||
(parse-clause!)))
|
||||
((= u "UNTIL")
|
||||
(do
|
||||
(next!)
|
||||
(set! test-forms (append test-forms (list {:expr (next!) :type ":until"})))
|
||||
(parse-clause!)))
|
||||
((= u "REPEAT")
|
||||
(do
|
||||
(next!)
|
||||
(set! repeat-count (next!))
|
||||
(set! repeat-var "#REPEAT-COUNT")
|
||||
(parse-clause!)))
|
||||
((or (= u "COLLECT") (= u "COLLECTING"))
|
||||
(do
|
||||
(next!)
|
||||
(let
|
||||
((expr (next!)) (into-var nil))
|
||||
(when
|
||||
(kw? (lp-peek) "INTO")
|
||||
(do (next!) (set! into-var (next!))))
|
||||
(add-accum! ":collect" expr into-var)
|
||||
(parse-clause!))))
|
||||
((or (= u "APPEND") (= u "APPENDING"))
|
||||
(do
|
||||
(next!)
|
||||
(let
|
||||
((expr (next!)) (into-var nil))
|
||||
(when
|
||||
(kw? (lp-peek) "INTO")
|
||||
(do (next!) (set! into-var (next!))))
|
||||
(add-accum! ":append" expr into-var)
|
||||
(parse-clause!))))
|
||||
((or (= u "NCONC") (= u "NCONCING"))
|
||||
(do
|
||||
(next!)
|
||||
(let
|
||||
((expr (next!)) (into-var nil))
|
||||
(when
|
||||
(kw? (lp-peek) "INTO")
|
||||
(do (next!) (set! into-var (next!))))
|
||||
(add-accum! ":nconc" expr into-var)
|
||||
(parse-clause!))))
|
||||
((or (= u "SUM") (= u "SUMMING"))
|
||||
(do
|
||||
(next!)
|
||||
(let
|
||||
((expr (next!)) (into-var nil))
|
||||
(when
|
||||
(kw? (lp-peek) "INTO")
|
||||
(do (next!) (set! into-var (next!))))
|
||||
(add-accum! ":sum" expr into-var)
|
||||
(parse-clause!))))
|
||||
((or (= u "COUNT") (= u "COUNTING"))
|
||||
(do
|
||||
(next!)
|
||||
(let
|
||||
((expr (next!)) (into-var nil))
|
||||
(when
|
||||
(kw? (lp-peek) "INTO")
|
||||
(do (next!) (set! into-var (next!))))
|
||||
(add-accum! ":count" expr into-var)
|
||||
(parse-clause!))))
|
||||
((or (= u "MAXIMIZE") (= u "MAXIMIZING"))
|
||||
(do
|
||||
(next!)
|
||||
(let
|
||||
((expr (next!)) (into-var nil))
|
||||
(when
|
||||
(kw? (lp-peek) "INTO")
|
||||
(do (next!) (set! into-var (next!))))
|
||||
(add-accum! ":maximize" expr into-var)
|
||||
(parse-clause!))))
|
||||
((or (= u "MINIMIZE") (= u "MINIMIZING"))
|
||||
(do
|
||||
(next!)
|
||||
(let
|
||||
((expr (next!)) (into-var nil))
|
||||
(when
|
||||
(kw? (lp-peek) "INTO")
|
||||
(do (next!) (set! into-var (next!))))
|
||||
(add-accum! ":minimize" expr into-var)
|
||||
(parse-clause!))))
|
||||
((= u "DO")
|
||||
(do
|
||||
(next!)
|
||||
(define
|
||||
(loop-kw? s)
|
||||
(let
|
||||
((us (upcase-str s)))
|
||||
(some
|
||||
(fn (k) (= us k))
|
||||
(list
|
||||
"FOR"
|
||||
"WITH"
|
||||
"WHILE"
|
||||
"UNTIL"
|
||||
"REPEAT"
|
||||
"COLLECT"
|
||||
"COLLECTING"
|
||||
"APPEND"
|
||||
"APPENDING"
|
||||
"NCONC"
|
||||
"NCONCING"
|
||||
"SUM"
|
||||
"SUMMING"
|
||||
"COUNT"
|
||||
"COUNTING"
|
||||
"MAXIMIZE"
|
||||
"MAXIMIZING"
|
||||
"MINIMIZE"
|
||||
"MINIMIZING"
|
||||
"DO"
|
||||
"WHEN"
|
||||
"IF"
|
||||
"UNLESS"
|
||||
"FINALLY"
|
||||
"ALWAYS"
|
||||
"NEVER"
|
||||
"THEREIS"
|
||||
"RETURN"
|
||||
"NAMED"))))
|
||||
(define
|
||||
(collect-do-forms!)
|
||||
(if
|
||||
(or (nil? (lp-peek)) (loop-kw? (lp-peek)))
|
||||
nil
|
||||
(do
|
||||
(set!
|
||||
body-forms
|
||||
(append body-forms (list (next!))))
|
||||
(collect-do-forms!))))
|
||||
(collect-do-forms!)
|
||||
(parse-clause!)))
|
||||
((or (= u "WHEN") (= u "IF"))
|
||||
(do
|
||||
(next!)
|
||||
(let
|
||||
((cond-expr (next!))
|
||||
(body-start (len body-forms)))
|
||||
(parse-clause!)
|
||||
;; wrap forms added since body-start in (WHEN cond ...)
|
||||
(when (> (len body-forms) body-start)
|
||||
(let ((added (list (nth body-forms body-start))))
|
||||
(set! body-forms
|
||||
(append
|
||||
(if (> body-start 0)
|
||||
(list (nth body-forms (- body-start 1)))
|
||||
(list))
|
||||
(list (list "WHEN" cond-expr (first added)))))
|
||||
nil)))))
|
||||
((= u "UNLESS")
|
||||
(do
|
||||
(next!)
|
||||
(let
|
||||
((cond-expr (next!))
|
||||
(body-start (len body-forms)))
|
||||
(parse-clause!)
|
||||
(when (> (len body-forms) body-start)
|
||||
(let ((added (list (nth body-forms body-start))))
|
||||
(set! body-forms
|
||||
(append
|
||||
(if (> body-start 0)
|
||||
(list (nth body-forms (- body-start 1)))
|
||||
(list))
|
||||
(list (list "UNLESS" cond-expr (first added)))))
|
||||
nil)))))
|
||||
((= u "ALWAYS")
|
||||
(do (next!) (set! termination {:expr (next!) :type ":always"}) (parse-clause!)))
|
||||
((= u "NEVER")
|
||||
(do (next!) (set! termination {:expr (next!) :type ":never"}) (parse-clause!)))
|
||||
((= u "THEREIS")
|
||||
(do (next!) (set! termination {:expr (next!) :type ":thereis"}) (parse-clause!)))
|
||||
((= u "RETURN")
|
||||
(do (next!) (set! return-expr (next!)) (parse-clause!)))
|
||||
((= u "FINALLY")
|
||||
(do
|
||||
(next!)
|
||||
(define
|
||||
(collect-finally!)
|
||||
(if
|
||||
(nil? (lp-peek))
|
||||
nil
|
||||
(do
|
||||
(set!
|
||||
finally-forms
|
||||
(append finally-forms (list (next!))))
|
||||
(collect-finally!))))
|
||||
(collect-finally!)
|
||||
(parse-clause!)))
|
||||
(:else
|
||||
(do
|
||||
(set! body-forms (append body-forms (list (next!))))
|
||||
(parse-clause!)))))))))
|
||||
(parse-clause!)
|
||||
(define let-bindings (list))
|
||||
(for-each
|
||||
(fn (wb) (set! let-bindings (append let-bindings (list wb))))
|
||||
with-bindings)
|
||||
(for-each
|
||||
(fn
|
||||
(v)
|
||||
(set!
|
||||
let-bindings
|
||||
(append let-bindings (list (list v (get accum-vars v))))))
|
||||
(keys accum-vars))
|
||||
(when
|
||||
(not (nil? repeat-var))
|
||||
(set!
|
||||
let-bindings
|
||||
(append let-bindings (list (list repeat-var repeat-count)))))
|
||||
(for-each
|
||||
(fn
|
||||
(fb)
|
||||
(let
|
||||
((type (get fb "type")))
|
||||
(cond
|
||||
((= type ":list")
|
||||
(do
|
||||
(set!
|
||||
let-bindings
|
||||
(append
|
||||
let-bindings
|
||||
(list (list (get fb "tail") (get fb "list")))
|
||||
(list
|
||||
(list
|
||||
(get fb "var")
|
||||
(list
|
||||
"IF"
|
||||
(list "CONSP" (get fb "tail"))
|
||||
(list "CAR" (get fb "tail"))
|
||||
nil)))))
|
||||
nil))
|
||||
((= type ":numeric")
|
||||
(set!
|
||||
let-bindings
|
||||
(append
|
||||
let-bindings
|
||||
(list (list (get fb "var") (get fb "from"))))))
|
||||
((= type ":general")
|
||||
(set!
|
||||
let-bindings
|
||||
(append
|
||||
let-bindings
|
||||
(list (list (get fb "var") (get fb "init"))))))
|
||||
(:else nil))))
|
||||
for-bindings)
|
||||
(define all-tests (list))
|
||||
(when
|
||||
(not (nil? repeat-var))
|
||||
(set!
|
||||
all-tests
|
||||
(append
|
||||
all-tests
|
||||
(list
|
||||
(list
|
||||
"WHEN"
|
||||
(list "<=" repeat-var 0)
|
||||
(list "RETURN-FROM" block-name (if (nil? result-var) nil result-var))))))
|
||||
(set!
|
||||
body-forms
|
||||
(append
|
||||
(list (list "SETQ" repeat-var (list "-" repeat-var 1)))
|
||||
body-forms)))
|
||||
(for-each
|
||||
(fn
|
||||
(fb)
|
||||
(when
|
||||
(= (get fb "type") ":list")
|
||||
(let
|
||||
((tvar (get fb "tail")) (var (get fb "var")))
|
||||
(set!
|
||||
all-tests
|
||||
(append
|
||||
all-tests
|
||||
(list
|
||||
(list
|
||||
"WHEN"
|
||||
(list "NULL" tvar)
|
||||
(list
|
||||
"RETURN-FROM"
|
||||
block-name
|
||||
(if (nil? result-var) nil result-var))))))
|
||||
(set!
|
||||
body-forms
|
||||
(append
|
||||
body-forms
|
||||
(list
|
||||
(list "SETQ" tvar (list "CDR" tvar))
|
||||
(list
|
||||
"SETQ"
|
||||
var
|
||||
(list "IF" (list "CONSP" tvar) (list "CAR" tvar) nil))))))))
|
||||
for-bindings)
|
||||
(for-each
|
||||
(fn
|
||||
(fb)
|
||||
(when
|
||||
(= (get fb "type") ":numeric")
|
||||
(let
|
||||
((var (get fb "var"))
|
||||
(dir (get fb "dir"))
|
||||
(lim (get fb "limit"))
|
||||
(ltype (get fb "limit-type"))
|
||||
(step (get fb "step")))
|
||||
(when
|
||||
(not (nil? lim))
|
||||
(let
|
||||
((test-op (cond ((or (= ltype "BELOW") (= ltype "ABOVE")) (if (= dir ":up") ">=" "<=")) ((or (= ltype "TO") (= ltype "UPTO")) ">") ((= ltype "DOWNTO") "<") (:else (if (= dir ":up") ">" "<")))))
|
||||
(set!
|
||||
all-tests
|
||||
(append
|
||||
all-tests
|
||||
(list
|
||||
(list
|
||||
"WHEN"
|
||||
(list test-op var lim)
|
||||
(list
|
||||
"RETURN-FROM"
|
||||
block-name
|
||||
(if (nil? result-var) nil result-var))))))))
|
||||
(let
|
||||
((step-op (if (or (= dir ":down") (= ltype "DOWNTO") (= ltype "ABOVE")) "-" "+")))
|
||||
(set!
|
||||
body-forms
|
||||
(append
|
||||
body-forms
|
||||
(list (list "SETQ" var (list step-op var step)))))))))
|
||||
for-bindings)
|
||||
(for-each
|
||||
(fn
|
||||
(fb)
|
||||
(when
|
||||
(= (get fb "type") ":general")
|
||||
(set!
|
||||
body-forms
|
||||
(append
|
||||
body-forms
|
||||
(list (list "SETQ" (get fb "var") (get fb "then")))))))
|
||||
for-bindings)
|
||||
(for-each
|
||||
(fn
|
||||
(t)
|
||||
(let
|
||||
((type (get t "type")) (expr (get t "expr")))
|
||||
(if
|
||||
(= type ":while")
|
||||
(set!
|
||||
all-tests
|
||||
(append
|
||||
all-tests
|
||||
(list
|
||||
(list
|
||||
"WHEN"
|
||||
(list "NOT" expr)
|
||||
(list
|
||||
"RETURN-FROM"
|
||||
block-name
|
||||
(if (nil? result-var) nil result-var))))))
|
||||
(set!
|
||||
all-tests
|
||||
(append
|
||||
all-tests
|
||||
(list
|
||||
(list
|
||||
"WHEN"
|
||||
expr
|
||||
(list
|
||||
"RETURN-FROM"
|
||||
block-name
|
||||
(if (nil? result-var) nil result-var)))))))))
|
||||
test-forms)
|
||||
(when
|
||||
(not (nil? termination))
|
||||
(let
|
||||
((type (get termination "type")) (expr (get termination "expr")))
|
||||
(cond
|
||||
((= type ":always")
|
||||
(set!
|
||||
body-forms
|
||||
(append
|
||||
body-forms
|
||||
(list
|
||||
(list "UNLESS" expr (list "RETURN-FROM" block-name false)))))
|
||||
(set! return-expr true))
|
||||
((= type ":never")
|
||||
(set!
|
||||
body-forms
|
||||
(append
|
||||
body-forms
|
||||
(list
|
||||
(list "WHEN" expr (list "RETURN-FROM" block-name false)))))
|
||||
(set! return-expr true))
|
||||
((= type ":thereis")
|
||||
(set!
|
||||
body-forms
|
||||
(append
|
||||
body-forms
|
||||
(list
|
||||
(list "WHEN" expr (list "RETURN-FROM" block-name expr)))))))))
|
||||
(define tag "#LOOP-START")
|
||||
(define
|
||||
inner-body
|
||||
(append (list tag) all-tests body-forms (list (list "GO" tag))))
|
||||
(define
|
||||
result-form
|
||||
(cond
|
||||
((not (nil? return-expr)) return-expr)
|
||||
((not (nil? result-var)) result-var)
|
||||
(:else nil)))
|
||||
(define
|
||||
full-body
|
||||
(if
|
||||
(= (len let-bindings) 0)
|
||||
(append
|
||||
(list "PROGN")
|
||||
(list (append (list "TAGBODY") inner-body))
|
||||
finally-forms
|
||||
(list result-form))
|
||||
(list
|
||||
"LET*"
|
||||
let-bindings
|
||||
(append (list "TAGBODY") inner-body)
|
||||
(append (list "PROGN") finally-forms (list result-form)))))
|
||||
(list "BLOCK" block-name full-body)))
|
||||
|
||||
;; ── Install LOOP as a CL macro ────────────────────────────────────────────
|
||||
;;
|
||||
;; (loop ...) — the form arrives with head "LOOP" and rest = clauses.
|
||||
;; The macro fn receives the full form.
|
||||
|
||||
(dict-set!
|
||||
cl-macro-registry
|
||||
"LOOP"
|
||||
(fn (form env) (cl-loop-parse (rest form))))
|
||||
@@ -1,377 +0,0 @@
|
||||
;; Common Lisp reader — converts token stream to CL AST forms.
|
||||
;;
|
||||
;; Depends on: lib/common-lisp/reader.sx (cl-tokenize)
|
||||
;;
|
||||
;; AST representation:
|
||||
;; integer/float → SX number (or {:cl-type "float"/:ratio ...})
|
||||
;; string "hello" → {:cl-type "string" :value "hello"}
|
||||
;; symbol FOO → SX string "FOO" (upcase)
|
||||
;; symbol NIL → nil
|
||||
;; symbol T → true
|
||||
;; :keyword → {:cl-type "keyword" :name "FOO"}
|
||||
;; #\char → {:cl-type "char" :value "a"}
|
||||
;; #:uninterned → {:cl-type "uninterned" :name "FOO"}
|
||||
;; ratio 1/3 → {:cl-type "ratio" :value "1/3"}
|
||||
;; float 3.14 → {:cl-type "float" :value "3.14"}
|
||||
;; proper list (a b c) → SX list (a b c)
|
||||
;; dotted pair (a . b) → {:cl-type "cons" :car a :cdr b}
|
||||
;; vector #(a b) → {:cl-type "vector" :elements (list a b)}
|
||||
;; 'x → ("QUOTE" x)
|
||||
;; `x → ("QUASIQUOTE" x)
|
||||
;; ,x → ("UNQUOTE" x)
|
||||
;; ,@x → ("UNQUOTE-SPLICING" x)
|
||||
;; #'x → ("FUNCTION" x)
|
||||
;;
|
||||
;; Public API:
|
||||
;; (cl-read src) — parse first form from string, return form
|
||||
;; (cl-read-all src) — parse all top-level forms, return list
|
||||
|
||||
;; ── number conversion ─────────────────────────────────────────────
|
||||
|
||||
(define
|
||||
cl-hex-val
|
||||
(fn
|
||||
(c)
|
||||
(let
|
||||
((o (cl-ord c)))
|
||||
(cond
|
||||
((and (>= o 48) (<= o 57)) (- o 48))
|
||||
((and (>= o 65) (<= o 70)) (+ 10 (- o 65)))
|
||||
((and (>= o 97) (<= o 102)) (+ 10 (- o 97)))
|
||||
(:else 0)))))
|
||||
|
||||
(define
|
||||
cl-parse-radix-str
|
||||
(fn
|
||||
(s radix start)
|
||||
(let
|
||||
((n (string-length s)) (i start) (acc 0))
|
||||
(define
|
||||
loop
|
||||
(fn
|
||||
()
|
||||
(when
|
||||
(< i n)
|
||||
(do
|
||||
(set! acc (+ (* acc radix) (cl-hex-val (substring s i (+ i 1)))))
|
||||
(set! i (+ i 1))
|
||||
(loop)))))
|
||||
(loop)
|
||||
acc)))
|
||||
|
||||
(define
|
||||
cl-convert-integer
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((n (string-length s)) (neg false))
|
||||
(cond
|
||||
((and (> n 2) (= (substring s 0 1) "#"))
|
||||
(let
|
||||
((letter (downcase (substring s 1 2))))
|
||||
(cond
|
||||
((= letter "x") (cl-parse-radix-str s 16 2))
|
||||
((= letter "b") (cl-parse-radix-str s 2 2))
|
||||
((= letter "o") (cl-parse-radix-str s 8 2))
|
||||
(:else (parse-int s 0)))))
|
||||
(:else (parse-int s 0))))))
|
||||
|
||||
;; ── reader ────────────────────────────────────────────────────────
|
||||
|
||||
;; Read one form from token list.
|
||||
;; Returns {:form F :rest remaining-toks} or {:form nil :rest toks :eof true}
|
||||
(define
|
||||
cl-read-form
|
||||
(fn
|
||||
(toks)
|
||||
(if
|
||||
(not toks)
|
||||
{:form nil :rest toks :eof true}
|
||||
(let
|
||||
((tok (nth toks 0)) (nxt (rest toks)))
|
||||
(let
|
||||
((type (get tok "type")) (val (get tok "value")))
|
||||
(cond
|
||||
((= type "eof") {:form nil :rest toks :eof true})
|
||||
((= type "integer") {:form (cl-convert-integer val) :rest nxt})
|
||||
((= type "float") {:form {:cl-type "float" :value val} :rest nxt})
|
||||
((= type "ratio") {:form {:cl-type "ratio" :value val} :rest nxt})
|
||||
((= type "string") {:form {:cl-type "string" :value val} :rest nxt})
|
||||
((= type "char") {:form {:cl-type "char" :value val} :rest nxt})
|
||||
((= type "keyword") {:form {:cl-type "keyword" :name val} :rest nxt})
|
||||
((= type "uninterned") {:form {:cl-type "uninterned" :name val} :rest nxt})
|
||||
((= type "symbol")
|
||||
(cond
|
||||
((= val "NIL") {:form nil :rest nxt})
|
||||
((= val "T") {:form true :rest nxt})
|
||||
(:else {:form val :rest nxt})))
|
||||
;; list forms
|
||||
((= type "lparen") (cl-read-list nxt))
|
||||
((= type "hash-paren") (cl-read-vector nxt))
|
||||
;; reader macros that wrap the next form
|
||||
((= type "quote") (cl-read-wrap "QUOTE" nxt))
|
||||
((= type "backquote") (cl-read-wrap "QUASIQUOTE" nxt))
|
||||
((= type "comma") (cl-read-wrap "UNQUOTE" nxt))
|
||||
((= type "comma-at") (cl-read-wrap "UNQUOTE-SPLICING" nxt))
|
||||
((= type "hash-quote") (cl-read-wrap "FUNCTION" nxt))
|
||||
;; skip unrecognised tokens
|
||||
(:else (cl-read-form nxt))))))))
|
||||
|
||||
;; Wrap next form in a list: (name form)
|
||||
(define
|
||||
cl-read-wrap
|
||||
(fn
|
||||
(name toks)
|
||||
(let
|
||||
((inner (cl-read-form toks)))
|
||||
{:form (list name (get inner "form")) :rest (get inner "rest")})))
|
||||
|
||||
;; Read list forms until ')'; handles dotted pair (a . b)
|
||||
;; Called after consuming '('
|
||||
(define
|
||||
cl-read-list
|
||||
(fn
|
||||
(toks)
|
||||
(let
|
||||
((result (cl-read-list-items toks (list))))
|
||||
{:form (get result "items") :rest (get result "rest")})))
|
||||
|
||||
(define
|
||||
cl-read-list-items
|
||||
(fn
|
||||
(toks acc)
|
||||
(if
|
||||
(not toks)
|
||||
{:items acc :rest toks}
|
||||
(let
|
||||
((tok (nth toks 0)))
|
||||
(let
|
||||
((type (get tok "type")))
|
||||
(cond
|
||||
((= type "eof") {:items acc :rest toks})
|
||||
((= type "rparen") {:items acc :rest (rest toks)})
|
||||
;; dotted pair: read one more form then expect ')'
|
||||
((= type "dot")
|
||||
(let
|
||||
((cdr-result (cl-read-form (rest toks))))
|
||||
(let
|
||||
((cdr-form (get cdr-result "form"))
|
||||
(after-cdr (get cdr-result "rest")))
|
||||
;; skip the closing ')'
|
||||
(let
|
||||
((close (if after-cdr (nth after-cdr 0) nil)))
|
||||
(let
|
||||
((remaining
|
||||
(if
|
||||
(and close (= (get close "type") "rparen"))
|
||||
(rest after-cdr)
|
||||
after-cdr)))
|
||||
;; build dotted structure
|
||||
(let
|
||||
((dotted (cl-build-dotted acc cdr-form)))
|
||||
{:items dotted :rest remaining}))))))
|
||||
(:else
|
||||
(let
|
||||
((item (cl-read-form toks)))
|
||||
(cl-read-list-items
|
||||
(get item "rest")
|
||||
(concat acc (list (get item "form"))))))))))))
|
||||
|
||||
;; Build dotted form: (a b . c) → ((DOTTED a b) . c) style
|
||||
;; In CL (a b c . d) means a proper dotted structure.
|
||||
;; We represent it as {:cl-type "cons" :car a :cdr (list->dotted b c d)}
|
||||
(define
|
||||
cl-build-dotted
|
||||
(fn
|
||||
(head-items tail)
|
||||
(if
|
||||
(= (len head-items) 0)
|
||||
tail
|
||||
(if
|
||||
(= (len head-items) 1)
|
||||
{:cl-type "cons" :car (nth head-items 0) :cdr tail}
|
||||
(let
|
||||
((last-item (nth head-items (- (len head-items) 1)))
|
||||
(but-last (slice head-items 0 (- (len head-items) 1))))
|
||||
{:cl-type "cons"
|
||||
:car (cl-build-dotted but-last (list last-item))
|
||||
:cdr tail})))))
|
||||
|
||||
;; Read vector #(…) elements until ')'
|
||||
(define
|
||||
cl-read-vector
|
||||
(fn
|
||||
(toks)
|
||||
(let
|
||||
((result (cl-read-vector-items toks (list))))
|
||||
{:form {:cl-type "vector" :elements (get result "items")} :rest (get result "rest")})))
|
||||
|
||||
(define
|
||||
cl-read-vector-items
|
||||
(fn
|
||||
(toks acc)
|
||||
(if
|
||||
(not toks)
|
||||
{:items acc :rest toks}
|
||||
(let
|
||||
((tok (nth toks 0)))
|
||||
(let
|
||||
((type (get tok "type")))
|
||||
(cond
|
||||
((= type "eof") {:items acc :rest toks})
|
||||
((= type "rparen") {:items acc :rest (rest toks)})
|
||||
(:else
|
||||
(let
|
||||
((item (cl-read-form toks)))
|
||||
(cl-read-vector-items
|
||||
(get item "rest")
|
||||
(concat acc (list (get item "form"))))))))))))
|
||||
|
||||
;; ── lambda-list parser ───────────────────────────────────────────
|
||||
;;
|
||||
;; (cl-parse-lambda-list forms) — parse a list of CL forms (already read)
|
||||
;; into a structured dict:
|
||||
;; {:required (list sym ...)
|
||||
;; :optional (list {:name N :default D :supplied S} ...)
|
||||
;; :rest nil | "SYM"
|
||||
;; :key (list {:name N :keyword K :default D :supplied S} ...)
|
||||
;; :allow-other-keys false | true
|
||||
;; :aux (list {:name N :init I} ...)}
|
||||
;;
|
||||
;; Symbols arrive as SX strings (upcase). &-markers are strings like "&OPTIONAL".
|
||||
;; Key params: keyword is the upcase name string; caller uses it as :keyword.
|
||||
;; Supplied-p: nil when absent.
|
||||
|
||||
(define
|
||||
cl-parse-opt-spec
|
||||
(fn
|
||||
(spec)
|
||||
(if
|
||||
(list? spec)
|
||||
{:name (nth spec 0)
|
||||
:default (if (> (len spec) 1) (nth spec 1) nil)
|
||||
:supplied (if (> (len spec) 2) (nth spec 2) nil)}
|
||||
{:name spec :default nil :supplied nil})))
|
||||
|
||||
(define
|
||||
cl-parse-key-spec
|
||||
(fn
|
||||
(spec)
|
||||
(if
|
||||
(list? spec)
|
||||
(let
|
||||
((first (nth spec 0)))
|
||||
(if
|
||||
(list? first)
|
||||
;; ((:keyword var) default supplied-p)
|
||||
{:name (nth first 1)
|
||||
:keyword (get first "name")
|
||||
:default (if (> (len spec) 1) (nth spec 1) nil)
|
||||
:supplied (if (> (len spec) 2) (nth spec 2) nil)}
|
||||
;; (var default supplied-p)
|
||||
{:name first
|
||||
:keyword first
|
||||
:default (if (> (len spec) 1) (nth spec 1) nil)
|
||||
:supplied (if (> (len spec) 2) (nth spec 2) nil)}))
|
||||
{:name spec :keyword spec :default nil :supplied nil})))
|
||||
|
||||
(define
|
||||
cl-parse-aux-spec
|
||||
(fn
|
||||
(spec)
|
||||
(if
|
||||
(list? spec)
|
||||
{:name (nth spec 0) :init (if (> (len spec) 1) (nth spec 1) nil)}
|
||||
{:name spec :init nil})))
|
||||
|
||||
(define
|
||||
cl-parse-lambda-list
|
||||
(fn
|
||||
(forms)
|
||||
(let
|
||||
((state "required")
|
||||
(required (list))
|
||||
(optional (list))
|
||||
(rest-name nil)
|
||||
(key (list))
|
||||
(allow-other-keys false)
|
||||
(aux (list)))
|
||||
|
||||
(define
|
||||
scan
|
||||
(fn
|
||||
(items)
|
||||
(when
|
||||
(> (len items) 0)
|
||||
(let
|
||||
((item (nth items 0)) (tail (rest items)))
|
||||
(cond
|
||||
((= item "&OPTIONAL")
|
||||
(do (set! state "optional") (scan tail)))
|
||||
((= item "&REST")
|
||||
(do (set! state "rest") (scan tail)))
|
||||
((= item "&BODY")
|
||||
(do (set! state "rest") (scan tail)))
|
||||
((= item "&KEY")
|
||||
(do (set! state "key") (scan tail)))
|
||||
((= item "&AUX")
|
||||
(do (set! state "aux") (scan tail)))
|
||||
((= item "&ALLOW-OTHER-KEYS")
|
||||
(do (set! allow-other-keys true) (scan tail)))
|
||||
((= state "required")
|
||||
(do (append! required item) (scan tail)))
|
||||
((= state "optional")
|
||||
(do (append! optional (cl-parse-opt-spec item)) (scan tail)))
|
||||
((= state "rest")
|
||||
(do (set! rest-name item) (set! state "done") (scan tail)))
|
||||
((= state "key")
|
||||
(do (append! key (cl-parse-key-spec item)) (scan tail)))
|
||||
((= state "aux")
|
||||
(do (append! aux (cl-parse-aux-spec item)) (scan tail)))
|
||||
(:else (scan tail)))))))
|
||||
|
||||
(scan forms)
|
||||
{:required required
|
||||
:optional optional
|
||||
:rest rest-name
|
||||
:key key
|
||||
:allow-other-keys allow-other-keys
|
||||
:aux aux})))
|
||||
|
||||
;; Convenience: parse lambda list from a CL source string
|
||||
(define
|
||||
cl-parse-lambda-list-str
|
||||
(fn
|
||||
(src)
|
||||
(cl-parse-lambda-list (cl-read src))))
|
||||
|
||||
;; ── public API ────────────────────────────────────────────────────
|
||||
|
||||
(define
|
||||
cl-read
|
||||
(fn
|
||||
(src)
|
||||
(let
|
||||
((toks (cl-tokenize src)))
|
||||
(get (cl-read-form toks) "form"))))
|
||||
|
||||
(define
|
||||
cl-read-all
|
||||
(fn
|
||||
(src)
|
||||
(let
|
||||
((toks (cl-tokenize src)))
|
||||
(define
|
||||
loop
|
||||
(fn
|
||||
(toks acc)
|
||||
(if
|
||||
(or (not toks) (= (get (nth toks 0) "type") "eof"))
|
||||
acc
|
||||
(let
|
||||
((result (cl-read-form toks)))
|
||||
(if
|
||||
(get result "eof")
|
||||
acc
|
||||
(loop (get result "rest") (concat acc (list (get result "form")))))))))
|
||||
(loop toks (list)))))
|
||||
@@ -1,381 +0,0 @@
|
||||
;; Common Lisp tokenizer
|
||||
;;
|
||||
;; Tokens: {:type T :value V :pos P}
|
||||
;;
|
||||
;; Types:
|
||||
;; "symbol" — FOO, PKG:SYM, PKG::SYM, T, NIL (upcase)
|
||||
;; "keyword" — :foo (value is upcase name without colon)
|
||||
;; "integer" — 42, -5, #xFF, #b1010, #o17 (string)
|
||||
;; "float" — 3.14, 1.0e10 (string)
|
||||
;; "ratio" — 1/3 (string "N/D")
|
||||
;; "string" — unescaped content
|
||||
;; "char" — single-character string
|
||||
;; "lparen" "rparen" "quote" "backquote" "comma" "comma-at"
|
||||
;; "hash-quote" — #'
|
||||
;; "hash-paren" — #(
|
||||
;; "uninterned" — #:foo (upcase name)
|
||||
;; "dot" — standalone . (dotted pair separator)
|
||||
;; "eof"
|
||||
|
||||
(define cl-make-tok (fn (type value pos) {:type type :value value :pos pos}))
|
||||
|
||||
;; ── char ordinal table ────────────────────────────────────────────
|
||||
|
||||
(define
|
||||
cl-ord-table
|
||||
(let
|
||||
((t (dict)) (i 0))
|
||||
(define
|
||||
cl-fill
|
||||
(fn
|
||||
()
|
||||
(when
|
||||
(< i 128)
|
||||
(do
|
||||
(dict-set! t (char-from-code i) i)
|
||||
(set! i (+ i 1))
|
||||
(cl-fill)))))
|
||||
(cl-fill)
|
||||
t))
|
||||
|
||||
(define cl-ord (fn (c) (or (get cl-ord-table c) 0)))
|
||||
|
||||
;; ── character predicates ──────────────────────────────────────────
|
||||
|
||||
(define cl-digit? (fn (c) (and (>= (cl-ord c) 48) (<= (cl-ord c) 57))))
|
||||
|
||||
(define
|
||||
cl-hex?
|
||||
(fn
|
||||
(c)
|
||||
(or
|
||||
(cl-digit? c)
|
||||
(and (>= (cl-ord c) 65) (<= (cl-ord c) 70))
|
||||
(and (>= (cl-ord c) 97) (<= (cl-ord c) 102)))))
|
||||
|
||||
(define cl-octal? (fn (c) (and (>= (cl-ord c) 48) (<= (cl-ord c) 55))))
|
||||
|
||||
(define cl-binary? (fn (c) (or (= c "0") (= c "1"))))
|
||||
|
||||
(define cl-ws? (fn (c) (or (= c " ") (= c "\t") (= c "\n") (= c "\r"))))
|
||||
|
||||
(define
|
||||
cl-alpha?
|
||||
(fn
|
||||
(c)
|
||||
(or
|
||||
(and (>= (cl-ord c) 65) (<= (cl-ord c) 90))
|
||||
(and (>= (cl-ord c) 97) (<= (cl-ord c) 122)))))
|
||||
|
||||
;; Characters that end a token (whitespace + terminating macro chars)
|
||||
(define
|
||||
cl-terminating?
|
||||
(fn
|
||||
(c)
|
||||
(or
|
||||
(cl-ws? c)
|
||||
(= c "(")
|
||||
(= c ")")
|
||||
(= c "\"")
|
||||
(= c ";")
|
||||
(= c "`")
|
||||
(= c ","))))
|
||||
|
||||
;; Symbol constituent: not terminating, not reader-special
|
||||
(define
|
||||
cl-sym-char?
|
||||
(fn
|
||||
(c)
|
||||
(not
|
||||
(or
|
||||
(cl-terminating? c)
|
||||
(= c "#")
|
||||
(= c "|")
|
||||
(= c "\\")
|
||||
(= c "'")))))
|
||||
|
||||
;; ── named character table ─────────────────────────────────────────
|
||||
|
||||
(define
|
||||
cl-named-chars
|
||||
{:space " "
|
||||
:newline "\n"
|
||||
:tab "\t"
|
||||
:return "\r"
|
||||
:backspace (char-from-code 8)
|
||||
:rubout (char-from-code 127)
|
||||
:delete (char-from-code 127)
|
||||
:escape (char-from-code 27)
|
||||
:altmode (char-from-code 27)
|
||||
:null (char-from-code 0)
|
||||
:nul (char-from-code 0)
|
||||
:page (char-from-code 12)
|
||||
:formfeed (char-from-code 12)})
|
||||
|
||||
;; ── main tokenizer ────────────────────────────────────────────────
|
||||
|
||||
(define
|
||||
cl-tokenize
|
||||
(fn
|
||||
(src)
|
||||
(let
|
||||
((pos 0) (n (string-length src)) (toks (list)))
|
||||
|
||||
(define at (fn () (if (< pos n) (substring src pos (+ pos 1)) nil)))
|
||||
(define peek1 (fn () (if (< (+ pos 1) n) (substring src (+ pos 1) (+ pos 2)) nil)))
|
||||
(define adv (fn () (set! pos (+ pos 1))))
|
||||
|
||||
;; Advance while predicate holds; return substring from start to end
|
||||
(define
|
||||
read-while
|
||||
(fn
|
||||
(pred)
|
||||
(let
|
||||
((start pos))
|
||||
(define
|
||||
rw-loop
|
||||
(fn
|
||||
()
|
||||
(when
|
||||
(and (at) (pred (at)))
|
||||
(do (adv) (rw-loop)))))
|
||||
(rw-loop)
|
||||
(substring src start pos))))
|
||||
|
||||
(define
|
||||
skip-line
|
||||
(fn
|
||||
()
|
||||
(when
|
||||
(and (at) (not (= (at) "\n")))
|
||||
(do (adv) (skip-line)))))
|
||||
|
||||
(define
|
||||
skip-block
|
||||
(fn
|
||||
(depth)
|
||||
(when
|
||||
(at)
|
||||
(cond
|
||||
((and (= (at) "#") (= (peek1) "|"))
|
||||
(do (adv) (adv) (skip-block (+ depth 1))))
|
||||
((and (= (at) "|") (= (peek1) "#"))
|
||||
(do
|
||||
(adv)
|
||||
(adv)
|
||||
(when (> depth 1) (skip-block (- depth 1)))))
|
||||
(:else (do (adv) (skip-block depth)))))))
|
||||
|
||||
;; Read string literal — called with pos just past opening "
|
||||
(define
|
||||
read-str
|
||||
(fn
|
||||
(acc)
|
||||
(if
|
||||
(not (at))
|
||||
acc
|
||||
(cond
|
||||
((= (at) "\"") (do (adv) acc))
|
||||
((= (at) "\\")
|
||||
(do
|
||||
(adv)
|
||||
(let
|
||||
((e (at)))
|
||||
(adv)
|
||||
(read-str
|
||||
(str
|
||||
acc
|
||||
(cond
|
||||
((= e "n") "\n")
|
||||
((= e "t") "\t")
|
||||
((= e "r") "\r")
|
||||
((= e "\"") "\"")
|
||||
((= e "\\") "\\")
|
||||
(:else e)))))))
|
||||
(:else
|
||||
(let
|
||||
((c (at)))
|
||||
(adv)
|
||||
(read-str (str acc c))))))))
|
||||
|
||||
;; Read #\ char literal — called with pos just past the backslash
|
||||
(define
|
||||
read-char-lit
|
||||
(fn
|
||||
()
|
||||
(let
|
||||
((first (at)))
|
||||
(adv)
|
||||
(let
|
||||
((rest (if (and (at) (cl-alpha? (at))) (read-while cl-alpha?) "")))
|
||||
(if
|
||||
(= rest "")
|
||||
first
|
||||
(let
|
||||
((name (downcase (str first rest))))
|
||||
(or (get cl-named-chars name) first)))))))
|
||||
|
||||
;; Number scanner — called with pos just past first digit(s).
|
||||
;; acc holds what was already consumed (first digit or sign+digit).
|
||||
(define
|
||||
scan-num
|
||||
(fn
|
||||
(p acc)
|
||||
(let
|
||||
((more (read-while cl-digit?)))
|
||||
(set! acc (str acc more))
|
||||
(cond
|
||||
;; ratio N/D
|
||||
((and (at) (= (at) "/") (peek1) (cl-digit? (peek1)))
|
||||
(do
|
||||
(adv)
|
||||
(let
|
||||
((denom (read-while cl-digit?)))
|
||||
{:type "ratio" :value (str acc "/" denom) :pos p})))
|
||||
;; float: decimal point N.M[eE]
|
||||
((and (at) (= (at) ".") (peek1) (cl-digit? (peek1)))
|
||||
(do
|
||||
(adv)
|
||||
(let
|
||||
((frac (read-while cl-digit?)))
|
||||
(set! acc (str acc "." frac))
|
||||
(when
|
||||
(and (at) (or (= (at) "e") (= (at) "E")))
|
||||
(do
|
||||
(set! acc (str acc (at)))
|
||||
(adv)
|
||||
(when
|
||||
(and (at) (or (= (at) "+") (= (at) "-")))
|
||||
(do (set! acc (str acc (at))) (adv)))
|
||||
(set! acc (str acc (read-while cl-digit?)))))
|
||||
{:type "float" :value acc :pos p})))
|
||||
;; float: exponent only NeE
|
||||
((and (at) (or (= (at) "e") (= (at) "E")))
|
||||
(do
|
||||
(set! acc (str acc (at)))
|
||||
(adv)
|
||||
(when
|
||||
(and (at) (or (= (at) "+") (= (at) "-")))
|
||||
(do (set! acc (str acc (at))) (adv)))
|
||||
(set! acc (str acc (read-while cl-digit?)))
|
||||
{:type "float" :value acc :pos p}))
|
||||
(:else {:type "integer" :value acc :pos p})))))
|
||||
|
||||
(define
|
||||
read-radix
|
||||
(fn
|
||||
(letter p)
|
||||
(let
|
||||
((pred
|
||||
(cond
|
||||
((or (= letter "x") (= letter "X")) cl-hex?)
|
||||
((or (= letter "b") (= letter "B")) cl-binary?)
|
||||
((or (= letter "o") (= letter "O")) cl-octal?)
|
||||
(:else cl-digit?))))
|
||||
{:type "integer"
|
||||
:value (str "#" letter (read-while pred))
|
||||
:pos p})))
|
||||
|
||||
(define emit (fn (tok) (append! toks tok)))
|
||||
|
||||
(define
|
||||
scan
|
||||
(fn
|
||||
()
|
||||
(when
|
||||
(< pos n)
|
||||
(let
|
||||
((c (at)) (p pos))
|
||||
(cond
|
||||
((cl-ws? c) (do (adv) (scan)))
|
||||
((= c ";") (do (adv) (skip-line) (scan)))
|
||||
((= c "(") (do (adv) (emit (cl-make-tok "lparen" "(" p)) (scan)))
|
||||
((= c ")") (do (adv) (emit (cl-make-tok "rparen" ")" p)) (scan)))
|
||||
((= c "'") (do (adv) (emit (cl-make-tok "quote" "'" p)) (scan)))
|
||||
((= c "`") (do (adv) (emit (cl-make-tok "backquote" "`" p)) (scan)))
|
||||
((= c ",")
|
||||
(do
|
||||
(adv)
|
||||
(if
|
||||
(= (at) "@")
|
||||
(do (adv) (emit (cl-make-tok "comma-at" ",@" p)))
|
||||
(emit (cl-make-tok "comma" "," p)))
|
||||
(scan)))
|
||||
((= c "\"")
|
||||
(do
|
||||
(adv)
|
||||
(emit (cl-make-tok "string" (read-str "") p))
|
||||
(scan)))
|
||||
;; :keyword
|
||||
((= c ":")
|
||||
(do
|
||||
(adv)
|
||||
(emit (cl-make-tok "keyword" (upcase (read-while cl-sym-char?)) p))
|
||||
(scan)))
|
||||
;; dispatch macro #
|
||||
((= c "#")
|
||||
(do
|
||||
(adv)
|
||||
(let
|
||||
((d (at)))
|
||||
(cond
|
||||
((= d "'") (do (adv) (emit (cl-make-tok "hash-quote" "#'" p)) (scan)))
|
||||
((= d "(") (do (adv) (emit (cl-make-tok "hash-paren" "#(" p)) (scan)))
|
||||
((= d ":")
|
||||
(do
|
||||
(adv)
|
||||
(emit
|
||||
(cl-make-tok "uninterned" (upcase (read-while cl-sym-char?)) p))
|
||||
(scan)))
|
||||
((= d "|") (do (adv) (skip-block 1) (scan)))
|
||||
((= d "\\")
|
||||
(do (adv) (emit (cl-make-tok "char" (read-char-lit) p)) (scan)))
|
||||
((or (= d "x") (= d "X"))
|
||||
(do (adv) (emit (read-radix d p)) (scan)))
|
||||
((or (= d "b") (= d "B"))
|
||||
(do (adv) (emit (read-radix d p)) (scan)))
|
||||
((or (= d "o") (= d "O"))
|
||||
(do (adv) (emit (read-radix d p)) (scan)))
|
||||
(:else (scan))))))
|
||||
;; standalone dot, float .5, or symbol starting with dots
|
||||
((= c ".")
|
||||
(do
|
||||
(adv)
|
||||
(cond
|
||||
((or (not (at)) (cl-terminating? (at)))
|
||||
(do (emit (cl-make-tok "dot" "." p)) (scan)))
|
||||
((cl-digit? (at))
|
||||
(do
|
||||
(emit
|
||||
(cl-make-tok "float" (str "0." (read-while cl-digit?)) p))
|
||||
(scan)))
|
||||
(:else
|
||||
(do
|
||||
(emit
|
||||
(cl-make-tok "symbol" (upcase (str "." (read-while cl-sym-char?))) p))
|
||||
(scan))))))
|
||||
;; sign followed by digit → number
|
||||
((and (or (= c "+") (= c "-")) (peek1) (cl-digit? (peek1)))
|
||||
(do
|
||||
(adv)
|
||||
(let
|
||||
((first-d (at)))
|
||||
(adv)
|
||||
(emit (scan-num p (str c first-d))))
|
||||
(scan)))
|
||||
;; decimal digit → number
|
||||
((cl-digit? c)
|
||||
(do
|
||||
(adv)
|
||||
(emit (scan-num p c))
|
||||
(scan)))
|
||||
;; symbol constituent (includes bare +, -, etc.)
|
||||
((cl-sym-char? c)
|
||||
(do
|
||||
(emit (cl-make-tok "symbol" (upcase (read-while cl-sym-char?)) p))
|
||||
(scan)))
|
||||
(:else (do (adv) (scan))))))))
|
||||
|
||||
(scan)
|
||||
(append! toks (cl-make-tok "eof" nil n))
|
||||
toks)))
|
||||
@@ -1,724 +0,0 @@
|
||||
;; lib/common-lisp/runtime.sx — CL built-ins + condition system on SX
|
||||
;;
|
||||
;; Section 1-9: Type predicates, arithmetic, characters, strings, gensym,
|
||||
;; multiple values, sets, radix formatting, list utilities.
|
||||
;; Section 10: Condition system (define-condition, signal/error/warn,
|
||||
;; handler-bind, handler-case, restart-case, invoke-restart).
|
||||
;;
|
||||
;; Primitives used from spec:
|
||||
;; char/char->integer/integer->char/char-upcase/char-downcase
|
||||
;; format gensym rational/rational? make-set/set-member?/etc
|
||||
;; modulo/remainder/quotient/gcd/lcm/expt number->string
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 1. Type predicates
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(define (cl-null? x) (= x nil))
|
||||
(define (cl-consp? x) (and (list? x) (not (cl-empty? x))))
|
||||
(define (cl-listp? x) (or (cl-empty? x) (list? x)))
|
||||
(define (cl-atom? x) (not (cl-consp? x)))
|
||||
|
||||
(define
|
||||
(cl-numberp? x)
|
||||
(let ((t (type-of x))) (or (= t "number") (= t "rational"))))
|
||||
|
||||
(define cl-integerp? integer?)
|
||||
(define cl-floatp? float?)
|
||||
(define cl-rationalp? rational?)
|
||||
|
||||
(define (cl-realp? x) (or (integer? x) (float? x) (rational? x)))
|
||||
|
||||
(define cl-characterp? char?)
|
||||
(define cl-stringp? (fn (x) (= (type-of x) "string")))
|
||||
(define cl-symbolp? (fn (x) (= (type-of x) "symbol")))
|
||||
(define cl-keywordp? (fn (x) (= (type-of x) "keyword")))
|
||||
|
||||
(define
|
||||
(cl-functionp? x)
|
||||
(let
|
||||
((t (type-of x)))
|
||||
(or
|
||||
(= t "function")
|
||||
(= t "lambda")
|
||||
(= t "native-fn")
|
||||
(= t "component"))))
|
||||
|
||||
(define cl-vectorp? vector?)
|
||||
(define cl-arrayp? vector?)
|
||||
|
||||
;; sx_server: (rest (list x)) returns () not nil — cl-empty? handles both
|
||||
(define
|
||||
(cl-empty? x)
|
||||
(or (nil? x) (and (list? x) (= (len x) 0))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 2. Arithmetic — thin aliases to spec primitives
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(define cl-mod modulo)
|
||||
(define cl-rem remainder)
|
||||
(define cl-gcd gcd)
|
||||
(define cl-lcm lcm)
|
||||
(define cl-expt expt)
|
||||
(define cl-floor floor)
|
||||
(define cl-ceiling ceil)
|
||||
(define cl-truncate truncate)
|
||||
(define cl-round round)
|
||||
(define cl-abs (fn (x) (if (< x 0) (- 0 x) x)))
|
||||
(define cl-min (fn (a b) (if (< a b) a b)))
|
||||
(define cl-max (fn (a b) (if (> a b) a b)))
|
||||
(define cl-quotient quotient)
|
||||
|
||||
(define
|
||||
(cl-signum x)
|
||||
(cond
|
||||
((> x 0) 1)
|
||||
((< x 0) -1)
|
||||
(else 0)))
|
||||
|
||||
(define (cl-evenp? n) (= (modulo n 2) 0))
|
||||
(define (cl-oddp? n) (= (modulo n 2) 1))
|
||||
(define (cl-zerop? n) (= n 0))
|
||||
(define (cl-plusp? n) (> n 0))
|
||||
(define (cl-minusp? n) (< n 0))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 3. Character functions — alias spec char primitives + CL name mapping
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(define cl-char->integer char->integer)
|
||||
(define cl-integer->char integer->char)
|
||||
(define cl-char-upcase char-upcase)
|
||||
(define cl-char-downcase char-downcase)
|
||||
(define cl-char-code char->integer)
|
||||
(define cl-code-char integer->char)
|
||||
|
||||
(define cl-char=? char=?)
|
||||
(define cl-char<? char<?)
|
||||
(define cl-char>? char>?)
|
||||
(define cl-char<=? char<=?)
|
||||
(define cl-char>=? char>=?)
|
||||
(define cl-char-ci=? char-ci=?)
|
||||
(define cl-char-ci<? char-ci<?)
|
||||
(define cl-char-ci>? char-ci>?)
|
||||
|
||||
;; Inline predicates — char-alphabetic?/char-numeric? unreliable in sx_server
|
||||
(define
|
||||
(cl-alpha-char-p c)
|
||||
(let
|
||||
((n (char->integer c)))
|
||||
(or
|
||||
(and (>= n 65) (<= n 90))
|
||||
(and (>= n 97) (<= n 122)))))
|
||||
|
||||
(define
|
||||
(cl-digit-char-p c)
|
||||
(let ((n (char->integer c))) (and (>= n 48) (<= n 57))))
|
||||
|
||||
(define
|
||||
(cl-alphanumericp c)
|
||||
(let
|
||||
((n (char->integer c)))
|
||||
(or
|
||||
(and (>= n 48) (<= n 57))
|
||||
(and (>= n 65) (<= n 90))
|
||||
(and (>= n 97) (<= n 122)))))
|
||||
|
||||
(define
|
||||
(cl-upper-case-p c)
|
||||
(let ((n (char->integer c))) (and (>= n 65) (<= n 90))))
|
||||
|
||||
(define
|
||||
(cl-lower-case-p c)
|
||||
(let ((n (char->integer c))) (and (>= n 97) (<= n 122))))
|
||||
|
||||
;; Named character constants
|
||||
(define cl-char-space (integer->char 32))
|
||||
(define cl-char-newline (integer->char 10))
|
||||
(define cl-char-tab (integer->char 9))
|
||||
(define cl-char-backspace (integer->char 8))
|
||||
(define cl-char-return (integer->char 13))
|
||||
(define cl-char-null (integer->char 0))
|
||||
(define cl-char-escape (integer->char 27))
|
||||
(define cl-char-delete (integer->char 127))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 4. String + IO — use spec format and ports
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
;; CL format: (cl-format nil "~a ~a" x y) — nil destination means return string
|
||||
(define
|
||||
(cl-format dest template &rest args)
|
||||
(let ((s (apply format (cons template args)))) (if (= dest nil) s s)))
|
||||
|
||||
(define cl-write-to-string write-to-string)
|
||||
(define cl-princ-to-string display-to-string)
|
||||
|
||||
;; CL read-from-string: parse value from a string using SX port
|
||||
(define
|
||||
(cl-read-from-string s)
|
||||
(let ((p (open-input-string s))) (read p)))
|
||||
|
||||
;; String stream (output)
|
||||
(define cl-make-string-output-stream open-output-string)
|
||||
(define cl-get-output-stream-string get-output-string)
|
||||
|
||||
;; String stream (input)
|
||||
(define cl-make-string-input-stream open-input-string)
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 5. Gensym
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(define cl-gensym gensym)
|
||||
(define cl-gentemp gensym)
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 6. Multiple values (CL: values / nth-value)
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(define (cl-values &rest args) {:_values true :_list args})
|
||||
|
||||
(define
|
||||
(cl-call-with-values producer consumer)
|
||||
(let
|
||||
((mv (producer)))
|
||||
(if
|
||||
(and (dict? mv) (get mv :_values))
|
||||
(apply consumer (get mv :_list))
|
||||
(consumer mv))))
|
||||
|
||||
(define
|
||||
(cl-nth-value n mv)
|
||||
(cond
|
||||
((and (dict? mv) (get mv :_values))
|
||||
(let
|
||||
((lst (get mv :_list)))
|
||||
(if (>= n (len lst)) nil (nth lst n))))
|
||||
((= n 0) mv)
|
||||
(else nil)))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 7. Sets (CL: adjoin / member / union / intersection / set-difference)
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(define cl-make-set make-set)
|
||||
(define cl-set? set?)
|
||||
(define cl-set-add set-add!)
|
||||
(define cl-set-memberp set-member?)
|
||||
(define cl-set-remove set-remove!)
|
||||
(define cl-set-union set-union)
|
||||
(define cl-set-intersect set-intersection)
|
||||
(define cl-set-difference set-difference)
|
||||
(define cl-list->set list->set)
|
||||
(define cl-set->list set->list)
|
||||
|
||||
;; CL: (member item list) — returns tail starting at item, or nil
|
||||
(define
|
||||
(cl-member item lst)
|
||||
(cond
|
||||
((cl-empty? lst) nil)
|
||||
((equal? item (first lst)) lst)
|
||||
(else (cl-member item (rest lst)))))
|
||||
|
||||
;; CL: (adjoin item list) — cons only if not already present
|
||||
(define (cl-adjoin item lst) (if (cl-member item lst) lst (cons item lst)))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 8. Radix formatting (CL: (write-to-string n :base radix))
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(define (cl-integer-to-string n radix) (number->string n radix))
|
||||
|
||||
(define (cl-string-to-integer s radix) (string->number s radix))
|
||||
|
||||
;; CL ~R directive helpers
|
||||
(define (cl-format-binary n) (number->string n 2))
|
||||
(define (cl-format-octal n) (number->string n 8))
|
||||
(define (cl-format-hex n) (number->string n 16))
|
||||
(define (cl-format-decimal n) (number->string n 10))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 9. List utilities — cl-empty? guards against () from rest
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(define
|
||||
(cl-last lst)
|
||||
(cond
|
||||
((cl-empty? lst) nil)
|
||||
((cl-empty? (rest lst)) lst)
|
||||
(else (cl-last (rest lst)))))
|
||||
|
||||
(define
|
||||
(cl-butlast lst)
|
||||
(if
|
||||
(or (cl-empty? lst) (cl-empty? (rest lst)))
|
||||
nil
|
||||
(cons (first lst) (cl-butlast (rest lst)))))
|
||||
|
||||
(define
|
||||
(cl-nthcdr n lst)
|
||||
(if (= n 0) lst (cl-nthcdr (- n 1) (rest lst))))
|
||||
|
||||
(define (cl-nth n lst) (first (cl-nthcdr n lst)))
|
||||
|
||||
(define (cl-list-length lst) (len lst))
|
||||
|
||||
(define
|
||||
(cl-copy-list lst)
|
||||
(if (cl-empty? lst) nil (cons (first lst) (cl-copy-list (rest lst)))))
|
||||
|
||||
(define
|
||||
(cl-flatten lst)
|
||||
(cond
|
||||
((cl-empty? lst) nil)
|
||||
((list? (first lst))
|
||||
(append (cl-flatten (first lst)) (cl-flatten (rest lst))))
|
||||
(else (cons (first lst) (cl-flatten (rest lst))))))
|
||||
|
||||
;; CL: (assoc key alist) — returns matching pair or nil
|
||||
(define
|
||||
(cl-assoc key alist)
|
||||
(cond
|
||||
((cl-empty? alist) nil)
|
||||
((equal? key (first (first alist))) (first alist))
|
||||
(else (cl-assoc key (rest alist)))))
|
||||
|
||||
;; CL: (rassoc val alist) — reverse assoc (match on second element)
|
||||
(define
|
||||
(cl-rassoc val alist)
|
||||
(cond
|
||||
((cl-empty? alist) nil)
|
||||
((equal? val (first (rest (first alist)))) (first alist))
|
||||
(else (cl-rassoc val (rest alist)))))
|
||||
|
||||
;; CL: (getf plist key) — property list lookup
|
||||
(define
|
||||
(cl-getf plist key)
|
||||
(cond
|
||||
((or (cl-empty? plist) (cl-empty? (rest plist))) nil)
|
||||
((equal? (first plist) key) (first (rest plist)))
|
||||
(else (cl-getf (rest (rest plist)) key))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 10. Condition system (Phase 3)
|
||||
;;
|
||||
;; Condition objects:
|
||||
;; {:cl-type "cl-condition" :class "NAME" :slots {slot-name val ...}}
|
||||
;;
|
||||
;; The built-in handler-bind / restart-case expect LITERAL handler specs in
|
||||
;; source (they operate on the raw AST), so we implement our own handler and
|
||||
;; restart stacks as mutable SX globals.
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
;; ── condition class registry ───────────────────────────────────────────────
|
||||
;;
|
||||
;; Populated at load time with all ANSI standard condition types.
|
||||
;; Also mutated by cl-define-condition.
|
||||
|
||||
(define
|
||||
cl-condition-classes
|
||||
(dict
|
||||
"condition"
|
||||
{:parents (list) :slots (list) :name "condition"}
|
||||
"serious-condition"
|
||||
{:parents (list "condition") :slots (list) :name "serious-condition"}
|
||||
"error"
|
||||
{:parents (list "serious-condition") :slots (list) :name "error"}
|
||||
"warning"
|
||||
{:parents (list "condition") :slots (list) :name "warning"}
|
||||
"simple-condition"
|
||||
{:parents (list "condition") :slots (list "format-control" "format-arguments") :name "simple-condition"}
|
||||
"simple-error"
|
||||
{:parents (list "error" "simple-condition") :slots (list "format-control" "format-arguments") :name "simple-error"}
|
||||
"simple-warning"
|
||||
{:parents (list "warning" "simple-condition") :slots (list "format-control" "format-arguments") :name "simple-warning"}
|
||||
"type-error"
|
||||
{:parents (list "error") :slots (list "datum" "expected-type") :name "type-error"}
|
||||
"arithmetic-error"
|
||||
{:parents (list "error") :slots (list "operation" "operands") :name "arithmetic-error"}
|
||||
"division-by-zero"
|
||||
{:parents (list "arithmetic-error") :slots (list) :name "division-by-zero"}
|
||||
"cell-error"
|
||||
{:parents (list "error") :slots (list "name") :name "cell-error"}
|
||||
"unbound-variable"
|
||||
{:parents (list "cell-error") :slots (list) :name "unbound-variable"}
|
||||
"undefined-function"
|
||||
{:parents (list "cell-error") :slots (list) :name "undefined-function"}
|
||||
"program-error"
|
||||
{:parents (list "error") :slots (list) :name "program-error"}
|
||||
"storage-condition"
|
||||
{:parents (list "serious-condition") :slots (list) :name "storage-condition"}))
|
||||
|
||||
;; ── condition predicates ───────────────────────────────────────────────────
|
||||
|
||||
(define
|
||||
cl-condition?
|
||||
(fn (x) (and (dict? x) (= (get x "cl-type") "cl-condition"))))
|
||||
|
||||
;; cl-condition-of-type? walks the class hierarchy.
|
||||
;; We capture cl-condition-classes at define time via let to avoid
|
||||
;; free-variable scoping issues at call time.
|
||||
|
||||
(define
|
||||
cl-condition-of-type?
|
||||
(let
|
||||
((classes cl-condition-classes))
|
||||
(fn
|
||||
(c type-name)
|
||||
(if
|
||||
(not (cl-condition? c))
|
||||
false
|
||||
(let
|
||||
((class-name (get c "class")))
|
||||
(define
|
||||
check
|
||||
(fn
|
||||
(n)
|
||||
(if
|
||||
(= n type-name)
|
||||
true
|
||||
(let
|
||||
((entry (get classes n)))
|
||||
(if
|
||||
(nil? entry)
|
||||
false
|
||||
(some (fn (p) (check p)) (get entry "parents")))))))
|
||||
(check class-name))))))
|
||||
|
||||
;; ── condition constructors ─────────────────────────────────────────────────
|
||||
|
||||
;; cl-define-condition registers a new condition class.
|
||||
;; name: string (condition class name)
|
||||
;; parents: list of strings (parent class names)
|
||||
;; slot-names: list of strings
|
||||
|
||||
(define
|
||||
cl-define-condition
|
||||
(fn
|
||||
(name parents slot-names)
|
||||
(begin (dict-set! cl-condition-classes name {:parents parents :slots slot-names :name name}) name)))
|
||||
|
||||
;; cl-make-condition constructs a condition object.
|
||||
;; Keyword args (alternating slot-name/value pairs) populate the slots dict.
|
||||
|
||||
(define
|
||||
cl-make-condition
|
||||
(fn
|
||||
(name &rest kw-args)
|
||||
(let
|
||||
((slots (dict)))
|
||||
(define
|
||||
fill
|
||||
(fn
|
||||
(args)
|
||||
(when
|
||||
(>= (len args) 2)
|
||||
(begin
|
||||
(dict-set! slots (first args) (first (rest args)))
|
||||
(fill (rest (rest args)))))))
|
||||
(fill kw-args)
|
||||
{:cl-type "cl-condition" :slots slots :class name})))
|
||||
|
||||
;; ── condition accessors ────────────────────────────────────────────────────
|
||||
|
||||
(define
|
||||
cl-condition-slot
|
||||
(fn
|
||||
(c slot-name)
|
||||
(if (cl-condition? c) (get (get c "slots") slot-name) nil)))
|
||||
|
||||
(define
|
||||
cl-condition-message
|
||||
(fn
|
||||
(c)
|
||||
(if
|
||||
(not (cl-condition? c))
|
||||
(str c)
|
||||
(let
|
||||
((slots (get c "slots")))
|
||||
(or
|
||||
(get slots "message")
|
||||
(get slots "format-control")
|
||||
(str "Condition: " (get c "class")))))))
|
||||
|
||||
(define
|
||||
cl-simple-condition-format-control
|
||||
(fn (c) (cl-condition-slot c "format-control")))
|
||||
|
||||
(define
|
||||
cl-simple-condition-format-arguments
|
||||
(fn (c) (cl-condition-slot c "format-arguments")))
|
||||
|
||||
(define cl-type-error-datum (fn (c) (cl-condition-slot c "datum")))
|
||||
|
||||
(define
|
||||
cl-type-error-expected-type
|
||||
(fn (c) (cl-condition-slot c "expected-type")))
|
||||
|
||||
(define
|
||||
cl-arithmetic-error-operation
|
||||
(fn (c) (cl-condition-slot c "operation")))
|
||||
|
||||
(define
|
||||
cl-arithmetic-error-operands
|
||||
(fn (c) (cl-condition-slot c "operands")))
|
||||
|
||||
;; ── mutable handler + restart stacks ──────────────────────────────────────
|
||||
;;
|
||||
;; Handler entry: {:type "type-name" :fn (fn (condition) result)}
|
||||
;; Restart entry: {:name "restart-name" :fn (fn (&optional arg) result) :escape k}
|
||||
;;
|
||||
;; New handlers are prepended (checked first = most recent handler wins).
|
||||
|
||||
(define cl-handler-stack (list))
|
||||
(define cl-restart-stack (list))
|
||||
|
||||
(define
|
||||
cl-push-handlers
|
||||
(fn (entries) (set! cl-handler-stack (append entries cl-handler-stack))))
|
||||
|
||||
(define
|
||||
cl-pop-handlers
|
||||
(fn
|
||||
(n)
|
||||
(set! cl-handler-stack (slice cl-handler-stack n (len cl-handler-stack)))))
|
||||
|
||||
(define
|
||||
cl-push-restarts
|
||||
(fn (entries) (set! cl-restart-stack (append entries cl-restart-stack))))
|
||||
|
||||
(define
|
||||
cl-pop-restarts
|
||||
(fn
|
||||
(n)
|
||||
(set! cl-restart-stack (slice cl-restart-stack n (len cl-restart-stack)))))
|
||||
|
||||
;; ── *debugger-hook* + invoke-debugger ────────────────────────────────────
|
||||
;;
|
||||
;; cl-debugger-hook: called when an error propagates with no handler.
|
||||
;; Signature: (fn (condition hook) result). The hook arg is itself
|
||||
;; (so the hook can rebind it to nil to prevent recursion).
|
||||
;; nil = use default (re-raise as host error).
|
||||
|
||||
(define cl-debugger-hook nil)
|
||||
|
||||
(define cl-invoke-debugger
|
||||
(fn (c)
|
||||
(if (nil? cl-debugger-hook)
|
||||
(error (str "Debugger: " (cl-condition-message c)))
|
||||
(let ((hook cl-debugger-hook))
|
||||
(set! cl-debugger-hook nil)
|
||||
(let ((result (hook c hook)))
|
||||
(set! cl-debugger-hook hook)
|
||||
result)))))
|
||||
|
||||
;; ── *break-on-signals* ────────────────────────────────────────────────────
|
||||
;;
|
||||
;; When set to a type name string, cl-signal invokes the debugger hook
|
||||
;; before walking handlers if the condition is of that type.
|
||||
;; nil = disabled (ANSI default).
|
||||
|
||||
(define cl-break-on-signals nil)
|
||||
|
||||
;; ── invoke-restart-interactively ──────────────────────────────────────────
|
||||
;;
|
||||
;; Like invoke-restart but calls the restart's fn with no arguments
|
||||
;; (real CL would prompt the user for each arg via :interactive).
|
||||
|
||||
(define cl-invoke-restart-interactively
|
||||
(fn (name)
|
||||
(let ((entry (cl-find-restart-entry name cl-restart-stack)))
|
||||
(if (nil? entry)
|
||||
(error (str "No active restart: " name))
|
||||
(let ((restart-fn (get entry "fn"))
|
||||
(escape (get entry "escape")))
|
||||
(escape (restart-fn)))))))
|
||||
|
||||
;; ── cl-signal (non-unwinding) ─────────────────────────────────────────────
|
||||
;;
|
||||
;; Walks cl-handler-stack; for each matching entry, calls the handler fn.
|
||||
;; Handlers return normally — signal continues to the next matching handler.
|
||||
|
||||
(define
|
||||
cl-signal-obj
|
||||
(fn
|
||||
(obj stack)
|
||||
(if
|
||||
(empty? stack)
|
||||
nil
|
||||
(let
|
||||
((entry (first stack)))
|
||||
(if
|
||||
(cl-condition-of-type? obj (get entry "type"))
|
||||
(begin ((get entry "fn") obj) (cl-signal-obj obj (rest stack)))
|
||||
(cl-signal-obj obj (rest stack)))))))
|
||||
|
||||
(define cl-signal
|
||||
(fn (c)
|
||||
(let ((obj (if (cl-condition? c)
|
||||
c
|
||||
(cl-make-condition "simple-condition"
|
||||
"format-control" (str c)))))
|
||||
;; *break-on-signals*: invoke debugger hook when type matches
|
||||
(when (and (not (nil? cl-break-on-signals))
|
||||
(cl-condition-of-type? obj cl-break-on-signals))
|
||||
(cl-invoke-debugger obj))
|
||||
(cl-signal-obj obj cl-handler-stack))))
|
||||
|
||||
;; ── cl-error ───────────────────────────────────────────────────────────────
|
||||
;;
|
||||
;; Signals an error. If no handler catches it, raises a host-level error.
|
||||
|
||||
(define
|
||||
cl-error
|
||||
(fn
|
||||
(c &rest args)
|
||||
(let
|
||||
((obj (cond ((cl-condition? c) c) ((string? c) (cl-make-condition "simple-error" "format-control" c "format-arguments" args)) (:else (cl-make-condition "simple-error" "format-control" (str c))))))
|
||||
(cl-signal-obj obj cl-handler-stack)
|
||||
(cl-invoke-debugger obj))))
|
||||
|
||||
;; ── cl-warn ────────────────────────────────────────────────────────────────
|
||||
|
||||
(define
|
||||
cl-warn
|
||||
(fn
|
||||
(c &rest args)
|
||||
(let
|
||||
((obj (cond ((cl-condition? c) c) ((string? c) (cl-make-condition "simple-warning" "format-control" c "format-arguments" args)) (:else (cl-make-condition "simple-warning" "format-control" (str c))))))
|
||||
(cl-signal-obj obj cl-handler-stack))))
|
||||
|
||||
;; ── cl-handler-bind (non-unwinding) ───────────────────────────────────────
|
||||
;;
|
||||
;; bindings: list of (type-name handler-fn) pairs
|
||||
;; thunk: (fn () body)
|
||||
|
||||
(define
|
||||
cl-handler-bind
|
||||
(fn
|
||||
(bindings thunk)
|
||||
(let
|
||||
((entries (map (fn (b) {:fn (first (rest b)) :type (first b)}) bindings)))
|
||||
(begin
|
||||
(cl-push-handlers entries)
|
||||
(let
|
||||
((result (thunk)))
|
||||
(begin (cl-pop-handlers (len entries)) result))))))
|
||||
|
||||
;; ── cl-handler-case (unwinding) ───────────────────────────────────────────
|
||||
;;
|
||||
;; thunk: (fn () body)
|
||||
;; cases: list of (type-name handler-fn) pairs
|
||||
;;
|
||||
;; Uses call/cc for the escape continuation.
|
||||
|
||||
(define
|
||||
cl-handler-case
|
||||
(fn
|
||||
(thunk &rest cases)
|
||||
(call/cc
|
||||
(fn
|
||||
(escape)
|
||||
(let
|
||||
((entries (map (fn (c) {:fn (fn (x) (escape ((first (rest c)) x))) :type (first c)}) cases)))
|
||||
(begin
|
||||
(cl-push-handlers entries)
|
||||
(let
|
||||
((result (thunk)))
|
||||
(begin (cl-pop-handlers (len entries)) result))))))))
|
||||
|
||||
;; ── cl-restart-case ────────────────────────────────────────────────────────
|
||||
;;
|
||||
;; thunk: (fn () body)
|
||||
;; restarts: list of (name params body-fn) triples
|
||||
;; body-fn is (fn () val) or (fn (arg) val)
|
||||
|
||||
(define
|
||||
cl-restart-case
|
||||
(fn
|
||||
(thunk &rest restarts)
|
||||
(call/cc
|
||||
(fn
|
||||
(escape)
|
||||
(let
|
||||
((entries (map (fn (r) {:fn (first (rest (rest r))) :escape escape :name (first r)}) restarts)))
|
||||
(begin
|
||||
(cl-push-restarts entries)
|
||||
(let
|
||||
((result (thunk)))
|
||||
(begin (cl-pop-restarts (len entries)) result))))))))
|
||||
|
||||
;; ── cl-with-simple-restart ─────────────────────────────────────────────────
|
||||
|
||||
(define
|
||||
cl-with-simple-restart
|
||||
(fn
|
||||
(name description thunk)
|
||||
(cl-restart-case thunk (list name (list) (fn () nil)))))
|
||||
|
||||
;; ── find-restart / invoke-restart / compute-restarts ──────────────────────
|
||||
|
||||
(define
|
||||
cl-find-restart-entry
|
||||
(fn
|
||||
(name stack)
|
||||
(if
|
||||
(empty? stack)
|
||||
nil
|
||||
(let
|
||||
((entry (first stack)))
|
||||
(if
|
||||
(= (get entry "name") name)
|
||||
entry
|
||||
(cl-find-restart-entry name (rest stack)))))))
|
||||
|
||||
(define
|
||||
cl-find-restart
|
||||
(fn (name) (cl-find-restart-entry name cl-restart-stack)))
|
||||
|
||||
(define
|
||||
cl-invoke-restart
|
||||
(fn
|
||||
(name &rest args)
|
||||
(let
|
||||
((entry (cl-find-restart-entry name cl-restart-stack)))
|
||||
(if
|
||||
(nil? entry)
|
||||
(error (str "No active restart: " name))
|
||||
(let
|
||||
((restart-fn (get entry "fn")) (escape (get entry "escape")))
|
||||
(escape
|
||||
(if (empty? args) (restart-fn) (restart-fn (first args)))))))))
|
||||
|
||||
(define
|
||||
cl-compute-restarts
|
||||
(fn () (map (fn (e) (get e "name")) cl-restart-stack)))
|
||||
|
||||
;; ── with-condition-restarts (stub — association is advisory) ──────────────
|
||||
|
||||
(define cl-with-condition-restarts (fn (c restarts thunk) (thunk)))
|
||||
|
||||
;; ── cl-cerror ──────────────────────────────────────────────────────────────
|
||||
;;
|
||||
;; Signals a continuable error. The "continue" restart is established;
|
||||
;; invoke-restart "continue" to proceed past the error.
|
||||
|
||||
|
||||
|
||||
;; ── cl-cerror ──────────────────────────────────────────────────────────────
|
||||
;;
|
||||
;; Signals a continuable error. The "continue" restart is established;
|
||||
;; invoke-restart "continue" to proceed past the error.
|
||||
|
||||
(define cl-cerror
|
||||
(fn (continue-string c &rest args)
|
||||
(let ((obj (if (cl-condition? c)
|
||||
c
|
||||
(cl-make-condition "simple-error"
|
||||
"format-control" (str c)
|
||||
"format-arguments" args))))
|
||||
(cl-restart-case
|
||||
(fn () (cl-signal-obj obj cl-handler-stack))
|
||||
(list "continue" (list) (fn () nil))))))
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"generated": "2026-05-05T12:35:09Z",
|
||||
"total_pass": 518,
|
||||
"total_fail": 0,
|
||||
"suites": [
|
||||
{"name": "Phase 1: tokenizer/reader", "pass": 79, "fail": 0},
|
||||
{"name": "Phase 1: parser/lambda-lists", "pass": 31, "fail": 0},
|
||||
{"name": "Phase 2: evaluator", "pass": 182, "fail": 0},
|
||||
{"name": "Phase 3: condition system", "pass": 59, "fail": 0},
|
||||
{"name": "Phase 3: restart-demo", "pass": 7, "fail": 0},
|
||||
{"name": "Phase 3: parse-recover", "pass": 6, "fail": 0},
|
||||
{"name": "Phase 3: interactive-debugger", "pass": 7, "fail": 0},
|
||||
{"name": "Phase 4: CLOS", "pass": 41, "fail": 0},
|
||||
{"name": "Phase 4: geometry", "pass": 12, "fail": 0},
|
||||
{"name": "Phase 4: mop-trace", "pass": 13, "fail": 0},
|
||||
{"name": "Phase 5: macros+LOOP", "pass": 27, "fail": 0},
|
||||
{"name": "Phase 6: stdlib", "pass": 54, "fail": 0}
|
||||
]
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
# Common Lisp on SX — Scoreboard
|
||||
|
||||
_Generated: 2026-05-05 12:35 UTC_
|
||||
|
||||
| Suite | Pass | Fail | Status |
|
||||
|-------|------|------|--------|
|
||||
| Phase 1: tokenizer/reader | 79 | 0 | pass |
|
||||
| Phase 1: parser/lambda-lists | 31 | 0 | pass |
|
||||
| Phase 2: evaluator | 182 | 0 | pass |
|
||||
| Phase 3: condition system | 59 | 0 | pass |
|
||||
| Phase 3: restart-demo | 7 | 0 | pass |
|
||||
| Phase 3: parse-recover | 6 | 0 | pass |
|
||||
| Phase 3: interactive-debugger | 7 | 0 | pass |
|
||||
| Phase 4: CLOS | 41 | 0 | pass |
|
||||
| Phase 4: geometry | 12 | 0 | pass |
|
||||
| Phase 4: mop-trace | 13 | 0 | pass |
|
||||
| Phase 5: macros+LOOP | 27 | 0 | pass |
|
||||
| Phase 6: stdlib | 54 | 0 | pass |
|
||||
|
||||
**Total: 518 passed, 0 failed**
|
||||
@@ -1,443 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# lib/common-lisp/test.sh — quick smoke-test the CL runtime layer.
|
||||
# Uses sx_server.exe epoch protocol (same as lib/lua/test.sh).
|
||||
#
|
||||
# Usage:
|
||||
# bash lib/common-lisp/test.sh
|
||||
# bash lib/common-lisp/test.sh -v
|
||||
|
||||
set -uo pipefail
|
||||
cd "$(git rev-parse --show-toplevel)"
|
||||
|
||||
SX_SERVER="${SX_SERVER:-hosts/ocaml/_build/default/bin/sx_server.exe}"
|
||||
if [ ! -x "$SX_SERVER" ]; then
|
||||
SX_SERVER="/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe"
|
||||
fi
|
||||
if [ ! -x "$SX_SERVER" ]; then
|
||||
echo "ERROR: sx_server.exe not found. Run: cd hosts/ocaml && dune build"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERBOSE="${1:-}"
|
||||
PASS=0; FAIL=0; ERRORS=""
|
||||
TMPFILE=$(mktemp); trap "rm -f $TMPFILE" EXIT
|
||||
|
||||
cat > "$TMPFILE" << 'EPOCHS'
|
||||
(epoch 1)
|
||||
(load "spec/stdlib.sx")
|
||||
(load "lib/common-lisp/runtime.sx")
|
||||
|
||||
;; --- Type predicates ---
|
||||
(epoch 10)
|
||||
(eval "(cl-null? nil)")
|
||||
(epoch 11)
|
||||
(eval "(cl-null? false)")
|
||||
(epoch 12)
|
||||
(eval "(cl-consp? (list 1 2))")
|
||||
(epoch 13)
|
||||
(eval "(cl-consp? nil)")
|
||||
(epoch 14)
|
||||
(eval "(cl-listp? nil)")
|
||||
(epoch 15)
|
||||
(eval "(cl-listp? (list 1))")
|
||||
(epoch 16)
|
||||
(eval "(cl-atom? nil)")
|
||||
(epoch 17)
|
||||
(eval "(cl-atom? (list 1))")
|
||||
(epoch 18)
|
||||
(eval "(cl-integerp? 42)")
|
||||
(epoch 19)
|
||||
(eval "(cl-floatp? 3.14)")
|
||||
(epoch 20)
|
||||
(eval "(cl-characterp? (integer->char 65))")
|
||||
(epoch 21)
|
||||
(eval "(cl-stringp? \"hello\")")
|
||||
|
||||
;; --- Arithmetic ---
|
||||
(epoch 30)
|
||||
(eval "(cl-mod 10 3)")
|
||||
(epoch 31)
|
||||
(eval "(cl-rem 10 3)")
|
||||
(epoch 32)
|
||||
(eval "(cl-quotient 10 3)")
|
||||
(epoch 33)
|
||||
(eval "(cl-gcd 12 8)")
|
||||
(epoch 34)
|
||||
(eval "(cl-lcm 4 6)")
|
||||
(epoch 35)
|
||||
(eval "(cl-abs -5)")
|
||||
(epoch 36)
|
||||
(eval "(cl-abs 5)")
|
||||
(epoch 37)
|
||||
(eval "(cl-min 2 7)")
|
||||
(epoch 38)
|
||||
(eval "(cl-max 2 7)")
|
||||
(epoch 39)
|
||||
(eval "(cl-evenp? 4)")
|
||||
(epoch 40)
|
||||
(eval "(cl-evenp? 3)")
|
||||
(epoch 41)
|
||||
(eval "(cl-oddp? 7)")
|
||||
(epoch 42)
|
||||
(eval "(cl-zerop? 0)")
|
||||
(epoch 43)
|
||||
(eval "(cl-plusp? 1)")
|
||||
(epoch 44)
|
||||
(eval "(cl-minusp? -1)")
|
||||
(epoch 45)
|
||||
(eval "(cl-signum 42)")
|
||||
(epoch 46)
|
||||
(eval "(cl-signum -7)")
|
||||
(epoch 47)
|
||||
(eval "(cl-signum 0)")
|
||||
|
||||
;; --- Characters ---
|
||||
(epoch 50)
|
||||
(eval "(cl-char-code (integer->char 65))")
|
||||
(epoch 51)
|
||||
(eval "(char? (cl-code-char 65))")
|
||||
(epoch 52)
|
||||
(eval "(cl-char=? (integer->char 65) (integer->char 65))")
|
||||
(epoch 53)
|
||||
(eval "(cl-char<? (integer->char 65) (integer->char 90))")
|
||||
(epoch 54)
|
||||
(eval "(cl-char-code cl-char-space)")
|
||||
(epoch 55)
|
||||
(eval "(cl-char-code cl-char-newline)")
|
||||
(epoch 56)
|
||||
(eval "(cl-alpha-char-p (integer->char 65))")
|
||||
(epoch 57)
|
||||
(eval "(cl-digit-char-p (integer->char 48))")
|
||||
|
||||
;; --- Format ---
|
||||
(epoch 60)
|
||||
(eval "(cl-format nil \"hello\")")
|
||||
(epoch 61)
|
||||
(eval "(cl-format nil \"~a\" \"world\")")
|
||||
(epoch 62)
|
||||
(eval "(cl-format nil \"~d\" 42)")
|
||||
(epoch 63)
|
||||
(eval "(cl-format nil \"~x\" 255)")
|
||||
(epoch 64)
|
||||
(eval "(cl-format nil \"x=~d y=~d\" 3 4)")
|
||||
|
||||
;; --- Gensym ---
|
||||
(epoch 70)
|
||||
(eval "(= (type-of (cl-gensym)) \"symbol\")")
|
||||
(epoch 71)
|
||||
(eval "(not (= (cl-gensym) (cl-gensym)))")
|
||||
|
||||
;; --- Sets ---
|
||||
(epoch 80)
|
||||
(eval "(cl-set? (cl-make-set))")
|
||||
(epoch 81)
|
||||
(eval "(let ((s (cl-make-set))) (do (cl-set-add s 1) (cl-set-memberp s 1)))")
|
||||
(epoch 82)
|
||||
(eval "(cl-set-memberp (cl-make-set) 42)")
|
||||
(epoch 83)
|
||||
(eval "(cl-set-memberp (cl-list->set (list 1 2 3)) 2)")
|
||||
|
||||
;; --- Lists ---
|
||||
(epoch 90)
|
||||
(eval "(cl-nth 0 (list 1 2 3))")
|
||||
(epoch 91)
|
||||
(eval "(cl-nth 2 (list 1 2 3))")
|
||||
(epoch 92)
|
||||
(eval "(cl-last (list 1 2 3))")
|
||||
(epoch 93)
|
||||
(eval "(cl-butlast (list 1 2 3))")
|
||||
(epoch 94)
|
||||
(eval "(cl-nthcdr 1 (list 1 2 3))")
|
||||
(epoch 95)
|
||||
(eval "(cl-assoc \"b\" (list (list \"a\" 1) (list \"b\" 2)))")
|
||||
(epoch 96)
|
||||
(eval "(cl-assoc \"z\" (list (list \"a\" 1)))")
|
||||
(epoch 97)
|
||||
(eval "(cl-getf (list \"x\" 42 \"y\" 99) \"x\")")
|
||||
(epoch 98)
|
||||
(eval "(cl-adjoin 0 (list 1 2))")
|
||||
(epoch 99)
|
||||
(eval "(cl-adjoin 1 (list 1 2))")
|
||||
(epoch 100)
|
||||
(eval "(cl-member 2 (list 1 2 3))")
|
||||
(epoch 101)
|
||||
(eval "(cl-member 9 (list 1 2 3))")
|
||||
(epoch 102)
|
||||
(eval "(cl-flatten (list 1 (list 2 3) 4))")
|
||||
|
||||
;; --- Radix ---
|
||||
(epoch 110)
|
||||
(eval "(cl-format-binary 10)")
|
||||
(epoch 111)
|
||||
(eval "(cl-format-octal 15)")
|
||||
(epoch 112)
|
||||
(eval "(cl-format-hex 255)")
|
||||
(epoch 113)
|
||||
(eval "(cl-format-decimal 42)")
|
||||
(epoch 114)
|
||||
(eval "(cl-integer-to-string 31 16)")
|
||||
(epoch 115)
|
||||
(eval "(cl-string-to-integer \"1f\" 16)")
|
||||
|
||||
EPOCHS
|
||||
|
||||
OUTPUT=$(timeout 30 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
|
||||
|
||||
check() {
|
||||
local epoch="$1" desc="$2" expected="$3"
|
||||
local actual
|
||||
# ok-len format: value appears on the line AFTER "(ok-len N length)"
|
||||
actual=$(echo "$OUTPUT" | grep -A1 "^(ok-len $epoch " | tail -1 || true)
|
||||
# strip any leading "(ok-len ...)" if grep -A1 returned it instead
|
||||
if echo "$actual" | grep -q "^(ok-len"; then actual=""; fi
|
||||
if [ -z "$actual" ]; then
|
||||
actual=$(echo "$OUTPUT" | grep "^(ok $epoch " | head -1 || true)
|
||||
fi
|
||||
if [ -z "$actual" ]; then
|
||||
actual=$(echo "$OUTPUT" | grep "^(error $epoch " | head -1 || true)
|
||||
fi
|
||||
[ -z "$actual" ] && actual="<no output for epoch $epoch>"
|
||||
|
||||
if echo "$actual" | grep -qF -- "$expected"; then
|
||||
PASS=$((PASS+1))
|
||||
[ "$VERBOSE" = "-v" ] && echo " ok $desc"
|
||||
else
|
||||
FAIL=$((FAIL+1))
|
||||
ERRORS+=" FAIL [$desc] (epoch $epoch) expected: $expected | actual: $actual
|
||||
"
|
||||
fi
|
||||
}
|
||||
|
||||
# Type predicates
|
||||
check 10 "cl-null? nil" "true"
|
||||
check 11 "cl-null? false" "false"
|
||||
check 12 "cl-consp? pair" "true"
|
||||
check 13 "cl-consp? nil" "false"
|
||||
check 14 "cl-listp? nil" "true"
|
||||
check 15 "cl-listp? list" "true"
|
||||
check 16 "cl-atom? nil" "true"
|
||||
check 17 "cl-atom? pair" "false"
|
||||
check 18 "cl-integerp?" "true"
|
||||
check 19 "cl-floatp?" "true"
|
||||
check 20 "cl-characterp?" "true"
|
||||
check 21 "cl-stringp?" "true"
|
||||
|
||||
# Arithmetic
|
||||
check 30 "cl-mod 10 3" "1"
|
||||
check 31 "cl-rem 10 3" "1"
|
||||
check 32 "cl-quotient 10 3" "3"
|
||||
check 33 "cl-gcd 12 8" "4"
|
||||
check 34 "cl-lcm 4 6" "12"
|
||||
check 35 "cl-abs -5" "5"
|
||||
check 36 "cl-abs 5" "5"
|
||||
check 37 "cl-min 2 7" "2"
|
||||
check 38 "cl-max 2 7" "7"
|
||||
check 39 "cl-evenp? 4" "true"
|
||||
check 40 "cl-evenp? 3" "false"
|
||||
check 41 "cl-oddp? 7" "true"
|
||||
check 42 "cl-zerop? 0" "true"
|
||||
check 43 "cl-plusp? 1" "true"
|
||||
check 44 "cl-minusp? -1" "true"
|
||||
check 45 "cl-signum pos" "1"
|
||||
check 46 "cl-signum neg" "-1"
|
||||
check 47 "cl-signum zero" "0"
|
||||
|
||||
# Characters
|
||||
check 50 "cl-char-code" "65"
|
||||
check 51 "code-char returns char" "true"
|
||||
check 52 "cl-char=?" "true"
|
||||
check 53 "cl-char<?" "true"
|
||||
check 54 "cl-char-space code" "32"
|
||||
check 55 "cl-char-newline code" "10"
|
||||
check 56 "cl-alpha-char-p A" "true"
|
||||
check 57 "cl-digit-char-p 0" "true"
|
||||
|
||||
# Format
|
||||
check 60 "cl-format plain" '"hello"'
|
||||
check 61 "cl-format ~a" '"world"'
|
||||
check 62 "cl-format ~d" '"42"'
|
||||
check 63 "cl-format ~x" '"ff"'
|
||||
check 64 "cl-format multi" '"x=3 y=4"'
|
||||
|
||||
# Gensym
|
||||
check 70 "gensym returns symbol" "true"
|
||||
check 71 "gensyms are unique" "true"
|
||||
|
||||
# Sets
|
||||
check 80 "make-set is set?" "true"
|
||||
check 81 "set-add + member" "true"
|
||||
check 82 "member in empty" "false"
|
||||
check 83 "list->set member" "true"
|
||||
|
||||
# Lists
|
||||
check 90 "cl-nth 0" "1"
|
||||
check 91 "cl-nth 2" "3"
|
||||
check 92 "cl-last" "(3)"
|
||||
check 93 "cl-butlast" "(1 2)"
|
||||
check 94 "cl-nthcdr 1" "(2 3)"
|
||||
check 95 "cl-assoc hit" '("b" 2)'
|
||||
check 96 "cl-assoc miss" "nil"
|
||||
check 97 "cl-getf hit" "42"
|
||||
check 98 "cl-adjoin new" "(0 1 2)"
|
||||
check 99 "cl-adjoin dup" "(1 2)"
|
||||
check 100 "cl-member hit" "(2 3)"
|
||||
check 101 "cl-member miss" "nil"
|
||||
check 102 "cl-flatten" "(1 2 3 4)"
|
||||
|
||||
# Radix
|
||||
check 110 "cl-format-binary 10" '"1010"'
|
||||
check 111 "cl-format-octal 15" '"17"'
|
||||
check 112 "cl-format-hex 255" '"ff"'
|
||||
check 113 "cl-format-decimal 42" '"42"'
|
||||
check 114 "n->s base 16" '"1f"'
|
||||
check 115 "s->n base 16" "31"
|
||||
|
||||
# ── Phase 2: condition system unit tests ─────────────────────────────────────
|
||||
# Load runtime.sx then conditions.sx; query the passed/failed/failures globals.
|
||||
UNIT_FILE=$(mktemp); trap "rm -f $UNIT_FILE" EXIT
|
||||
cat > "$UNIT_FILE" << 'UNIT'
|
||||
(epoch 1)
|
||||
(load "spec/stdlib.sx")
|
||||
(epoch 2)
|
||||
(load "lib/common-lisp/runtime.sx")
|
||||
(epoch 3)
|
||||
(load "lib/common-lisp/tests/conditions.sx")
|
||||
(epoch 4)
|
||||
(eval "passed")
|
||||
(epoch 5)
|
||||
(eval "failed")
|
||||
(epoch 6)
|
||||
(eval "failures")
|
||||
UNIT
|
||||
|
||||
UNIT_OUT=$(timeout 30 "$SX_SERVER" < "$UNIT_FILE" 2>/dev/null)
|
||||
|
||||
# extract passed/failed counts from ok-len lines
|
||||
UNIT_PASSED=$(echo "$UNIT_OUT" | grep -A1 "^(ok-len 4 " | tail -1 || true)
|
||||
UNIT_FAILED=$(echo "$UNIT_OUT" | grep -A1 "^(ok-len 5 " | tail -1 || true)
|
||||
UNIT_ERRS=$(echo "$UNIT_OUT" | grep -A1 "^(ok-len 6 " | tail -1 || true)
|
||||
# fallback: try plain ok lines
|
||||
[ -z "$UNIT_PASSED" ] && UNIT_PASSED=$(echo "$UNIT_OUT" | grep "^(ok 4 " | awk '{print $3}' | tr -d ')' || true)
|
||||
[ -z "$UNIT_FAILED" ] && UNIT_FAILED=$(echo "$UNIT_OUT" | grep "^(ok 5 " | awk '{print $3}' | tr -d ')' || true)
|
||||
[ -z "$UNIT_PASSED" ] && UNIT_PASSED=0
|
||||
[ -z "$UNIT_FAILED" ] && UNIT_FAILED=0
|
||||
|
||||
if [ "$UNIT_FAILED" = "0" ] && [ "$UNIT_PASSED" -gt 0 ] 2>/dev/null; then
|
||||
PASS=$((PASS + UNIT_PASSED))
|
||||
[ "$VERBOSE" = "-v" ] && echo " ok condition tests ($UNIT_PASSED)"
|
||||
else
|
||||
FAIL=$((FAIL + 1))
|
||||
ERRORS+=" FAIL [condition tests] (${UNIT_PASSED} passed, ${UNIT_FAILED} failed) ${UNIT_ERRS}
|
||||
"
|
||||
fi
|
||||
|
||||
# ── Phase 3: classic program tests ───────────────────────────────────────────
|
||||
run_program_suite() {
|
||||
local prog="$1" pass_var="$2" fail_var="$3" failures_var="$4"
|
||||
local PROG_FILE=$(mktemp)
|
||||
printf '(epoch 1)\n(load "spec/stdlib.sx")\n(epoch 2)\n(load "lib/common-lisp/runtime.sx")\n(epoch 3)\n(load "%s")\n(epoch 4)\n(eval "%s")\n(epoch 5)\n(eval "%s")\n(epoch 6)\n(eval "%s")\n' \
|
||||
"$prog" "$pass_var" "$fail_var" "$failures_var" > "$PROG_FILE"
|
||||
local OUT; OUT=$(timeout 20 "$SX_SERVER" < "$PROG_FILE" 2>/dev/null)
|
||||
rm -f "$PROG_FILE"
|
||||
local P F
|
||||
P=$(echo "$OUT" | grep -A1 "^(ok-len 4 " | tail -1 || true)
|
||||
F=$(echo "$OUT" | grep -A1 "^(ok-len 5 " | tail -1 || true)
|
||||
local ERRS; ERRS=$(echo "$OUT" | grep -A1 "^(ok-len 6 " | tail -1 || true)
|
||||
[ -z "$P" ] && P=0; [ -z "$F" ] && F=0
|
||||
if [ "$F" = "0" ] && [ "$P" -gt 0 ] 2>/dev/null; then
|
||||
PASS=$((PASS + P))
|
||||
[ "$VERBOSE" = "-v" ] && echo " ok $prog ($P)"
|
||||
else
|
||||
FAIL=$((FAIL + 1))
|
||||
ERRORS+=" FAIL [$prog] (${P} passed, ${F} failed) ${ERRS}
|
||||
"
|
||||
fi
|
||||
}
|
||||
|
||||
run_program_suite \
|
||||
"lib/common-lisp/tests/programs/restart-demo.sx" \
|
||||
"demo-passed" "demo-failed" "demo-failures"
|
||||
|
||||
run_program_suite \
|
||||
"lib/common-lisp/tests/programs/parse-recover.sx" \
|
||||
"parse-passed" "parse-failed" "parse-failures"
|
||||
|
||||
run_program_suite \
|
||||
"lib/common-lisp/tests/programs/interactive-debugger.sx" \
|
||||
"debugger-passed" "debugger-failed" "debugger-failures"
|
||||
|
||||
# ── Phase 4: CLOS unit tests ─────────────────────────────────────────────────
|
||||
CLOS_FILE=$(mktemp); trap "rm -f $CLOS_FILE" EXIT
|
||||
printf '(epoch 1)\n(load "spec/stdlib.sx")\n(epoch 2)\n(load "lib/common-lisp/runtime.sx")\n(epoch 3)\n(load "lib/common-lisp/clos.sx")\n(epoch 4)\n(load "lib/common-lisp/tests/clos.sx")\n(epoch 5)\n(eval "passed")\n(epoch 6)\n(eval "failed")\n(epoch 7)\n(eval "failures")\n' > "$CLOS_FILE"
|
||||
CLOS_OUT=$(timeout 30 "$SX_SERVER" < "$CLOS_FILE" 2>/dev/null)
|
||||
rm -f "$CLOS_FILE"
|
||||
CLOS_PASSED=$(echo "$CLOS_OUT" | grep -A1 "^(ok-len 5 " | tail -1 || true)
|
||||
CLOS_FAILED=$(echo "$CLOS_OUT" | grep -A1 "^(ok-len 6 " | tail -1 || true)
|
||||
[ -z "$CLOS_PASSED" ] && CLOS_PASSED=$(echo "$CLOS_OUT" | grep "^(ok 5 " | awk '{print $3}' | tr -d ')' || true)
|
||||
[ -z "$CLOS_FAILED" ] && CLOS_FAILED=$(echo "$CLOS_OUT" | grep "^(ok 6 " | awk '{print $3}' | tr -d ')' || true)
|
||||
[ -z "$CLOS_PASSED" ] && CLOS_PASSED=0; [ -z "$CLOS_FAILED" ] && CLOS_FAILED=0
|
||||
if [ "$CLOS_FAILED" = "0" ] && [ "$CLOS_PASSED" -gt 0 ] 2>/dev/null; then
|
||||
PASS=$((PASS + CLOS_PASSED))
|
||||
[ "$VERBOSE" = "-v" ] && echo " ok CLOS unit tests ($CLOS_PASSED)"
|
||||
else
|
||||
FAIL=$((FAIL + 1))
|
||||
ERRORS+=" FAIL [CLOS unit tests] (${CLOS_PASSED} passed, ${CLOS_FAILED} failed)
|
||||
"
|
||||
fi
|
||||
|
||||
# ── Phase 4: CLOS classic programs ───────────────────────────────────────────
|
||||
run_clos_suite() {
|
||||
local prog="$1" pass_var="$2" fail_var="$3" failures_var="$4"
|
||||
local PROG_FILE=$(mktemp)
|
||||
printf '(epoch 1)\n(load "spec/stdlib.sx")\n(epoch 2)\n(load "lib/common-lisp/runtime.sx")\n(epoch 3)\n(load "lib/common-lisp/clos.sx")\n(epoch 4)\n(load "%s")\n(epoch 5)\n(eval "%s")\n(epoch 6)\n(eval "%s")\n(epoch 7)\n(eval "%s")\n' \
|
||||
"$prog" "$pass_var" "$fail_var" "$failures_var" > "$PROG_FILE"
|
||||
local OUT; OUT=$(timeout 20 "$SX_SERVER" < "$PROG_FILE" 2>/dev/null)
|
||||
rm -f "$PROG_FILE"
|
||||
local P F
|
||||
P=$(echo "$OUT" | grep -A1 "^(ok-len 5 " | tail -1 || true)
|
||||
F=$(echo "$OUT" | grep -A1 "^(ok-len 6 " | tail -1 || true)
|
||||
local ERRS; ERRS=$(echo "$OUT" | grep -A1 "^(ok-len 7 " | tail -1 || true)
|
||||
[ -z "$P" ] && P=0; [ -z "$F" ] && F=0
|
||||
if [ "$F" = "0" ] && [ "$P" -gt 0 ] 2>/dev/null; then
|
||||
PASS=$((PASS + P))
|
||||
[ "$VERBOSE" = "-v" ] && echo " ok $prog ($P)"
|
||||
else
|
||||
FAIL=$((FAIL + 1))
|
||||
ERRORS+=" FAIL [$prog] (${P} passed, ${F} failed) ${ERRS}
|
||||
"
|
||||
fi
|
||||
}
|
||||
|
||||
run_clos_suite \
|
||||
"lib/common-lisp/tests/programs/geometry.sx" \
|
||||
"geo-passed" "geo-failed" "geo-failures"
|
||||
|
||||
run_clos_suite \
|
||||
"lib/common-lisp/tests/programs/mop-trace.sx" \
|
||||
"mop-passed" "mop-failed" "mop-failures"
|
||||
|
||||
# ── Phase 5: macros + LOOP ───────────────────────────────────────────────────
|
||||
MACRO_FILE=$(mktemp); trap "rm -f $MACRO_FILE" EXIT
|
||||
printf '(epoch 1)\n(load "spec/stdlib.sx")\n(epoch 2)\n(load "lib/common-lisp/reader.sx")\n(epoch 3)\n(load "lib/common-lisp/parser.sx")\n(epoch 4)\n(load "lib/common-lisp/eval.sx")\n(epoch 5)\n(load "lib/common-lisp/loop.sx")\n(epoch 6)\n(load "lib/common-lisp/tests/macros.sx")\n(epoch 7)\n(eval "macro-passed")\n(epoch 8)\n(eval "macro-failed")\n(epoch 9)\n(eval "macro-failures")\n' > "$MACRO_FILE"
|
||||
MACRO_OUT=$(timeout 60 "$SX_SERVER" < "$MACRO_FILE" 2>/dev/null)
|
||||
rm -f "$MACRO_FILE"
|
||||
MACRO_PASSED=$(echo "$MACRO_OUT" | grep -A1 "^(ok-len 7 " | tail -1 || true)
|
||||
MACRO_FAILED=$(echo "$MACRO_OUT" | grep -A1 "^(ok-len 8 " | tail -1 || true)
|
||||
[ -z "$MACRO_PASSED" ] && MACRO_PASSED=0; [ -z "$MACRO_FAILED" ] && MACRO_FAILED=0
|
||||
if [ "$MACRO_FAILED" = "0" ] && [ "$MACRO_PASSED" -gt 0 ] 2>/dev/null; then
|
||||
PASS=$((PASS + MACRO_PASSED))
|
||||
[ "$VERBOSE" = "-v" ] && echo " ok Phase 5 macros+LOOP ($MACRO_PASSED)"
|
||||
else
|
||||
FAIL=$((FAIL + 1))
|
||||
ERRORS+=" FAIL [Phase 5 macros+LOOP] (${MACRO_PASSED} passed, ${MACRO_FAILED} failed)
|
||||
"
|
||||
fi
|
||||
|
||||
TOTAL=$((PASS+FAIL))
|
||||
if [ $FAIL -eq 0 ]; then
|
||||
echo "ok $PASS/$TOTAL lib/common-lisp tests passed"
|
||||
else
|
||||
echo "FAIL $PASS/$TOTAL passed, $FAIL failed:"
|
||||
echo "$ERRORS"
|
||||
fi
|
||||
[ $FAIL -eq 0 ]
|
||||
@@ -1,334 +0,0 @@
|
||||
;; lib/common-lisp/tests/clos.sx — CLOS test suite
|
||||
;;
|
||||
;; Loaded after: spec/stdlib.sx, lib/common-lisp/runtime.sx, lib/common-lisp/clos.sx
|
||||
|
||||
(define passed 0)
|
||||
(define failed 0)
|
||||
(define failures (list))
|
||||
|
||||
(define
|
||||
assert-equal
|
||||
(fn
|
||||
(label got expected)
|
||||
(if
|
||||
(= got expected)
|
||||
(set! passed (+ passed 1))
|
||||
(begin
|
||||
(set! failed (+ failed 1))
|
||||
(set!
|
||||
failures
|
||||
(append
|
||||
failures
|
||||
(list
|
||||
(str
|
||||
"FAIL ["
|
||||
label
|
||||
"]: got="
|
||||
(inspect got)
|
||||
" expected="
|
||||
(inspect expected)))))))))
|
||||
|
||||
(define
|
||||
assert-true
|
||||
(fn
|
||||
(label got)
|
||||
(if
|
||||
got
|
||||
(set! passed (+ passed 1))
|
||||
(begin
|
||||
(set! failed (+ failed 1))
|
||||
(set!
|
||||
failures
|
||||
(append
|
||||
failures
|
||||
(list
|
||||
(str "FAIL [" label "]: expected true, got " (inspect got)))))))))
|
||||
|
||||
(define
|
||||
assert-nil
|
||||
(fn
|
||||
(label got)
|
||||
(if
|
||||
(nil? got)
|
||||
(set! passed (+ passed 1))
|
||||
(begin
|
||||
(set! failed (+ failed 1))
|
||||
(set!
|
||||
failures
|
||||
(append
|
||||
failures
|
||||
(list (str "FAIL [" label "]: expected nil, got " (inspect got)))))))))
|
||||
|
||||
;; ── 1. class-of for built-in types ────────────────────────────────────────
|
||||
|
||||
(assert-equal "class-of integer" (clos-class-of 42) "integer")
|
||||
(assert-equal "class-of float" (clos-class-of 3.14) "float")
|
||||
(assert-equal "class-of string" (clos-class-of "hi") "string")
|
||||
(assert-equal "class-of nil" (clos-class-of nil) "null")
|
||||
(assert-equal "class-of list" (clos-class-of (list 1)) "cons")
|
||||
(assert-equal "class-of empty" (clos-class-of (list)) "null")
|
||||
|
||||
;; ── 2. subclass-of? ───────────────────────────────────────────────────────
|
||||
|
||||
(assert-true "integer subclass-of t" (clos-subclass-of? "integer" "t"))
|
||||
(assert-true "float subclass-of t" (clos-subclass-of? "float" "t"))
|
||||
(assert-true "t subclass-of t" (clos-subclass-of? "t" "t"))
|
||||
(assert-equal
|
||||
"integer not subclass-of float"
|
||||
(clos-subclass-of? "integer" "float")
|
||||
false)
|
||||
|
||||
;; ── 3. defclass + make-instance ───────────────────────────────────────────
|
||||
|
||||
(clos-defclass "point" (list "t") (list {:initform 0 :initarg ":x" :reader nil :writer nil :accessor "point-x" :name "x"} {:initform 0 :initarg ":y" :reader nil :writer nil :accessor "point-y" :name "y"}))
|
||||
|
||||
(let
|
||||
((p (clos-make-instance "point" ":x" 3 ":y" 4)))
|
||||
(begin
|
||||
(assert-equal "make-instance slot x" (clos-slot-value p "x") 3)
|
||||
(assert-equal "make-instance slot y" (clos-slot-value p "y") 4)
|
||||
(assert-equal "class-of instance" (clos-class-of p) "point")
|
||||
(assert-true "instance-of? point" (clos-instance-of? p "point"))
|
||||
(assert-true "instance-of? t" (clos-instance-of? p "t"))
|
||||
(assert-equal "instance-of? string" (clos-instance-of? p "string") false)))
|
||||
|
||||
;; initform defaults
|
||||
(let
|
||||
((p0 (clos-make-instance "point")))
|
||||
(begin
|
||||
(assert-equal "initform default x=0" (clos-slot-value p0 "x") 0)
|
||||
(assert-equal "initform default y=0" (clos-slot-value p0 "y") 0)))
|
||||
|
||||
;; ── 4. slot-value / set-slot-value! ──────────────────────────────────────
|
||||
|
||||
(let
|
||||
((p (clos-make-instance "point" ":x" 10 ":y" 20)))
|
||||
(begin
|
||||
(clos-set-slot-value! p "x" 99)
|
||||
(assert-equal "set-slot-value! x" (clos-slot-value p "x") 99)
|
||||
(assert-equal "slot-value y unchanged" (clos-slot-value p "y") 20)))
|
||||
|
||||
;; ── 5. slot-boundp ────────────────────────────────────────────────────────
|
||||
|
||||
(let
|
||||
((p (clos-make-instance "point" ":x" 5)))
|
||||
(begin
|
||||
(assert-true "slot-boundp x" (clos-slot-boundp p "x"))
|
||||
(assert-true "slot-boundp y (initform 0)" (clos-slot-boundp p "y"))))
|
||||
|
||||
;; ── 6. find-class ─────────────────────────────────────────────────────────
|
||||
|
||||
(assert-equal
|
||||
"find-class point"
|
||||
(get (clos-find-class "point") "name")
|
||||
"point")
|
||||
(assert-nil "find-class missing" (clos-find-class "no-such-class"))
|
||||
|
||||
;; ── 7. inheritance ────────────────────────────────────────────────────────
|
||||
|
||||
(clos-defclass "colored-point" (list "point") (list {:initform "white" :initarg ":color" :reader nil :writer nil :accessor nil :name "color"}))
|
||||
|
||||
(let
|
||||
((cp (clos-make-instance "colored-point" ":x" 1 ":y" 2 ":color" "red")))
|
||||
(begin
|
||||
(assert-equal "inherited slot x" (clos-slot-value cp "x") 1)
|
||||
(assert-equal "inherited slot y" (clos-slot-value cp "y") 2)
|
||||
(assert-equal "own slot color" (clos-slot-value cp "color") "red")
|
||||
(assert-true
|
||||
"instance-of? colored-point"
|
||||
(clos-instance-of? cp "colored-point"))
|
||||
(assert-true "instance-of? point (parent)" (clos-instance-of? cp "point"))
|
||||
(assert-true "instance-of? t (root)" (clos-instance-of? cp "t"))))
|
||||
|
||||
;; ── 8. defgeneric + primary method ───────────────────────────────────────
|
||||
|
||||
(clos-defgeneric "describe-obj" {})
|
||||
|
||||
(clos-defmethod
|
||||
"describe-obj"
|
||||
(list)
|
||||
(list "point")
|
||||
(fn
|
||||
(args next-fn)
|
||||
(let
|
||||
((p (first args)))
|
||||
(str "(" (clos-slot-value p "x") "," (clos-slot-value p "y") ")"))))
|
||||
|
||||
(clos-defmethod
|
||||
"describe-obj"
|
||||
(list)
|
||||
(list "t")
|
||||
(fn (args next-fn) (str "object:" (inspect (first args)))))
|
||||
|
||||
(let
|
||||
((p (clos-make-instance "point" ":x" 3 ":y" 4)))
|
||||
(begin
|
||||
(assert-equal
|
||||
"primary method for point"
|
||||
(clos-call-generic "describe-obj" (list p))
|
||||
"(3,4)")
|
||||
(assert-equal
|
||||
"fallback t method"
|
||||
(clos-call-generic "describe-obj" (list 42))
|
||||
"object:42")))
|
||||
|
||||
;; ── 9. method inheritance + specificity ───────────────────────────────────
|
||||
|
||||
(clos-defmethod
|
||||
"describe-obj"
|
||||
(list)
|
||||
(list "colored-point")
|
||||
(fn
|
||||
(args next-fn)
|
||||
(let
|
||||
((cp (first args)))
|
||||
(str
|
||||
(clos-slot-value cp "color")
|
||||
"@("
|
||||
(clos-slot-value cp "x")
|
||||
","
|
||||
(clos-slot-value cp "y")
|
||||
")"))))
|
||||
|
||||
(let
|
||||
((cp (clos-make-instance "colored-point" ":x" 5 ":y" 6 ":color" "blue")))
|
||||
(assert-equal
|
||||
"most specific method wins"
|
||||
(clos-call-generic "describe-obj" (list cp))
|
||||
"blue@(5,6)"))
|
||||
|
||||
;; ── 10. :before / :after / :around qualifiers ─────────────────────────────
|
||||
|
||||
(clos-defgeneric "logged-action" {})
|
||||
|
||||
(clos-defmethod
|
||||
"logged-action"
|
||||
(list "before")
|
||||
(list "t")
|
||||
(fn (args next-fn) (set! action-log (append action-log (list "before")))))
|
||||
|
||||
(clos-defmethod
|
||||
"logged-action"
|
||||
(list)
|
||||
(list "t")
|
||||
(fn
|
||||
(args next-fn)
|
||||
(set! action-log (append action-log (list "primary")))
|
||||
"result"))
|
||||
|
||||
(clos-defmethod
|
||||
"logged-action"
|
||||
(list "after")
|
||||
(list "t")
|
||||
(fn (args next-fn) (set! action-log (append action-log (list "after")))))
|
||||
|
||||
(define action-log (list))
|
||||
(clos-call-generic "logged-action" (list 1))
|
||||
(assert-equal
|
||||
":before/:after order"
|
||||
action-log
|
||||
(list "before" "primary" "after"))
|
||||
|
||||
;; :around
|
||||
(define around-log (list))
|
||||
|
||||
(clos-defgeneric "wrapped-action" {})
|
||||
|
||||
(clos-defmethod
|
||||
"wrapped-action"
|
||||
(list "around")
|
||||
(list "t")
|
||||
(fn
|
||||
(args next-fn)
|
||||
(set! around-log (append around-log (list "around-enter")))
|
||||
(let
|
||||
((r (next-fn)))
|
||||
(set! around-log (append around-log (list "around-exit")))
|
||||
r)))
|
||||
|
||||
(clos-defmethod
|
||||
"wrapped-action"
|
||||
(list)
|
||||
(list "t")
|
||||
(fn
|
||||
(args next-fn)
|
||||
(set! around-log (append around-log (list "primary")))
|
||||
42))
|
||||
|
||||
(let
|
||||
((r (clos-call-generic "wrapped-action" (list nil))))
|
||||
(begin
|
||||
(assert-equal ":around result" r 42)
|
||||
(assert-equal
|
||||
":around log"
|
||||
around-log
|
||||
(list "around-enter" "primary" "around-exit"))))
|
||||
|
||||
;; ── 11. call-next-method ─────────────────────────────────────────────────
|
||||
|
||||
(clos-defgeneric "chain-test" {})
|
||||
|
||||
(clos-defmethod
|
||||
"chain-test"
|
||||
(list)
|
||||
(list "colored-point")
|
||||
(fn (args next-fn) (str "colored:" (clos-call-next-method next-fn))))
|
||||
|
||||
(clos-defmethod
|
||||
"chain-test"
|
||||
(list)
|
||||
(list "point")
|
||||
(fn (args next-fn) "point-base"))
|
||||
|
||||
(let
|
||||
((cp (clos-make-instance "colored-point" ":x" 0 ":y" 0 ":color" "green")))
|
||||
(assert-equal
|
||||
"call-next-method chains"
|
||||
(clos-call-generic "chain-test" (list cp))
|
||||
"colored:point-base"))
|
||||
|
||||
;; ── 12. accessor methods ──────────────────────────────────────────────────
|
||||
|
||||
(let
|
||||
((p (clos-make-instance "point" ":x" 7 ":y" 8)))
|
||||
(begin
|
||||
(assert-equal
|
||||
"accessor point-x"
|
||||
(clos-call-generic "point-x" (list p))
|
||||
7)
|
||||
(assert-equal
|
||||
"accessor point-y"
|
||||
(clos-call-generic "point-y" (list p))
|
||||
8)))
|
||||
|
||||
;; ── 13. with-slots ────────────────────────────────────────────────────────
|
||||
|
||||
(let
|
||||
((p (clos-make-instance "point" ":x" 3 ":y" 4)))
|
||||
(assert-equal
|
||||
"with-slots"
|
||||
(clos-with-slots p (list "x" "y") (fn (x y) (* x y)))
|
||||
12))
|
||||
|
||||
;; ── 14. change-class ─────────────────────────────────────────────────────
|
||||
|
||||
(clos-defclass "special-point" (list "point") (list {:initform "" :initarg ":label" :reader nil :writer nil :accessor nil :name "label"}))
|
||||
|
||||
(let
|
||||
((p (clos-make-instance "point" ":x" 1 ":y" 2)))
|
||||
(begin
|
||||
(clos-change-class! p "special-point")
|
||||
(assert-equal
|
||||
"change-class updates class"
|
||||
(clos-class-of p)
|
||||
"special-point")))
|
||||
|
||||
;; ── summary ────────────────────────────────────────────────────────────────
|
||||
|
||||
(if
|
||||
(= failed 0)
|
||||
(print (str "ok " passed "/" (+ passed failed) " CLOS tests passed"))
|
||||
(begin
|
||||
(for-each (fn (f) (print f)) failures)
|
||||
(print
|
||||
(str "FAIL " passed "/" (+ passed failed) " passed, " failed " failed"))))
|
||||
@@ -1,478 +0,0 @@
|
||||
;; lib/common-lisp/tests/conditions.sx — Phase 3 condition system tests
|
||||
;;
|
||||
;; Loaded by lib/common-lisp/test.sh after:
|
||||
;; (load "spec/stdlib.sx")
|
||||
;; (load "lib/common-lisp/runtime.sx")
|
||||
;;
|
||||
;; Each test resets the handler/restart stacks to ensure isolation.
|
||||
|
||||
(define
|
||||
reset-stacks!
|
||||
(fn () (set! cl-handler-stack (list)) (set! cl-restart-stack (list))))
|
||||
|
||||
;; ── helpers ────────────────────────────────────────────────────────────────
|
||||
|
||||
(define passed 0)
|
||||
(define failed 0)
|
||||
(define failures (list))
|
||||
|
||||
(define
|
||||
assert-equal
|
||||
(fn
|
||||
(label got expected)
|
||||
(if
|
||||
(= got expected)
|
||||
(set! passed (+ passed 1))
|
||||
(begin
|
||||
(set! failed (+ failed 1))
|
||||
(set!
|
||||
failures
|
||||
(append
|
||||
failures
|
||||
(list
|
||||
(str
|
||||
"FAIL ["
|
||||
label
|
||||
"]: got="
|
||||
(inspect got)
|
||||
" expected="
|
||||
(inspect expected)))))))))
|
||||
|
||||
(define
|
||||
assert-true
|
||||
(fn
|
||||
(label got)
|
||||
(if
|
||||
got
|
||||
(set! passed (+ passed 1))
|
||||
(begin
|
||||
(set! failed (+ failed 1))
|
||||
(set!
|
||||
failures
|
||||
(append
|
||||
failures
|
||||
(list
|
||||
(str "FAIL [" label "]: expected true, got " (inspect got)))))))))
|
||||
|
||||
(define
|
||||
assert-nil
|
||||
(fn
|
||||
(label got)
|
||||
(if
|
||||
(nil? got)
|
||||
(set! passed (+ passed 1))
|
||||
(begin
|
||||
(set! failed (+ failed 1))
|
||||
(set!
|
||||
failures
|
||||
(append
|
||||
failures
|
||||
(list (str "FAIL [" label "]: expected nil, got " (inspect got)))))))))
|
||||
|
||||
;; ── 1. condition predicates ────────────────────────────────────────────────
|
||||
|
||||
(reset-stacks!)
|
||||
|
||||
(let
|
||||
((c (cl-make-condition "simple-error" "format-control" "oops")))
|
||||
(begin
|
||||
(assert-true "cl-condition? on condition" (cl-condition? c))
|
||||
(assert-equal "cl-condition? on string" (cl-condition? "hello") false)
|
||||
(assert-equal "cl-condition? on number" (cl-condition? 42) false)
|
||||
(assert-equal "cl-condition? on nil" (cl-condition? nil) false)))
|
||||
|
||||
;; ── 2. cl-make-condition + slot access ────────────────────────────────────
|
||||
|
||||
(reset-stacks!)
|
||||
|
||||
(let
|
||||
((c (cl-make-condition "simple-error" "format-control" "msg" "format-arguments" (list 1 2))))
|
||||
(begin
|
||||
(assert-equal "class field" (get c "class") "simple-error")
|
||||
(assert-equal "cl-type field" (get c "cl-type") "cl-condition")
|
||||
(assert-equal
|
||||
"format-control slot"
|
||||
(cl-condition-slot c "format-control")
|
||||
"msg")
|
||||
(assert-equal
|
||||
"format-arguments slot"
|
||||
(cl-condition-slot c "format-arguments")
|
||||
(list 1 2))
|
||||
(assert-nil "missing slot is nil" (cl-condition-slot c "no-such-slot"))
|
||||
(assert-equal "condition-message" (cl-condition-message c) "msg")))
|
||||
|
||||
;; ── 3. cl-condition-of-type? — hierarchy walking ─────────────────────────
|
||||
|
||||
(reset-stacks!)
|
||||
|
||||
(let
|
||||
((se (cl-make-condition "simple-error" "format-control" "x"))
|
||||
(w (cl-make-condition "simple-warning" "format-control" "y"))
|
||||
(te
|
||||
(cl-make-condition
|
||||
"type-error"
|
||||
"datum"
|
||||
5
|
||||
"expected-type"
|
||||
"string"))
|
||||
(dz (cl-make-condition "division-by-zero")))
|
||||
(begin
|
||||
(assert-true
|
||||
"se isa simple-error"
|
||||
(cl-condition-of-type? se "simple-error"))
|
||||
(assert-true "se isa error" (cl-condition-of-type? se "error"))
|
||||
(assert-true
|
||||
"se isa serious-condition"
|
||||
(cl-condition-of-type? se "serious-condition"))
|
||||
(assert-true "se isa condition" (cl-condition-of-type? se "condition"))
|
||||
(assert-equal
|
||||
"se not isa warning"
|
||||
(cl-condition-of-type? se "warning")
|
||||
false)
|
||||
(assert-true
|
||||
"w isa simple-warning"
|
||||
(cl-condition-of-type? w "simple-warning"))
|
||||
(assert-true "w isa warning" (cl-condition-of-type? w "warning"))
|
||||
(assert-true "w isa condition" (cl-condition-of-type? w "condition"))
|
||||
(assert-equal "w not isa error" (cl-condition-of-type? w "error") false)
|
||||
(assert-true "te isa type-error" (cl-condition-of-type? te "type-error"))
|
||||
(assert-true "te isa error" (cl-condition-of-type? te "error"))
|
||||
(assert-true
|
||||
"dz isa division-by-zero"
|
||||
(cl-condition-of-type? dz "division-by-zero"))
|
||||
(assert-true
|
||||
"dz isa arithmetic-error"
|
||||
(cl-condition-of-type? dz "arithmetic-error"))
|
||||
(assert-true "dz isa error" (cl-condition-of-type? dz "error"))
|
||||
(assert-equal
|
||||
"non-condition not isa anything"
|
||||
(cl-condition-of-type? 42 "error")
|
||||
false)))
|
||||
|
||||
;; ── 4. cl-define-condition ────────────────────────────────────────────────
|
||||
|
||||
(reset-stacks!)
|
||||
|
||||
(begin
|
||||
(cl-define-condition "my-app-error" (list "error") (list "code" "detail"))
|
||||
(let
|
||||
((c (cl-make-condition "my-app-error" "code" 404 "detail" "not found")))
|
||||
(begin
|
||||
(assert-true "user condition: cl-condition?" (cl-condition? c))
|
||||
(assert-true
|
||||
"user condition isa my-app-error"
|
||||
(cl-condition-of-type? c "my-app-error"))
|
||||
(assert-true
|
||||
"user condition isa error"
|
||||
(cl-condition-of-type? c "error"))
|
||||
(assert-true
|
||||
"user condition isa condition"
|
||||
(cl-condition-of-type? c "condition"))
|
||||
(assert-equal
|
||||
"user condition slot code"
|
||||
(cl-condition-slot c "code")
|
||||
404)
|
||||
(assert-equal
|
||||
"user condition slot detail"
|
||||
(cl-condition-slot c "detail")
|
||||
"not found"))))
|
||||
|
||||
;; ── 5. cl-handler-bind (non-unwinding) ───────────────────────────────────
|
||||
|
||||
(reset-stacks!)
|
||||
|
||||
(let
|
||||
((log (list)))
|
||||
(begin
|
||||
(cl-handler-bind
|
||||
(list
|
||||
(list
|
||||
"error"
|
||||
(fn (c) (set! log (append log (list (cl-condition-message c)))))))
|
||||
(fn
|
||||
()
|
||||
(cl-signal (cl-make-condition "simple-error" "format-control" "oops"))))
|
||||
(assert-equal "handler-bind: handler fired" log (list "oops"))))
|
||||
|
||||
(reset-stacks!)
|
||||
|
||||
;; Non-unwinding: body continues after signal
|
||||
(let
|
||||
((body-ran false))
|
||||
(begin
|
||||
(cl-handler-bind
|
||||
(list (list "error" (fn (c) nil)))
|
||||
(fn
|
||||
()
|
||||
(cl-signal (cl-make-condition "simple-error" "format-control" "x"))
|
||||
(set! body-ran true)))
|
||||
(assert-true "handler-bind: body continues after signal" body-ran)))
|
||||
|
||||
(reset-stacks!)
|
||||
|
||||
;; Type filtering: warning handler does not fire for error
|
||||
(let
|
||||
((w-fired false))
|
||||
(begin
|
||||
(cl-handler-bind
|
||||
(list (list "warning" (fn (c) (set! w-fired true))))
|
||||
(fn
|
||||
()
|
||||
(cl-signal (cl-make-condition "simple-error" "format-control" "e"))))
|
||||
(assert-equal
|
||||
"handler-bind: type filter (warning ignores error)"
|
||||
w-fired
|
||||
false)))
|
||||
|
||||
(reset-stacks!)
|
||||
|
||||
;; Multiple handlers: both matching handlers fire
|
||||
(let
|
||||
((log (list)))
|
||||
(begin
|
||||
(cl-handler-bind
|
||||
(list
|
||||
(list "error" (fn (c) (set! log (append log (list "e1")))))
|
||||
(list "condition" (fn (c) (set! log (append log (list "e2"))))))
|
||||
(fn
|
||||
()
|
||||
(cl-signal (cl-make-condition "simple-error" "format-control" "x"))))
|
||||
(assert-equal "handler-bind: both handlers fire" log (list "e1" "e2"))))
|
||||
|
||||
(reset-stacks!)
|
||||
|
||||
;; ── 6. cl-handler-case (unwinding) ───────────────────────────────────────
|
||||
|
||||
;; Catches error, returns handler result
|
||||
(let
|
||||
((result (cl-handler-case (fn () (cl-error "boom") 99) (list "error" (fn (c) (str "caught: " (cl-condition-message c)))))))
|
||||
(assert-equal "handler-case: catches error" result "caught: boom"))
|
||||
|
||||
(reset-stacks!)
|
||||
|
||||
;; Returns body result when no signal
|
||||
(let
|
||||
((result (cl-handler-case (fn () 42) (list "error" (fn (c) -1)))))
|
||||
(assert-equal "handler-case: body result" result 42))
|
||||
|
||||
(reset-stacks!)
|
||||
|
||||
;; Only first matching handler runs (unwinding)
|
||||
(let
|
||||
((result (cl-handler-case (fn () (cl-error "x")) (list "simple-error" (fn (c) "simple")) (list "error" (fn (c) "error")))))
|
||||
(assert-equal "handler-case: most specific wins" result "simple"))
|
||||
|
||||
(reset-stacks!)
|
||||
|
||||
;; ── 7. cl-warn ────────────────────────────────────────────────────────────
|
||||
|
||||
(let
|
||||
((warned false))
|
||||
(begin
|
||||
(cl-handler-bind
|
||||
(list (list "warning" (fn (c) (set! warned true))))
|
||||
(fn () (cl-warn "be careful")))
|
||||
(assert-true "cl-warn: fires warning handler" warned)))
|
||||
|
||||
(reset-stacks!)
|
||||
|
||||
;; Warn with condition object
|
||||
(let
|
||||
((msg ""))
|
||||
(begin
|
||||
(cl-handler-bind
|
||||
(list (list "warning" (fn (c) (set! msg (cl-condition-message c)))))
|
||||
(fn
|
||||
()
|
||||
(cl-warn
|
||||
(cl-make-condition "simple-warning" "format-control" "take care"))))
|
||||
(assert-equal "cl-warn: condition object" msg "take care")))
|
||||
|
||||
(reset-stacks!)
|
||||
|
||||
;; ── 8. cl-restart-case + cl-invoke-restart ───────────────────────────────
|
||||
|
||||
;; Basic restart invocation
|
||||
(let
|
||||
((result (cl-restart-case (fn () (cl-invoke-restart "use-zero")) (list "use-zero" (list) (fn () 0)))))
|
||||
(assert-equal "restart-case: invoke-restart use-zero" result 0))
|
||||
|
||||
(reset-stacks!)
|
||||
|
||||
;; Restart with argument
|
||||
(let
|
||||
((result (cl-restart-case (fn () (cl-invoke-restart "use-value" 77)) (list "use-value" (list "v") (fn (v) v)))))
|
||||
(assert-equal "restart-case: invoke-restart with arg" result 77))
|
||||
|
||||
(reset-stacks!)
|
||||
|
||||
;; Body returns normally when restart not invoked
|
||||
(let
|
||||
((result (cl-restart-case (fn () 42) (list "never-used" (list) (fn () -1)))))
|
||||
(assert-equal "restart-case: body result" result 42))
|
||||
|
||||
(reset-stacks!)
|
||||
|
||||
;; ── 9. cl-with-simple-restart ─────────────────────────────────────────────
|
||||
|
||||
(let
|
||||
((result (cl-with-simple-restart "skip" "Skip this step" (fn () (cl-invoke-restart "skip") 99))))
|
||||
(assert-nil "with-simple-restart: invoke returns nil" result))
|
||||
|
||||
(reset-stacks!)
|
||||
|
||||
;; ── 10. cl-find-restart ───────────────────────────────────────────────────
|
||||
|
||||
(let
|
||||
((found (cl-restart-case (fn () (cl-find-restart "retry")) (list "retry" (list) (fn () nil)))))
|
||||
(assert-true "find-restart: finds active restart" (not (nil? found))))
|
||||
|
||||
(reset-stacks!)
|
||||
|
||||
(let
|
||||
((not-found (cl-restart-case (fn () (cl-find-restart "nonexistent")) (list "retry" (list) (fn () nil)))))
|
||||
(assert-nil "find-restart: nil for inactive restart" not-found))
|
||||
|
||||
(reset-stacks!)
|
||||
|
||||
;; ── 11. cl-compute-restarts ───────────────────────────────────────────────
|
||||
|
||||
(let
|
||||
((names (cl-restart-case (fn () (cl-restart-case (fn () (cl-compute-restarts)) (list "inner" (list) (fn () nil)))) (list "outer" (list) (fn () nil)))))
|
||||
(assert-equal
|
||||
"compute-restarts: both restarts"
|
||||
names
|
||||
(list "inner" "outer")))
|
||||
|
||||
(reset-stacks!)
|
||||
|
||||
;; ── 12. handler-bind + restart-case interop ───────────────────────────────
|
||||
|
||||
;; Classic CL pattern: error handler invokes a restart
|
||||
(let
|
||||
((result (cl-restart-case (fn () (cl-handler-bind (list (list "error" (fn (c) (cl-invoke-restart "use-zero")))) (fn () (cl-error "divide by zero")))) (list "use-zero" (list) (fn () 0)))))
|
||||
(assert-equal "interop: handler invokes restart" result 0))
|
||||
|
||||
(reset-stacks!)
|
||||
|
||||
;; ── 13. cl-cerror ─────────────────────────────────────────────────────────
|
||||
|
||||
;; When "continue" restart is invoked, cerror returns nil
|
||||
(let
|
||||
((result (cl-restart-case (fn () (cl-cerror "continue anyway" "something bad") 42) (list "continue" (list) (fn () "resumed")))))
|
||||
(assert-true
|
||||
"cerror: returns"
|
||||
(or (nil? result) (= result 42) (= result "resumed"))))
|
||||
|
||||
(reset-stacks!)
|
||||
|
||||
;; ── 14. slot accessor helpers ─────────────────────────────────────────────
|
||||
|
||||
(let
|
||||
((c (cl-make-condition "simple-error" "format-control" "msg" "format-arguments" (list 1 2))))
|
||||
(begin
|
||||
(assert-equal
|
||||
"simple-condition-format-control"
|
||||
(cl-simple-condition-format-control c)
|
||||
"msg")
|
||||
(assert-equal
|
||||
"simple-condition-format-arguments"
|
||||
(cl-simple-condition-format-arguments c)
|
||||
(list 1 2))))
|
||||
|
||||
(let
|
||||
((c (cl-make-condition "type-error" "datum" 42 "expected-type" "string")))
|
||||
(begin
|
||||
(assert-equal "type-error-datum" (cl-type-error-datum c) 42)
|
||||
(assert-equal
|
||||
"type-error-expected-type"
|
||||
(cl-type-error-expected-type c)
|
||||
"string")))
|
||||
|
||||
(let
|
||||
((c (cl-make-condition "arithmetic-error" "operation" "/" "operands" (list 1 0))))
|
||||
(begin
|
||||
(assert-equal
|
||||
"arithmetic-error-operation"
|
||||
(cl-arithmetic-error-operation c)
|
||||
"/")
|
||||
(assert-equal
|
||||
"arithmetic-error-operands"
|
||||
(cl-arithmetic-error-operands c)
|
||||
(list 1 0))))
|
||||
|
||||
|
||||
;; ── 15. *debugger-hook* ───────────────────────────────────────────────────
|
||||
|
||||
(reset-stacks!)
|
||||
|
||||
(let ((received nil))
|
||||
(begin
|
||||
(set! cl-debugger-hook
|
||||
(fn (c h)
|
||||
(set! received (cl-condition-message c))
|
||||
(cl-invoke-restart "escape")))
|
||||
(cl-restart-case
|
||||
(fn () (cl-error "debugger test"))
|
||||
(list "escape" (list) (fn () nil)))
|
||||
(set! cl-debugger-hook nil)
|
||||
(assert-equal "debugger-hook receives condition" received "debugger test")))
|
||||
|
||||
(reset-stacks!)
|
||||
|
||||
;; ── 16. *break-on-signals* ────────────────────────────────────────────────
|
||||
|
||||
(reset-stacks!)
|
||||
|
||||
(let ((triggered false))
|
||||
(begin
|
||||
(set! cl-break-on-signals "error")
|
||||
(set! cl-debugger-hook
|
||||
(fn (c h)
|
||||
(set! triggered true)
|
||||
(cl-invoke-restart "abort")))
|
||||
(cl-restart-case
|
||||
(fn ()
|
||||
(cl-signal (cl-make-condition "simple-error" "format-control" "x")))
|
||||
(list "abort" (list) (fn () nil)))
|
||||
(set! cl-break-on-signals nil)
|
||||
(set! cl-debugger-hook nil)
|
||||
(assert-true "break-on-signals fires hook" triggered)))
|
||||
|
||||
(reset-stacks!)
|
||||
|
||||
;; break-on-signals: non-matching type does NOT fire hook
|
||||
(let ((triggered false))
|
||||
(begin
|
||||
(set! cl-break-on-signals "error")
|
||||
(set! cl-debugger-hook
|
||||
(fn (c h) (set! triggered true) nil))
|
||||
(cl-handler-bind
|
||||
(list (list "warning" (fn (c) nil)))
|
||||
(fn ()
|
||||
(cl-signal (cl-make-condition "simple-warning" "format-control" "w"))))
|
||||
(set! cl-break-on-signals nil)
|
||||
(set! cl-debugger-hook nil)
|
||||
(assert-equal "break-on-signals: type mismatch not triggered" triggered false)))
|
||||
|
||||
(reset-stacks!)
|
||||
|
||||
;; ── 17. cl-invoke-restart-interactively ──────────────────────────────────
|
||||
|
||||
(let ((result
|
||||
(cl-restart-case
|
||||
(fn () (cl-invoke-restart-interactively "use-default"))
|
||||
(list "use-default" (list) (fn () 99)))))
|
||||
(assert-equal "invoke-restart-interactively: returns restart value" result 99))
|
||||
|
||||
(reset-stacks!)
|
||||
|
||||
;; ── summary ────────────────────────────────────────────────────────────────
|
||||
|
||||
(if
|
||||
(= failed 0)
|
||||
(print (str "ok " passed "/" (+ passed failed) " condition tests passed"))
|
||||
(begin
|
||||
(for-each (fn (f) (print f)) failures)
|
||||
(print
|
||||
(str "FAIL " passed "/" (+ passed failed) " passed, " failed " failed"))))
|
||||
@@ -1,466 +0,0 @@
|
||||
;; CL evaluator tests
|
||||
|
||||
(define cl-test-pass 0)
|
||||
(define cl-test-fail 0)
|
||||
(define cl-test-fails (list))
|
||||
|
||||
(define
|
||||
cl-deep=
|
||||
(fn
|
||||
(a b)
|
||||
(cond
|
||||
((= a b) true)
|
||||
((and (dict? a) (dict? b))
|
||||
(let
|
||||
((ak (keys a)) (bk (keys b)))
|
||||
(if
|
||||
(not (= (len ak) (len bk)))
|
||||
false
|
||||
(every?
|
||||
(fn (k) (and (has-key? b k) (cl-deep= (get a k) (get b k))))
|
||||
ak))))
|
||||
((and (list? a) (list? b))
|
||||
(if
|
||||
(not (= (len a) (len b)))
|
||||
false
|
||||
(let
|
||||
((i 0) (ok true))
|
||||
(define
|
||||
chk
|
||||
(fn
|
||||
()
|
||||
(when
|
||||
(and ok (< i (len a)))
|
||||
(do
|
||||
(when
|
||||
(not (cl-deep= (nth a i) (nth b i)))
|
||||
(set! ok false))
|
||||
(set! i (+ i 1))
|
||||
(chk)))))
|
||||
(chk)
|
||||
ok)))
|
||||
(:else false))))
|
||||
|
||||
(define
|
||||
cl-test
|
||||
(fn
|
||||
(name actual expected)
|
||||
(if
|
||||
(cl-deep= actual expected)
|
||||
(set! cl-test-pass (+ cl-test-pass 1))
|
||||
(do
|
||||
(set! cl-test-fail (+ cl-test-fail 1))
|
||||
(append! cl-test-fails {:name name :expected expected :actual actual})))))
|
||||
|
||||
;; Convenience: evaluate CL string with fresh env each time
|
||||
(define ev (fn (src) (cl-eval-str src (cl-make-env))))
|
||||
(define evall (fn (src) (cl-eval-all-str src (cl-make-env))))
|
||||
|
||||
;; ── self-evaluating literals ──────────────────────────────────────
|
||||
|
||||
(cl-test "lit: nil" (ev "nil") nil)
|
||||
(cl-test "lit: t" (ev "t") true)
|
||||
(cl-test "lit: integer" (ev "42") 42)
|
||||
(cl-test "lit: negative" (ev "-7") -7)
|
||||
(cl-test "lit: zero" (ev "0") 0)
|
||||
(cl-test "lit: string" (ev "\"hello\"") "hello")
|
||||
(cl-test "lit: empty string" (ev "\"\"") "")
|
||||
(cl-test "lit: keyword type" (get (ev ":foo") "cl-type") "keyword")
|
||||
(cl-test "lit: keyword name" (get (ev ":foo") "name") "FOO")
|
||||
(cl-test "lit: float type" (get (ev "3.14") "cl-type") "float")
|
||||
|
||||
;; ── QUOTE ─────────────────────────────────────────────────────────
|
||||
|
||||
(cl-test "quote: symbol" (ev "'x") "X")
|
||||
(cl-test "quote: list" (ev "'(a b c)") (list "A" "B" "C"))
|
||||
(cl-test "quote: nil" (ev "'nil") nil)
|
||||
(cl-test "quote: integer" (ev "'42") 42)
|
||||
(cl-test "quote: nested" (ev "'(a (b c))") (list "A" (list "B" "C")))
|
||||
|
||||
;; ── IF ────────────────────────────────────────────────────────────
|
||||
|
||||
(cl-test "if: true branch" (ev "(if t 1 2)") 1)
|
||||
(cl-test "if: false branch" (ev "(if nil 1 2)") 2)
|
||||
(cl-test "if: no else nil" (ev "(if nil 99)") nil)
|
||||
(cl-test "if: number truthy" (ev "(if 0 'yes 'no)") "YES")
|
||||
(cl-test "if: empty string truthy" (ev "(if \"\" 'yes 'no)") "YES")
|
||||
(cl-test "if: nested" (ev "(if t (if nil 1 2) 3)") 2)
|
||||
|
||||
;; ── PROGN ────────────────────────────────────────────────────────
|
||||
|
||||
(cl-test "progn: single" (ev "(progn 42)") 42)
|
||||
(cl-test "progn: multiple" (ev "(progn 1 2 3)") 3)
|
||||
(cl-test "progn: nil last" (ev "(progn 1 nil)") nil)
|
||||
|
||||
;; ── AND / OR ─────────────────────────────────────────────────────
|
||||
|
||||
(cl-test "and: empty" (ev "(and)") true)
|
||||
(cl-test "and: all true" (ev "(and 1 2 3)") 3)
|
||||
(cl-test "and: short-circuit" (ev "(and nil 99)") nil)
|
||||
(cl-test "and: returns last" (ev "(and 1 2)") 2)
|
||||
(cl-test "or: empty" (ev "(or)") nil)
|
||||
(cl-test "or: first truthy" (ev "(or 1 2)") 1)
|
||||
(cl-test "or: all nil" (ev "(or nil nil)") nil)
|
||||
(cl-test "or: short-circuit" (ev "(or nil 42)") 42)
|
||||
|
||||
;; ── COND ─────────────────────────────────────────────────────────
|
||||
|
||||
(cl-test "cond: first match" (ev "(cond (t 1) (t 2))") 1)
|
||||
(cl-test "cond: second match" (ev "(cond (nil 1) (t 2))") 2)
|
||||
(cl-test "cond: no match" (ev "(cond (nil 1) (nil 2))") nil)
|
||||
(cl-test "cond: returns test value" (ev "(cond (42))") 42)
|
||||
|
||||
;; ── WHEN / UNLESS ─────────────────────────────────────────────────
|
||||
|
||||
(cl-test "when: true" (ev "(when t 1 2 3)") 3)
|
||||
(cl-test "when: nil" (ev "(when nil 99)") nil)
|
||||
(cl-test "unless: nil runs" (ev "(unless nil 42)") 42)
|
||||
(cl-test "unless: true skips" (ev "(unless t 99)") nil)
|
||||
|
||||
;; ── LET ──────────────────────────────────────────────────────────
|
||||
|
||||
(cl-test "let: empty bindings" (ev "(let () 42)") 42)
|
||||
(cl-test "let: single binding" (ev "(let ((x 5)) x)") 5)
|
||||
(cl-test "let: two bindings" (ev "(let ((x 3) (y 4)) (+ x y))") 7)
|
||||
(cl-test "let: parallel" (ev "(let ((x 1)) (let ((x 2) (y x)) y))") 1)
|
||||
(cl-test "let: nested" (ev "(let ((x 1)) (let ((y 2)) (+ x y)))") 3)
|
||||
(cl-test "let: progn body" (ev "(let ((x 5)) (+ x 1) (* x 2))") 10)
|
||||
(cl-test "let: bare name nil" (ev "(let (x) x)") nil)
|
||||
|
||||
;; ── LET* ─────────────────────────────────────────────────────────
|
||||
|
||||
(cl-test "let*: sequential" (ev "(let* ((x 1) (y (+ x 1))) y)") 2)
|
||||
(cl-test "let*: chain" (ev "(let* ((a 2) (b (* a 3)) (c (+ b 1))) c)") 7)
|
||||
(cl-test "let*: shadow" (ev "(let ((x 1)) (let* ((x 2) (y x)) y))") 2)
|
||||
|
||||
;; ── SETQ / SETF ──────────────────────────────────────────────────
|
||||
|
||||
(cl-test "setq: basic" (ev "(let ((x 0)) (setq x 5) x)") 5)
|
||||
(cl-test "setq: returns value" (ev "(let ((x 0)) (setq x 99))") 99)
|
||||
(cl-test "setf: basic" (ev "(let ((x 0)) (setf x 7) x)") 7)
|
||||
|
||||
;; ── LAMBDA ────────────────────────────────────────────────────────
|
||||
|
||||
(cl-test "lambda: call" (ev "((lambda (x) x) 42)") 42)
|
||||
(cl-test "lambda: multi-arg" (ev "((lambda (x y) (+ x y)) 3 4)") 7)
|
||||
(cl-test "lambda: closure" (ev "(let ((n 10)) ((lambda (x) (+ x n)) 5))") 15)
|
||||
(cl-test "lambda: rest arg"
|
||||
(ev "((lambda (x &rest xs) (cons x xs)) 1 2 3)")
|
||||
{:cl-type "cons" :car 1 :cdr (list 2 3)})
|
||||
(cl-test "lambda: optional no default"
|
||||
(ev "((lambda (&optional x) x))")
|
||||
nil)
|
||||
(cl-test "lambda: optional with arg"
|
||||
(ev "((lambda (&optional (x 99)) x) 42)")
|
||||
42)
|
||||
(cl-test "lambda: optional default used"
|
||||
(ev "((lambda (&optional (x 7)) x))")
|
||||
7)
|
||||
|
||||
;; ── FUNCTION ─────────────────────────────────────────────────────
|
||||
|
||||
(cl-test "function: lambda" (get (ev "(function (lambda (x) x))") "cl-type") "function")
|
||||
|
||||
;; ── DEFUN ────────────────────────────────────────────────────────
|
||||
|
||||
(cl-test "defun: returns name" (evall "(defun sq (x) (* x x))") "SQ")
|
||||
(cl-test "defun: call" (evall "(defun sq (x) (* x x)) (sq 5)") 25)
|
||||
(cl-test "defun: multi-arg" (evall "(defun add (x y) (+ x y)) (add 3 4)") 7)
|
||||
(cl-test "defun: recursive factorial"
|
||||
(evall "(defun fact (n) (if (<= n 1) 1 (* n (fact (- n 1))))) (fact 5)")
|
||||
120)
|
||||
(cl-test "defun: multiple calls"
|
||||
(evall "(defun double (x) (* x 2)) (+ (double 3) (double 5))")
|
||||
16)
|
||||
|
||||
;; ── FLET ─────────────────────────────────────────────────────────
|
||||
|
||||
(cl-test "flet: basic"
|
||||
(ev "(flet ((double (x) (* x 2))) (double 5))")
|
||||
10)
|
||||
(cl-test "flet: sees outer vars"
|
||||
(ev "(let ((n 3)) (flet ((add-n (x) (+ x n))) (add-n 7)))")
|
||||
10)
|
||||
(cl-test "flet: non-recursive"
|
||||
(ev "(flet ((f (x) (+ x 1))) (flet ((f (x) (f (f x)))) (f 5)))")
|
||||
7)
|
||||
|
||||
;; ── LABELS ────────────────────────────────────────────────────────
|
||||
|
||||
(cl-test "labels: basic"
|
||||
(ev "(labels ((greet (x) x)) (greet 42))")
|
||||
42)
|
||||
(cl-test "labels: recursive"
|
||||
(ev "(labels ((count (n) (if (<= n 0) 0 (+ 1 (count (- n 1)))))) (count 5))")
|
||||
5)
|
||||
(cl-test "labels: mutual recursion"
|
||||
(ev "(labels
|
||||
((even? (n) (if (= n 0) t (odd? (- n 1))))
|
||||
(odd? (n) (if (= n 0) nil (even? (- n 1)))))
|
||||
(list (even? 4) (odd? 3)))")
|
||||
(list true true))
|
||||
|
||||
;; ── THE / LOCALLY / EVAL-WHEN ────────────────────────────────────
|
||||
|
||||
(cl-test "the: passthrough" (ev "(the integer 42)") 42)
|
||||
(cl-test "the: string" (ev "(the string \"hi\")") "hi")
|
||||
(cl-test "locally: body" (ev "(locally 1 2 3)") 3)
|
||||
(cl-test "eval-when: execute" (ev "(eval-when (:execute) 99)") 99)
|
||||
(cl-test "eval-when: no execute" (ev "(eval-when (:compile-toplevel) 99)") nil)
|
||||
|
||||
;; ── DEFVAR / DEFPARAMETER ────────────────────────────────────────
|
||||
|
||||
(cl-test "defvar: returns name" (evall "(defvar *x* 10)") "*X*")
|
||||
(cl-test "defparameter: sets value" (evall "(defparameter *y* 42) *y*") 42)
|
||||
(cl-test "defvar: no reinit" (evall "(defvar *z* 1) (defvar *z* 99) *z*") 1)
|
||||
|
||||
;; ── built-in arithmetic ───────────────────────────────────────────
|
||||
|
||||
(cl-test "arith: +" (ev "(+ 1 2 3)") 6)
|
||||
(cl-test "arith: + zero" (ev "(+)") 0)
|
||||
(cl-test "arith: -" (ev "(- 10 3 2)") 5)
|
||||
(cl-test "arith: - negate" (ev "(- 5)") -5)
|
||||
(cl-test "arith: *" (ev "(* 2 3 4)") 24)
|
||||
(cl-test "arith: * one" (ev "(*)") 1)
|
||||
(cl-test "arith: /" (ev "(/ 12 3)") 4)
|
||||
(cl-test "arith: max" (ev "(max 3 1 4 1 5)") 5)
|
||||
(cl-test "arith: min" (ev "(min 3 1 4 1 5)") 1)
|
||||
(cl-test "arith: abs neg" (ev "(abs -7)") 7)
|
||||
(cl-test "arith: abs pos" (ev "(abs 7)") 7)
|
||||
|
||||
;; ── built-in comparisons ──────────────────────────────────────────
|
||||
|
||||
(cl-test "cmp: = true" (ev "(= 3 3)") true)
|
||||
(cl-test "cmp: = false" (ev "(= 3 4)") nil)
|
||||
(cl-test "cmp: /=" (ev "(/= 3 4)") true)
|
||||
(cl-test "cmp: <" (ev "(< 1 2)") true)
|
||||
(cl-test "cmp: > false" (ev "(> 1 2)") nil)
|
||||
(cl-test "cmp: <=" (ev "(<= 2 2)") true)
|
||||
|
||||
;; ── built-in predicates ───────────────────────────────────────────
|
||||
|
||||
(cl-test "pred: null nil" (ev "(null nil)") true)
|
||||
(cl-test "pred: null non-nil" (ev "(null 5)") nil)
|
||||
(cl-test "pred: not nil" (ev "(not nil)") true)
|
||||
(cl-test "pred: not truthy" (ev "(not 5)") nil)
|
||||
(cl-test "pred: numberp" (ev "(numberp 5)") true)
|
||||
(cl-test "pred: numberp str" (ev "(numberp \"x\")") nil)
|
||||
(cl-test "pred: stringp" (ev "(stringp \"hello\")") true)
|
||||
(cl-test "pred: listp list" (ev "(listp '(1))") true)
|
||||
(cl-test "pred: listp nil" (ev "(listp nil)") true)
|
||||
(cl-test "pred: zerop" (ev "(zerop 0)") true)
|
||||
(cl-test "pred: plusp" (ev "(plusp 3)") true)
|
||||
(cl-test "pred: evenp" (ev "(evenp 4)") true)
|
||||
(cl-test "pred: oddp" (ev "(oddp 3)") true)
|
||||
|
||||
;; ── built-in list ops ─────────────────────────────────────────────
|
||||
|
||||
(cl-test "list: car" (ev "(car '(1 2 3))") 1)
|
||||
(cl-test "list: cdr" (ev "(cdr '(1 2 3))") (list 2 3))
|
||||
(cl-test "list: cons" (get (ev "(cons 1 2)") "car") 1)
|
||||
(cl-test "list: list fn" (ev "(list 1 2 3)") (list 1 2 3))
|
||||
(cl-test "list: length" (ev "(length '(a b c))") 3)
|
||||
(cl-test "list: length nil" (ev "(length nil)") 0)
|
||||
(cl-test "list: append" (ev "(append '(1 2) '(3 4))") (list 1 2 3 4))
|
||||
(cl-test "list: first" (ev "(first '(10 20 30))") 10)
|
||||
(cl-test "list: second" (ev "(second '(10 20 30))") 20)
|
||||
(cl-test "list: third" (ev "(third '(10 20 30))") 30)
|
||||
(cl-test "list: rest" (ev "(rest '(1 2 3))") (list 2 3))
|
||||
(cl-test "list: nth" (ev "(nth 1 '(a b c))") "B")
|
||||
(cl-test "list: reverse" (ev "(reverse '(1 2 3))") (list 3 2 1))
|
||||
|
||||
;; ── FUNCALL / APPLY / MAPCAR ─────────────────────────────────────
|
||||
|
||||
(cl-test "funcall: lambda"
|
||||
(ev "(funcall (lambda (x) (* x x)) 5)")
|
||||
25)
|
||||
(cl-test "apply: basic"
|
||||
(ev "(apply #'+ '(1 2 3))")
|
||||
6)
|
||||
(cl-test "apply: leading args"
|
||||
(ev "(apply #'+ 1 2 '(3 4))")
|
||||
10)
|
||||
(cl-test "mapcar: basic"
|
||||
(ev "(mapcar (lambda (x) (* x 2)) '(1 2 3))")
|
||||
(list 2 4 6))
|
||||
|
||||
;; ── BLOCK / RETURN-FROM / RETURN ─────────────────────────────────
|
||||
|
||||
(cl-test "block: last form value"
|
||||
(ev "(block done 1 2 3)")
|
||||
3)
|
||||
(cl-test "block: empty body"
|
||||
(ev "(block done)")
|
||||
nil)
|
||||
(cl-test "block: single form"
|
||||
(ev "(block foo 42)")
|
||||
42)
|
||||
(cl-test "block: return-from"
|
||||
(ev "(block done 1 (return-from done 99) 2)")
|
||||
99)
|
||||
(cl-test "block: return-from nil block"
|
||||
(ev "(block nil 1 (return-from nil 42) 3)")
|
||||
42)
|
||||
(cl-test "block: return-from no value"
|
||||
(ev "(block done (return-from done))")
|
||||
nil)
|
||||
(cl-test "block: nested inner return stays inner"
|
||||
(ev "(block outer (block inner (return-from inner 1) 2) 3)")
|
||||
3)
|
||||
(cl-test "block: nested outer return"
|
||||
(ev "(block outer (block inner 1 2) (return-from outer 99) 3)")
|
||||
99)
|
||||
(cl-test "return: shorthand for nil block"
|
||||
(ev "(block nil (return 77))")
|
||||
77)
|
||||
(cl-test "return: no value"
|
||||
(ev "(block nil 1 (return) 2)")
|
||||
nil)
|
||||
(cl-test "block: return-from inside let"
|
||||
(ev "(block done (let ((x 5)) (when (> x 3) (return-from done x))) 0)")
|
||||
5)
|
||||
(cl-test "block: return-from inside progn"
|
||||
(ev "(block done (progn (return-from done 7) 99))")
|
||||
7)
|
||||
(cl-test "block: return-from through function"
|
||||
(ev "(block done (flet ((f () (return-from done 42))) (f)) nil)")
|
||||
42)
|
||||
|
||||
;; ── TAGBODY / GO ─────────────────────────────────────────────────
|
||||
|
||||
(cl-test "tagbody: empty returns nil"
|
||||
(ev "(tagbody)")
|
||||
nil)
|
||||
(cl-test "tagbody: forms only, returns nil"
|
||||
(ev "(let ((x 0)) (tagbody (setq x 1) (setq x 2)) x)")
|
||||
2)
|
||||
(cl-test "tagbody: tag only, returns nil"
|
||||
(ev "(tagbody done)")
|
||||
nil)
|
||||
(cl-test "tagbody: go skips forms"
|
||||
(ev "(let ((x 0)) (tagbody (go done) (setq x 99) done) x)")
|
||||
0)
|
||||
(cl-test "tagbody: go to later tag"
|
||||
(ev "(let ((x 0)) (tagbody start (setq x (+ x 1)) (go done) (setq x 99) done) x)")
|
||||
1)
|
||||
(cl-test "tagbody: loop with counter"
|
||||
(ev "(let ((n 0)) (tagbody loop (when (>= n 3) (go done)) (setq n (+ n 1)) (go loop) done) n)")
|
||||
3)
|
||||
(cl-test "tagbody: go inside when"
|
||||
(ev "(let ((x 0)) (tagbody (setq x 1) (when t (go done)) (setq x 99) done) x)")
|
||||
1)
|
||||
(cl-test "tagbody: go inside progn"
|
||||
(ev "(let ((x 0)) (tagbody (progn (setq x 1) (go done)) (setq x 99) done) x)")
|
||||
1)
|
||||
(cl-test "tagbody: go inside let"
|
||||
(ev "(let ((acc 0)) (tagbody (let ((y 5)) (when (> y 3) (go done))) (setq acc 99) done) acc)")
|
||||
0)
|
||||
(cl-test "tagbody: integer tags"
|
||||
(ev "(let ((x 0)) (tagbody (go 2) 1 (setq x 1) (go 3) 2 (setq x 2) (go 3) 3) x)")
|
||||
2)
|
||||
(cl-test "tagbody: block-return propagates out"
|
||||
(ev "(block done (tagbody (return-from done 42)) nil)")
|
||||
42)
|
||||
|
||||
;; ── UNWIND-PROTECT ───────────────────────────────────────────────
|
||||
|
||||
(cl-test "unwind-protect: normal returns protected"
|
||||
(ev "(unwind-protect 42 nil)")
|
||||
42)
|
||||
(cl-test "unwind-protect: cleanup runs"
|
||||
(ev "(let ((x 0)) (unwind-protect 1 (setq x 99)) x)")
|
||||
99)
|
||||
(cl-test "unwind-protect: cleanup result ignored"
|
||||
(ev "(unwind-protect 42 777)")
|
||||
42)
|
||||
(cl-test "unwind-protect: multiple cleanup forms"
|
||||
(ev "(let ((x 0)) (unwind-protect 1 (setq x (+ x 1)) (setq x (+ x 1))) x)")
|
||||
2)
|
||||
(cl-test "unwind-protect: cleanup on return-from"
|
||||
(ev "(let ((x 0)) (block done (unwind-protect (return-from done 7) (setq x 99))) x)")
|
||||
99)
|
||||
(cl-test "unwind-protect: return-from still propagates"
|
||||
(ev "(block done (unwind-protect (return-from done 42) nil))")
|
||||
42)
|
||||
(cl-test "unwind-protect: cleanup on go"
|
||||
(ev "(let ((x 0)) (tagbody (unwind-protect (go done) (setq x 1)) done) x)")
|
||||
1)
|
||||
(cl-test "unwind-protect: nested, inner cleanup first"
|
||||
(ev "(let ((n 0)) (unwind-protect (unwind-protect 1 (setq n (+ n 10))) (setq n (+ n 1))) n)")
|
||||
11)
|
||||
|
||||
;; ── VALUES / MULTIPLE-VALUE-BIND / NTH-VALUE ────────────────────
|
||||
|
||||
(cl-test "values: single returns plain"
|
||||
(ev "(values 42)")
|
||||
42)
|
||||
(cl-test "values: zero returns nil"
|
||||
(ev "(values)")
|
||||
nil)
|
||||
(cl-test "values: multi — primary via funcall"
|
||||
(ev "(car (list (values 1 2)))")
|
||||
1)
|
||||
(cl-test "multiple-value-bind: basic"
|
||||
(ev "(multiple-value-bind (a b) (values 1 2) (+ a b))")
|
||||
3)
|
||||
(cl-test "multiple-value-bind: extra vars get nil"
|
||||
(ev "(multiple-value-bind (a b c) (values 10 20) (list a b c))")
|
||||
(list 10 20 nil))
|
||||
(cl-test "multiple-value-bind: extra values ignored"
|
||||
(ev "(multiple-value-bind (a) (values 1 2 3) a)")
|
||||
1)
|
||||
(cl-test "multiple-value-bind: single value source"
|
||||
(ev "(multiple-value-bind (a b) 42 (list a b))")
|
||||
(list 42 nil))
|
||||
(cl-test "nth-value: 0"
|
||||
(ev "(nth-value 0 (values 10 20 30))")
|
||||
10)
|
||||
(cl-test "nth-value: 1"
|
||||
(ev "(nth-value 1 (values 10 20 30))")
|
||||
20)
|
||||
(cl-test "nth-value: out of range"
|
||||
(ev "(nth-value 5 (values 10 20))")
|
||||
nil)
|
||||
(cl-test "multiple-value-call: basic"
|
||||
(ev "(multiple-value-call #'+ (values 1 2) (values 3 4))")
|
||||
10)
|
||||
(cl-test "multiple-value-prog1: returns first"
|
||||
(ev "(multiple-value-prog1 1 2 3)")
|
||||
1)
|
||||
(cl-test "multiple-value-prog1: side effects run"
|
||||
(ev "(let ((x 0)) (multiple-value-prog1 99 (setq x 7)) x)")
|
||||
7)
|
||||
(cl-test "values: nil primary in if"
|
||||
(ev "(if (values nil t) 'yes 'no)")
|
||||
"NO")
|
||||
(cl-test "values: truthy primary in if"
|
||||
(ev "(if (values 42 nil) 'yes 'no)")
|
||||
"YES")
|
||||
|
||||
;; --- Dynamic variables ---
|
||||
(cl-test "defvar marks special"
|
||||
(do (ev "(defvar *dv* 10)")
|
||||
(cl-special? "*DV*"))
|
||||
true)
|
||||
(cl-test "defvar: let rebinds dynamically"
|
||||
(ev "(progn (defvar *x* 1) (defun get-x () *x*) (let ((*x* 99)) (get-x)))")
|
||||
99)
|
||||
(cl-test "defvar: binding restores after let"
|
||||
(ev "(progn (defvar *yrst* 5) (let ((*yrst* 42)) *yrst*) *yrst*)")
|
||||
5)
|
||||
(cl-test "defparameter marks special"
|
||||
(do (ev "(defparameter *dp* 0)")
|
||||
(cl-special? "*DP*"))
|
||||
true)
|
||||
(cl-test "defparameter: let rebinds dynamically"
|
||||
(ev "(progn (defparameter *z* 10) (defun get-z () *z*) (let ((*z* 77)) (get-z)))")
|
||||
77)
|
||||
(cl-test "defparameter: always assigns"
|
||||
(ev "(progn (defparameter *p* 1) (defparameter *p* 2) *p*)")
|
||||
2)
|
||||
(cl-test "dynamic binding: nested lets"
|
||||
(ev "(progn (defvar *n* 0) (let ((*n* 1)) (let ((*n* 2)) *n*)))")
|
||||
2)
|
||||
(cl-test "dynamic binding: restores across nesting"
|
||||
(ev "(progn (defvar *m* 10) (let ((*m* 20)) (let ((*m* 30)) nil)) *m*)")
|
||||
10)
|
||||
@@ -1,204 +0,0 @@
|
||||
;; Lambda list parser tests
|
||||
|
||||
(define cl-test-pass 0)
|
||||
(define cl-test-fail 0)
|
||||
(define cl-test-fails (list))
|
||||
|
||||
;; Deep structural equality for dicts and lists
|
||||
(define
|
||||
cl-deep=
|
||||
(fn
|
||||
(a b)
|
||||
(cond
|
||||
((= a b) true)
|
||||
((and (dict? a) (dict? b))
|
||||
(let
|
||||
((ak (keys a)) (bk (keys b)))
|
||||
(if
|
||||
(not (= (len ak) (len bk)))
|
||||
false
|
||||
(every?
|
||||
(fn (k) (and (has-key? b k) (cl-deep= (get a k) (get b k))))
|
||||
ak))))
|
||||
((and (list? a) (list? b))
|
||||
(if
|
||||
(not (= (len a) (len b)))
|
||||
false
|
||||
(let
|
||||
((i 0) (ok true))
|
||||
(define
|
||||
chk
|
||||
(fn
|
||||
()
|
||||
(when
|
||||
(and ok (< i (len a)))
|
||||
(do
|
||||
(when
|
||||
(not (cl-deep= (nth a i) (nth b i)))
|
||||
(set! ok false))
|
||||
(set! i (+ i 1))
|
||||
(chk)))))
|
||||
(chk)
|
||||
ok)))
|
||||
(:else false))))
|
||||
|
||||
(define
|
||||
cl-test
|
||||
(fn
|
||||
(name actual expected)
|
||||
(if
|
||||
(cl-deep= actual expected)
|
||||
(set! cl-test-pass (+ cl-test-pass 1))
|
||||
(do
|
||||
(set! cl-test-fail (+ cl-test-fail 1))
|
||||
(append! cl-test-fails {:name name :expected expected :actual actual})))))
|
||||
|
||||
;; Helper: parse lambda list from string "(x y ...)"
|
||||
(define ll (fn (src) (cl-parse-lambda-list-str src)))
|
||||
(define ll-req (fn (src) (get (ll src) "required")))
|
||||
(define ll-opt (fn (src) (get (ll src) "optional")))
|
||||
(define ll-rest (fn (src) (get (ll src) "rest")))
|
||||
(define ll-key (fn (src) (get (ll src) "key")))
|
||||
(define ll-aok (fn (src) (get (ll src) "allow-other-keys")))
|
||||
(define ll-aux (fn (src) (get (ll src) "aux")))
|
||||
|
||||
;; ── required parameters ───────────────────────────────────────────
|
||||
|
||||
(cl-test "required: empty" (ll-req "()") (list))
|
||||
(cl-test "required: one" (ll-req "(x)") (list "X"))
|
||||
(cl-test "required: two" (ll-req "(x y)") (list "X" "Y"))
|
||||
(cl-test "required: three" (ll-req "(a b c)") (list "A" "B" "C"))
|
||||
(cl-test "required: upcased" (ll-req "(foo bar)") (list "FOO" "BAR"))
|
||||
|
||||
;; ── &optional ─────────────────────────────────────────────────────
|
||||
|
||||
(cl-test "optional: none" (ll-opt "(x)") (list))
|
||||
|
||||
(cl-test
|
||||
"optional: bare symbol"
|
||||
(ll-opt "(x &optional z)")
|
||||
(list {:name "Z" :default nil :supplied nil}))
|
||||
|
||||
(cl-test
|
||||
"optional: with default"
|
||||
(ll-opt "(x &optional (z 0))")
|
||||
(list {:name "Z" :default 0 :supplied nil}))
|
||||
|
||||
(cl-test
|
||||
"optional: with supplied-p"
|
||||
(ll-opt "(x &optional (z 0 z-p))")
|
||||
(list {:name "Z" :default 0 :supplied "Z-P"}))
|
||||
|
||||
(cl-test
|
||||
"optional: two params"
|
||||
(ll-opt "(&optional a (b 1))")
|
||||
(list {:name "A" :default nil :supplied nil} {:name "B" :default 1 :supplied nil}))
|
||||
|
||||
(cl-test
|
||||
"optional: string default"
|
||||
(ll-opt "(&optional (name \"world\"))")
|
||||
(list {:name "NAME" :default {:cl-type "string" :value "world"} :supplied nil}))
|
||||
|
||||
;; ── &rest ─────────────────────────────────────────────────────────
|
||||
|
||||
(cl-test "rest: none" (ll-rest "(x)") nil)
|
||||
(cl-test "rest: present" (ll-rest "(x &rest args)") "ARGS")
|
||||
(cl-test "rest: with required" (ll-rest "(a b &rest tail)") "TAIL")
|
||||
|
||||
;; &body is an alias for &rest
|
||||
(cl-test "body: alias for rest" (ll-rest "(&body forms)") "FORMS")
|
||||
|
||||
;; rest doesn't consume required params
|
||||
(cl-test "rest: required still there" (ll-req "(a b &rest rest)") (list "A" "B"))
|
||||
|
||||
;; ── &key ──────────────────────────────────────────────────────────
|
||||
|
||||
(cl-test "key: none" (ll-key "(x)") (list))
|
||||
|
||||
(cl-test
|
||||
"key: bare symbol"
|
||||
(ll-key "(&key x)")
|
||||
(list {:name "X" :keyword "X" :default nil :supplied nil}))
|
||||
|
||||
(cl-test
|
||||
"key: with default"
|
||||
(ll-key "(&key (x 42))")
|
||||
(list {:name "X" :keyword "X" :default 42 :supplied nil}))
|
||||
|
||||
(cl-test
|
||||
"key: with supplied-p"
|
||||
(ll-key "(&key (x 42 x-p))")
|
||||
(list {:name "X" :keyword "X" :default 42 :supplied "X-P"}))
|
||||
|
||||
(cl-test
|
||||
"key: two params"
|
||||
(ll-key "(&key a b)")
|
||||
(list
|
||||
{:name "A" :keyword "A" :default nil :supplied nil}
|
||||
{:name "B" :keyword "B" :default nil :supplied nil}))
|
||||
|
||||
;; ── &allow-other-keys ─────────────────────────────────────────────
|
||||
|
||||
(cl-test "aok: absent" (ll-aok "(x)") false)
|
||||
(cl-test "aok: present" (ll-aok "(&key x &allow-other-keys)") true)
|
||||
|
||||
;; ── &aux ──────────────────────────────────────────────────────────
|
||||
|
||||
(cl-test "aux: none" (ll-aux "(x)") (list))
|
||||
|
||||
(cl-test
|
||||
"aux: bare symbol"
|
||||
(ll-aux "(&aux temp)")
|
||||
(list {:name "TEMP" :init nil}))
|
||||
|
||||
(cl-test
|
||||
"aux: with init"
|
||||
(ll-aux "(&aux (count 0))")
|
||||
(list {:name "COUNT" :init 0}))
|
||||
|
||||
(cl-test
|
||||
"aux: two vars"
|
||||
(ll-aux "(&aux a (b 1))")
|
||||
(list {:name "A" :init nil} {:name "B" :init 1}))
|
||||
|
||||
;; ── combined ──────────────────────────────────────────────────────
|
||||
|
||||
(cl-test
|
||||
"combined: full lambda list"
|
||||
(let
|
||||
((parsed (ll "(x y &optional (z 0 z-p) &rest args &key a (b nil b-p) &aux temp)")))
|
||||
(list
|
||||
(get parsed "required")
|
||||
(get (nth (get parsed "optional") 0) "name")
|
||||
(get (nth (get parsed "optional") 0) "default")
|
||||
(get (nth (get parsed "optional") 0) "supplied")
|
||||
(get parsed "rest")
|
||||
(get (nth (get parsed "key") 0) "name")
|
||||
(get (nth (get parsed "key") 1) "supplied")
|
||||
(get (nth (get parsed "aux") 0) "name")))
|
||||
(list
|
||||
(list "X" "Y")
|
||||
"Z"
|
||||
0
|
||||
"Z-P"
|
||||
"ARGS"
|
||||
"A"
|
||||
"B-P"
|
||||
"TEMP"))
|
||||
|
||||
(cl-test
|
||||
"combined: required only stops before &"
|
||||
(ll-req "(a b &optional c)")
|
||||
(list "A" "B"))
|
||||
|
||||
(cl-test
|
||||
"combined: required only with &key"
|
||||
(ll-req "(x &key y)")
|
||||
(list "X"))
|
||||
|
||||
(cl-test
|
||||
"combined: &rest and &key together"
|
||||
(let
|
||||
((parsed (ll "(&rest args &key verbose)")))
|
||||
(list (get parsed "rest") (get (nth (get parsed "key") 0) "name")))
|
||||
(list "ARGS" "VERBOSE"))
|
||||
@@ -1,204 +0,0 @@
|
||||
;; lib/common-lisp/tests/macros.sx — Phase 5: defmacro, gensym, LOOP tests
|
||||
;;
|
||||
;; Depends on: runtime.sx, eval.sx, loop.sx already loaded.
|
||||
;; Tests via (ev "...") using the CL evaluator.
|
||||
|
||||
(define ev (fn (src) (cl-eval-str src (cl-make-env))))
|
||||
(define evall (fn (src) (cl-eval-all-str src (cl-make-env))))
|
||||
|
||||
(define passed 0)
|
||||
(define failed 0)
|
||||
(define failures (list))
|
||||
|
||||
(define
|
||||
check
|
||||
(fn
|
||||
(label got expected)
|
||||
(if
|
||||
(= got expected)
|
||||
(set! passed (+ passed 1))
|
||||
(begin
|
||||
(set! failed (+ failed 1))
|
||||
(set!
|
||||
failures
|
||||
(append
|
||||
failures
|
||||
(list
|
||||
(str
|
||||
"FAIL ["
|
||||
label
|
||||
"]: got="
|
||||
(inspect got)
|
||||
" expected="
|
||||
(inspect expected)))))))))
|
||||
|
||||
;; ── defmacro basics ──────────────────────────────────────────────────────────
|
||||
|
||||
(check
|
||||
"defmacro returns name"
|
||||
(ev "(defmacro my-or (a b) (list 'if a a b))")
|
||||
"MY-OR")
|
||||
|
||||
(check
|
||||
"defmacro expansion works"
|
||||
(ev "(progn (defmacro my-inc (x) (list '+ x 1)) (my-inc 5))")
|
||||
6)
|
||||
|
||||
(check
|
||||
"defmacro with &rest"
|
||||
(ev "(progn (defmacro my-list (&rest xs) (cons 'list xs)) (my-list 1 2 3))")
|
||||
(list 1 2 3))
|
||||
|
||||
(check
|
||||
"nested macro expansion"
|
||||
(ev "(progn (defmacro sq (x) (list '* x x)) (sq 7))")
|
||||
49)
|
||||
|
||||
(check
|
||||
"macro in conditional"
|
||||
(ev
|
||||
"(progn (defmacro my-when (c &rest body) (list 'if c (cons 'progn body) nil)) (my-when t 10 20))")
|
||||
20)
|
||||
|
||||
(check
|
||||
"macro returns nil branch"
|
||||
(ev
|
||||
"(progn (defmacro my-when (c &rest body) (list 'if c (cons 'progn body) nil)) (my-when nil 42))")
|
||||
nil)
|
||||
|
||||
;; ── macroexpand ───────────────────────────────────────────────────────────────
|
||||
|
||||
(check
|
||||
"macroexpand returns expanded form"
|
||||
(ev "(progn (defmacro double (x) (list '+ x x)) (macroexpand '(double 5)))")
|
||||
(list "+" 5 5))
|
||||
|
||||
;; ── gensym ────────────────────────────────────────────────────────────────────
|
||||
|
||||
(check "gensym returns string" (ev "(stringp (gensym))") true)
|
||||
|
||||
(check
|
||||
"gensym prefix"
|
||||
(ev "(let ((g (gensym \"MY\"))) (not (= g nil)))")
|
||||
true)
|
||||
|
||||
(check "gensyms are unique" (ev "(not (= (gensym) (gensym)))") true)
|
||||
|
||||
;; ── swap! macro with gensym ───────────────────────────────────────────────────
|
||||
|
||||
(check
|
||||
"swap! macro"
|
||||
(evall
|
||||
"(defmacro swap! (a b) (let ((tmp (gensym))) (list 'let (list (list tmp a)) (list 'setq a b) (list 'setq b tmp)))) (defvar *a* 10) (defvar *b* 20) (swap! *a* *b*) (list *a* *b*)")
|
||||
(list 20 10))
|
||||
|
||||
;; ── LOOP: basic repeat and collect ────────────────────────────────────────────
|
||||
|
||||
(check
|
||||
"loop repeat collect"
|
||||
(ev "(loop repeat 3 collect 99)")
|
||||
(list 99 99 99))
|
||||
|
||||
(check
|
||||
"loop for-in collect"
|
||||
(ev "(loop for x in '(1 2 3) collect (* x x))")
|
||||
(list 1 4 9))
|
||||
|
||||
(check
|
||||
"loop for-from-to collect"
|
||||
(ev "(loop for i from 1 to 5 collect i)")
|
||||
(list 1 2 3 4 5))
|
||||
|
||||
(check
|
||||
"loop for-from-below collect"
|
||||
(ev "(loop for i from 0 below 4 collect i)")
|
||||
(list 0 1 2 3))
|
||||
|
||||
(check
|
||||
"loop for-downto collect"
|
||||
(ev "(loop for i from 5 downto 1 collect i)")
|
||||
(list 5 4 3 2 1))
|
||||
|
||||
(check
|
||||
"loop for-by collect"
|
||||
(ev "(loop for i from 0 to 10 by 2 collect i)")
|
||||
(list 0 2 4 6 8 10))
|
||||
|
||||
;; ── LOOP: sum, count, maximize, minimize ─────────────────────────────────────
|
||||
|
||||
(check "loop sum" (ev "(loop for i from 1 to 5 sum i)") 15)
|
||||
|
||||
(check
|
||||
"loop count"
|
||||
(ev "(loop for x in '(1 2 3 4 5) count (> x 3))")
|
||||
2)
|
||||
|
||||
(check
|
||||
"loop maximize"
|
||||
(ev "(loop for x in '(3 1 4 1 5 9 2 6) maximize x)")
|
||||
9)
|
||||
|
||||
(check
|
||||
"loop minimize"
|
||||
(ev "(loop for x in '(3 1 4 1 5 9 2 6) minimize x)")
|
||||
1)
|
||||
|
||||
;; ── LOOP: while and until ─────────────────────────────────────────────────────
|
||||
|
||||
(check
|
||||
"loop while"
|
||||
(ev "(loop for i from 1 to 10 while (< i 5) collect i)")
|
||||
(list 1 2 3 4))
|
||||
|
||||
(check
|
||||
"loop until"
|
||||
(ev "(loop for i from 1 to 10 until (= i 5) collect i)")
|
||||
(list 1 2 3 4))
|
||||
|
||||
;; ── LOOP: when / unless ───────────────────────────────────────────────────────
|
||||
|
||||
(check
|
||||
"loop when filter"
|
||||
(ev "(loop for i from 0 below 8 when (evenp i) collect i)")
|
||||
(list 0 2 4 6))
|
||||
|
||||
(check
|
||||
"loop unless filter"
|
||||
(ev "(loop for i from 0 below 8 unless (evenp i) collect i)")
|
||||
(list 1 3 5 7))
|
||||
|
||||
;; ── LOOP: append ─────────────────────────────────────────────────────────────
|
||||
|
||||
(check
|
||||
"loop append"
|
||||
(ev "(loop for x in '((1 2) (3 4) (5 6)) append x)")
|
||||
(list 1 2 3 4 5 6))
|
||||
|
||||
;; ── LOOP: always, never, thereis ─────────────────────────────────────────────
|
||||
|
||||
(check
|
||||
"loop always true"
|
||||
(ev "(loop for x in '(2 4 6) always (evenp x))")
|
||||
true)
|
||||
|
||||
(check
|
||||
"loop always false"
|
||||
(ev "(loop for x in '(2 3 6) always (evenp x))")
|
||||
false)
|
||||
|
||||
(check "loop never" (ev "(loop for x in '(1 3 5) never (evenp x))") true)
|
||||
|
||||
(check "loop thereis" (ev "(loop for x in '(1 2 3) thereis (> x 2))") true)
|
||||
|
||||
;; ── LOOP: for = then (general iteration) ─────────────────────────────────────
|
||||
|
||||
(check
|
||||
"loop for = then doubling"
|
||||
(ev "(loop repeat 5 for x = 1 then (* x 2) collect x)")
|
||||
(list 1 2 4 8 16))
|
||||
|
||||
;; ── summary ────────────────────────────────────────────────────────────────
|
||||
|
||||
(define macro-passed passed)
|
||||
(define macro-failed failed)
|
||||
(define macro-failures failures)
|
||||
@@ -1,160 +0,0 @@
|
||||
;; Common Lisp reader/parser tests
|
||||
|
||||
(define cl-test-pass 0)
|
||||
(define cl-test-fail 0)
|
||||
(define cl-test-fails (list))
|
||||
|
||||
(define
|
||||
cl-deep=
|
||||
(fn
|
||||
(a b)
|
||||
(cond
|
||||
((= a b) true)
|
||||
((and (dict? a) (dict? b))
|
||||
(let
|
||||
((ak (keys a)) (bk (keys b)))
|
||||
(if
|
||||
(not (= (len ak) (len bk)))
|
||||
false
|
||||
(every?
|
||||
(fn (k) (and (has-key? b k) (cl-deep= (get a k) (get b k))))
|
||||
ak))))
|
||||
((and (list? a) (list? b))
|
||||
(if
|
||||
(not (= (len a) (len b)))
|
||||
false
|
||||
(let
|
||||
((i 0) (ok true))
|
||||
(define
|
||||
chk
|
||||
(fn
|
||||
()
|
||||
(when
|
||||
(and ok (< i (len a)))
|
||||
(do
|
||||
(when
|
||||
(not (cl-deep= (nth a i) (nth b i)))
|
||||
(set! ok false))
|
||||
(set! i (+ i 1))
|
||||
(chk)))))
|
||||
(chk)
|
||||
ok)))
|
||||
(:else false))))
|
||||
|
||||
(define
|
||||
cl-test
|
||||
(fn
|
||||
(name actual expected)
|
||||
(if
|
||||
(cl-deep= actual expected)
|
||||
(set! cl-test-pass (+ cl-test-pass 1))
|
||||
(do
|
||||
(set! cl-test-fail (+ cl-test-fail 1))
|
||||
(append! cl-test-fails {:name name :expected expected :actual actual})))))
|
||||
|
||||
;; ── atoms ─────────────────────────────────────────────────────────
|
||||
|
||||
(cl-test "integer: 42" (cl-read "42") 42)
|
||||
(cl-test "integer: 0" (cl-read "0") 0)
|
||||
(cl-test "integer: negative" (cl-read "-5") -5)
|
||||
(cl-test "integer: positive sign" (cl-read "+3") 3)
|
||||
(cl-test "integer: hex #xFF" (cl-read "#xFF") 255)
|
||||
(cl-test "integer: hex #xAB" (cl-read "#xAB") 171)
|
||||
(cl-test "integer: binary #b1010" (cl-read "#b1010") 10)
|
||||
(cl-test "integer: octal #o17" (cl-read "#o17") 15)
|
||||
|
||||
(cl-test "float: type" (get (cl-read "3.14") "cl-type") "float")
|
||||
(cl-test "float: value" (get (cl-read "3.14") "value") "3.14")
|
||||
(cl-test "float: neg" (get (cl-read "-2.5") "value") "-2.5")
|
||||
(cl-test "float: exp" (get (cl-read "1.0e10") "value") "1.0e10")
|
||||
|
||||
(cl-test "ratio: type" (get (cl-read "1/3") "cl-type") "ratio")
|
||||
(cl-test "ratio: value" (get (cl-read "1/3") "value") "1/3")
|
||||
(cl-test "ratio: 22/7" (get (cl-read "22/7") "value") "22/7")
|
||||
|
||||
(cl-test "string: basic" (cl-read "\"hello\"") {:cl-type "string" :value "hello"})
|
||||
(cl-test "string: empty" (cl-read "\"\"") {:cl-type "string" :value ""})
|
||||
(cl-test "string: with escape" (cl-read "\"a\\nb\"") {:cl-type "string" :value "a\nb"})
|
||||
|
||||
(cl-test "symbol: foo" (cl-read "foo") "FOO")
|
||||
(cl-test "symbol: BAR" (cl-read "BAR") "BAR")
|
||||
(cl-test "symbol: pkg:sym" (cl-read "cl:car") "CL:CAR")
|
||||
(cl-test "symbol: pkg::sym" (cl-read "pkg::foo") "PKG::FOO")
|
||||
|
||||
(cl-test "nil: symbol" (cl-read "nil") nil)
|
||||
(cl-test "nil: uppercase" (cl-read "NIL") nil)
|
||||
(cl-test "t: symbol" (cl-read "t") true)
|
||||
(cl-test "t: uppercase" (cl-read "T") true)
|
||||
|
||||
(cl-test "keyword: type" (get (cl-read ":foo") "cl-type") "keyword")
|
||||
(cl-test "keyword: name" (get (cl-read ":foo") "name") "FOO")
|
||||
(cl-test "keyword: :test" (get (cl-read ":test") "name") "TEST")
|
||||
|
||||
(cl-test "char: type" (get (cl-read "#\\a") "cl-type") "char")
|
||||
(cl-test "char: value" (get (cl-read "#\\a") "value") "a")
|
||||
(cl-test "char: Space" (get (cl-read "#\\Space") "value") " ")
|
||||
(cl-test "char: Newline" (get (cl-read "#\\Newline") "value") "\n")
|
||||
|
||||
(cl-test "uninterned: type" (get (cl-read "#:foo") "cl-type") "uninterned")
|
||||
(cl-test "uninterned: name" (get (cl-read "#:foo") "name") "FOO")
|
||||
|
||||
;; ── lists ─────────────────────────────────────────────────────────
|
||||
|
||||
(cl-test "list: empty" (cl-read "()") (list))
|
||||
(cl-test "list: one element" (cl-read "(foo)") (list "FOO"))
|
||||
(cl-test "list: two elements" (cl-read "(foo bar)") (list "FOO" "BAR"))
|
||||
(cl-test "list: nested" (cl-read "((a b) c)") (list (list "A" "B") "C"))
|
||||
(cl-test "list: with integer" (cl-read "(+ 1 2)") (list "+" 1 2))
|
||||
(cl-test "list: with string" (cl-read "(print \"hi\")") (list "PRINT" {:cl-type "string" :value "hi"}))
|
||||
(cl-test "list: nil element" (cl-read "(a nil b)") (list "A" nil "B"))
|
||||
(cl-test "list: t element" (cl-read "(a t b)") (list "A" true "B"))
|
||||
|
||||
;; ── dotted pairs ──────────────────────────────────────────────<E29480><E29480>──
|
||||
|
||||
(cl-test "dotted: type" (get (cl-read "(a . b)") "cl-type") "cons")
|
||||
(cl-test "dotted: car" (get (cl-read "(a . b)") "car") "A")
|
||||
(cl-test "dotted: cdr" (get (cl-read "(a . b)") "cdr") "B")
|
||||
(cl-test "dotted: number cdr" (get (cl-read "(x . 42)") "cdr") 42)
|
||||
|
||||
;; ── reader macros ────────────────────────────────────────────────<E29480><E29480>
|
||||
|
||||
(cl-test "quote: form" (cl-read "'x") (list "QUOTE" "X"))
|
||||
(cl-test "quote: list" (cl-read "'(a b)") (list "QUOTE" (list "A" "B")))
|
||||
(cl-test "backquote: form" (cl-read "`x") (list "QUASIQUOTE" "X"))
|
||||
(cl-test "unquote: form" (cl-read ",x") (list "UNQUOTE" "X"))
|
||||
(cl-test "comma-at: form" (cl-read ",@x") (list "UNQUOTE-SPLICING" "X"))
|
||||
(cl-test "function: form" (cl-read "#'foo") (list "FUNCTION" "FOO"))
|
||||
|
||||
;; ── vector ────────────────────────────────────────────────────────
|
||||
|
||||
(cl-test "vector: type" (get (cl-read "#(1 2 3)") "cl-type") "vector")
|
||||
(cl-test "vector: elements" (get (cl-read "#(1 2 3)") "elements") (list 1 2 3))
|
||||
(cl-test "vector: empty" (get (cl-read "#()") "elements") (list))
|
||||
(cl-test "vector: mixed" (get (cl-read "#(a 1 \"s\")") "elements") (list "A" 1 {:cl-type "string" :value "s"}))
|
||||
|
||||
;; ── cl-read-all ───────────────────────────────────────────────────
|
||||
|
||||
(cl-test
|
||||
"read-all: empty"
|
||||
(cl-read-all "")
|
||||
(list))
|
||||
|
||||
(cl-test
|
||||
"read-all: two forms"
|
||||
(cl-read-all "42 foo")
|
||||
(list 42 "FOO"))
|
||||
|
||||
(cl-test
|
||||
"read-all: three forms"
|
||||
(cl-read-all "(+ 1 2) (+ 3 4) hello")
|
||||
(list (list "+" 1 2) (list "+" 3 4) "HELLO"))
|
||||
|
||||
(cl-test
|
||||
"read-all: with comments"
|
||||
(cl-read-all "; this is a comment\n42 ; inline\nfoo")
|
||||
(list 42 "FOO"))
|
||||
|
||||
(cl-test
|
||||
"read-all: defun form"
|
||||
(nth (cl-read-all "(defun square (x) (* x x))") 0)
|
||||
(list "DEFUN" "SQUARE" (list "X") (list "*" "X" "X")))
|
||||
@@ -1,291 +0,0 @@
|
||||
;; geometry.sx — Multiple dispatch with CLOS
|
||||
;;
|
||||
;; Demonstrates generic functions dispatching on combinations of
|
||||
;; geometric types: point, line, plane.
|
||||
;;
|
||||
;; Depends on: lib/common-lisp/runtime.sx, lib/common-lisp/clos.sx
|
||||
|
||||
;; ── geometric classes ──────────────────────────────────────────────────────
|
||||
|
||||
(clos-defclass "geo-point" (list "t") (list {:initform 0 :initarg ":px" :reader nil :writer nil :accessor nil :name "px"} {:initform 0 :initarg ":py" :reader nil :writer nil :accessor nil :name "py"}))
|
||||
|
||||
(clos-defclass "geo-line" (list "t") (list {:initform nil :initarg ":p1" :reader nil :writer nil :accessor nil :name "p1"} {:initform nil :initarg ":p2" :reader nil :writer nil :accessor nil :name "p2"}))
|
||||
|
||||
(clos-defclass "geo-plane" (list "t") (list {:initform nil :initarg ":normal" :reader nil :writer nil :accessor nil :name "normal"} {:initform 0 :initarg ":d" :reader nil :writer nil :accessor nil :name "d"}))
|
||||
|
||||
;; ── helpers ────────────────────────────────────────────────────────────────
|
||||
|
||||
(define geo-point-x (fn (p) (clos-slot-value p "px")))
|
||||
(define geo-point-y (fn (p) (clos-slot-value p "py")))
|
||||
|
||||
(define
|
||||
geo-make-point
|
||||
(fn (x y) (clos-make-instance "geo-point" ":px" x ":py" y)))
|
||||
|
||||
(define
|
||||
geo-make-line
|
||||
(fn (p1 p2) (clos-make-instance "geo-line" ":p1" p1 ":p2" p2)))
|
||||
|
||||
(define
|
||||
geo-make-plane
|
||||
(fn
|
||||
(nx ny d)
|
||||
(clos-make-instance "geo-plane" ":normal" (list nx ny) ":d" d)))
|
||||
|
||||
;; ── describe generic ───────────────────────────────────────────────────────
|
||||
|
||||
(clos-defgeneric "geo-describe" {})
|
||||
|
||||
(clos-defmethod
|
||||
"geo-describe"
|
||||
(list)
|
||||
(list "geo-point")
|
||||
(fn
|
||||
(args next-fn)
|
||||
(let
|
||||
((p (first args)))
|
||||
(str "P(" (geo-point-x p) "," (geo-point-y p) ")"))))
|
||||
|
||||
(clos-defmethod
|
||||
"geo-describe"
|
||||
(list)
|
||||
(list "geo-line")
|
||||
(fn
|
||||
(args next-fn)
|
||||
(let
|
||||
((l (first args)))
|
||||
(str
|
||||
"L["
|
||||
(clos-call-generic "geo-describe" (list (clos-slot-value l "p1")))
|
||||
"-"
|
||||
(clos-call-generic "geo-describe" (list (clos-slot-value l "p2")))
|
||||
"]"))))
|
||||
|
||||
(clos-defmethod
|
||||
"geo-describe"
|
||||
(list)
|
||||
(list "geo-plane")
|
||||
(fn
|
||||
(args next-fn)
|
||||
(let
|
||||
((pl (first args)))
|
||||
(str "Plane(d=" (clos-slot-value pl "d") ")"))))
|
||||
|
||||
;; ── intersect: multi-dispatch generic ─────────────────────────────────────
|
||||
;;
|
||||
;; Returns a string description of the intersection result.
|
||||
|
||||
(clos-defgeneric "intersect" {})
|
||||
|
||||
;; point ∩ point: same if coordinates match
|
||||
(clos-defmethod
|
||||
"intersect"
|
||||
(list)
|
||||
(list "geo-point" "geo-point")
|
||||
(fn
|
||||
(args next-fn)
|
||||
(let
|
||||
((p1 (first args)) (p2 (first (rest args))))
|
||||
(if
|
||||
(and
|
||||
(= (geo-point-x p1) (geo-point-x p2))
|
||||
(= (geo-point-y p1) (geo-point-y p2)))
|
||||
"point"
|
||||
"empty"))))
|
||||
|
||||
;; point ∩ line: check if point lies on line (cross product = 0)
|
||||
(clos-defmethod
|
||||
"intersect"
|
||||
(list)
|
||||
(list "geo-point" "geo-line")
|
||||
(fn
|
||||
(args next-fn)
|
||||
(let
|
||||
((pt (first args)) (ln (first (rest args))))
|
||||
(let
|
||||
((lp1 (clos-slot-value ln "p1")) (lp2 (clos-slot-value ln "p2")))
|
||||
(let
|
||||
((dx (- (geo-point-x lp2) (geo-point-x lp1)))
|
||||
(dy (- (geo-point-y lp2) (geo-point-y lp1)))
|
||||
(ex (- (geo-point-x pt) (geo-point-x lp1)))
|
||||
(ey (- (geo-point-y pt) (geo-point-y lp1))))
|
||||
(if (= (- (* dx ey) (* dy ex)) 0) "point" "empty"))))))
|
||||
|
||||
;; line ∩ line: parallel (same slope = empty) or point
|
||||
(clos-defmethod
|
||||
"intersect"
|
||||
(list)
|
||||
(list "geo-line" "geo-line")
|
||||
(fn
|
||||
(args next-fn)
|
||||
(let
|
||||
((l1 (first args)) (l2 (first (rest args))))
|
||||
(let
|
||||
((p1 (clos-slot-value l1 "p1"))
|
||||
(p2 (clos-slot-value l1 "p2"))
|
||||
(p3 (clos-slot-value l2 "p1"))
|
||||
(p4 (clos-slot-value l2 "p2")))
|
||||
(let
|
||||
((dx1 (- (geo-point-x p2) (geo-point-x p1)))
|
||||
(dy1 (- (geo-point-y p2) (geo-point-y p1)))
|
||||
(dx2 (- (geo-point-x p4) (geo-point-x p3)))
|
||||
(dy2 (- (geo-point-y p4) (geo-point-y p3))))
|
||||
(let
|
||||
((cross (- (* dx1 dy2) (* dy1 dx2))))
|
||||
(if (= cross 0) "parallel" "point")))))))
|
||||
|
||||
;; line ∩ plane: general case = point (or parallel if line ⊥ normal)
|
||||
(clos-defmethod
|
||||
"intersect"
|
||||
(list)
|
||||
(list "geo-line" "geo-plane")
|
||||
(fn
|
||||
(args next-fn)
|
||||
(let
|
||||
((ln (first args)) (pl (first (rest args))))
|
||||
(let
|
||||
((p1 (clos-slot-value ln "p1"))
|
||||
(p2 (clos-slot-value ln "p2"))
|
||||
(n (clos-slot-value pl "normal")))
|
||||
(let
|
||||
((dx (- (geo-point-x p2) (geo-point-x p1)))
|
||||
(dy (- (geo-point-y p2) (geo-point-y p1)))
|
||||
(nx (first n))
|
||||
(ny (first (rest n))))
|
||||
(let
|
||||
((dot (+ (* dx nx) (* dy ny))))
|
||||
(if (= dot 0) "parallel" "point")))))))
|
||||
|
||||
;; ── tests ─────────────────────────────────────────────────────────────────
|
||||
|
||||
(define passed 0)
|
||||
(define failed 0)
|
||||
(define failures (list))
|
||||
|
||||
(define
|
||||
check
|
||||
(fn
|
||||
(label got expected)
|
||||
(if
|
||||
(= got expected)
|
||||
(set! passed (+ passed 1))
|
||||
(begin
|
||||
(set! failed (+ failed 1))
|
||||
(set!
|
||||
failures
|
||||
(append
|
||||
failures
|
||||
(list
|
||||
(str
|
||||
"FAIL ["
|
||||
label
|
||||
"]: got="
|
||||
(inspect got)
|
||||
" expected="
|
||||
(inspect expected)))))))))
|
||||
|
||||
;; describe
|
||||
(check
|
||||
"describe point"
|
||||
(clos-call-generic
|
||||
"geo-describe"
|
||||
(list (geo-make-point 3 4)))
|
||||
"P(3,4)")
|
||||
(check
|
||||
"describe line"
|
||||
(clos-call-generic
|
||||
"geo-describe"
|
||||
(list
|
||||
(geo-make-line
|
||||
(geo-make-point 0 0)
|
||||
(geo-make-point 1 1))))
|
||||
"L[P(0,0)-P(1,1)]")
|
||||
(check
|
||||
"describe plane"
|
||||
(clos-call-generic
|
||||
"geo-describe"
|
||||
(list (geo-make-plane 0 1 5)))
|
||||
"Plane(d=5)")
|
||||
|
||||
;; intersect point×point
|
||||
(check
|
||||
"P∩P same"
|
||||
(clos-call-generic
|
||||
"intersect"
|
||||
(list
|
||||
(geo-make-point 2 3)
|
||||
(geo-make-point 2 3)))
|
||||
"point")
|
||||
(check
|
||||
"P∩P diff"
|
||||
(clos-call-generic
|
||||
"intersect"
|
||||
(list
|
||||
(geo-make-point 1 2)
|
||||
(geo-make-point 3 4)))
|
||||
"empty")
|
||||
|
||||
;; intersect point×line
|
||||
(let
|
||||
((origin (geo-make-point 0 0))
|
||||
(p10 (geo-make-point 10 0))
|
||||
(p55 (geo-make-point 5 5))
|
||||
(l-x
|
||||
(geo-make-line
|
||||
(geo-make-point 0 0)
|
||||
(geo-make-point 10 0))))
|
||||
(begin
|
||||
(check
|
||||
"P∩L on line"
|
||||
(clos-call-generic "intersect" (list p10 l-x))
|
||||
"point")
|
||||
(check
|
||||
"P∩L on x-axis"
|
||||
(clos-call-generic "intersect" (list origin l-x))
|
||||
"point")
|
||||
(check
|
||||
"P∩L off line"
|
||||
(clos-call-generic "intersect" (list p55 l-x))
|
||||
"empty")))
|
||||
|
||||
;; intersect line×line
|
||||
(let
|
||||
((horiz (geo-make-line (geo-make-point 0 0) (geo-make-point 10 0)))
|
||||
(vert
|
||||
(geo-make-line
|
||||
(geo-make-point 5 -5)
|
||||
(geo-make-point 5 5)))
|
||||
(horiz2
|
||||
(geo-make-line
|
||||
(geo-make-point 0 3)
|
||||
(geo-make-point 10 3))))
|
||||
(begin
|
||||
(check
|
||||
"L∩L crossing"
|
||||
(clos-call-generic "intersect" (list horiz vert))
|
||||
"point")
|
||||
(check
|
||||
"L∩L parallel"
|
||||
(clos-call-generic "intersect" (list horiz horiz2))
|
||||
"parallel")))
|
||||
|
||||
;; intersect line×plane
|
||||
(let
|
||||
((diag (geo-make-line (geo-make-point 0 0) (geo-make-point 1 1)))
|
||||
(vert-plane (geo-make-plane 1 0 5))
|
||||
(diag-plane (geo-make-plane -1 1 0)))
|
||||
(begin
|
||||
(check
|
||||
"L∩Plane cross"
|
||||
(clos-call-generic "intersect" (list diag vert-plane))
|
||||
"point")
|
||||
(check
|
||||
"L∩Plane parallel"
|
||||
(clos-call-generic "intersect" (list diag diag-plane))
|
||||
"parallel")))
|
||||
|
||||
;; ── summary ────────────────────────────────────────────────────────────────
|
||||
|
||||
(define geo-passed passed)
|
||||
(define geo-failed failed)
|
||||
(define geo-failures failures)
|
||||
@@ -1,196 +0,0 @@
|
||||
;; interactive-debugger.sx — Condition debugger using *debugger-hook*
|
||||
;;
|
||||
;; Demonstrates the classic CL debugger pattern:
|
||||
;; - *debugger-hook* is invoked when an unhandled error reaches the top level
|
||||
;; - The hook receives the condition and a reference to itself
|
||||
;; - It can offer restarts interactively (here simulated with a policy fn)
|
||||
;;
|
||||
;; In real CL the debugger reads from the terminal. Here we simulate
|
||||
;; the "user input" via a policy function passed in at call time.
|
||||
;;
|
||||
;; Depends on: lib/common-lisp/runtime.sx already loaded.
|
||||
|
||||
;; ── *debugger-hook* global ────────────────────────────────────────────────
|
||||
;;
|
||||
;; CL: when error is unhandled, invoke *debugger-hook* with (condition hook).
|
||||
;; A nil hook means use the system default (which we simulate as re-raise).
|
||||
|
||||
(define cl-debugger-hook nil)
|
||||
|
||||
;; ── invoke-debugger ────────────────────────────────────────────────────────
|
||||
;;
|
||||
;; Called when cl-error finds no handler. Tries cl-debugger-hook first;
|
||||
;; falls back to a simple error report.
|
||||
|
||||
(define
|
||||
cl-invoke-debugger
|
||||
(fn
|
||||
(c)
|
||||
(if
|
||||
(nil? cl-debugger-hook)
|
||||
(error (str "Debugger: " (cl-condition-message c)))
|
||||
(begin
|
||||
(let
|
||||
((hook cl-debugger-hook))
|
||||
(set! cl-debugger-hook nil)
|
||||
(let
|
||||
((result (hook c hook)))
|
||||
(set! cl-debugger-hook hook)
|
||||
result))))))
|
||||
|
||||
;; ── cl-error/debugger — error that routes through invoke-debugger ─────────
|
||||
|
||||
(define
|
||||
cl-error-with-debugger
|
||||
(fn
|
||||
(c &rest args)
|
||||
(let
|
||||
((obj (cond ((cl-condition? c) c) ((string? c) (cl-make-condition "simple-error" "format-control" c "format-arguments" args)) (:else (cl-make-condition "simple-error" "format-control" (str c))))))
|
||||
(cl-signal-obj obj cl-handler-stack)
|
||||
(cl-invoke-debugger obj))))
|
||||
|
||||
;; ── simulated debugger session ────────────────────────────────────────────
|
||||
;;
|
||||
;; A debugger hook takes (condition hook) and "reads" user commands.
|
||||
;; We simulate this with a policy function: (fn (c restarts) restart-name)
|
||||
;; that picks a restart given the condition and available restarts.
|
||||
|
||||
(define
|
||||
make-policy-debugger
|
||||
(fn
|
||||
(policy)
|
||||
(fn
|
||||
(c hook)
|
||||
(let
|
||||
((available (cl-compute-restarts)))
|
||||
(let
|
||||
((choice (policy c available)))
|
||||
(if
|
||||
(and choice (not (nil? (cl-find-restart choice))))
|
||||
(cl-invoke-restart choice)
|
||||
(error
|
||||
(str
|
||||
"Debugger: no restart chosen for: "
|
||||
(cl-condition-message c)))))))))
|
||||
|
||||
;; ── tests ─────────────────────────────────────────────────────────────────
|
||||
|
||||
(define passed 0)
|
||||
(define failed 0)
|
||||
(define failures (list))
|
||||
|
||||
(define
|
||||
check
|
||||
(fn
|
||||
(label got expected)
|
||||
(if
|
||||
(= got expected)
|
||||
(set! passed (+ passed 1))
|
||||
(begin
|
||||
(set! failed (+ failed 1))
|
||||
(set!
|
||||
failures
|
||||
(append
|
||||
failures
|
||||
(list
|
||||
(str
|
||||
"FAIL ["
|
||||
label
|
||||
"]: got="
|
||||
(inspect got)
|
||||
" expected="
|
||||
(inspect expected)))))))))
|
||||
|
||||
(define
|
||||
reset-stacks!
|
||||
(fn
|
||||
()
|
||||
(set! cl-handler-stack (list))
|
||||
(set! cl-restart-stack (list))
|
||||
(set! cl-debugger-hook nil)))
|
||||
|
||||
;; Test 1: debugger hook receives condition
|
||||
(reset-stacks!)
|
||||
(let
|
||||
((received-msg ""))
|
||||
(begin
|
||||
(set!
|
||||
cl-debugger-hook
|
||||
(fn (c hook) (set! received-msg (cl-condition-message c)) nil))
|
||||
(cl-restart-case
|
||||
(fn () (cl-error-with-debugger "something broke"))
|
||||
(list "abort" (list) (fn () nil)))
|
||||
(check "debugger hook receives condition" received-msg "something broke")))
|
||||
|
||||
;; Test 2: policy-driven restart selection (use-zero)
|
||||
(reset-stacks!)
|
||||
(let
|
||||
((result (begin (set! cl-debugger-hook (make-policy-debugger (fn (c restarts) "use-zero"))) (cl-restart-case (fn () (cl-error-with-debugger (cl-make-condition "division-by-zero")) 999) (list "use-zero" (list) (fn () 0))))))
|
||||
(check "policy debugger: use-zero restart" result 0))
|
||||
|
||||
;; Test 3: policy selects abort
|
||||
(reset-stacks!)
|
||||
(let
|
||||
((result (begin (set! cl-debugger-hook (make-policy-debugger (fn (c restarts) "abort"))) (cl-restart-case (fn () (cl-error-with-debugger "aborting error") 999) (list "abort" (list) (fn () "aborted"))))))
|
||||
(check "policy debugger: abort restart" result "aborted"))
|
||||
|
||||
;; Test 4: compute-restarts inside debugger hook
|
||||
(reset-stacks!)
|
||||
(let
|
||||
((seen-restarts (list)))
|
||||
(begin
|
||||
(set!
|
||||
cl-debugger-hook
|
||||
(fn
|
||||
(c hook)
|
||||
(set! seen-restarts (cl-compute-restarts))
|
||||
(cl-invoke-restart "continue")))
|
||||
(cl-restart-case
|
||||
(fn () (cl-error-with-debugger "test") 42)
|
||||
(list "continue" (list) (fn () "ok"))
|
||||
(list "abort" (list) (fn () "no")))
|
||||
(check
|
||||
"debugger: compute-restarts visible"
|
||||
(= (len seen-restarts) 2)
|
||||
true)))
|
||||
|
||||
;; Test 5: hook not invoked when handler catches first
|
||||
(reset-stacks!)
|
||||
(let
|
||||
((hook-called false)
|
||||
(result
|
||||
(begin
|
||||
(set! cl-debugger-hook (fn (c hook) (set! hook-called true) nil))
|
||||
(cl-handler-case
|
||||
(fn () (cl-error-with-debugger "handled"))
|
||||
(list "error" (fn (c) "handler-won"))))))
|
||||
(check "handler wins; hook not called" hook-called false)
|
||||
(check "handler result returned" result "handler-won"))
|
||||
|
||||
;; Test 6: debugger-hook nil after re-raise guard
|
||||
(reset-stacks!)
|
||||
(let
|
||||
((hook-calls 0))
|
||||
(begin
|
||||
(set!
|
||||
cl-debugger-hook
|
||||
(fn
|
||||
(c hook)
|
||||
(set! hook-calls (+ hook-calls 1))
|
||||
(if
|
||||
(> hook-calls 1)
|
||||
(error "infinite loop guard")
|
||||
(cl-invoke-restart "escape"))))
|
||||
(cl-restart-case
|
||||
(fn () (cl-error-with-debugger "once"))
|
||||
(list "escape" (list) (fn () nil)))
|
||||
(check
|
||||
"hook called exactly once (no infinite recursion)"
|
||||
hook-calls
|
||||
1)))
|
||||
|
||||
;; ── summary ────────────────────────────────────────────────────────────────
|
||||
|
||||
(define debugger-passed passed)
|
||||
(define debugger-failed failed)
|
||||
(define debugger-failures failures)
|
||||
@@ -1,228 +0,0 @@
|
||||
;; mop-trace.sx — :before/:after method tracing with CLOS
|
||||
;;
|
||||
;; Classic CLOS pattern: instrument generic functions with :before and :after
|
||||
;; qualifiers to print call/return traces without modifying the primary method.
|
||||
;;
|
||||
;; Depends on: lib/common-lisp/runtime.sx, lib/common-lisp/clos.sx
|
||||
|
||||
;; ── trace log (mutable accumulator) ───────────────────────────────────────
|
||||
|
||||
(define trace-log (list))
|
||||
|
||||
(define
|
||||
trace-push
|
||||
(fn (msg) (set! trace-log (append trace-log (list msg)))))
|
||||
|
||||
(define trace-clear (fn () (set! trace-log (list))))
|
||||
|
||||
;; ── domain classes ─────────────────────────────────────────────────────────
|
||||
|
||||
(clos-defclass "shape" (list "t") (list {:initform "white" :initarg ":color" :reader nil :writer nil :accessor nil :name "color"}))
|
||||
|
||||
(clos-defclass "circle" (list "shape") (list {:initform 1 :initarg ":radius" :reader nil :writer nil :accessor nil :name "radius"}))
|
||||
|
||||
(clos-defclass "rect" (list "shape") (list {:initform 1 :initarg ":width" :reader nil :writer nil :accessor nil :name "width"} {:initform 1 :initarg ":height" :reader nil :writer nil :accessor nil :name "height"}))
|
||||
|
||||
;; ── generic function: area ─────────────────────────────────────────────────
|
||||
|
||||
(clos-defgeneric "area" {})
|
||||
|
||||
;; primary methods
|
||||
(clos-defmethod
|
||||
"area"
|
||||
(list)
|
||||
(list "circle")
|
||||
(fn
|
||||
(args next-fn)
|
||||
(let
|
||||
((c (first args)))
|
||||
(let ((r (clos-slot-value c "radius"))) (* r r)))))
|
||||
|
||||
(clos-defmethod
|
||||
"area"
|
||||
(list)
|
||||
(list "rect")
|
||||
(fn
|
||||
(args next-fn)
|
||||
(let
|
||||
((r (first args)))
|
||||
(* (clos-slot-value r "width") (clos-slot-value r "height")))))
|
||||
|
||||
;; :before tracing
|
||||
(clos-defmethod
|
||||
"area"
|
||||
(list "before")
|
||||
(list "shape")
|
||||
(fn
|
||||
(args next-fn)
|
||||
(trace-push (str "BEFORE area(" (clos-class-of (first args)) ")"))))
|
||||
|
||||
;; :after tracing
|
||||
(clos-defmethod
|
||||
"area"
|
||||
(list "after")
|
||||
(list "shape")
|
||||
(fn
|
||||
(args next-fn)
|
||||
(trace-push (str "AFTER area(" (clos-class-of (first args)) ")"))))
|
||||
|
||||
;; ── generic function: describe-shape ──────────────────────────────────────
|
||||
|
||||
(clos-defgeneric "describe-shape" {})
|
||||
|
||||
(clos-defmethod
|
||||
"describe-shape"
|
||||
(list)
|
||||
(list "shape")
|
||||
(fn
|
||||
(args next-fn)
|
||||
(let
|
||||
((s (first args)))
|
||||
(str "shape[" (clos-slot-value s "color") "]"))))
|
||||
|
||||
(clos-defmethod
|
||||
"describe-shape"
|
||||
(list)
|
||||
(list "circle")
|
||||
(fn
|
||||
(args next-fn)
|
||||
(let
|
||||
((c (first args)))
|
||||
(str
|
||||
"circle[r="
|
||||
(clos-slot-value c "radius")
|
||||
" "
|
||||
(clos-call-next-method next-fn)
|
||||
"]"))))
|
||||
|
||||
(clos-defmethod
|
||||
"describe-shape"
|
||||
(list)
|
||||
(list "rect")
|
||||
(fn
|
||||
(args next-fn)
|
||||
(let
|
||||
((r (first args)))
|
||||
(str
|
||||
"rect["
|
||||
(clos-slot-value r "width")
|
||||
"x"
|
||||
(clos-slot-value r "height")
|
||||
" "
|
||||
(clos-call-next-method next-fn)
|
||||
"]"))))
|
||||
|
||||
;; :before on base shape (fires for all subclasses too)
|
||||
(clos-defmethod
|
||||
"describe-shape"
|
||||
(list "before")
|
||||
(list "shape")
|
||||
(fn
|
||||
(args next-fn)
|
||||
(trace-push
|
||||
(str "BEFORE describe-shape(" (clos-class-of (first args)) ")"))))
|
||||
|
||||
;; ── tests ─────────────────────────────────────────────────────────────────
|
||||
|
||||
(define passed 0)
|
||||
(define failed 0)
|
||||
(define failures (list))
|
||||
|
||||
(define
|
||||
check
|
||||
(fn
|
||||
(label got expected)
|
||||
(if
|
||||
(= got expected)
|
||||
(set! passed (+ passed 1))
|
||||
(begin
|
||||
(set! failed (+ failed 1))
|
||||
(set!
|
||||
failures
|
||||
(append
|
||||
failures
|
||||
(list
|
||||
(str
|
||||
"FAIL ["
|
||||
label
|
||||
"]: got="
|
||||
(inspect got)
|
||||
" expected="
|
||||
(inspect expected)))))))))
|
||||
|
||||
;; ── area tests ────────────────────────────────────────────────────────────
|
||||
|
||||
;; circle area = r*r (no pi — integer arithmetic for predictability)
|
||||
(let
|
||||
((c (clos-make-instance "circle" ":radius" 5 ":color" "red")))
|
||||
(do
|
||||
(trace-clear)
|
||||
(check "circle area" (clos-call-generic "area" (list c)) 25)
|
||||
(check
|
||||
":before fired for circle"
|
||||
(= (first trace-log) "BEFORE area(circle)")
|
||||
true)
|
||||
(check
|
||||
":after fired for circle"
|
||||
(= (first (rest trace-log)) "AFTER area(circle)")
|
||||
true)
|
||||
(check "trace length 2" (len trace-log) 2)))
|
||||
|
||||
;; rect area = w*h
|
||||
(let
|
||||
((r (clos-make-instance "rect" ":width" 4 ":height" 6 ":color" "blue")))
|
||||
(do
|
||||
(trace-clear)
|
||||
(check "rect area" (clos-call-generic "area" (list r)) 24)
|
||||
(check
|
||||
":before fired for rect"
|
||||
(= (first trace-log) "BEFORE area(rect)")
|
||||
true)
|
||||
(check
|
||||
":after fired for rect"
|
||||
(= (first (rest trace-log)) "AFTER area(rect)")
|
||||
true)
|
||||
(check "trace length 2 (rect)" (len trace-log) 2)))
|
||||
|
||||
;; ── describe-shape tests ───────────────────────────────────────────────────
|
||||
|
||||
(let
|
||||
((c (clos-make-instance "circle" ":radius" 3 ":color" "green")))
|
||||
(do
|
||||
(trace-clear)
|
||||
(check
|
||||
"circle describe"
|
||||
(clos-call-generic "describe-shape" (list c))
|
||||
"circle[r=3 shape[green]]")
|
||||
(check
|
||||
":before fired for describe circle"
|
||||
(= (first trace-log) "BEFORE describe-shape(circle)")
|
||||
true)))
|
||||
|
||||
(let
|
||||
((r (clos-make-instance "rect" ":width" 2 ":height" 7 ":color" "black")))
|
||||
(do
|
||||
(trace-clear)
|
||||
(check
|
||||
"rect describe"
|
||||
(clos-call-generic "describe-shape" (list r))
|
||||
"rect[2x7 shape[black]]")
|
||||
(check
|
||||
":before fired for describe rect"
|
||||
(= (first trace-log) "BEFORE describe-shape(rect)")
|
||||
true)))
|
||||
|
||||
;; ── call-next-method: circle -> shape ─────────────────────────────────────
|
||||
|
||||
(let
|
||||
((c (clos-make-instance "circle" ":radius" 1 ":color" "purple")))
|
||||
(check
|
||||
"call-next-method result in describe"
|
||||
(clos-call-generic "describe-shape" (list c))
|
||||
"circle[r=1 shape[purple]]"))
|
||||
|
||||
;; ── summary ────────────────────────────────────────────────────────────────
|
||||
|
||||
(define mop-passed passed)
|
||||
(define mop-failed failed)
|
||||
(define mop-failures failures)
|
||||
@@ -1,163 +0,0 @@
|
||||
;; parse-recover.sx — Parser with skipped-token restart
|
||||
;;
|
||||
;; Classic CL pattern: a simple token parser that signals a condition
|
||||
;; when it encounters an unexpected token. The :skip-token restart
|
||||
;; allows the parser to continue past the offending token.
|
||||
;;
|
||||
;; Depends on: lib/common-lisp/runtime.sx already loaded.
|
||||
|
||||
;; ── condition type ─────────────────────────────────────────────────────────
|
||||
|
||||
(cl-define-condition "parse-error" (list "error") (list "token" "position"))
|
||||
|
||||
;; ── simple token parser ────────────────────────────────────────────────────
|
||||
;;
|
||||
;; parse-numbers: given a list of tokens (strings), parse integers.
|
||||
;; Non-integer tokens signal parse-error with two restarts:
|
||||
;; skip-token — skip the bad token and continue
|
||||
;; use-zero — use 0 in place of the bad token
|
||||
|
||||
(define
|
||||
parse-numbers
|
||||
(fn
|
||||
(tokens)
|
||||
(define result (list))
|
||||
(define
|
||||
process
|
||||
(fn
|
||||
(toks)
|
||||
(if
|
||||
(empty? toks)
|
||||
result
|
||||
(let
|
||||
((tok (first toks)) (rest-toks (rest toks)))
|
||||
(let
|
||||
((n (string->number tok 10)))
|
||||
(if
|
||||
n
|
||||
(begin
|
||||
(set! result (append result (list n)))
|
||||
(process rest-toks))
|
||||
(cl-restart-case
|
||||
(fn
|
||||
()
|
||||
(cl-signal
|
||||
(cl-make-condition
|
||||
"parse-error"
|
||||
"token"
|
||||
tok
|
||||
"position"
|
||||
(len result)))
|
||||
(set! result (append result (list 0)))
|
||||
(process rest-toks))
|
||||
(list "skip-token" (list) (fn () (process rest-toks)))
|
||||
(list
|
||||
"use-zero"
|
||||
(list)
|
||||
(fn
|
||||
()
|
||||
(begin
|
||||
(set! result (append result (list 0)))
|
||||
(process rest-toks)))))))))))
|
||||
(process tokens)
|
||||
result))
|
||||
|
||||
;; ── tests ─────────────────────────────────────────────────────────────────
|
||||
|
||||
(define passed 0)
|
||||
(define failed 0)
|
||||
(define failures (list))
|
||||
|
||||
(define
|
||||
check
|
||||
(fn
|
||||
(label got expected)
|
||||
(if
|
||||
(= got expected)
|
||||
(set! passed (+ passed 1))
|
||||
(begin
|
||||
(set! failed (+ failed 1))
|
||||
(set!
|
||||
failures
|
||||
(append
|
||||
failures
|
||||
(list
|
||||
(str
|
||||
"FAIL ["
|
||||
label
|
||||
"]: got="
|
||||
(inspect got)
|
||||
" expected="
|
||||
(inspect expected)))))))))
|
||||
|
||||
(define
|
||||
reset-stacks!
|
||||
(fn () (set! cl-handler-stack (list)) (set! cl-restart-stack (list))))
|
||||
|
||||
;; All valid tokens
|
||||
(reset-stacks!)
|
||||
(check
|
||||
"all valid: 1 2 3"
|
||||
(cl-handler-bind
|
||||
(list (list "parse-error" (fn (c) (cl-invoke-restart "skip-token"))))
|
||||
(fn () (parse-numbers (list "1" "2" "3"))))
|
||||
(list 1 2 3))
|
||||
|
||||
;; Skip bad token
|
||||
(reset-stacks!)
|
||||
(check
|
||||
"skip bad token: 1 x 3 -> (1 3)"
|
||||
(cl-handler-bind
|
||||
(list (list "parse-error" (fn (c) (cl-invoke-restart "skip-token"))))
|
||||
(fn () (parse-numbers (list "1" "x" "3"))))
|
||||
(list 1 3))
|
||||
|
||||
;; Use zero for bad token
|
||||
(reset-stacks!)
|
||||
(check
|
||||
"use-zero for bad: 1 x 3 -> (1 0 3)"
|
||||
(cl-handler-bind
|
||||
(list (list "parse-error" (fn (c) (cl-invoke-restart "use-zero"))))
|
||||
(fn () (parse-numbers (list "1" "x" "3"))))
|
||||
(list 1 0 3))
|
||||
|
||||
;; Multiple bad tokens, all skipped
|
||||
(reset-stacks!)
|
||||
(check
|
||||
"skip multiple bad: a 2 b 4 -> (2 4)"
|
||||
(cl-handler-bind
|
||||
(list (list "parse-error" (fn (c) (cl-invoke-restart "skip-token"))))
|
||||
(fn () (parse-numbers (list "a" "2" "b" "4"))))
|
||||
(list 2 4))
|
||||
|
||||
;; handler-case: abort on first bad token
|
||||
(reset-stacks!)
|
||||
(check
|
||||
"handler-case: abort on first bad"
|
||||
(cl-handler-case
|
||||
(fn () (parse-numbers (list "1" "bad" "3")))
|
||||
(list
|
||||
"parse-error"
|
||||
(fn
|
||||
(c)
|
||||
(str
|
||||
"parse error at position "
|
||||
(cl-condition-slot c "position")
|
||||
": "
|
||||
(cl-condition-slot c "token")))))
|
||||
"parse error at position 1: bad")
|
||||
|
||||
;; Verify condition type hierarchy
|
||||
(reset-stacks!)
|
||||
(check
|
||||
"parse-error isa error"
|
||||
(cl-condition-of-type?
|
||||
(cl-make-condition "parse-error" "token" "x" "position" 0)
|
||||
"error")
|
||||
true)
|
||||
|
||||
;; ── summary ────────────────────────────────────────────────────────────────
|
||||
|
||||
(define parse-passed passed)
|
||||
(define parse-failed failed)
|
||||
(define parse-failures failures)
|
||||
@@ -1,141 +0,0 @@
|
||||
;; restart-demo.sx — Classic CL condition system demo
|
||||
;;
|
||||
;; Demonstrates resumable exceptions via restarts.
|
||||
;; The `safe-divide` function signals a division-by-zero condition
|
||||
;; and offers two restarts:
|
||||
;; :use-zero — return 0 as the result
|
||||
;; :retry — call safe-divide again with a corrected divisor
|
||||
;;
|
||||
;; Depends on: lib/common-lisp/runtime.sx already loaded.
|
||||
|
||||
;; ── safe-divide ────────────────────────────────────────────────────────────
|
||||
;;
|
||||
;; Divides numerator by denominator.
|
||||
;; When denominator is 0, signals division-by-zero with two restarts.
|
||||
|
||||
(define
|
||||
safe-divide
|
||||
(fn
|
||||
(n d)
|
||||
(if
|
||||
(= d 0)
|
||||
(cl-restart-case
|
||||
(fn
|
||||
()
|
||||
(cl-signal
|
||||
(cl-make-condition
|
||||
"division-by-zero"
|
||||
"operation"
|
||||
"/"
|
||||
"operands"
|
||||
(list n d)))
|
||||
(error "division by zero — no restart invoked"))
|
||||
(list "use-zero" (list) (fn () 0))
|
||||
(list "retry" (list "d") (fn (d2) (safe-divide n d2))))
|
||||
(/ n d))))
|
||||
|
||||
;; ── tests ─────────────────────────────────────────────────────────────────
|
||||
|
||||
(define passed 0)
|
||||
(define failed 0)
|
||||
(define failures (list))
|
||||
|
||||
(define
|
||||
check
|
||||
(fn
|
||||
(label got expected)
|
||||
(if
|
||||
(= got expected)
|
||||
(set! passed (+ passed 1))
|
||||
(begin
|
||||
(set! failed (+ failed 1))
|
||||
(set!
|
||||
failures
|
||||
(append
|
||||
failures
|
||||
(list
|
||||
(str
|
||||
"FAIL ["
|
||||
label
|
||||
"]: got="
|
||||
(inspect got)
|
||||
" expected="
|
||||
(inspect expected)))))))))
|
||||
|
||||
(define
|
||||
reset-stacks!
|
||||
(fn () (set! cl-handler-stack (list)) (set! cl-restart-stack (list))))
|
||||
|
||||
;; Normal division
|
||||
(reset-stacks!)
|
||||
(check "10 / 2 = 5" (safe-divide 10 2) 5)
|
||||
|
||||
;; Invoke use-zero restart
|
||||
(reset-stacks!)
|
||||
(check
|
||||
"10 / 0 -> use-zero"
|
||||
(cl-handler-bind
|
||||
(list
|
||||
(list "division-by-zero" (fn (c) (cl-invoke-restart "use-zero"))))
|
||||
(fn () (safe-divide 10 0)))
|
||||
0)
|
||||
|
||||
;; Invoke retry restart with a corrected denominator
|
||||
(reset-stacks!)
|
||||
(check
|
||||
"10 / 0 -> retry with 2"
|
||||
(cl-handler-bind
|
||||
(list
|
||||
(list
|
||||
"division-by-zero"
|
||||
(fn (c) (cl-invoke-restart "retry" 2))))
|
||||
(fn () (safe-divide 10 0)))
|
||||
5)
|
||||
|
||||
;; Nested calls: outer handles the inner divide-by-zero
|
||||
(reset-stacks!)
|
||||
(check
|
||||
"nested: 20 / (0->4) = 5"
|
||||
(cl-handler-bind
|
||||
(list
|
||||
(list
|
||||
"division-by-zero"
|
||||
(fn (c) (cl-invoke-restart "retry" 4))))
|
||||
(fn () (let ((r1 (safe-divide 20 0))) r1)))
|
||||
5)
|
||||
|
||||
;; handler-case — unwinding version
|
||||
(reset-stacks!)
|
||||
(check
|
||||
"handler-case: catches division-by-zero"
|
||||
(cl-handler-case
|
||||
(fn () (safe-divide 9 0))
|
||||
(list "division-by-zero" (fn (c) "caught!")))
|
||||
"caught!")
|
||||
|
||||
;; Verify use-zero is idempotent (two uses)
|
||||
(reset-stacks!)
|
||||
(check
|
||||
"two use-zero invocations"
|
||||
(cl-handler-bind
|
||||
(list
|
||||
(list "division-by-zero" (fn (c) (cl-invoke-restart "use-zero"))))
|
||||
(fn
|
||||
()
|
||||
(+
|
||||
(safe-divide 10 0)
|
||||
(safe-divide 3 0))))
|
||||
0)
|
||||
|
||||
;; No restart needed for normal division
|
||||
(reset-stacks!)
|
||||
(check
|
||||
"no restart needed for 8/4"
|
||||
(safe-divide 8 4)
|
||||
2)
|
||||
|
||||
;; ── summary ────────────────────────────────────────────────────────────────
|
||||
|
||||
(define demo-passed passed)
|
||||
(define demo-failed failed)
|
||||
(define demo-failures failures)
|
||||
@@ -1,180 +0,0 @@
|
||||
;; Common Lisp tokenizer tests
|
||||
|
||||
(define cl-test-pass 0)
|
||||
(define cl-test-fail 0)
|
||||
(define cl-test-fails (list))
|
||||
|
||||
(define
|
||||
cl-test
|
||||
(fn
|
||||
(name actual expected)
|
||||
(if
|
||||
(= actual expected)
|
||||
(set! cl-test-pass (+ cl-test-pass 1))
|
||||
(do
|
||||
(set! cl-test-fail (+ cl-test-fail 1))
|
||||
(append! cl-test-fails {:name name :expected expected :actual actual})))))
|
||||
|
||||
;; Helpers: extract types and values from token stream (drops eof)
|
||||
(define
|
||||
cl-tok-types
|
||||
(fn
|
||||
(src)
|
||||
(map
|
||||
(fn (t) (get t "type"))
|
||||
(filter (fn (t) (not (= (get t "type") "eof"))) (cl-tokenize src)))))
|
||||
|
||||
(define
|
||||
cl-tok-values
|
||||
(fn
|
||||
(src)
|
||||
(map
|
||||
(fn (t) (get t "value"))
|
||||
(filter (fn (t) (not (= (get t "type") "eof"))) (cl-tokenize src)))))
|
||||
|
||||
(define
|
||||
cl-tok-first
|
||||
(fn (src) (nth (cl-tokenize src) 0)))
|
||||
|
||||
;; ── symbols ───────────────────────────────────────────────────────
|
||||
|
||||
(cl-test "symbol: bare lowercase" (cl-tok-values "foo") (list "FOO"))
|
||||
(cl-test "symbol: uppercase" (cl-tok-values "BAR") (list "BAR"))
|
||||
(cl-test "symbol: mixed case folded" (cl-tok-values "FooBar") (list "FOOBAR"))
|
||||
(cl-test "symbol: with hyphen" (cl-tok-values "foo-bar") (list "FOO-BAR"))
|
||||
(cl-test "symbol: with star" (cl-tok-values "*special*") (list "*SPECIAL*"))
|
||||
(cl-test "symbol: with question" (cl-tok-values "null?") (list "NULL?"))
|
||||
(cl-test "symbol: with exclamation" (cl-tok-values "set!") (list "SET!"))
|
||||
(cl-test "symbol: plus sign alone" (cl-tok-values "+") (list "+"))
|
||||
(cl-test "symbol: minus sign alone" (cl-tok-values "-") (list "-"))
|
||||
(cl-test "symbol: type is symbol" (cl-tok-types "foo") (list "symbol"))
|
||||
|
||||
;; ── package-qualified symbols ─────────────────────────────────────
|
||||
|
||||
(cl-test "symbol: pkg:sym external" (cl-tok-values "cl:car") (list "CL:CAR"))
|
||||
(cl-test "symbol: pkg::sym internal" (cl-tok-values "pkg::foo") (list "PKG::FOO"))
|
||||
(cl-test "symbol: cl:car type" (cl-tok-types "cl:car") (list "symbol"))
|
||||
|
||||
;; ── keywords ──────────────────────────────────────────────────────
|
||||
|
||||
(cl-test "keyword: basic" (cl-tok-values ":foo") (list "FOO"))
|
||||
(cl-test "keyword: type" (cl-tok-types ":foo") (list "keyword"))
|
||||
(cl-test "keyword: upcase" (cl-tok-values ":hello-world") (list "HELLO-WORLD"))
|
||||
(cl-test "keyword: multiple" (cl-tok-types ":a :b :c") (list "keyword" "keyword" "keyword"))
|
||||
|
||||
;; ── integers ──────────────────────────────────────────────────────
|
||||
|
||||
(cl-test "integer: zero" (cl-tok-values "0") (list "0"))
|
||||
(cl-test "integer: positive" (cl-tok-values "42") (list "42"))
|
||||
(cl-test "integer: negative" (cl-tok-values "-5") (list "-5"))
|
||||
(cl-test "integer: positive-sign" (cl-tok-values "+3") (list "+3"))
|
||||
(cl-test "integer: type" (cl-tok-types "42") (list "integer"))
|
||||
(cl-test "integer: multi-digit" (cl-tok-values "12345678") (list "12345678"))
|
||||
|
||||
;; ── hex, binary, octal ───────────────────────────────────────────
|
||||
|
||||
(cl-test "hex: lowercase x" (cl-tok-values "#xFF") (list "#xFF"))
|
||||
(cl-test "hex: uppercase X" (cl-tok-values "#XFF") (list "#XFF"))
|
||||
(cl-test "hex: type" (cl-tok-types "#xFF") (list "integer"))
|
||||
(cl-test "hex: zero" (cl-tok-values "#x0") (list "#x0"))
|
||||
(cl-test "binary: #b" (cl-tok-values "#b1010") (list "#b1010"))
|
||||
(cl-test "binary: type" (cl-tok-types "#b1010") (list "integer"))
|
||||
(cl-test "octal: #o" (cl-tok-values "#o17") (list "#o17"))
|
||||
(cl-test "octal: type" (cl-tok-types "#o17") (list "integer"))
|
||||
|
||||
;; ── floats ────────────────────────────────────────────────────────
|
||||
|
||||
(cl-test "float: basic" (cl-tok-values "3.14") (list "3.14"))
|
||||
(cl-test "float: type" (cl-tok-types "3.14") (list "float"))
|
||||
(cl-test "float: negative" (cl-tok-values "-2.5") (list "-2.5"))
|
||||
(cl-test "float: exponent" (cl-tok-values "1.0e10") (list "1.0e10"))
|
||||
(cl-test "float: neg exponent" (cl-tok-values "1.5e-3") (list "1.5e-3"))
|
||||
(cl-test "float: leading dot" (cl-tok-values ".5") (list "0.5"))
|
||||
(cl-test "float: exp only" (cl-tok-values "1e5") (list "1e5"))
|
||||
|
||||
;; ── ratios ────────────────────────────────────────────────────────
|
||||
|
||||
(cl-test "ratio: 1/3" (cl-tok-values "1/3") (list "1/3"))
|
||||
(cl-test "ratio: type" (cl-tok-types "1/3") (list "ratio"))
|
||||
(cl-test "ratio: 22/7" (cl-tok-values "22/7") (list "22/7"))
|
||||
(cl-test "ratio: negative" (cl-tok-values "-1/2") (list "-1/2"))
|
||||
|
||||
;; ── strings ───────────────────────────────────────────────────────
|
||||
|
||||
(cl-test "string: empty" (cl-tok-values "\"\"") (list ""))
|
||||
(cl-test "string: basic" (cl-tok-values "\"hello\"") (list "hello"))
|
||||
(cl-test "string: type" (cl-tok-types "\"hello\"") (list "string"))
|
||||
(cl-test "string: with space" (cl-tok-values "\"hello world\"") (list "hello world"))
|
||||
(cl-test "string: escaped quote" (cl-tok-values "\"say \\\"hi\\\"\"") (list "say \"hi\""))
|
||||
(cl-test "string: escaped backslash" (cl-tok-values "\"a\\\\b\"") (list "a\\b"))
|
||||
(cl-test "string: newline escape" (cl-tok-values "\"a\\nb\"") (list "a\nb"))
|
||||
(cl-test "string: tab escape" (cl-tok-values "\"a\\tb\"") (list "a\tb"))
|
||||
|
||||
;; ── characters ────────────────────────────────────────────────────
|
||||
|
||||
(cl-test "char: lowercase a" (cl-tok-values "#\\a") (list "a"))
|
||||
(cl-test "char: uppercase A" (cl-tok-values "#\\A") (list "A"))
|
||||
(cl-test "char: digit" (cl-tok-values "#\\1") (list "1"))
|
||||
(cl-test "char: type" (cl-tok-types "#\\a") (list "char"))
|
||||
(cl-test "char: Space" (cl-tok-values "#\\Space") (list " "))
|
||||
(cl-test "char: Newline" (cl-tok-values "#\\Newline") (list "\n"))
|
||||
(cl-test "char: Tab" (cl-tok-values "#\\Tab") (list "\t"))
|
||||
(cl-test "char: Return" (cl-tok-values "#\\Return") (list "\r"))
|
||||
|
||||
;; ── reader macros ─────────────────────────────────────────────────
|
||||
|
||||
(cl-test "quote: type" (cl-tok-types "'x") (list "quote" "symbol"))
|
||||
(cl-test "backquote: type" (cl-tok-types "`x") (list "backquote" "symbol"))
|
||||
(cl-test "comma: type" (cl-tok-types ",x") (list "comma" "symbol"))
|
||||
(cl-test "comma-at: type" (cl-tok-types ",@x") (list "comma-at" "symbol"))
|
||||
(cl-test "hash-quote: type" (cl-tok-types "#'foo") (list "hash-quote" "symbol"))
|
||||
(cl-test "hash-paren: type" (cl-tok-types "#(1 2)") (list "hash-paren" "integer" "integer" "rparen"))
|
||||
|
||||
;; ── uninterned ────────────────────────────────────────────────────
|
||||
|
||||
(cl-test "uninterned: type" (cl-tok-types "#:foo") (list "uninterned"))
|
||||
(cl-test "uninterned: value upcase" (cl-tok-values "#:foo") (list "FOO"))
|
||||
(cl-test "uninterned: compound" (cl-tok-values "#:my-sym") (list "MY-SYM"))
|
||||
|
||||
;; ── parens and structure ──────────────────────────────────────────
|
||||
|
||||
(cl-test "paren: empty list" (cl-tok-types "()") (list "lparen" "rparen"))
|
||||
(cl-test "paren: nested" (cl-tok-types "((a))") (list "lparen" "lparen" "symbol" "rparen" "rparen"))
|
||||
(cl-test "dot: standalone" (cl-tok-types "(a . b)") (list "lparen" "symbol" "dot" "symbol" "rparen"))
|
||||
|
||||
;; ── comments ──────────────────────────────────────────────────────
|
||||
|
||||
(cl-test "comment: line" (cl-tok-types "; comment\nfoo") (list "symbol"))
|
||||
(cl-test "comment: inline" (cl-tok-values "foo ; bar\nbaz") (list "FOO" "BAZ"))
|
||||
(cl-test "block-comment: basic" (cl-tok-types "#| hello |# foo") (list "symbol"))
|
||||
(cl-test "block-comment: nested" (cl-tok-types "#| a #| b |# c |# x") (list "symbol"))
|
||||
|
||||
;; ── combined ──────────────────────────────────────────────────────
|
||||
|
||||
(cl-test
|
||||
"combined: defun skeleton"
|
||||
(cl-tok-types "(defun foo (x) x)")
|
||||
(list "lparen" "symbol" "symbol" "lparen" "symbol" "rparen" "symbol" "rparen"))
|
||||
|
||||
(cl-test
|
||||
"combined: let form"
|
||||
(cl-tok-types "(let ((x 1)) x)")
|
||||
(list
|
||||
"lparen"
|
||||
"symbol"
|
||||
"lparen"
|
||||
"lparen"
|
||||
"symbol"
|
||||
"integer"
|
||||
"rparen"
|
||||
"rparen"
|
||||
"symbol"
|
||||
"rparen"))
|
||||
|
||||
(cl-test
|
||||
"combined: whitespace skip"
|
||||
(cl-tok-values " foo bar baz ")
|
||||
(list "FOO" "BAR" "BAZ"))
|
||||
|
||||
(cl-test "eof: present" (get (nth (cl-tokenize "") 0) "type") "eof")
|
||||
(cl-test "eof: at end of tokens" (get (nth (cl-tokenize "x") 1) "type") "eof")
|
||||
@@ -1,285 +0,0 @@
|
||||
;; lib/common-lisp/tests/stdlib.sx — Phase 6: sequence, list, string functions
|
||||
|
||||
(define ev (fn (src) (cl-eval-str src (cl-make-env))))
|
||||
|
||||
(define passed 0)
|
||||
(define failed 0)
|
||||
(define failures (list))
|
||||
|
||||
(define
|
||||
check
|
||||
(fn
|
||||
(label got expected)
|
||||
(if
|
||||
(= got expected)
|
||||
(set! passed (+ passed 1))
|
||||
(begin
|
||||
(set! failed (+ failed 1))
|
||||
(set!
|
||||
failures
|
||||
(append
|
||||
failures
|
||||
(list
|
||||
(str
|
||||
"FAIL ["
|
||||
label
|
||||
"]: got="
|
||||
(inspect got)
|
||||
" expected="
|
||||
(inspect expected)))))))))
|
||||
|
||||
;; ── mapc ─────────────────────────────────────────────────────────
|
||||
|
||||
(check "mapc returns list"
|
||||
(ev "(mapc #'1+ '(1 2 3))")
|
||||
(list 1 2 3))
|
||||
|
||||
;; ── mapcan ───────────────────────────────────────────────────────
|
||||
|
||||
(check "mapcan basic"
|
||||
(ev "(mapcan (lambda (x) (list x (* x x))) '(1 2 3))")
|
||||
(list 1 1 2 4 3 9))
|
||||
|
||||
(check "mapcan filter-like"
|
||||
(ev "(mapcan (lambda (x) (if (evenp x) (list x) nil)) '(1 2 3 4 5 6))")
|
||||
(list 2 4 6))
|
||||
|
||||
;; ── reduce ───────────────────────────────────────────────────────
|
||||
|
||||
(check "reduce sum"
|
||||
(ev "(reduce #'+ '(1 2 3 4 5))")
|
||||
15)
|
||||
|
||||
(check "reduce with initial-value"
|
||||
(ev "(reduce #'+ '(1 2 3) :initial-value 10)")
|
||||
16)
|
||||
|
||||
(check "reduce max"
|
||||
(ev "(reduce (lambda (a b) (if (> a b) a b)) '(3 1 4 1 5 9 2 6))")
|
||||
9)
|
||||
|
||||
;; ── find ─────────────────────────────────────────────────────────
|
||||
|
||||
(check "find present"
|
||||
(ev "(find 3 '(1 2 3 4 5))")
|
||||
3)
|
||||
|
||||
(check "find absent"
|
||||
(ev "(find 9 '(1 2 3))")
|
||||
nil)
|
||||
|
||||
(check "find-if present"
|
||||
(ev "(find-if #'evenp '(1 3 4 7))")
|
||||
4)
|
||||
|
||||
(check "find-if absent"
|
||||
(ev "(find-if #'evenp '(1 3 5))")
|
||||
nil)
|
||||
|
||||
(check "find-if-not"
|
||||
(ev "(find-if-not #'evenp '(2 4 5 6))")
|
||||
5)
|
||||
|
||||
;; ── position ─────────────────────────────────────────────────────
|
||||
|
||||
(check "position found"
|
||||
(ev "(position 3 '(1 2 3 4 5))")
|
||||
2)
|
||||
|
||||
(check "position not found"
|
||||
(ev "(position 9 '(1 2 3))")
|
||||
nil)
|
||||
|
||||
(check "position-if"
|
||||
(ev "(position-if #'evenp '(1 3 4 8))")
|
||||
2)
|
||||
|
||||
;; ── count ────────────────────────────────────────────────────────
|
||||
|
||||
(check "count"
|
||||
(ev "(count 2 '(1 2 3 2 4 2))")
|
||||
3)
|
||||
|
||||
(check "count-if"
|
||||
(ev "(count-if #'evenp '(1 2 3 4 5 6))")
|
||||
3)
|
||||
|
||||
;; ── every / some / notany / notevery ─────────────────────────────
|
||||
|
||||
(check "every true"
|
||||
(ev "(every #'evenp '(2 4 6))")
|
||||
true)
|
||||
|
||||
(check "every false"
|
||||
(ev "(every #'evenp '(2 3 6))")
|
||||
nil)
|
||||
|
||||
(check "every empty"
|
||||
(ev "(every #'evenp '())")
|
||||
true)
|
||||
|
||||
(check "some truthy"
|
||||
(ev "(some #'evenp '(1 3 4))")
|
||||
true)
|
||||
|
||||
(check "some nil"
|
||||
(ev "(some #'evenp '(1 3 5))")
|
||||
nil)
|
||||
|
||||
(check "notany true"
|
||||
(ev "(notany #'evenp '(1 3 5))")
|
||||
true)
|
||||
|
||||
(check "notany false"
|
||||
(ev "(notany #'evenp '(1 2 5))")
|
||||
nil)
|
||||
|
||||
(check "notevery false"
|
||||
(ev "(notevery #'evenp '(2 4 6))")
|
||||
nil)
|
||||
|
||||
(check "notevery true"
|
||||
(ev "(notevery #'evenp '(2 3 6))")
|
||||
true)
|
||||
|
||||
;; ── remove ───────────────────────────────────────────────────────
|
||||
|
||||
(check "remove"
|
||||
(ev "(remove 3 '(1 2 3 4 3 5))")
|
||||
(list 1 2 4 5))
|
||||
|
||||
(check "remove-if"
|
||||
(ev "(remove-if #'evenp '(1 2 3 4 5 6))")
|
||||
(list 1 3 5))
|
||||
|
||||
(check "remove-if-not"
|
||||
(ev "(remove-if-not #'evenp '(1 2 3 4 5 6))")
|
||||
(list 2 4 6))
|
||||
|
||||
;; ── member ───────────────────────────────────────────────────────
|
||||
|
||||
(check "member found"
|
||||
(ev "(member 3 '(1 2 3 4 5))")
|
||||
(list 3 4 5))
|
||||
|
||||
(check "member not found"
|
||||
(ev "(member 9 '(1 2 3))")
|
||||
nil)
|
||||
|
||||
;; ── subst ────────────────────────────────────────────────────────
|
||||
|
||||
(check "subst flat"
|
||||
(ev "(subst 'b 'a '(a b c a))")
|
||||
(list "B" "B" "C" "B"))
|
||||
|
||||
(check "subst nested"
|
||||
(ev "(subst 99 1 '(1 (2 1) 3))")
|
||||
(list 99 (list 2 99) 3))
|
||||
|
||||
;; ── assoc ────────────────────────────────────────────────────────
|
||||
|
||||
(check "assoc found"
|
||||
(ev "(assoc 'b '((a 1) (b 2) (c 3)))")
|
||||
(list "B" 2))
|
||||
|
||||
(check "assoc not found"
|
||||
(ev "(assoc 'z '((a 1) (b 2)))")
|
||||
nil)
|
||||
|
||||
;; ── list ops ─────────────────────────────────────────────────────
|
||||
|
||||
(check "last"
|
||||
(ev "(last '(1 2 3 4))")
|
||||
(list 4))
|
||||
|
||||
(check "butlast"
|
||||
(ev "(butlast '(1 2 3 4))")
|
||||
(list 1 2 3))
|
||||
|
||||
(check "nthcdr"
|
||||
(ev "(nthcdr 2 '(a b c d))")
|
||||
(list "C" "D"))
|
||||
|
||||
(check "list*"
|
||||
(ev "(list* 1 2 '(3 4))")
|
||||
(list 1 2 3 4))
|
||||
|
||||
(check "cadr"
|
||||
(ev "(cadr '(1 2 3))")
|
||||
2)
|
||||
|
||||
(check "caddr"
|
||||
(ev "(caddr '(1 2 3))")
|
||||
3)
|
||||
|
||||
(check "cadddr"
|
||||
(ev "(cadddr '(1 2 3 4))")
|
||||
4)
|
||||
|
||||
(check "cddr"
|
||||
(ev "(cddr '(1 2 3 4))")
|
||||
(list 3 4))
|
||||
|
||||
;; ── subseq ───────────────────────────────────────────────────────
|
||||
|
||||
(check "subseq string"
|
||||
(ev "(subseq \"hello\" 1 3)")
|
||||
"el")
|
||||
|
||||
(check "subseq list"
|
||||
(ev "(subseq '(a b c d) 1 3)")
|
||||
(list "B" "C"))
|
||||
|
||||
(check "subseq no end"
|
||||
(ev "(subseq \"hello\" 2)")
|
||||
"llo")
|
||||
|
||||
;; ── FORMAT ─────────────────────────────────────────────────────────
|
||||
|
||||
(check "format ~A"
|
||||
(ev "(format nil \"hello ~A\" \"world\")")
|
||||
"hello world")
|
||||
|
||||
(check "format ~D"
|
||||
(ev "(format nil \"~D items\" 42)")
|
||||
"42 items")
|
||||
|
||||
(check "format two args"
|
||||
(ev "(format nil \"~A ~A\" 1 2)")
|
||||
"1 2")
|
||||
|
||||
(check "format ~A+~A=~A"
|
||||
(ev "(format nil \"~A + ~A = ~A\" 1 2 3)")
|
||||
"1 + 2 = 3")
|
||||
|
||||
(check "format iterate"
|
||||
(ev "(format nil \"~{~A~}\" (quote (1 2 3)))")
|
||||
"123")
|
||||
|
||||
(check "format iterate with space"
|
||||
(ev "(format nil \"(~{~A ~})\" (quote (1 2 3)))")
|
||||
"(1 2 3 )")
|
||||
|
||||
;; ── packages ─────────────────────────────────────────────────────
|
||||
|
||||
(check "defpackage returns name"
|
||||
(ev "(defpackage :my-pkg (:use :cl))")
|
||||
"MY-PKG")
|
||||
|
||||
(check "in-package"
|
||||
(ev "(progn (defpackage :test-pkg) (in-package :test-pkg) (package-name))")
|
||||
"TEST-PKG")
|
||||
|
||||
(check "package-qualified function"
|
||||
(ev "(cl:car (quote (1 2 3)))")
|
||||
1)
|
||||
|
||||
(check "package-qualified function 2"
|
||||
(ev "(cl:mapcar (function evenp) (quote (2 3 4)))")
|
||||
(list true nil true))
|
||||
|
||||
;; ── summary ──────────────────────────────────────────────────────
|
||||
|
||||
(define stdlib-passed passed)
|
||||
(define stdlib-failed failed)
|
||||
(define stdlib-failures failures)
|
||||
@@ -1,86 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Erlang-on-SX ring benchmark.
|
||||
#
|
||||
# Spawns N processes in a ring, passes a token N hops (one full round),
|
||||
# and reports wall-clock time + throughput. Aspirational target from
|
||||
# the plan is 1M processes; current sync-scheduler architecture caps out
|
||||
# orders of magnitude lower — this script measures honestly across a
|
||||
# range of N so the result/scaling is recorded.
|
||||
#
|
||||
# Usage:
|
||||
# bash lib/erlang/bench_ring.sh # default ladder
|
||||
# bash lib/erlang/bench_ring.sh 100 1000 5000 # custom Ns
|
||||
|
||||
set -uo pipefail
|
||||
cd "$(git rev-parse --show-toplevel)"
|
||||
|
||||
SX_SERVER="${SX_SERVER:-hosts/ocaml/_build/default/bin/sx_server.exe}"
|
||||
if [ ! -x "$SX_SERVER" ]; then
|
||||
SX_SERVER="/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe"
|
||||
fi
|
||||
if [ ! -x "$SX_SERVER" ]; then
|
||||
echo "ERROR: sx_server.exe not found." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$#" -gt 0 ]; then
|
||||
NS=("$@")
|
||||
else
|
||||
NS=(10 100 500 1000)
|
||||
fi
|
||||
|
||||
TMPFILE=$(mktemp)
|
||||
trap "rm -f $TMPFILE" EXIT
|
||||
|
||||
# One-line Erlang program. Replaces __N__ with the size for each run.
|
||||
PROGRAM='Me = self(), N = __N__, Spawner = fun () -> receive {setup, Next} -> Loop = fun () -> receive {token, 0, Parent} -> Parent ! done; {token, K, Parent} -> Next ! {token, K-1, Parent}, Loop() end end, Loop() end end, BuildRing = fun (K, Acc) -> if K =:= 0 -> Acc; true -> BuildRing(K-1, [spawn(Spawner) | Acc]) end end, Pids = BuildRing(N, []), Wire = fun (Ps) -> case Ps of [P, Q | _] -> P ! {setup, Q}, Wire(tl(Ps)); [Last] -> Last ! {setup, hd(Pids)} end end, Wire(Pids), hd(Pids) ! {token, N, Me}, receive done -> done end'
|
||||
|
||||
run_n() {
|
||||
local n="$1"
|
||||
local prog="${PROGRAM//__N__/$n}"
|
||||
cat > "$TMPFILE" <<EPOCHS
|
||||
(epoch 1)
|
||||
(load "lib/erlang/tokenizer.sx")
|
||||
(load "lib/erlang/parser.sx")
|
||||
(load "lib/erlang/parser-core.sx")
|
||||
(load "lib/erlang/parser-expr.sx")
|
||||
(load "lib/erlang/parser-module.sx")
|
||||
(load "lib/erlang/transpile.sx")
|
||||
(load "lib/erlang/runtime.sx")
|
||||
(epoch 2)
|
||||
(eval "(erlang-eval-ast \"${prog//\"/\\\"}\")")
|
||||
EPOCHS
|
||||
|
||||
local start_s start_ns end_s end_ns elapsed_ms
|
||||
start_s=$(date +%s)
|
||||
start_ns=$(date +%N)
|
||||
out=$(timeout 300 "$SX_SERVER" < "$TMPFILE" 2>&1)
|
||||
end_s=$(date +%s)
|
||||
end_ns=$(date +%N)
|
||||
|
||||
local ok="false"
|
||||
if echo "$out" | grep -q ':name "done"'; then ok="true"; fi
|
||||
|
||||
# ms = (end_s - start_s)*1000 + (end_ns - start_ns)/1e6
|
||||
elapsed_ms=$(awk -v s1="$start_s" -v n1="$start_ns" -v s2="$end_s" -v n2="$end_ns" \
|
||||
'BEGIN { printf "%d", (s2 - s1) * 1000 + (n2 - n1) / 1000000 }')
|
||||
|
||||
if [ "$ok" = "true" ]; then
|
||||
local hops_per_s
|
||||
hops_per_s=$(awk -v n="$n" -v ms="$elapsed_ms" \
|
||||
'BEGIN { if (ms == 0) ms = 1; printf "%.0f", n * 1000 / ms }')
|
||||
printf " N=%-8s hops=%-8s %sms (%s hops/s)\n" "$n" "$n" "$elapsed_ms" "$hops_per_s"
|
||||
else
|
||||
printf " N=%-8s FAILED %sms\n" "$n" "$elapsed_ms"
|
||||
fi
|
||||
}
|
||||
|
||||
echo "Ring benchmark — sx_server.exe (synchronous scheduler)"
|
||||
echo
|
||||
for n in "${NS[@]}"; do
|
||||
run_n "$n"
|
||||
done
|
||||
echo
|
||||
echo "Note: 1M-process target from the plan is aspirational; the synchronous"
|
||||
echo "scheduler with shift-based suspension and dict-based env copies is not"
|
||||
echo "engineered for that scale. Numbers above are honest baselines."
|
||||
@@ -1,35 +0,0 @@
|
||||
# Ring Benchmark Results
|
||||
|
||||
Generated by `lib/erlang/bench_ring.sh` against `sx_server.exe` on the
|
||||
synchronous Erlang-on-SX scheduler.
|
||||
|
||||
| N (processes) | Hops | Wall-clock | Throughput |
|
||||
|---|---|---|---|
|
||||
| 10 | 10 | 907ms | 11 hops/s |
|
||||
| 50 | 50 | 2107ms | 24 hops/s |
|
||||
| 100 | 100 | 3827ms | 26 hops/s |
|
||||
| 500 | 500 | 17004ms | 29 hops/s |
|
||||
| 1000 | 1000 | 29832ms | 34 hops/s |
|
||||
|
||||
(Each `Nm` row spawns N processes connected in a ring and passes a
|
||||
single token N hops total — i.e. the token completes one full lap.)
|
||||
|
||||
## Status of the 1M-process target
|
||||
|
||||
Phase 3's stretch goal in `plans/erlang-on-sx.md` is a million-process
|
||||
ring benchmark. **That target is not met** in the current synchronous
|
||||
scheduler; extrapolating from the table above, 1M hops would take
|
||||
~30 000 s. Correctness is fine — the program runs at every measured
|
||||
size — but throughput is bound by per-hop overhead.
|
||||
|
||||
Per-hop cost is dominated by:
|
||||
- `er-env-copy` per fun clause attempt (whole-dict copy each time)
|
||||
- `call/cc` capture + `raise`/`guard` unwind on every `receive`
|
||||
- `er-q-delete-at!` rebuilds the mailbox backing list on every match
|
||||
- `dict-set!`/`dict-has?` lookups in the global processes table
|
||||
|
||||
To reach 1M-process throughput in this architecture would need at
|
||||
least: persistent (path-copying) envs, an inline scheduler that
|
||||
doesn't call/cc on the common path (msg-already-in-mailbox), and a
|
||||
linked-list mailbox. None of those are in scope for the Phase 3
|
||||
checkbox — captured here as the floor we're starting from.
|
||||
@@ -1,153 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Erlang-on-SX conformance runner.
|
||||
#
|
||||
# Loads every erlang test suite via the epoch protocol, collects
|
||||
# pass/fail counts, and writes lib/erlang/scoreboard.json + .md.
|
||||
#
|
||||
# Usage:
|
||||
# bash lib/erlang/conformance.sh # run all suites
|
||||
# bash lib/erlang/conformance.sh -v # verbose per-suite
|
||||
|
||||
set -uo pipefail
|
||||
cd "$(git rev-parse --show-toplevel)"
|
||||
|
||||
SX_SERVER="${SX_SERVER:-hosts/ocaml/_build/default/bin/sx_server.exe}"
|
||||
if [ ! -x "$SX_SERVER" ]; then
|
||||
SX_SERVER="/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe"
|
||||
fi
|
||||
if [ ! -x "$SX_SERVER" ]; then
|
||||
echo "ERROR: sx_server.exe not found." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERBOSE="${1:-}"
|
||||
TMPFILE=$(mktemp)
|
||||
OUTFILE=$(mktemp)
|
||||
trap "rm -f $TMPFILE $OUTFILE" EXIT
|
||||
|
||||
# Each suite: name | counter pass | counter total
|
||||
SUITES=(
|
||||
"tokenize|er-test-pass|er-test-count"
|
||||
"parse|er-parse-test-pass|er-parse-test-count"
|
||||
"eval|er-eval-test-pass|er-eval-test-count"
|
||||
"runtime|er-rt-test-pass|er-rt-test-count"
|
||||
"ring|er-ring-test-pass|er-ring-test-count"
|
||||
"ping-pong|er-pp-test-pass|er-pp-test-count"
|
||||
"bank|er-bank-test-pass|er-bank-test-count"
|
||||
"echo|er-echo-test-pass|er-echo-test-count"
|
||||
"fib|er-fib-test-pass|er-fib-test-count"
|
||||
)
|
||||
|
||||
cat > "$TMPFILE" << 'EPOCHS'
|
||||
(epoch 1)
|
||||
(load "lib/erlang/tokenizer.sx")
|
||||
(load "lib/erlang/parser.sx")
|
||||
(load "lib/erlang/parser-core.sx")
|
||||
(load "lib/erlang/parser-expr.sx")
|
||||
(load "lib/erlang/parser-module.sx")
|
||||
(load "lib/erlang/transpile.sx")
|
||||
(load "lib/erlang/runtime.sx")
|
||||
(load "lib/erlang/tests/tokenize.sx")
|
||||
(load "lib/erlang/tests/parse.sx")
|
||||
(load "lib/erlang/tests/eval.sx")
|
||||
(load "lib/erlang/tests/runtime.sx")
|
||||
(load "lib/erlang/tests/programs/ring.sx")
|
||||
(load "lib/erlang/tests/programs/ping_pong.sx")
|
||||
(load "lib/erlang/tests/programs/bank.sx")
|
||||
(load "lib/erlang/tests/programs/echo.sx")
|
||||
(load "lib/erlang/tests/programs/fib_server.sx")
|
||||
(epoch 100)
|
||||
(eval "(list er-test-pass er-test-count)")
|
||||
(epoch 101)
|
||||
(eval "(list er-parse-test-pass er-parse-test-count)")
|
||||
(epoch 102)
|
||||
(eval "(list er-eval-test-pass er-eval-test-count)")
|
||||
(epoch 103)
|
||||
(eval "(list er-rt-test-pass er-rt-test-count)")
|
||||
(epoch 104)
|
||||
(eval "(list er-ring-test-pass er-ring-test-count)")
|
||||
(epoch 105)
|
||||
(eval "(list er-pp-test-pass er-pp-test-count)")
|
||||
(epoch 106)
|
||||
(eval "(list er-bank-test-pass er-bank-test-count)")
|
||||
(epoch 107)
|
||||
(eval "(list er-echo-test-pass er-echo-test-count)")
|
||||
(epoch 108)
|
||||
(eval "(list er-fib-test-pass er-fib-test-count)")
|
||||
EPOCHS
|
||||
|
||||
timeout 120 "$SX_SERVER" < "$TMPFILE" > "$OUTFILE" 2>&1
|
||||
|
||||
# Parse "(N M)" from the line after each "(ok-len <epoch> ...)" marker.
|
||||
parse_pair() {
|
||||
local epoch="$1"
|
||||
local line
|
||||
line=$(grep -A1 "^(ok-len $epoch " "$OUTFILE" | tail -1)
|
||||
echo "$line" | sed -E 's/[()]//g'
|
||||
}
|
||||
|
||||
TOTAL_PASS=0
|
||||
TOTAL_COUNT=0
|
||||
JSON_SUITES=""
|
||||
MD_ROWS=""
|
||||
|
||||
idx=0
|
||||
for entry in "${SUITES[@]}"; do
|
||||
name="${entry%%|*}"
|
||||
epoch=$((100 + idx))
|
||||
pair=$(parse_pair "$epoch")
|
||||
pass=$(echo "$pair" | awk '{print $1}')
|
||||
count=$(echo "$pair" | awk '{print $2}')
|
||||
if [ -z "$pass" ] || [ -z "$count" ]; then
|
||||
pass=0
|
||||
count=0
|
||||
fi
|
||||
TOTAL_PASS=$((TOTAL_PASS + pass))
|
||||
TOTAL_COUNT=$((TOTAL_COUNT + count))
|
||||
status="ok"
|
||||
marker="✅"
|
||||
if [ "$pass" != "$count" ]; then
|
||||
status="fail"
|
||||
marker="❌"
|
||||
fi
|
||||
if [ "$VERBOSE" = "-v" ]; then
|
||||
printf " %-12s %s/%s\n" "$name" "$pass" "$count"
|
||||
fi
|
||||
if [ -n "$JSON_SUITES" ]; then JSON_SUITES+=","; fi
|
||||
JSON_SUITES+=$'\n '
|
||||
JSON_SUITES+="{\"name\":\"$name\",\"pass\":$pass,\"total\":$count,\"status\":\"$status\"}"
|
||||
MD_ROWS+="| $marker | $name | $pass | $count |"$'\n'
|
||||
idx=$((idx + 1))
|
||||
done
|
||||
|
||||
printf '\nErlang-on-SX conformance: %d / %d\n' "$TOTAL_PASS" "$TOTAL_COUNT"
|
||||
|
||||
# scoreboard.json
|
||||
cat > lib/erlang/scoreboard.json <<JSON
|
||||
{
|
||||
"language": "erlang",
|
||||
"total_pass": $TOTAL_PASS,
|
||||
"total": $TOTAL_COUNT,
|
||||
"suites": [$JSON_SUITES
|
||||
]
|
||||
}
|
||||
JSON
|
||||
|
||||
# scoreboard.md
|
||||
cat > lib/erlang/scoreboard.md <<MD
|
||||
# Erlang-on-SX Scoreboard
|
||||
|
||||
**Total: ${TOTAL_PASS} / ${TOTAL_COUNT} tests passing**
|
||||
|
||||
| | Suite | Pass | Total |
|
||||
|---|---|---|---|
|
||||
$MD_ROWS
|
||||
|
||||
Generated by \`lib/erlang/conformance.sh\`.
|
||||
MD
|
||||
|
||||
if [ "$TOTAL_PASS" -eq "$TOTAL_COUNT" ]; then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
@@ -237,8 +237,6 @@
|
||||
(er-parse-fun-expr st)
|
||||
(er-is? st "keyword" "try")
|
||||
(er-parse-try st)
|
||||
(er-is? st "punct" "<<")
|
||||
(er-parse-binary st)
|
||||
:else (error
|
||||
(str
|
||||
"Erlang parse: unexpected "
|
||||
@@ -283,56 +281,12 @@
|
||||
(fn
|
||||
(st)
|
||||
(er-expect! st "punct" "[")
|
||||
(cond
|
||||
(if
|
||||
(er-is? st "punct" "]")
|
||||
(do (er-advance! st) {:type "nil"})
|
||||
:else (let
|
||||
((first (er-parse-expr-prec st 0)))
|
||||
(cond
|
||||
(er-is? st "punct" "||") (er-parse-list-comp st first)
|
||||
:else (er-parse-list-tail st (list first)))))))
|
||||
|
||||
(define
|
||||
er-parse-list-comp
|
||||
(fn
|
||||
(st head)
|
||||
(er-advance! st)
|
||||
(let
|
||||
((quals (list (er-parse-lc-qualifier st))))
|
||||
(er-parse-list-comp-tail st head quals))))
|
||||
|
||||
(define
|
||||
er-parse-list-comp-tail
|
||||
(fn
|
||||
(st head quals)
|
||||
(cond
|
||||
(er-is? st "punct" ",")
|
||||
(do
|
||||
(er-advance! st)
|
||||
(append! quals (er-parse-lc-qualifier st))
|
||||
(er-parse-list-comp-tail st head quals))
|
||||
(er-is? st "punct" "]")
|
||||
(do (er-advance! st) {:head head :qualifiers quals :type "lc"})
|
||||
:else (error
|
||||
(str
|
||||
"Erlang parse: expected ',' or ']' in list comprehension, got '"
|
||||
(er-cur-value st)
|
||||
"'")))))
|
||||
|
||||
(define
|
||||
er-parse-lc-qualifier
|
||||
(fn
|
||||
(st)
|
||||
(let
|
||||
((e (er-parse-expr-prec st 0)))
|
||||
(cond
|
||||
(er-is? st "punct" "<-")
|
||||
(do
|
||||
(er-advance! st)
|
||||
(let
|
||||
((source (er-parse-expr-prec st 0)))
|
||||
{:kind "gen" :pattern e :source source}))
|
||||
:else {:kind "filter" :expr e}))))
|
||||
(let
|
||||
((elems (list (er-parse-expr-prec st 0))))
|
||||
(er-parse-list-tail st elems)))))
|
||||
|
||||
(define
|
||||
er-parse-list-tail
|
||||
@@ -578,63 +532,3 @@
|
||||
((guards (if (er-is? st "keyword" "when") (do (er-advance! st) (er-parse-guards st)) (list))))
|
||||
(er-expect! st "punct" "->")
|
||||
(let ((body (er-parse-body st))) {:pattern pat :body body :class klass :guards guards}))))))
|
||||
|
||||
;; ── binary literals / patterns ────────────────────────────────
|
||||
;; `<< [Seg {, Seg}] >>` where Seg = Value [: Size] [/ Spec]. Size is
|
||||
;; a literal integer (multiple of 8 supported); Spec is `integer`
|
||||
;; (default) or `binary` (rest-of-binary tail). Sufficient for the
|
||||
;; common `<<A:8, B:16, Rest/binary>>` patterns.
|
||||
(define
|
||||
er-parse-binary
|
||||
(fn
|
||||
(st)
|
||||
(er-expect! st "punct" "<<")
|
||||
(cond
|
||||
(er-is? st "punct" ">>")
|
||||
(do (er-advance! st) {:segments (list) :type "binary"})
|
||||
:else (let
|
||||
((segs (list (er-parse-binary-segment st))))
|
||||
(er-parse-binary-tail st segs)))))
|
||||
|
||||
(define
|
||||
er-parse-binary-tail
|
||||
(fn
|
||||
(st segs)
|
||||
(cond
|
||||
(er-is? st "punct" ",")
|
||||
(do
|
||||
(er-advance! st)
|
||||
(append! segs (er-parse-binary-segment st))
|
||||
(er-parse-binary-tail st segs))
|
||||
(er-is? st "punct" ">>")
|
||||
(do (er-advance! st) {:segments segs :type "binary"})
|
||||
:else (error
|
||||
(str
|
||||
"Erlang parse: expected ',' or '>>' in binary, got '"
|
||||
(er-cur-value st)
|
||||
"'")))))
|
||||
|
||||
(define
|
||||
er-parse-binary-segment
|
||||
(fn
|
||||
(st)
|
||||
;; Use `er-parse-primary` for the value so a leading `:` falls
|
||||
;; through to the segment's size suffix instead of being eaten
|
||||
;; by `er-parse-postfix-loop` as a `Mod:Fun` remote call.
|
||||
(let
|
||||
((v (er-parse-primary st)))
|
||||
(let
|
||||
((size (cond
|
||||
(er-is? st "punct" ":")
|
||||
(do (er-advance! st) (er-parse-primary st))
|
||||
:else nil))
|
||||
(spec (cond
|
||||
(er-is? st "op" "/")
|
||||
(do
|
||||
(er-advance! st)
|
||||
(let
|
||||
((tok (er-cur st)))
|
||||
(er-advance! st)
|
||||
(get tok :value)))
|
||||
:else "integer")))
|
||||
{:size size :spec spec :value v}))))
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"language": "erlang",
|
||||
"total_pass": 530,
|
||||
"total": 530,
|
||||
"suites": [
|
||||
{"name":"tokenize","pass":62,"total":62,"status":"ok"},
|
||||
{"name":"parse","pass":52,"total":52,"status":"ok"},
|
||||
{"name":"eval","pass":346,"total":346,"status":"ok"},
|
||||
{"name":"runtime","pass":39,"total":39,"status":"ok"},
|
||||
{"name":"ring","pass":4,"total":4,"status":"ok"},
|
||||
{"name":"ping-pong","pass":4,"total":4,"status":"ok"},
|
||||
{"name":"bank","pass":8,"total":8,"status":"ok"},
|
||||
{"name":"echo","pass":7,"total":7,"status":"ok"},
|
||||
{"name":"fib","pass":8,"total":8,"status":"ok"}
|
||||
]
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
# Erlang-on-SX Scoreboard
|
||||
|
||||
**Total: 530 / 530 tests passing**
|
||||
|
||||
| | Suite | Pass | Total |
|
||||
|---|---|---|---|
|
||||
| ✅ | tokenize | 62 | 62 |
|
||||
| ✅ | parse | 52 | 52 |
|
||||
| ✅ | eval | 346 | 346 |
|
||||
| ✅ | runtime | 39 | 39 |
|
||||
| ✅ | ring | 4 | 4 |
|
||||
| ✅ | ping-pong | 4 | 4 |
|
||||
| ✅ | bank | 8 | 8 |
|
||||
| ✅ | echo | 7 | 7 |
|
||||
| ✅ | fib | 8 | 8 |
|
||||
|
||||
|
||||
Generated by `lib/erlang/conformance.sh`.
|
||||
@@ -1,260 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# lib/erlang/test.sh — smoke-test the Erlang runtime layer.
|
||||
# Uses sx_server.exe epoch protocol.
|
||||
#
|
||||
# Usage:
|
||||
# bash lib/erlang/test.sh
|
||||
# bash lib/erlang/test.sh -v
|
||||
|
||||
set -uo pipefail
|
||||
cd "$(git rev-parse --show-toplevel)"
|
||||
|
||||
SX_SERVER="${SX_SERVER:-hosts/ocaml/_build/default/bin/sx_server.exe}"
|
||||
if [ ! -x "$SX_SERVER" ]; then
|
||||
SX_SERVER="/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe"
|
||||
fi
|
||||
if [ ! -x "$SX_SERVER" ]; then
|
||||
echo "ERROR: sx_server.exe not found. Run: cd hosts/ocaml && dune build"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERBOSE="${1:-}"
|
||||
PASS=0; FAIL=0; ERRORS=""
|
||||
TMPFILE=$(mktemp); trap "rm -f $TMPFILE" EXIT
|
||||
|
||||
cat > "$TMPFILE" << 'EPOCHS'
|
||||
(epoch 1)
|
||||
(load "lib/erlang/runtime.sx")
|
||||
|
||||
;; --- Numeric tower ---
|
||||
(epoch 10)
|
||||
(eval "(er-is-integer? 42)")
|
||||
(epoch 11)
|
||||
(eval "(er-is-integer? 3.14)")
|
||||
(epoch 12)
|
||||
(eval "(er-is-float? 3.14)")
|
||||
(epoch 13)
|
||||
(eval "(er-is-float? 42)")
|
||||
(epoch 14)
|
||||
(eval "(er-is-number? 42)")
|
||||
(epoch 15)
|
||||
(eval "(er-is-number? 3.14)")
|
||||
(epoch 16)
|
||||
(eval "(er-float 5)")
|
||||
(epoch 17)
|
||||
(eval "(er-trunc 3.9)")
|
||||
(epoch 18)
|
||||
(eval "(er-round 3.5)")
|
||||
(epoch 19)
|
||||
(eval "(er-abs -7)")
|
||||
(epoch 20)
|
||||
(eval "(er-max 3 7)")
|
||||
(epoch 21)
|
||||
(eval "(er-min 3 7)")
|
||||
|
||||
;; --- div + rem ---
|
||||
(epoch 30)
|
||||
(eval "(er-div 10 3)")
|
||||
(epoch 31)
|
||||
(eval "(er-div -10 3)")
|
||||
(epoch 32)
|
||||
(eval "(er-rem 10 3)")
|
||||
(epoch 33)
|
||||
(eval "(er-rem -10 3)")
|
||||
(epoch 34)
|
||||
(eval "(er-gcd 12 8)")
|
||||
|
||||
;; --- Bitwise ---
|
||||
(epoch 40)
|
||||
(eval "(er-band 12 10)")
|
||||
(epoch 41)
|
||||
(eval "(er-bor 12 10)")
|
||||
(epoch 42)
|
||||
(eval "(er-bxor 12 10)")
|
||||
(epoch 43)
|
||||
(eval "(er-bnot 0)")
|
||||
(epoch 44)
|
||||
(eval "(er-bsl 1 4)")
|
||||
(epoch 45)
|
||||
(eval "(er-bsr 16 2)")
|
||||
|
||||
;; --- Sets ---
|
||||
(epoch 50)
|
||||
(eval "(er-sets-is-set? (er-sets-new))")
|
||||
(epoch 51)
|
||||
(eval "(let ((s (er-sets-new))) (do (er-sets-add-element s 1) (er-sets-is-element s 1)))")
|
||||
(epoch 52)
|
||||
(eval "(er-sets-is-element (er-sets-new) 42)")
|
||||
(epoch 53)
|
||||
(eval "(er-sets-is-element (er-sets-from-list (list 1 2 3)) 2)")
|
||||
(epoch 54)
|
||||
(eval "(er-sets-size (er-sets-from-list (list 1 2 3)))")
|
||||
(epoch 55)
|
||||
(eval "(len (er-sets-to-list (er-sets-from-list (list 1 2 3))))")
|
||||
|
||||
;; --- Regexp ---
|
||||
(epoch 60)
|
||||
(eval "(not (= (er-re-run \"hello\" \"ll\") nil))")
|
||||
(epoch 61)
|
||||
(eval "(= (er-re-run \"hello\" \"xyz\") nil)")
|
||||
(epoch 62)
|
||||
(eval "(get (er-re-run \"hello\" \"ll\") :match)")
|
||||
(epoch 63)
|
||||
(eval "(er-re-replace \"hello\" \"l\" \"r\")")
|
||||
(epoch 64)
|
||||
(eval "(er-re-replace-all \"hello\" \"l\" \"r\")")
|
||||
(epoch 65)
|
||||
(eval "(er-re-match-groups (er-re-run \"hello world\" \"(\\w+)\\s+(\\w+)\"))")
|
||||
(epoch 66)
|
||||
(eval "(len (er-re-split \"a,b,c\" \",\"))")
|
||||
|
||||
;; --- List BIFs ---
|
||||
(epoch 70)
|
||||
(eval "(er-hd (list 1 2 3))")
|
||||
(epoch 71)
|
||||
(eval "(er-tl (list 1 2 3))")
|
||||
(epoch 72)
|
||||
(eval "(er-length (list 1 2 3))")
|
||||
(epoch 73)
|
||||
(eval "(er-lists-member 2 (list 1 2 3))")
|
||||
(epoch 74)
|
||||
(eval "(er-lists-member 9 (list 1 2 3))")
|
||||
(epoch 75)
|
||||
(eval "(er-lists-reverse (list 1 2 3))")
|
||||
(epoch 76)
|
||||
(eval "(er-lists-nth 2 (list 10 20 30))")
|
||||
(epoch 77)
|
||||
(eval "(er-lists-foldl + 0 (list 1 2 3 4 5))")
|
||||
(epoch 78)
|
||||
(eval "(er-lists-seq 1 5)")
|
||||
(epoch 79)
|
||||
(eval "(er-lists-flatten (list 1 (list 2 3) (list 4 (list 5))))")
|
||||
|
||||
;; --- Type conversions ---
|
||||
(epoch 80)
|
||||
(eval "(er-integer-to-list 42)")
|
||||
(epoch 81)
|
||||
(eval "(er-list-to-integer \"42\")")
|
||||
(epoch 82)
|
||||
(eval "(er-integer-to-list-radix 255 16)")
|
||||
(epoch 83)
|
||||
(eval "(er-atom-to-list (make-symbol \"hello\"))")
|
||||
(epoch 84)
|
||||
(eval "(= (type-of (er-list-to-atom \"foo\")) \"symbol\")")
|
||||
|
||||
;; --- ok/error tuples ---
|
||||
(epoch 90)
|
||||
(eval "(er-is-ok? (er-ok 42))")
|
||||
(epoch 91)
|
||||
(eval "(er-is-error? (er-error \"reason\"))")
|
||||
(epoch 92)
|
||||
(eval "(er-unwrap (er-ok 42))")
|
||||
(epoch 93)
|
||||
(eval "(er-is-ok? (er-error \"bad\"))")
|
||||
|
||||
EPOCHS
|
||||
|
||||
OUTPUT=$(timeout 30 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
|
||||
|
||||
check() {
|
||||
local epoch="$1" desc="$2" expected="$3"
|
||||
local actual
|
||||
actual=$(echo "$OUTPUT" | grep -A1 "^(ok-len $epoch " | tail -1 || true)
|
||||
if echo "$actual" | grep -q "^(ok-len"; then actual=""; fi
|
||||
if [ -z "$actual" ]; then
|
||||
actual=$(echo "$OUTPUT" | grep "^(ok $epoch " | head -1 || true)
|
||||
fi
|
||||
if [ -z "$actual" ]; then
|
||||
actual=$(echo "$OUTPUT" | grep "^(error $epoch " | head -1 || true)
|
||||
fi
|
||||
[ -z "$actual" ] && actual="<no output for epoch $epoch>"
|
||||
|
||||
if echo "$actual" | grep -qF -- "$expected"; then
|
||||
PASS=$((PASS+1))
|
||||
[ "$VERBOSE" = "-v" ] && echo " ok $desc"
|
||||
else
|
||||
FAIL=$((FAIL+1))
|
||||
ERRORS+=" FAIL [$desc] (epoch $epoch) expected: $expected | actual: $actual
|
||||
"
|
||||
fi
|
||||
}
|
||||
|
||||
# Numeric tower
|
||||
check 10 "is-integer? 42" "true"
|
||||
check 11 "is-integer? float" "false"
|
||||
check 12 "is-float? 3.14" "true"
|
||||
check 13 "is-float? int" "false"
|
||||
check 14 "is-number? int" "true"
|
||||
check 15 "is-number? float" "true"
|
||||
check 16 "float 5" "5"
|
||||
check 17 "trunc 3.9" "3"
|
||||
check 18 "round 3.5" "4"
|
||||
check 19 "abs -7" "7"
|
||||
check 20 "max 3 7" "7"
|
||||
check 21 "min 3 7" "3"
|
||||
|
||||
# div + rem
|
||||
check 30 "div 10 3" "3"
|
||||
check 31 "div -10 3" "-3"
|
||||
check 32 "rem 10 3" "1"
|
||||
check 33 "rem -10 3" "-1"
|
||||
check 34 "gcd 12 8" "4"
|
||||
|
||||
# Bitwise
|
||||
check 40 "band 12 10" "8"
|
||||
check 41 "bor 12 10" "14"
|
||||
check 42 "bxor 12 10" "6"
|
||||
check 43 "bnot 0" "-1"
|
||||
check 44 "bsl 1 4" "16"
|
||||
check 45 "bsr 16 2" "4"
|
||||
|
||||
# Sets
|
||||
check 50 "sets-new is-set?" "true"
|
||||
check 51 "sets add+member" "true"
|
||||
check 52 "member empty" "false"
|
||||
check 53 "from-list member" "true"
|
||||
check 54 "sets-size" "3"
|
||||
check 55 "sets-to-list len" "3"
|
||||
|
||||
# Regexp
|
||||
check 60 "re-run match" "true"
|
||||
check 61 "re-run no match" "true"
|
||||
check 62 "re-run match text" '"ll"'
|
||||
check 63 "re-replace first" '"herlo"'
|
||||
check 64 "re-replace-all" '"herro"'
|
||||
check 65 "re-match-groups" '"hello"'
|
||||
check 66 "re-split count" "3"
|
||||
|
||||
# List BIFs
|
||||
check 70 "hd" "1"
|
||||
check 71 "tl" "(2 3)"
|
||||
check 72 "length" "3"
|
||||
check 73 "member hit" "true"
|
||||
check 74 "member miss" "false"
|
||||
check 75 "reverse" "(3 2 1)"
|
||||
check 76 "nth 2" "20"
|
||||
check 77 "foldl sum" "15"
|
||||
check 78 "seq 1..5" "(1 2 3 4 5)"
|
||||
check 79 "flatten" "(1 2 3 4 5)"
|
||||
|
||||
# Type conversions
|
||||
check 80 "integer-to-list" '"42"'
|
||||
check 81 "list-to-integer" "42"
|
||||
check 82 "integer-to-list hex" '"ff"'
|
||||
check 83 "atom-to-list" '"hello"'
|
||||
check 84 "list-to-atom" "true"
|
||||
|
||||
# ok/error
|
||||
check 90 "ok? ok-tuple" "true"
|
||||
check 91 "error? error-tuple" "true"
|
||||
check 92 "unwrap ok" "42"
|
||||
check 93 "ok? error-tuple" "false"
|
||||
|
||||
TOTAL=$((PASS+FAIL))
|
||||
if [ $FAIL -eq 0 ]; then
|
||||
echo "ok $PASS/$TOTAL lib/erlang tests passed"
|
||||
else
|
||||
echo "FAIL $PASS/$TOTAL passed, $FAIL failed:"
|
||||
echo "$ERRORS"
|
||||
fi
|
||||
[ $FAIL -eq 0 ]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,159 +0,0 @@
|
||||
;; Bank account server — stateful process, balance threaded through
|
||||
;; recursive loop. Handles {deposit, Amt, From}, {withdraw, Amt, From},
|
||||
;; {balance, From}, stop. Tests stateful process patterns.
|
||||
|
||||
(define er-bank-test-count 0)
|
||||
(define er-bank-test-pass 0)
|
||||
(define er-bank-test-fails (list))
|
||||
|
||||
(define
|
||||
er-bank-test
|
||||
(fn
|
||||
(name actual expected)
|
||||
(set! er-bank-test-count (+ er-bank-test-count 1))
|
||||
(if
|
||||
(= actual expected)
|
||||
(set! er-bank-test-pass (+ er-bank-test-pass 1))
|
||||
(append! er-bank-test-fails {:actual actual :expected expected :name name}))))
|
||||
|
||||
(define bank-ev erlang-eval-ast)
|
||||
|
||||
;; Server fun shared by all tests — threaded via the program string.
|
||||
(define
|
||||
er-bank-server-src
|
||||
"Server = fun (Balance) ->
|
||||
receive
|
||||
{deposit, Amt, From} -> From ! ok, Server(Balance + Amt);
|
||||
{withdraw, Amt, From} ->
|
||||
if Amt > Balance -> From ! insufficient, Server(Balance);
|
||||
true -> From ! ok, Server(Balance - Amt)
|
||||
end;
|
||||
{balance, From} -> From ! Balance, Server(Balance);
|
||||
stop -> ok
|
||||
end
|
||||
end")
|
||||
|
||||
;; Open account, deposit, check balance.
|
||||
(er-bank-test
|
||||
"deposit 100 -> balance 100"
|
||||
(bank-ev
|
||||
(str
|
||||
er-bank-server-src
|
||||
", Me = self(),
|
||||
Bank = spawn(fun () -> Server(0) end),
|
||||
Bank ! {deposit, 100, Me},
|
||||
receive ok -> ok end,
|
||||
Bank ! {balance, Me},
|
||||
receive B -> Bank ! stop, B end"))
|
||||
100)
|
||||
|
||||
;; Multiple deposits accumulate.
|
||||
(er-bank-test
|
||||
"deposits accumulate"
|
||||
(bank-ev
|
||||
(str
|
||||
er-bank-server-src
|
||||
", Me = self(),
|
||||
Bank = spawn(fun () -> Server(0) end),
|
||||
Bank ! {deposit, 50, Me}, receive ok -> ok end,
|
||||
Bank ! {deposit, 25, Me}, receive ok -> ok end,
|
||||
Bank ! {deposit, 10, Me}, receive ok -> ok end,
|
||||
Bank ! {balance, Me},
|
||||
receive B -> Bank ! stop, B end"))
|
||||
85)
|
||||
|
||||
;; Withdraw within balance succeeds; insufficient gets rejected.
|
||||
(er-bank-test
|
||||
"withdraw within balance"
|
||||
(bank-ev
|
||||
(str
|
||||
er-bank-server-src
|
||||
", Me = self(),
|
||||
Bank = spawn(fun () -> Server(100) end),
|
||||
Bank ! {withdraw, 30, Me}, receive ok -> ok end,
|
||||
Bank ! {balance, Me},
|
||||
receive B -> Bank ! stop, B end"))
|
||||
70)
|
||||
|
||||
(er-bank-test
|
||||
"withdraw insufficient"
|
||||
(get
|
||||
(bank-ev
|
||||
(str
|
||||
er-bank-server-src
|
||||
", Me = self(),
|
||||
Bank = spawn(fun () -> Server(20) end),
|
||||
Bank ! {withdraw, 100, Me},
|
||||
receive R -> Bank ! stop, R end"))
|
||||
:name)
|
||||
"insufficient")
|
||||
|
||||
;; State preserved across an insufficient withdrawal.
|
||||
(er-bank-test
|
||||
"state preserved on rejection"
|
||||
(bank-ev
|
||||
(str
|
||||
er-bank-server-src
|
||||
", Me = self(),
|
||||
Bank = spawn(fun () -> Server(50) end),
|
||||
Bank ! {withdraw, 1000, Me}, receive _ -> ok end,
|
||||
Bank ! {balance, Me},
|
||||
receive B -> Bank ! stop, B end"))
|
||||
50)
|
||||
|
||||
;; Mixed deposits and withdrawals.
|
||||
(er-bank-test
|
||||
"mixed transactions"
|
||||
(bank-ev
|
||||
(str
|
||||
er-bank-server-src
|
||||
", Me = self(),
|
||||
Bank = spawn(fun () -> Server(100) end),
|
||||
Bank ! {deposit, 50, Me}, receive ok -> ok end,
|
||||
Bank ! {withdraw, 30, Me}, receive ok -> ok end,
|
||||
Bank ! {deposit, 10, Me}, receive ok -> ok end,
|
||||
Bank ! {withdraw, 5, Me}, receive ok -> ok end,
|
||||
Bank ! {balance, Me},
|
||||
receive B -> Bank ! stop, B end"))
|
||||
125)
|
||||
|
||||
;; Server.stop terminates the bank cleanly — main can verify by
|
||||
;; sending stop and then exiting normally.
|
||||
(er-bank-test
|
||||
"server stops cleanly"
|
||||
(get
|
||||
(bank-ev
|
||||
(str
|
||||
er-bank-server-src
|
||||
", Me = self(),
|
||||
Bank = spawn(fun () -> Server(0) end),
|
||||
Bank ! stop,
|
||||
done"))
|
||||
:name)
|
||||
"done")
|
||||
|
||||
;; Two clients sharing one bank — interleaved transactions.
|
||||
(er-bank-test
|
||||
"two clients share bank"
|
||||
(bank-ev
|
||||
(str
|
||||
er-bank-server-src
|
||||
", Me = self(),
|
||||
Bank = spawn(fun () -> Server(0) end),
|
||||
Client = fun (Amt) ->
|
||||
spawn(fun () ->
|
||||
Bank ! {deposit, Amt, self()},
|
||||
receive ok -> Me ! deposited end
|
||||
end)
|
||||
end,
|
||||
Client(40),
|
||||
Client(60),
|
||||
receive deposited -> ok end,
|
||||
receive deposited -> ok end,
|
||||
Bank ! {balance, Me},
|
||||
receive B -> Bank ! stop, B end"))
|
||||
100)
|
||||
|
||||
(define
|
||||
er-bank-test-summary
|
||||
(str "bank " er-bank-test-pass "/" er-bank-test-count))
|
||||
@@ -1,140 +0,0 @@
|
||||
;; Echo server — minimal classic Erlang server. Receives {From, Msg}
|
||||
;; and sends Msg back to From, then loops. `stop` ends the server.
|
||||
|
||||
(define er-echo-test-count 0)
|
||||
(define er-echo-test-pass 0)
|
||||
(define er-echo-test-fails (list))
|
||||
|
||||
(define
|
||||
er-echo-test
|
||||
(fn
|
||||
(name actual expected)
|
||||
(set! er-echo-test-count (+ er-echo-test-count 1))
|
||||
(if
|
||||
(= actual expected)
|
||||
(set! er-echo-test-pass (+ er-echo-test-pass 1))
|
||||
(append! er-echo-test-fails {:actual actual :expected expected :name name}))))
|
||||
|
||||
(define echo-ev erlang-eval-ast)
|
||||
|
||||
(define
|
||||
er-echo-server-src
|
||||
"EchoSrv = fun () ->
|
||||
Loop = fun () ->
|
||||
receive
|
||||
{From, Msg} -> From ! Msg, Loop();
|
||||
stop -> ok
|
||||
end
|
||||
end,
|
||||
Loop()
|
||||
end")
|
||||
|
||||
;; Single round-trip with an atom.
|
||||
(er-echo-test
|
||||
"atom round-trip"
|
||||
(get
|
||||
(echo-ev
|
||||
(str
|
||||
er-echo-server-src
|
||||
", Me = self(),
|
||||
Echo = spawn(EchoSrv),
|
||||
Echo ! {Me, hello},
|
||||
receive R -> Echo ! stop, R end"))
|
||||
:name)
|
||||
"hello")
|
||||
|
||||
;; Number round-trip.
|
||||
(er-echo-test
|
||||
"number round-trip"
|
||||
(echo-ev
|
||||
(str
|
||||
er-echo-server-src
|
||||
", Me = self(),
|
||||
Echo = spawn(EchoSrv),
|
||||
Echo ! {Me, 42},
|
||||
receive R -> Echo ! stop, R end"))
|
||||
42)
|
||||
|
||||
;; Tuple round-trip — pattern-match the reply to extract V.
|
||||
(er-echo-test
|
||||
"tuple round-trip"
|
||||
(echo-ev
|
||||
(str
|
||||
er-echo-server-src
|
||||
", Me = self(),
|
||||
Echo = spawn(EchoSrv),
|
||||
Echo ! {Me, {ok, 7}},
|
||||
receive {ok, V} -> Echo ! stop, V end"))
|
||||
7)
|
||||
|
||||
;; List round-trip.
|
||||
(er-echo-test
|
||||
"list round-trip"
|
||||
(echo-ev
|
||||
(str
|
||||
er-echo-server-src
|
||||
", Me = self(),
|
||||
Echo = spawn(EchoSrv),
|
||||
Echo ! {Me, [1, 2, 3]},
|
||||
receive [H | _] -> Echo ! stop, H end"))
|
||||
1)
|
||||
|
||||
;; Multiple sequential round-trips.
|
||||
(er-echo-test
|
||||
"three round-trips"
|
||||
(echo-ev
|
||||
(str
|
||||
er-echo-server-src
|
||||
", Me = self(),
|
||||
Echo = spawn(EchoSrv),
|
||||
Echo ! {Me, 10}, A = receive Ra -> Ra end,
|
||||
Echo ! {Me, 20}, B = receive Rb -> Rb end,
|
||||
Echo ! {Me, 30}, C = receive Rc -> Rc end,
|
||||
Echo ! stop,
|
||||
A + B + C"))
|
||||
60)
|
||||
|
||||
;; Two clients sharing one echo server. Each gets its own reply.
|
||||
(er-echo-test
|
||||
"two clients"
|
||||
(get
|
||||
(echo-ev
|
||||
(str
|
||||
er-echo-server-src
|
||||
", Me = self(),
|
||||
Echo = spawn(EchoSrv),
|
||||
Client = fun (Tag) ->
|
||||
spawn(fun () ->
|
||||
Echo ! {self(), Tag},
|
||||
receive R -> Me ! {got, R} end
|
||||
end)
|
||||
end,
|
||||
Client(a),
|
||||
Client(b),
|
||||
receive {got, _} -> ok end,
|
||||
receive {got, _} -> ok end,
|
||||
Echo ! stop,
|
||||
finished"))
|
||||
:name)
|
||||
"finished")
|
||||
|
||||
;; Echo via io trace — verify each message round-trips through.
|
||||
(er-echo-test
|
||||
"trace 4 messages"
|
||||
(do
|
||||
(er-io-flush!)
|
||||
(echo-ev
|
||||
(str
|
||||
er-echo-server-src
|
||||
", Me = self(),
|
||||
Echo = spawn(EchoSrv),
|
||||
Send = fun (V) -> Echo ! {Me, V}, receive R -> io:format(\"~p \", [R]) end end,
|
||||
Send(1), Send(2), Send(3), Send(4),
|
||||
Echo ! stop,
|
||||
done"))
|
||||
(er-io-buffer-content))
|
||||
"1 2 3 4 ")
|
||||
|
||||
(define
|
||||
er-echo-test-summary
|
||||
(str "echo " er-echo-test-pass "/" er-echo-test-count))
|
||||
@@ -1,152 +0,0 @@
|
||||
;; Fib server — long-lived process that computes fibonacci numbers on
|
||||
;; request. Tests recursive function evaluation inside a server loop.
|
||||
|
||||
(define er-fib-test-count 0)
|
||||
(define er-fib-test-pass 0)
|
||||
(define er-fib-test-fails (list))
|
||||
|
||||
(define
|
||||
er-fib-test
|
||||
(fn
|
||||
(name actual expected)
|
||||
(set! er-fib-test-count (+ er-fib-test-count 1))
|
||||
(if
|
||||
(= actual expected)
|
||||
(set! er-fib-test-pass (+ er-fib-test-pass 1))
|
||||
(append! er-fib-test-fails {:actual actual :expected expected :name name}))))
|
||||
|
||||
(define fib-ev erlang-eval-ast)
|
||||
|
||||
;; Fib + server-loop source. Standalone so each test can chain queries.
|
||||
(define
|
||||
er-fib-server-src
|
||||
"Fib = fun (0) -> 0; (1) -> 1; (N) -> Fib(N-1) + Fib(N-2) end,
|
||||
FibSrv = fun () ->
|
||||
Loop = fun () ->
|
||||
receive
|
||||
{fib, N, From} -> From ! Fib(N), Loop();
|
||||
stop -> ok
|
||||
end
|
||||
end,
|
||||
Loop()
|
||||
end")
|
||||
|
||||
;; Base cases.
|
||||
(er-fib-test
|
||||
"fib(0)"
|
||||
(fib-ev
|
||||
(str
|
||||
er-fib-server-src
|
||||
", Me = self(),
|
||||
Srv = spawn(FibSrv),
|
||||
Srv ! {fib, 0, Me},
|
||||
receive R -> Srv ! stop, R end"))
|
||||
0)
|
||||
|
||||
(er-fib-test
|
||||
"fib(1)"
|
||||
(fib-ev
|
||||
(str
|
||||
er-fib-server-src
|
||||
", Me = self(),
|
||||
Srv = spawn(FibSrv),
|
||||
Srv ! {fib, 1, Me},
|
||||
receive R -> Srv ! stop, R end"))
|
||||
1)
|
||||
|
||||
;; Larger values.
|
||||
(er-fib-test
|
||||
"fib(10) = 55"
|
||||
(fib-ev
|
||||
(str
|
||||
er-fib-server-src
|
||||
", Me = self(),
|
||||
Srv = spawn(FibSrv),
|
||||
Srv ! {fib, 10, Me},
|
||||
receive R -> Srv ! stop, R end"))
|
||||
55)
|
||||
|
||||
(er-fib-test
|
||||
"fib(15) = 610"
|
||||
(fib-ev
|
||||
(str
|
||||
er-fib-server-src
|
||||
", Me = self(),
|
||||
Srv = spawn(FibSrv),
|
||||
Srv ! {fib, 15, Me},
|
||||
receive R -> Srv ! stop, R end"))
|
||||
610)
|
||||
|
||||
;; Multiple sequential queries to one server. Sum to avoid dict-equality.
|
||||
(er-fib-test
|
||||
"sequential fib(5..8) sum"
|
||||
(fib-ev
|
||||
(str
|
||||
er-fib-server-src
|
||||
", Me = self(),
|
||||
Srv = spawn(FibSrv),
|
||||
Srv ! {fib, 5, Me}, A = receive Ra -> Ra end,
|
||||
Srv ! {fib, 6, Me}, B = receive Rb -> Rb end,
|
||||
Srv ! {fib, 7, Me}, C = receive Rc -> Rc end,
|
||||
Srv ! {fib, 8, Me}, D = receive Rd -> Rd end,
|
||||
Srv ! stop,
|
||||
A + B + C + D"))
|
||||
47)
|
||||
|
||||
;; Verify Fib obeys the recurrence — fib(n) = fib(n-1) + fib(n-2).
|
||||
(er-fib-test
|
||||
"fib recurrence at n=12"
|
||||
(fib-ev
|
||||
(str
|
||||
er-fib-server-src
|
||||
", Me = self(),
|
||||
Srv = spawn(FibSrv),
|
||||
Srv ! {fib, 10, Me}, A = receive Ra -> Ra end,
|
||||
Srv ! {fib, 11, Me}, B = receive Rb -> Rb end,
|
||||
Srv ! {fib, 12, Me}, C = receive Rc -> Rc end,
|
||||
Srv ! stop,
|
||||
C - (A + B)"))
|
||||
0)
|
||||
|
||||
;; Two clients each get their own answer; main sums the results.
|
||||
(er-fib-test
|
||||
"two clients sum"
|
||||
(fib-ev
|
||||
(str
|
||||
er-fib-server-src
|
||||
", Me = self(),
|
||||
Srv = spawn(FibSrv),
|
||||
Client = fun (N) ->
|
||||
spawn(fun () ->
|
||||
Srv ! {fib, N, self()},
|
||||
receive R -> Me ! {result, R} end
|
||||
end)
|
||||
end,
|
||||
Client(7),
|
||||
Client(9),
|
||||
{result, A} = receive M1 -> M1 end,
|
||||
{result, B} = receive M2 -> M2 end,
|
||||
Srv ! stop,
|
||||
A + B"))
|
||||
47)
|
||||
|
||||
;; Trace queries via io-buffer.
|
||||
(er-fib-test
|
||||
"trace fib 0..6"
|
||||
(do
|
||||
(er-io-flush!)
|
||||
(fib-ev
|
||||
(str
|
||||
er-fib-server-src
|
||||
", Me = self(),
|
||||
Srv = spawn(FibSrv),
|
||||
Ask = fun (N) -> Srv ! {fib, N, Me}, receive R -> io:format(\"~p \", [R]) end end,
|
||||
Ask(0), Ask(1), Ask(2), Ask(3), Ask(4), Ask(5), Ask(6),
|
||||
Srv ! stop,
|
||||
done"))
|
||||
(er-io-buffer-content))
|
||||
"0 1 1 2 3 5 8 ")
|
||||
|
||||
(define
|
||||
er-fib-test-summary
|
||||
(str "fib " er-fib-test-pass "/" er-fib-test-count))
|
||||
@@ -1,127 +0,0 @@
|
||||
;; Ping-pong program — two processes exchange N messages, then signal
|
||||
;; main via separate `ping_done` / `pong_done` notifications.
|
||||
|
||||
(define er-pp-test-count 0)
|
||||
(define er-pp-test-pass 0)
|
||||
(define er-pp-test-fails (list))
|
||||
|
||||
(define
|
||||
er-pp-test
|
||||
(fn
|
||||
(name actual expected)
|
||||
(set! er-pp-test-count (+ er-pp-test-count 1))
|
||||
(if
|
||||
(= actual expected)
|
||||
(set! er-pp-test-pass (+ er-pp-test-pass 1))
|
||||
(append! er-pp-test-fails {:actual actual :expected expected :name name}))))
|
||||
|
||||
(define pp-ev erlang-eval-ast)
|
||||
|
||||
;; Three rounds of ping-pong, then stop. Main receives ping_done and
|
||||
;; pong_done in arrival order (Ping finishes first because Pong exits
|
||||
;; only after receiving stop).
|
||||
(define
|
||||
er-pp-program
|
||||
"Me = self(),
|
||||
Pong = spawn(fun () ->
|
||||
Loop = fun () ->
|
||||
receive
|
||||
{ping, From} -> From ! pong, Loop();
|
||||
stop -> Me ! pong_done
|
||||
end
|
||||
end,
|
||||
Loop()
|
||||
end),
|
||||
Ping = fun (Target, K) ->
|
||||
if K =:= 0 -> Target ! stop, Me ! ping_done;
|
||||
true -> Target ! {ping, self()}, receive pong -> Ping(Target, K - 1) end
|
||||
end
|
||||
end,
|
||||
spawn(fun () -> Ping(Pong, 3) end),
|
||||
receive ping_done -> ok end,
|
||||
receive pong_done -> both_done end")
|
||||
|
||||
(er-pp-test
|
||||
"ping-pong 3 rounds"
|
||||
(get (pp-ev er-pp-program) :name)
|
||||
"both_done")
|
||||
|
||||
;; Count exchanges via io-buffer — each pong trip prints "p".
|
||||
(er-pp-test
|
||||
"ping-pong 5 rounds trace"
|
||||
(do
|
||||
(er-io-flush!)
|
||||
(pp-ev
|
||||
"Me = self(),
|
||||
Pong = spawn(fun () ->
|
||||
Loop = fun () ->
|
||||
receive
|
||||
{ping, From} -> io:format(\"p\"), From ! pong, Loop();
|
||||
stop -> Me ! pong_done
|
||||
end
|
||||
end,
|
||||
Loop()
|
||||
end),
|
||||
Ping = fun (Target, K) ->
|
||||
if K =:= 0 -> Target ! stop, Me ! ping_done;
|
||||
true -> Target ! {ping, self()}, receive pong -> Ping(Target, K - 1) end
|
||||
end
|
||||
end,
|
||||
spawn(fun () -> Ping(Pong, 5) end),
|
||||
receive ping_done -> ok end,
|
||||
receive pong_done -> ok end")
|
||||
(er-io-buffer-content))
|
||||
"ppppp")
|
||||
|
||||
;; Main → Pong directly (no Ping process). Main plays the ping role.
|
||||
(er-pp-test
|
||||
"main-as-pinger 4 rounds"
|
||||
(pp-ev
|
||||
"Me = self(),
|
||||
Pong = spawn(fun () ->
|
||||
Loop = fun () ->
|
||||
receive
|
||||
{ping, From} -> From ! pong, Loop();
|
||||
stop -> ok
|
||||
end
|
||||
end,
|
||||
Loop()
|
||||
end),
|
||||
Go = fun (K) ->
|
||||
if K =:= 0 -> Pong ! stop, K;
|
||||
true -> Pong ! {ping, Me}, receive pong -> Go(K - 1) end
|
||||
end
|
||||
end,
|
||||
Go(4)")
|
||||
0)
|
||||
|
||||
;; Ensure the processes really interleave — inject an id into each
|
||||
;; ping and check we get them all back via trace (the order is
|
||||
;; deterministic under our sync scheduler).
|
||||
(er-pp-test
|
||||
"ids round-trip"
|
||||
(do
|
||||
(er-io-flush!)
|
||||
(pp-ev
|
||||
"Me = self(),
|
||||
Pong = spawn(fun () ->
|
||||
Loop = fun () ->
|
||||
receive
|
||||
{ping, From, Id} -> From ! {pong, Id}, Loop();
|
||||
stop -> ok
|
||||
end
|
||||
end,
|
||||
Loop()
|
||||
end),
|
||||
Go = fun (K) ->
|
||||
if K =:= 0 -> Pong ! stop, done;
|
||||
true -> Pong ! {ping, Me, K}, receive {pong, RId} -> io:format(\"~p \", [RId]), Go(K - 1) end
|
||||
end
|
||||
end,
|
||||
Go(4)")
|
||||
(er-io-buffer-content))
|
||||
"4 3 2 1 ")
|
||||
|
||||
(define
|
||||
er-pp-test-summary
|
||||
(str "ping-pong " er-pp-test-pass "/" er-pp-test-count))
|
||||
@@ -1,132 +0,0 @@
|
||||
;; Ring program — N processes in a ring, token passes M times.
|
||||
;;
|
||||
;; Each process waits for {setup, Next} so main can tie the knot
|
||||
;; (can't reference a pid before spawning it). Once wired, main
|
||||
;; injects the first token; each process forwards decrementing K
|
||||
;; until it hits 0, at which point it signals `done` to main.
|
||||
|
||||
(define er-ring-test-count 0)
|
||||
(define er-ring-test-pass 0)
|
||||
(define er-ring-test-fails (list))
|
||||
|
||||
(define
|
||||
er-ring-test
|
||||
(fn
|
||||
(name actual expected)
|
||||
(set! er-ring-test-count (+ er-ring-test-count 1))
|
||||
(if
|
||||
(= actual expected)
|
||||
(set! er-ring-test-pass (+ er-ring-test-pass 1))
|
||||
(append! er-ring-test-fails {:actual actual :expected expected :name name}))))
|
||||
|
||||
(define ring-ev erlang-eval-ast)
|
||||
|
||||
(define
|
||||
er-ring-program-3-6
|
||||
"Me = self(),
|
||||
Spawner = fun () ->
|
||||
receive {setup, Next} ->
|
||||
Loop = fun () ->
|
||||
receive
|
||||
{token, 0, Parent} -> Parent ! done;
|
||||
{token, K, Parent} -> Next ! {token, K-1, Parent}, Loop()
|
||||
end
|
||||
end,
|
||||
Loop()
|
||||
end
|
||||
end,
|
||||
P1 = spawn(Spawner),
|
||||
P2 = spawn(Spawner),
|
||||
P3 = spawn(Spawner),
|
||||
P1 ! {setup, P2},
|
||||
P2 ! {setup, P3},
|
||||
P3 ! {setup, P1},
|
||||
P1 ! {token, 5, Me},
|
||||
receive done -> finished end")
|
||||
|
||||
(er-ring-test
|
||||
"ring N=3 M=6"
|
||||
(get (ring-ev er-ring-program-3-6) :name)
|
||||
"finished")
|
||||
|
||||
;; Two-node ring — token bounces twice between P1 and P2.
|
||||
(er-ring-test
|
||||
"ring N=2 M=4"
|
||||
(get (ring-ev
|
||||
"Me = self(),
|
||||
Spawner = fun () ->
|
||||
receive {setup, Next} ->
|
||||
Loop = fun () ->
|
||||
receive
|
||||
{token, 0, Parent} -> Parent ! done;
|
||||
{token, K, Parent} -> Next ! {token, K-1, Parent}, Loop()
|
||||
end
|
||||
end,
|
||||
Loop()
|
||||
end
|
||||
end,
|
||||
P1 = spawn(Spawner),
|
||||
P2 = spawn(Spawner),
|
||||
P1 ! {setup, P2},
|
||||
P2 ! {setup, P1},
|
||||
P1 ! {token, 3, Me},
|
||||
receive done -> done end") :name)
|
||||
"done")
|
||||
|
||||
;; Single-node "ring" — P sends to itself M times.
|
||||
(er-ring-test
|
||||
"ring N=1 M=5"
|
||||
(get (ring-ev
|
||||
"Me = self(),
|
||||
Spawner = fun () ->
|
||||
receive {setup, Next} ->
|
||||
Loop = fun () ->
|
||||
receive
|
||||
{token, 0, Parent} -> Parent ! finished_loop;
|
||||
{token, K, Parent} -> Next ! {token, K-1, Parent}, Loop()
|
||||
end
|
||||
end,
|
||||
Loop()
|
||||
end
|
||||
end,
|
||||
P = spawn(Spawner),
|
||||
P ! {setup, P},
|
||||
P ! {token, 4, Me},
|
||||
receive finished_loop -> ok end") :name)
|
||||
"ok")
|
||||
|
||||
;; Confirm the token really went around — count hops via io-buffer.
|
||||
(er-ring-test
|
||||
"ring N=3 M=9 hop count"
|
||||
(do
|
||||
(er-io-flush!)
|
||||
(ring-ev
|
||||
"Me = self(),
|
||||
Spawner = fun () ->
|
||||
receive {setup, Next} ->
|
||||
Loop = fun () ->
|
||||
receive
|
||||
{token, 0, Parent} -> Parent ! done;
|
||||
{token, K, Parent} ->
|
||||
io:format(\"~p \", [K]),
|
||||
Next ! {token, K-1, Parent},
|
||||
Loop()
|
||||
end
|
||||
end,
|
||||
Loop()
|
||||
end
|
||||
end,
|
||||
P1 = spawn(Spawner),
|
||||
P2 = spawn(Spawner),
|
||||
P3 = spawn(Spawner),
|
||||
P1 ! {setup, P2},
|
||||
P2 ! {setup, P3},
|
||||
P3 ! {setup, P1},
|
||||
P1 ! {token, 8, Me},
|
||||
receive done -> done end")
|
||||
(er-io-buffer-content))
|
||||
"8 7 6 5 4 3 2 1 ")
|
||||
|
||||
(define
|
||||
er-ring-test-summary
|
||||
(str "ring " er-ring-test-pass "/" er-ring-test-count))
|
||||
@@ -1,139 +0,0 @@
|
||||
;; Erlang runtime tests — scheduler + process-record primitives.
|
||||
|
||||
(define er-rt-test-count 0)
|
||||
(define er-rt-test-pass 0)
|
||||
(define er-rt-test-fails (list))
|
||||
|
||||
(define
|
||||
er-rt-test
|
||||
(fn
|
||||
(name actual expected)
|
||||
(set! er-rt-test-count (+ er-rt-test-count 1))
|
||||
(if
|
||||
(= actual expected)
|
||||
(set! er-rt-test-pass (+ er-rt-test-pass 1))
|
||||
(append! er-rt-test-fails {:actual actual :expected expected :name name}))))
|
||||
|
||||
;; ── queue ─────────────────────────────────────────────────────────
|
||||
(er-rt-test "queue empty len" (er-q-len (er-q-new)) 0)
|
||||
(er-rt-test "queue empty?" (er-q-empty? (er-q-new)) true)
|
||||
|
||||
(define q1 (er-q-new))
|
||||
(er-q-push! q1 "a")
|
||||
(er-q-push! q1 "b")
|
||||
(er-q-push! q1 "c")
|
||||
(er-rt-test "queue push len" (er-q-len q1) 3)
|
||||
(er-rt-test "queue empty? after push" (er-q-empty? q1) false)
|
||||
(er-rt-test "queue peek" (er-q-peek q1) "a")
|
||||
(er-rt-test "queue pop 1" (er-q-pop! q1) "a")
|
||||
(er-rt-test "queue pop 2" (er-q-pop! q1) "b")
|
||||
(er-rt-test "queue len after pops" (er-q-len q1) 1)
|
||||
(er-rt-test "queue pop 3" (er-q-pop! q1) "c")
|
||||
(er-rt-test "queue empty again" (er-q-empty? q1) true)
|
||||
(er-rt-test "queue pop empty" (er-q-pop! q1) nil)
|
||||
|
||||
;; Queue FIFO under interleaved push/pop
|
||||
(define q2 (er-q-new))
|
||||
(er-q-push! q2 1)
|
||||
(er-q-push! q2 2)
|
||||
(er-q-pop! q2)
|
||||
(er-q-push! q2 3)
|
||||
(er-rt-test "queue interleave peek" (er-q-peek q2) 2)
|
||||
(er-rt-test "queue to-list" (er-q-to-list q2) (list 2 3))
|
||||
|
||||
;; ── scheduler init ─────────────────────────────────────────────
|
||||
(er-sched-init!)
|
||||
(er-rt-test "sched process count 0" (er-sched-process-count) 0)
|
||||
(er-rt-test "sched runnable count 0" (er-sched-runnable-count) 0)
|
||||
(er-rt-test "sched current nil" (er-sched-current-pid) nil)
|
||||
|
||||
;; ── pid allocation ─────────────────────────────────────────────
|
||||
(define pa (er-pid-new!))
|
||||
(define pb (er-pid-new!))
|
||||
(er-rt-test "pid tag" (get pa :tag) "pid")
|
||||
(er-rt-test "pid ids distinct" (= (er-pid-id pa) (er-pid-id pb)) false)
|
||||
(er-rt-test "pid? true" (er-pid? pa) true)
|
||||
(er-rt-test "pid? false" (er-pid? 42) false)
|
||||
(er-rt-test
|
||||
"pid-equal same"
|
||||
(er-pid-equal? pa (er-mk-pid (er-pid-id pa)))
|
||||
true)
|
||||
(er-rt-test "pid-equal diff" (er-pid-equal? pa pb) false)
|
||||
|
||||
;; ── process lifecycle ──────────────────────────────────────────
|
||||
(er-sched-init!)
|
||||
(define p1 (er-proc-new! {}))
|
||||
(define p2 (er-proc-new! {}))
|
||||
(er-rt-test "proc count 2" (er-sched-process-count) 2)
|
||||
(er-rt-test "runnable count 2" (er-sched-runnable-count) 2)
|
||||
(er-rt-test
|
||||
"proc state runnable"
|
||||
(er-proc-field (get p1 :pid) :state)
|
||||
"runnable")
|
||||
(er-rt-test
|
||||
"proc mailbox empty"
|
||||
(er-proc-mailbox-size (get p1 :pid))
|
||||
0)
|
||||
(er-rt-test
|
||||
"proc lookup"
|
||||
(er-pid-equal? (get (er-proc-get (get p1 :pid)) :pid) (get p1 :pid))
|
||||
true)
|
||||
(er-rt-test "proc exists" (er-proc-exists? (get p1 :pid)) true)
|
||||
(er-rt-test
|
||||
"proc no-such-pid"
|
||||
(er-proc-exists? (er-mk-pid 9999))
|
||||
false)
|
||||
|
||||
;; runnable queue dequeue order
|
||||
(er-rt-test
|
||||
"dequeue first"
|
||||
(er-pid-equal? (er-sched-next-runnable!) (get p1 :pid))
|
||||
true)
|
||||
(er-rt-test
|
||||
"dequeue second"
|
||||
(er-pid-equal? (er-sched-next-runnable!) (get p2 :pid))
|
||||
true)
|
||||
(er-rt-test "dequeue empty" (er-sched-next-runnable!) nil)
|
||||
|
||||
;; current-pid get/set
|
||||
(er-sched-set-current! (get p1 :pid))
|
||||
(er-rt-test
|
||||
"current pid set"
|
||||
(er-pid-equal? (er-sched-current-pid) (get p1 :pid))
|
||||
true)
|
||||
|
||||
;; ── mailbox push ──────────────────────────────────────────────
|
||||
(er-proc-mailbox-push! (get p1 :pid) {:tag "atom" :name "ping"})
|
||||
(er-proc-mailbox-push! (get p1 :pid) 42)
|
||||
(er-rt-test "mailbox size 2" (er-proc-mailbox-size (get p1 :pid)) 2)
|
||||
|
||||
;; ── field update ──────────────────────────────────────────────
|
||||
(er-proc-set! (get p1 :pid) :state "waiting")
|
||||
(er-rt-test
|
||||
"proc state waiting"
|
||||
(er-proc-field (get p1 :pid) :state)
|
||||
"waiting")
|
||||
(er-proc-set! (get p1 :pid) :trap-exit true)
|
||||
(er-rt-test
|
||||
"proc trap-exit"
|
||||
(er-proc-field (get p1 :pid) :trap-exit)
|
||||
true)
|
||||
|
||||
;; ── fresh scheduler ends in clean state ───────────────────────
|
||||
(er-sched-init!)
|
||||
(er-rt-test
|
||||
"sched init resets count"
|
||||
(er-sched-process-count)
|
||||
0)
|
||||
(er-rt-test
|
||||
"sched init resets queue"
|
||||
(er-sched-runnable-count)
|
||||
0)
|
||||
(er-rt-test
|
||||
"sched init resets current"
|
||||
(er-sched-current-pid)
|
||||
nil)
|
||||
|
||||
(define
|
||||
er-rt-test-summary
|
||||
(str "runtime " er-rt-test-pass "/" er-rt-test-count))
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,175 +1,433 @@
|
||||
;; lib/forth/runtime.sx — Forth primitives on SX
|
||||
;;
|
||||
;; Provides Forth-idiomatic wrappers over SX built-ins.
|
||||
;; Primitives used:
|
||||
;; bitwise-and/or/xor/not/arithmetic-shift/bit-count (Phase 7)
|
||||
;; make-bytevector/bytevector-u8-ref/u8-set!/... (Phase 20)
|
||||
;; quotient/remainder/modulo (Phase 15 / builtin)
|
||||
;;
|
||||
;; Naming: SX identifiers can't include @ or !-alone, so Forth words are:
|
||||
;; C@ → forth-cfetch C! → forth-cstore
|
||||
;; @ → forth-fetch ! → forth-store
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 1. Bitwise operations — Forth core words
|
||||
;; Forth TRUE = -1 (all bits set), FALSE = 0.
|
||||
;; All ops coerce to integer via truncate.
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(define (forth-and a b) (bitwise-and (truncate a) (truncate b)))
|
||||
(define (forth-or a b) (bitwise-or (truncate a) (truncate b)))
|
||||
(define (forth-xor a b) (bitwise-xor (truncate a) (truncate b)))
|
||||
|
||||
;; INVERT — bitwise NOT (Forth NOT is logical; INVERT is bitwise)
|
||||
(define (forth-invert a) (bitwise-not (truncate a)))
|
||||
|
||||
;; LSHIFT RSHIFT — n bit — shift a by n positions
|
||||
(define (forth-lshift a n) (arithmetic-shift (truncate a) (truncate n)))
|
||||
(define
|
||||
(forth-rshift a n)
|
||||
(arithmetic-shift (truncate a) (- 0 (truncate n))))
|
||||
|
||||
;; 2* 2/ — multiply/divide by 2 via bit shift
|
||||
(define (forth-2* a) (arithmetic-shift (truncate a) 1))
|
||||
(define (forth-2/ a) (arithmetic-shift (truncate a) -1))
|
||||
|
||||
;; BIT-COUNT — number of set bits (Kernighan popcount)
|
||||
(define (forth-bit-count a) (bit-count (truncate a)))
|
||||
|
||||
;; INTEGER-LENGTH — index of highest set bit (0 for zero)
|
||||
(define (forth-integer-length a) (integer-length (truncate a)))
|
||||
|
||||
;; WITHIN — ( u ul uh -- flag ) true if ul <= u < uh
|
||||
(define (forth-within u ul uh) (and (>= u ul) (< u uh)))
|
||||
|
||||
;; Arithmetic complements commonly used alongside bitwise ops
|
||||
(define (forth-negate a) (- 0 (truncate a)))
|
||||
(define (forth-abs a) (abs (truncate a)))
|
||||
(define (forth-min a b) (if (< a b) a b))
|
||||
(define (forth-max a b) (if (> a b) a b))
|
||||
(define (forth-mod a b) (modulo (truncate a) (truncate b)))
|
||||
|
||||
;; /MOD — ( n1 n2 -- rem quot ) returns list (remainder quotient)
|
||||
(define
|
||||
(forth-divmod a b)
|
||||
(list
|
||||
(remainder (truncate a) (truncate b))
|
||||
(quotient (truncate a) (truncate b))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 2. String buffer — word-definition / string accumulation
|
||||
;; EMIT appends one char; TYPE appends a string.
|
||||
;; Value is retrieved with forth-sb-value.
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Forth runtime — state, stacks, dictionary, output buffer.
|
||||
;; Data stack: mutable SX list, TOS = first.
|
||||
;; Return stack: separate mutable list.
|
||||
;; Dictionary: SX dict {lowercased-name -> word-record}.
|
||||
;; Word record: {"kind" "body" "immediate?"}; kind is "primitive" or "colon-def".
|
||||
;; Output buffer: mutable string appended to by `.`, `EMIT`, `CR`, etc.
|
||||
;; Compile-mode flag: "compiling" on the state.
|
||||
|
||||
(define
|
||||
(forth-sb-new)
|
||||
(let
|
||||
((sb (dict)))
|
||||
(dict-set! sb "_forth_sb" true)
|
||||
(dict-set! sb "_chars" (list))
|
||||
sb))
|
||||
|
||||
(define (forth-sb? v) (and (dict? v) (dict-has? v "_forth_sb")))
|
||||
|
||||
;; EMIT — append one character
|
||||
(define
|
||||
(forth-sb-emit! sb c)
|
||||
(dict-set! sb "_chars" (append (get sb "_chars") (list c)))
|
||||
sb)
|
||||
|
||||
;; TYPE — append a string
|
||||
(define
|
||||
(forth-sb-type! sb s)
|
||||
(dict-set! sb "_chars" (append (get sb "_chars") (string->list s)))
|
||||
sb)
|
||||
|
||||
(define (forth-sb-value sb) (list->string (get sb "_chars")))
|
||||
|
||||
(define (forth-sb-length sb) (len (get sb "_chars")))
|
||||
|
||||
(define (forth-sb-clear! sb) (dict-set! sb "_chars" (list)) sb)
|
||||
|
||||
;; Emit integer as decimal digits
|
||||
(define (forth-sb-emit-int! sb n) (forth-sb-type! sb (str (truncate n))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 3. Memory / Bytevectors — Forth raw memory model
|
||||
;; ALLOT allocates a bytevector. Byte and cell (32-bit LE) access.
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
;; ALLOT — allocate n bytes zero-initialised
|
||||
(define (forth-mem-new n) (make-bytevector (truncate n) 0))
|
||||
|
||||
(define (forth-mem? v) (bytevector? v))
|
||||
|
||||
(define (forth-mem-size v) (bytevector-length v))
|
||||
|
||||
;; C@ C! — byte fetch/store
|
||||
(define (forth-cfetch mem addr) (bytevector-u8-ref mem (truncate addr)))
|
||||
forth-make-state
|
||||
(fn
|
||||
()
|
||||
(let
|
||||
((s (dict)))
|
||||
(dict-set! s "dstack" (list))
|
||||
(dict-set! s "rstack" (list))
|
||||
(dict-set! s "dict" (dict))
|
||||
(dict-set! s "output" "")
|
||||
(dict-set! s "compiling" false)
|
||||
(dict-set! s "current-def" nil)
|
||||
(dict-set! s "base" 10)
|
||||
(dict-set! s "vars" (dict))
|
||||
s)))
|
||||
|
||||
(define
|
||||
(forth-cstore mem addr val)
|
||||
(bytevector-u8-set!
|
||||
mem
|
||||
(truncate addr)
|
||||
(modulo (truncate val) 256))
|
||||
mem)
|
||||
|
||||
;; @ ! — 32-bit little-endian cell fetch/store
|
||||
(define
|
||||
(forth-fetch mem addr)
|
||||
(let
|
||||
((a (truncate addr)))
|
||||
(+
|
||||
(bytevector-u8-ref mem a)
|
||||
(* 256 (bytevector-u8-ref mem (+ a 1)))
|
||||
(* 65536 (bytevector-u8-ref mem (+ a 2)))
|
||||
(* 16777216 (bytevector-u8-ref mem (+ a 3))))))
|
||||
forth-error
|
||||
(fn (state msg) (dict-set! state "error" msg) (raise msg)))
|
||||
|
||||
(define
|
||||
(forth-store mem addr val)
|
||||
(let
|
||||
((a (truncate addr)) (v (truncate val)))
|
||||
(bytevector-u8-set! mem a (modulo v 256))
|
||||
(bytevector-u8-set!
|
||||
mem
|
||||
(+ a 1)
|
||||
(modulo (quotient v 256) 256))
|
||||
(bytevector-u8-set!
|
||||
mem
|
||||
(+ a 2)
|
||||
(modulo (quotient v 65536) 256))
|
||||
(bytevector-u8-set!
|
||||
mem
|
||||
(+ a 3)
|
||||
(modulo (quotient v 16777216) 256)))
|
||||
mem)
|
||||
forth-push
|
||||
(fn (state v) (dict-set! state "dstack" (cons v (get state "dstack")))))
|
||||
|
||||
;; MOVE — copy count bytes from src[src-addr] to dst[dst-addr]
|
||||
(define
|
||||
(forth-move! src src-addr dst dst-addr count)
|
||||
(letrec
|
||||
((go (fn (i) (when (< i (truncate count)) (bytevector-u8-set! dst (+ (truncate dst-addr) i) (bytevector-u8-ref src (+ (truncate src-addr) i))) (go (+ i 1))))))
|
||||
(go 0))
|
||||
dst)
|
||||
forth-pop
|
||||
(fn
|
||||
(state)
|
||||
(let
|
||||
((st (get state "dstack")))
|
||||
(if
|
||||
(= (len st) 0)
|
||||
(forth-error state "stack underflow")
|
||||
(let ((top (first st))) (dict-set! state "dstack" (rest st)) top)))))
|
||||
|
||||
;; FILL — fill count bytes at addr with byte value
|
||||
(define
|
||||
(forth-fill! mem addr count byte)
|
||||
(letrec
|
||||
((go (fn (i) (when (< i (truncate count)) (bytevector-u8-set! mem (+ (truncate addr) i) (modulo (truncate byte) 256)) (go (+ i 1))))))
|
||||
(go 0))
|
||||
mem)
|
||||
forth-peek
|
||||
(fn
|
||||
(state)
|
||||
(let
|
||||
((st (get state "dstack")))
|
||||
(if (= (len st) 0) (forth-error state "stack underflow") (first st)))))
|
||||
|
||||
;; ERASE — fill with zeros (Forth: ERASE)
|
||||
(define
|
||||
(forth-erase! mem addr count)
|
||||
(forth-fill! mem addr count 0))
|
||||
(define forth-depth (fn (state) (len (get state "dstack"))))
|
||||
|
||||
;; Dump memory region as list of byte values
|
||||
(define
|
||||
(forth-mem->list mem addr count)
|
||||
(letrec
|
||||
((go (fn (i acc) (if (= i 0) acc (go (- i 1) (cons (bytevector-u8-ref mem (+ (truncate addr) (- i 1))) acc))))))
|
||||
(go (truncate count) (list))))
|
||||
forth-rpush
|
||||
(fn (state v) (dict-set! state "rstack" (cons v (get state "rstack")))))
|
||||
|
||||
(define
|
||||
forth-rpop
|
||||
(fn
|
||||
(state)
|
||||
(let
|
||||
((st (get state "rstack")))
|
||||
(if
|
||||
(= (len st) 0)
|
||||
(forth-error state "return stack underflow")
|
||||
(let ((top (first st))) (dict-set! state "rstack" (rest st)) top)))))
|
||||
|
||||
(define
|
||||
forth-rpeek
|
||||
(fn
|
||||
(state)
|
||||
(let
|
||||
((st (get state "rstack")))
|
||||
(if
|
||||
(= (len st) 0)
|
||||
(forth-error state "return stack underflow")
|
||||
(first st)))))
|
||||
|
||||
(define
|
||||
forth-emit-str
|
||||
(fn (state s) (dict-set! state "output" (str (get state "output") s))))
|
||||
|
||||
(define
|
||||
forth-make-word
|
||||
(fn
|
||||
(kind body immediate?)
|
||||
(let
|
||||
((w (dict)))
|
||||
(dict-set! w "kind" kind)
|
||||
(dict-set! w "body" body)
|
||||
(dict-set! w "immediate?" immediate?)
|
||||
w)))
|
||||
|
||||
(define
|
||||
forth-def-prim!
|
||||
(fn
|
||||
(state name body)
|
||||
(dict-set!
|
||||
(get state "dict")
|
||||
(downcase name)
|
||||
(forth-make-word "primitive" body false))))
|
||||
|
||||
(define
|
||||
forth-def-prim-imm!
|
||||
(fn
|
||||
(state name body)
|
||||
(dict-set!
|
||||
(get state "dict")
|
||||
(downcase name)
|
||||
(forth-make-word "primitive" body true))))
|
||||
|
||||
(define
|
||||
forth-lookup
|
||||
(fn (state name) (get (get state "dict") (downcase name))))
|
||||
|
||||
(define
|
||||
forth-binop
|
||||
(fn
|
||||
(op)
|
||||
(fn
|
||||
(state)
|
||||
(let
|
||||
((b (forth-pop state)) (a (forth-pop state)))
|
||||
(forth-push state (op a b))))))
|
||||
|
||||
(define
|
||||
forth-unop
|
||||
(fn
|
||||
(op)
|
||||
(fn (state) (let ((a (forth-pop state))) (forth-push state (op a))))))
|
||||
|
||||
(define
|
||||
forth-cmp
|
||||
(fn
|
||||
(op)
|
||||
(fn
|
||||
(state)
|
||||
(let
|
||||
((b (forth-pop state)) (a (forth-pop state)))
|
||||
(forth-push state (if (op a b) -1 0))))))
|
||||
|
||||
(define
|
||||
forth-cmp0
|
||||
(fn
|
||||
(op)
|
||||
(fn
|
||||
(state)
|
||||
(let ((a (forth-pop state))) (forth-push state (if (op a) -1 0))))))
|
||||
|
||||
(define
|
||||
forth-trunc
|
||||
(fn (x) (if (< x 0) (- 0 (floor (- 0 x))) (floor x))))
|
||||
|
||||
(define
|
||||
forth-div
|
||||
(fn
|
||||
(a b)
|
||||
(if (= b 0) (raise "division by zero") (forth-trunc (/ a b)))))
|
||||
|
||||
(define
|
||||
forth-mod
|
||||
(fn
|
||||
(a b)
|
||||
(if (= b 0) (raise "division by zero") (- a (* b (forth-div a b))))))
|
||||
|
||||
(define forth-bits-width 32)
|
||||
|
||||
(define
|
||||
forth-to-unsigned
|
||||
(fn (n w) (let ((m (pow 2 w))) (mod (+ (mod n m) m) m))))
|
||||
|
||||
(define
|
||||
forth-from-unsigned
|
||||
(fn
|
||||
(n w)
|
||||
(let ((half (pow 2 (- w 1)))) (if (>= n half) (- n (pow 2 w)) n))))
|
||||
|
||||
(define
|
||||
forth-bitwise-step
|
||||
(fn
|
||||
(op ua ub out place i w)
|
||||
(if
|
||||
(>= i w)
|
||||
out
|
||||
(let
|
||||
((da (mod ua 2)) (db (mod ub 2)))
|
||||
(forth-bitwise-step
|
||||
op
|
||||
(floor (/ ua 2))
|
||||
(floor (/ ub 2))
|
||||
(+ out (* place (op da db)))
|
||||
(* place 2)
|
||||
(+ i 1)
|
||||
w)))))
|
||||
|
||||
(define
|
||||
forth-bitwise-uu
|
||||
(fn
|
||||
(op)
|
||||
(fn
|
||||
(a b)
|
||||
(let
|
||||
((ua (forth-to-unsigned a forth-bits-width))
|
||||
(ub (forth-to-unsigned b forth-bits-width)))
|
||||
(forth-from-unsigned
|
||||
(forth-bitwise-step op ua ub 0 1 0 forth-bits-width)
|
||||
forth-bits-width)))))
|
||||
|
||||
(define
|
||||
forth-bit-and
|
||||
(forth-bitwise-uu (fn (x y) (if (and (= x 1) (= y 1)) 1 0))))
|
||||
|
||||
(define
|
||||
forth-bit-or
|
||||
(forth-bitwise-uu (fn (x y) (if (or (= x 1) (= y 1)) 1 0))))
|
||||
|
||||
(define forth-bit-xor (forth-bitwise-uu (fn (x y) (if (= x y) 0 1))))
|
||||
|
||||
(define forth-bit-invert (fn (a) (- 0 (+ a 1))))
|
||||
|
||||
(define
|
||||
forth-install-primitives!
|
||||
(fn
|
||||
(state)
|
||||
(forth-def-prim! state "DUP" (fn (s) (forth-push s (forth-peek s))))
|
||||
(forth-def-prim! state "DROP" (fn (s) (forth-pop s)))
|
||||
(forth-def-prim!
|
||||
state
|
||||
"SWAP"
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((b (forth-pop s)) (a (forth-pop s)))
|
||||
(forth-push s b)
|
||||
(forth-push s a))))
|
||||
(forth-def-prim!
|
||||
state
|
||||
"OVER"
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((b (forth-pop s)) (a (forth-pop s)))
|
||||
(forth-push s a)
|
||||
(forth-push s b)
|
||||
(forth-push s a))))
|
||||
(forth-def-prim!
|
||||
state
|
||||
"ROT"
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((c (forth-pop s)) (b (forth-pop s)) (a (forth-pop s)))
|
||||
(forth-push s b)
|
||||
(forth-push s c)
|
||||
(forth-push s a))))
|
||||
(forth-def-prim!
|
||||
state
|
||||
"-ROT"
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((c (forth-pop s)) (b (forth-pop s)) (a (forth-pop s)))
|
||||
(forth-push s c)
|
||||
(forth-push s a)
|
||||
(forth-push s b))))
|
||||
(forth-def-prim!
|
||||
state
|
||||
"NIP"
|
||||
(fn (s) (let ((b (forth-pop s))) (forth-pop s) (forth-push s b))))
|
||||
(forth-def-prim!
|
||||
state
|
||||
"TUCK"
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((b (forth-pop s)) (a (forth-pop s)))
|
||||
(forth-push s b)
|
||||
(forth-push s a)
|
||||
(forth-push s b))))
|
||||
(forth-def-prim!
|
||||
state
|
||||
"?DUP"
|
||||
(fn
|
||||
(s)
|
||||
(let ((a (forth-peek s))) (when (not (= a 0)) (forth-push s a)))))
|
||||
(forth-def-prim! state "DEPTH" (fn (s) (forth-push s (forth-depth s))))
|
||||
(forth-def-prim!
|
||||
state
|
||||
"PICK"
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((n (forth-pop s)) (st (get s "dstack")))
|
||||
(if
|
||||
(or (< n 0) (>= n (len st)))
|
||||
(forth-error s "PICK out of range")
|
||||
(forth-push s (nth st n))))))
|
||||
(forth-def-prim!
|
||||
state
|
||||
"ROLL"
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((n (forth-pop s)) (st (get s "dstack")))
|
||||
(if
|
||||
(or (< n 0) (>= n (len st)))
|
||||
(forth-error s "ROLL out of range")
|
||||
(let
|
||||
((taken (nth st n))
|
||||
(before (take st n))
|
||||
(after (drop st (+ n 1))))
|
||||
(dict-set! s "dstack" (concat before after))
|
||||
(forth-push s taken))))))
|
||||
(forth-def-prim!
|
||||
state
|
||||
"2DUP"
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((b (forth-pop s)) (a (forth-pop s)))
|
||||
(forth-push s a)
|
||||
(forth-push s b)
|
||||
(forth-push s a)
|
||||
(forth-push s b))))
|
||||
(forth-def-prim! state "2DROP" (fn (s) (forth-pop s) (forth-pop s)))
|
||||
(forth-def-prim!
|
||||
state
|
||||
"2SWAP"
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((d (forth-pop s))
|
||||
(c (forth-pop s))
|
||||
(b (forth-pop s))
|
||||
(a (forth-pop s)))
|
||||
(forth-push s c)
|
||||
(forth-push s d)
|
||||
(forth-push s a)
|
||||
(forth-push s b))))
|
||||
(forth-def-prim!
|
||||
state
|
||||
"2OVER"
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((d (forth-pop s))
|
||||
(c (forth-pop s))
|
||||
(b (forth-pop s))
|
||||
(a (forth-pop s)))
|
||||
(forth-push s a)
|
||||
(forth-push s b)
|
||||
(forth-push s c)
|
||||
(forth-push s d)
|
||||
(forth-push s a)
|
||||
(forth-push s b))))
|
||||
(forth-def-prim! state "+" (forth-binop (fn (a b) (+ a b))))
|
||||
(forth-def-prim! state "-" (forth-binop (fn (a b) (- a b))))
|
||||
(forth-def-prim! state "*" (forth-binop (fn (a b) (* a b))))
|
||||
(forth-def-prim! state "/" (forth-binop forth-div))
|
||||
(forth-def-prim! state "MOD" (forth-binop forth-mod))
|
||||
(forth-def-prim!
|
||||
state
|
||||
"/MOD"
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((b (forth-pop s)) (a (forth-pop s)))
|
||||
(forth-push s (forth-mod a b))
|
||||
(forth-push s (forth-div a b)))))
|
||||
(forth-def-prim! state "NEGATE" (forth-unop (fn (a) (- 0 a))))
|
||||
(forth-def-prim! state "ABS" (forth-unop abs))
|
||||
(forth-def-prim!
|
||||
state
|
||||
"MIN"
|
||||
(forth-binop (fn (a b) (if (< a b) a b))))
|
||||
(forth-def-prim!
|
||||
state
|
||||
"MAX"
|
||||
(forth-binop (fn (a b) (if (> a b) a b))))
|
||||
(forth-def-prim! state "1+" (forth-unop (fn (a) (+ a 1))))
|
||||
(forth-def-prim! state "1-" (forth-unop (fn (a) (- a 1))))
|
||||
(forth-def-prim! state "2+" (forth-unop (fn (a) (+ a 2))))
|
||||
(forth-def-prim! state "2-" (forth-unop (fn (a) (- a 2))))
|
||||
(forth-def-prim! state "2*" (forth-unop (fn (a) (* a 2))))
|
||||
(forth-def-prim! state "2/" (forth-unop (fn (a) (floor (/ a 2)))))
|
||||
(forth-def-prim! state "=" (forth-cmp (fn (a b) (= a b))))
|
||||
(forth-def-prim! state "<>" (forth-cmp (fn (a b) (not (= a b)))))
|
||||
(forth-def-prim! state "<" (forth-cmp (fn (a b) (< a b))))
|
||||
(forth-def-prim! state ">" (forth-cmp (fn (a b) (> a b))))
|
||||
(forth-def-prim! state "<=" (forth-cmp (fn (a b) (<= a b))))
|
||||
(forth-def-prim! state ">=" (forth-cmp (fn (a b) (>= a b))))
|
||||
(forth-def-prim! state "0=" (forth-cmp0 (fn (a) (= a 0))))
|
||||
(forth-def-prim! state "0<>" (forth-cmp0 (fn (a) (not (= a 0)))))
|
||||
(forth-def-prim! state "0<" (forth-cmp0 (fn (a) (< a 0))))
|
||||
(forth-def-prim! state "0>" (forth-cmp0 (fn (a) (> a 0))))
|
||||
(forth-def-prim! state "AND" (forth-binop forth-bit-and))
|
||||
(forth-def-prim! state "OR" (forth-binop forth-bit-or))
|
||||
(forth-def-prim! state "XOR" (forth-binop forth-bit-xor))
|
||||
(forth-def-prim! state "INVERT" (forth-unop forth-bit-invert))
|
||||
(forth-def-prim!
|
||||
state
|
||||
"."
|
||||
(fn (s) (forth-emit-str s (str (forth-pop s) " "))))
|
||||
(forth-def-prim!
|
||||
state
|
||||
".S"
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((st (reverse (get s "dstack"))))
|
||||
(forth-emit-str s "<")
|
||||
(forth-emit-str s (str (len st)))
|
||||
(forth-emit-str s "> ")
|
||||
(for-each (fn (v) (forth-emit-str s (str v " "))) st))))
|
||||
(forth-def-prim!
|
||||
state
|
||||
"EMIT"
|
||||
(fn (s) (forth-emit-str s (code-char (forth-pop s)))))
|
||||
(forth-def-prim! state "CR" (fn (s) (forth-emit-str s "\n")))
|
||||
(forth-def-prim! state "SPACE" (fn (s) (forth-emit-str s " ")))
|
||||
(forth-def-prim!
|
||||
state
|
||||
"SPACES"
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((n (forth-pop s)))
|
||||
(when
|
||||
(> n 0)
|
||||
(for-each (fn (_) (forth-emit-str s " ")) (range 0 n))))))
|
||||
(forth-def-prim! state "BL" (fn (s) (forth-push s 32)))
|
||||
state))
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# lib/forth/test.sh — smoke-test the Forth runtime layer.
|
||||
|
||||
set -uo pipefail
|
||||
cd "$(git rev-parse --show-toplevel)"
|
||||
|
||||
SX_SERVER="${SX_SERVER:-hosts/ocaml/_build/default/bin/sx_server.exe}"
|
||||
if [ ! -x "$SX_SERVER" ]; then
|
||||
SX_SERVER="/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe"
|
||||
fi
|
||||
if [ ! -x "$SX_SERVER" ]; then
|
||||
echo "ERROR: sx_server.exe not found."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TMPFILE=$(mktemp); trap "rm -f $TMPFILE" EXIT
|
||||
|
||||
cat > "$TMPFILE" << 'EPOCHS'
|
||||
(epoch 1)
|
||||
(load "lib/forth/runtime.sx")
|
||||
(epoch 2)
|
||||
(load "lib/forth/tests/runtime.sx")
|
||||
(epoch 3)
|
||||
(eval "(list forth-test-pass forth-test-fail)")
|
||||
EPOCHS
|
||||
|
||||
OUTPUT=$(timeout 60 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
|
||||
|
||||
LINE=$(echo "$OUTPUT" | awk '/^\(ok-len 3 / {getline; print; exit}')
|
||||
if [ -z "$LINE" ]; then
|
||||
LINE=$(echo "$OUTPUT" | grep -E '^\(ok 3 \([0-9]+ [0-9]+\)\)' | tail -1 \
|
||||
| sed -E 's/^\(ok 3 //; s/\)$//')
|
||||
fi
|
||||
if [ -z "$LINE" ]; then
|
||||
echo "ERROR: could not extract summary"
|
||||
echo "$OUTPUT" | tail -20
|
||||
exit 1
|
||||
fi
|
||||
|
||||
P=$(echo "$LINE" | sed -E 's/^\(([0-9]+) ([0-9]+)\).*/\1/')
|
||||
F=$(echo "$LINE" | sed -E 's/^\(([0-9]+) ([0-9]+)\).*/\2/')
|
||||
TOTAL=$((P + F))
|
||||
|
||||
if [ "$F" -eq 0 ]; then
|
||||
echo "ok $P/$TOTAL lib/forth tests passed"
|
||||
else
|
||||
echo "FAIL $P/$TOTAL passed, $F failed"
|
||||
TMPFILE2=$(mktemp)
|
||||
cat > "$TMPFILE2" << 'EPOCHS2'
|
||||
(epoch 1)
|
||||
(load "lib/forth/runtime.sx")
|
||||
(epoch 2)
|
||||
(load "lib/forth/tests/runtime.sx")
|
||||
(epoch 3)
|
||||
(eval "(map (fn (f) (list (get f :name) (get f :got) (get f :expected))) forth-test-fails)")
|
||||
EPOCHS2
|
||||
FAILS=$(timeout 60 "$SX_SERVER" < "$TMPFILE2" 2>/dev/null | grep -E '^\(ok-len 3' -A1 | tail -1 || true)
|
||||
echo " Details: $FAILS"
|
||||
rm -f "$TMPFILE2"
|
||||
fi
|
||||
|
||||
[ "$F" -eq 0 ]
|
||||
@@ -1,201 +0,0 @@
|
||||
;; lib/forth/tests/runtime.sx — Tests for lib/forth/runtime.sx
|
||||
|
||||
(define forth-test-pass 0)
|
||||
(define forth-test-fail 0)
|
||||
(define forth-test-fails (list))
|
||||
|
||||
(define
|
||||
(forth-test name got expected)
|
||||
(if
|
||||
(= got expected)
|
||||
(set! forth-test-pass (+ forth-test-pass 1))
|
||||
(begin
|
||||
(set! forth-test-fail (+ forth-test-fail 1))
|
||||
(set! forth-test-fails (append forth-test-fails (list {:got got :expected expected :name name}))))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 1. Bitwise operations
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
;; AND
|
||||
(forth-test "and 0b1100 0b1010" (forth-and 12 10) 8)
|
||||
(forth-test "and 0xFF 0x0F" (forth-and 255 15) 15)
|
||||
(forth-test "and 0 any" (forth-and 0 42) 0)
|
||||
|
||||
;; OR
|
||||
(forth-test "or 0b1100 0b1010" (forth-or 12 10) 14)
|
||||
(forth-test "or 0 x" (forth-or 0 7) 7)
|
||||
|
||||
;; XOR
|
||||
(forth-test "xor 0b1100 0b1010" (forth-xor 12 10) 6)
|
||||
(forth-test "xor x x" (forth-xor 42 42) 0)
|
||||
|
||||
;; INVERT
|
||||
(forth-test "invert 0" (forth-invert 0) -1)
|
||||
(forth-test "invert -1" (forth-invert -1) 0)
|
||||
(forth-test "invert 1" (forth-invert 1) -2)
|
||||
|
||||
;; LSHIFT RSHIFT
|
||||
(forth-test "lshift 1 3" (forth-lshift 1 3) 8)
|
||||
(forth-test "lshift 3 2" (forth-lshift 3 2) 12)
|
||||
(forth-test "rshift 8 3" (forth-rshift 8 3) 1)
|
||||
(forth-test "rshift 16 2" (forth-rshift 16 2) 4)
|
||||
|
||||
;; 2* 2/
|
||||
(forth-test "2* 5" (forth-2* 5) 10)
|
||||
(forth-test "2/ 10" (forth-2/ 10) 5)
|
||||
(forth-test "2/ 7" (forth-2/ 7) 3)
|
||||
|
||||
;; BIT-COUNT
|
||||
(forth-test "bit-count 0" (forth-bit-count 0) 0)
|
||||
(forth-test "bit-count 1" (forth-bit-count 1) 1)
|
||||
(forth-test "bit-count 7" (forth-bit-count 7) 3)
|
||||
(forth-test "bit-count 255" (forth-bit-count 255) 8)
|
||||
(forth-test "bit-count 256" (forth-bit-count 256) 1)
|
||||
|
||||
;; INTEGER-LENGTH
|
||||
(forth-test "integer-length 0" (forth-integer-length 0) 0)
|
||||
(forth-test "integer-length 1" (forth-integer-length 1) 1)
|
||||
(forth-test "integer-length 4" (forth-integer-length 4) 3)
|
||||
(forth-test "integer-length 255" (forth-integer-length 255) 8)
|
||||
|
||||
;; WITHIN
|
||||
(forth-test
|
||||
"within 5 0 10"
|
||||
(forth-within 5 0 10)
|
||||
true)
|
||||
(forth-test
|
||||
"within 0 0 10"
|
||||
(forth-within 0 0 10)
|
||||
true)
|
||||
(forth-test
|
||||
"within 10 0 10"
|
||||
(forth-within 10 0 10)
|
||||
false)
|
||||
(forth-test
|
||||
"within -1 0 10"
|
||||
(forth-within -1 0 10)
|
||||
false)
|
||||
|
||||
;; Arithmetic ops
|
||||
(forth-test "negate 5" (forth-negate 5) -5)
|
||||
(forth-test "negate -3" (forth-negate -3) 3)
|
||||
(forth-test "abs -7" (forth-abs -7) 7)
|
||||
(forth-test "min 3 5" (forth-min 3 5) 3)
|
||||
(forth-test "max 3 5" (forth-max 3 5) 5)
|
||||
(forth-test "mod 7 3" (forth-mod 7 3) 1)
|
||||
(forth-test
|
||||
"divmod 7 3"
|
||||
(forth-divmod 7 3)
|
||||
(list 1 2))
|
||||
(forth-test
|
||||
"divmod 10 5"
|
||||
(forth-divmod 10 5)
|
||||
(list 0 2))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 2. String buffer
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(define sb1 (forth-sb-new))
|
||||
(forth-test "sb? new" (forth-sb? sb1) true)
|
||||
(forth-test "sb? non-sb" (forth-sb? 42) false)
|
||||
(forth-test "sb value empty" (forth-sb-value sb1) "")
|
||||
(forth-test "sb length empty" (forth-sb-length sb1) 0)
|
||||
|
||||
(forth-sb-type! sb1 "HELLO")
|
||||
(forth-test "sb type" (forth-sb-value sb1) "HELLO")
|
||||
(forth-test "sb length after type" (forth-sb-length sb1) 5)
|
||||
|
||||
;; EMIT one char
|
||||
(define sb2 (forth-sb-new))
|
||||
(forth-sb-emit! sb2 (nth (string->list "A") 0))
|
||||
(forth-sb-emit! sb2 (nth (string->list "B") 0))
|
||||
(forth-sb-emit! sb2 (nth (string->list "C") 0))
|
||||
(forth-test "sb emit chars" (forth-sb-value sb2) "ABC")
|
||||
|
||||
;; Emit integer
|
||||
(define sb3 (forth-sb-new))
|
||||
(forth-sb-type! sb3 "n=")
|
||||
(forth-sb-emit-int! sb3 42)
|
||||
(forth-test "sb emit-int" (forth-sb-value sb3) "n=42")
|
||||
|
||||
(forth-sb-clear! sb1)
|
||||
(forth-test "sb clear" (forth-sb-value sb1) "")
|
||||
(forth-test "sb length after clear" (forth-sb-length sb1) 0)
|
||||
|
||||
;; Build a word definition-style name
|
||||
(define sb4 (forth-sb-new))
|
||||
(forth-sb-type! sb4 ": ")
|
||||
(forth-sb-type! sb4 "SQUARE")
|
||||
(forth-sb-type! sb4 " DUP * ;")
|
||||
(forth-test "sb word def" (forth-sb-value sb4) ": SQUARE DUP * ;")
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 3. Memory / Bytevectors
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(define m1 (forth-mem-new 8))
|
||||
(forth-test "mem? yes" (forth-mem? m1) true)
|
||||
(forth-test "mem? no" (forth-mem? 42) false)
|
||||
(forth-test "mem size" (forth-mem-size m1) 8)
|
||||
(forth-test "mem cfetch zero" (forth-cfetch m1 0) 0)
|
||||
|
||||
;; C! C@
|
||||
(forth-cstore m1 0 65)
|
||||
(forth-cstore m1 1 66)
|
||||
(forth-test "mem cstore/cfetch 0" (forth-cfetch m1 0) 65)
|
||||
(forth-test "mem cstore/cfetch 1" (forth-cfetch m1 1) 66)
|
||||
(forth-cstore m1 2 256)
|
||||
(forth-test
|
||||
"mem cstore wraps 256→0"
|
||||
(forth-cfetch m1 2)
|
||||
0)
|
||||
(forth-cstore m1 2 257)
|
||||
(forth-test
|
||||
"mem cstore wraps 257→1"
|
||||
(forth-cfetch m1 2)
|
||||
1)
|
||||
|
||||
;; @ ! (32-bit LE cell)
|
||||
(define m2 (forth-mem-new 8))
|
||||
(forth-store m2 0 305419896)
|
||||
(forth-test "mem store/fetch" (forth-fetch m2 0) 305419896)
|
||||
(forth-store m2 4 1)
|
||||
(forth-test "mem fetch byte 4" (forth-cfetch m2 4) 1)
|
||||
(forth-test "mem fetch byte 5" (forth-cfetch m2 5) 0)
|
||||
|
||||
;; FILL ERASE
|
||||
(define m3 (forth-mem-new 4))
|
||||
(forth-fill! m3 0 4 42)
|
||||
(forth-test
|
||||
"mem fill"
|
||||
(forth-mem->list m3 0 4)
|
||||
(list 42 42 42 42))
|
||||
(forth-erase! m3 1 2)
|
||||
(forth-test
|
||||
"mem erase middle"
|
||||
(forth-mem->list m3 0 4)
|
||||
(list 42 0 0 42))
|
||||
|
||||
;; MOVE
|
||||
(define m4 (forth-mem-new 4))
|
||||
(forth-cstore m4 0 1)
|
||||
(forth-cstore m4 1 2)
|
||||
(forth-cstore m4 2 3)
|
||||
(define m5 (forth-mem-new 4))
|
||||
(forth-move! m4 0 m5 0 3)
|
||||
(forth-test
|
||||
"mem move"
|
||||
(forth-mem->list m5 0 3)
|
||||
(list 1 2 3))
|
||||
|
||||
;; mem->list
|
||||
(define m6 (forth-mem-new 3))
|
||||
(forth-cstore m6 0 10)
|
||||
(forth-cstore m6 1 20)
|
||||
(forth-cstore m6 2 30)
|
||||
(forth-test
|
||||
"mem->list"
|
||||
(forth-mem->list m6 0 3)
|
||||
(list 10 20 30))
|
||||
@@ -1,507 +0,0 @@
|
||||
;; lib/haskell/runtime.sx — Haskell-on-SX runtime layer
|
||||
;;
|
||||
;; Covers the Haskell primitives now reachable via SX spec:
|
||||
;; 1. Numeric type class helpers (Num / Integral / Fractional)
|
||||
;; 2. Rational numbers (dict-based: {:_rational true :num n :den d})
|
||||
;; 3. Lazy evaluation — hk-force for promises created by delay
|
||||
;; 4. Char utilities (Data.Char)
|
||||
;; 5. Data.Set wrappers
|
||||
;; 6. Data.List utilities
|
||||
;; 7. Maybe / Either ADTs
|
||||
;; 8. Tuples (lists, since list->vector unreliable in sx_server)
|
||||
;; 9. String helpers (words/lines/isPrefixOf/etc.)
|
||||
;; 10. Show helper
|
||||
|
||||
;; ===========================================================================
|
||||
;; 1. Numeric type class helpers
|
||||
;; ===========================================================================
|
||||
|
||||
(define hk-is-integer? integer?)
|
||||
(define hk-is-float? float?)
|
||||
(define hk-is-num? number?)
|
||||
|
||||
;; fromIntegral — coerce integer to Float
|
||||
(define (hk-to-float x) (exact->inexact x))
|
||||
|
||||
;; truncate / round toward zero
|
||||
(define hk-to-integer truncate)
|
||||
(define hk-from-integer (fn (n) n))
|
||||
|
||||
;; Haskell div: floor division (rounds toward -inf)
|
||||
(define
|
||||
(hk-div a b)
|
||||
(let
|
||||
((q (quotient a b)) (r (remainder a b)))
|
||||
(if
|
||||
(and
|
||||
(not (= r 0))
|
||||
(or
|
||||
(and (< a 0) (> b 0))
|
||||
(and (> a 0) (< b 0))))
|
||||
(- q 1)
|
||||
q)))
|
||||
|
||||
;; Haskell mod: result has same sign as divisor
|
||||
(define hk-mod modulo)
|
||||
|
||||
;; Haskell rem: result has same sign as dividend
|
||||
(define hk-rem remainder)
|
||||
|
||||
;; Haskell quot: truncation division
|
||||
(define hk-quot quotient)
|
||||
|
||||
;; divMod and quotRem return pairs (lists)
|
||||
(define (hk-div-mod a b) (list (hk-div a b) (hk-mod a b)))
|
||||
(define (hk-quot-rem a b) (list (hk-quot a b) (hk-rem a b)))
|
||||
|
||||
(define (hk-abs x) (if (< x 0) (- 0 x) x))
|
||||
(define
|
||||
(hk-signum x)
|
||||
(cond
|
||||
((> x 0) 1)
|
||||
((< x 0) -1)
|
||||
(else 0)))
|
||||
|
||||
(define hk-gcd gcd)
|
||||
(define hk-lcm lcm)
|
||||
|
||||
(define (hk-even? n) (= (modulo n 2) 0))
|
||||
(define (hk-odd? n) (not (= (modulo n 2) 0)))
|
||||
|
||||
;; ===========================================================================
|
||||
;; 2. Rational numbers (dict implementation — no built-in rational in sx_server)
|
||||
;; ===========================================================================
|
||||
|
||||
(define
|
||||
(hk-make-rational n d)
|
||||
(let
|
||||
((g (gcd (hk-abs n) (hk-abs d))))
|
||||
(if (< d 0) {:num (quotient (- 0 n) g) :den (quotient (- 0 d) g) :_rational true} {:num (quotient n g) :den (quotient d g) :_rational true})))
|
||||
|
||||
(define
|
||||
(hk-rational? x)
|
||||
(and (dict? x) (not (= (get x :_rational) nil))))
|
||||
(define (hk-numerator r) (get r :num))
|
||||
(define (hk-denominator r) (get r :den))
|
||||
|
||||
(define
|
||||
(hk-rational-add r1 r2)
|
||||
(hk-make-rational
|
||||
(+
|
||||
(* (hk-numerator r1) (hk-denominator r2))
|
||||
(* (hk-numerator r2) (hk-denominator r1)))
|
||||
(* (hk-denominator r1) (hk-denominator r2))))
|
||||
|
||||
(define
|
||||
(hk-rational-sub r1 r2)
|
||||
(hk-make-rational
|
||||
(-
|
||||
(* (hk-numerator r1) (hk-denominator r2))
|
||||
(* (hk-numerator r2) (hk-denominator r1)))
|
||||
(* (hk-denominator r1) (hk-denominator r2))))
|
||||
|
||||
(define
|
||||
(hk-rational-mul r1 r2)
|
||||
(hk-make-rational
|
||||
(* (hk-numerator r1) (hk-numerator r2))
|
||||
(* (hk-denominator r1) (hk-denominator r2))))
|
||||
|
||||
(define
|
||||
(hk-rational-div r1 r2)
|
||||
(hk-make-rational
|
||||
(* (hk-numerator r1) (hk-denominator r2))
|
||||
(* (hk-denominator r1) (hk-numerator r2))))
|
||||
|
||||
(define
|
||||
(hk-rational-to-float r)
|
||||
(exact->inexact (/ (hk-numerator r) (hk-denominator r))))
|
||||
|
||||
(define (hk-show-rational r) (str (hk-numerator r) "%" (hk-denominator r)))
|
||||
|
||||
;; ===========================================================================
|
||||
;; 3. Lazy evaluation — promises (created via SX delay)
|
||||
;; ===========================================================================
|
||||
|
||||
(define
|
||||
(hk-force p)
|
||||
(if
|
||||
(and (dict? p) (not (= (get p :_promise) nil)))
|
||||
(if (get p :forced) (get p :value) ((get p :thunk)))
|
||||
p))
|
||||
|
||||
;; ===========================================================================
|
||||
;; 4. Char utilities (Data.Char)
|
||||
;; ===========================================================================
|
||||
|
||||
(define hk-ord char->integer)
|
||||
(define hk-chr integer->char)
|
||||
|
||||
;; Inline ASCII predicates — char-alphabetic?/char-numeric? unreliable in sx_server
|
||||
(define
|
||||
(hk-is-alpha? c)
|
||||
(let
|
||||
((n (char->integer c)))
|
||||
(or
|
||||
(and (>= n 65) (<= n 90))
|
||||
(and (>= n 97) (<= n 122)))))
|
||||
|
||||
(define
|
||||
(hk-is-digit? c)
|
||||
(let ((n (char->integer c))) (and (>= n 48) (<= n 57))))
|
||||
|
||||
(define
|
||||
(hk-is-alnum? c)
|
||||
(let
|
||||
((n (char->integer c)))
|
||||
(or
|
||||
(and (>= n 48) (<= n 57))
|
||||
(and (>= n 65) (<= n 90))
|
||||
(and (>= n 97) (<= n 122)))))
|
||||
|
||||
(define
|
||||
(hk-is-upper? c)
|
||||
(let ((n (char->integer c))) (and (>= n 65) (<= n 90))))
|
||||
|
||||
(define
|
||||
(hk-is-lower? c)
|
||||
(let ((n (char->integer c))) (and (>= n 97) (<= n 122))))
|
||||
|
||||
(define
|
||||
(hk-is-space? c)
|
||||
(let
|
||||
((n (char->integer c)))
|
||||
(or
|
||||
(= n 32)
|
||||
(= n 9)
|
||||
(= n 10)
|
||||
(= n 13)
|
||||
(= n 12)
|
||||
(= n 11))))
|
||||
|
||||
(define hk-to-upper char-upcase)
|
||||
(define hk-to-lower char-downcase)
|
||||
|
||||
;; digitToInt: '0'-'9' → 0-9, 'a'-'f'/'A'-'F' → 10-15
|
||||
(define
|
||||
(hk-digit-to-int c)
|
||||
(let
|
||||
((n (char->integer c)))
|
||||
(cond
|
||||
((and (>= n 48) (<= n 57)) (- n 48))
|
||||
((and (>= n 65) (<= n 70)) (- n 55))
|
||||
((and (>= n 97) (<= n 102)) (- n 87))
|
||||
(else (error (str "hk-digit-to-int: not a hex digit: " c))))))
|
||||
|
||||
;; intToDigit: 0-15 → char
|
||||
(define
|
||||
(hk-int-to-digit n)
|
||||
(cond
|
||||
((and (>= n 0) (<= n 9))
|
||||
(integer->char (+ n 48)))
|
||||
((and (>= n 10) (<= n 15))
|
||||
(integer->char (+ n 87)))
|
||||
(else (error (str "hk-int-to-digit: out of range: " n)))))
|
||||
|
||||
;; ===========================================================================
|
||||
;; 5. Data.Set wrappers
|
||||
;; ===========================================================================
|
||||
|
||||
(define (hk-set-empty) (make-set))
|
||||
(define hk-set? set?)
|
||||
(define hk-set-member? set-member?)
|
||||
|
||||
(define (hk-set-insert x s) (begin (set-add! s x) s))
|
||||
|
||||
(define (hk-set-delete x s) (begin (set-remove! s x) s))
|
||||
|
||||
(define hk-set-union set-union)
|
||||
(define hk-set-intersection set-intersection)
|
||||
(define hk-set-difference set-difference)
|
||||
(define hk-set-from-list list->set)
|
||||
(define hk-set-to-list set->list)
|
||||
(define (hk-set-null? s) (= (len (set->list s)) 0))
|
||||
(define (hk-set-size s) (len (set->list s)))
|
||||
|
||||
(define (hk-set-singleton x) (let ((s (make-set))) (set-add! s x) s))
|
||||
|
||||
;; ===========================================================================
|
||||
;; 6. Data.List utilities
|
||||
;; ===========================================================================
|
||||
|
||||
(define hk-head first)
|
||||
(define hk-tail rest)
|
||||
(define (hk-null? lst) (= (len lst) 0))
|
||||
(define hk-length len)
|
||||
|
||||
(define
|
||||
(hk-take n lst)
|
||||
(if
|
||||
(or (= n 0) (= (len lst) 0))
|
||||
(list)
|
||||
(cons (first lst) (hk-take (- n 1) (rest lst)))))
|
||||
|
||||
(define
|
||||
(hk-drop n lst)
|
||||
(if
|
||||
(or (= n 0) (= (len lst) 0))
|
||||
lst
|
||||
(hk-drop (- n 1) (rest lst))))
|
||||
|
||||
(define
|
||||
(hk-take-while pred lst)
|
||||
(if
|
||||
(or (= (len lst) 0) (not (pred (first lst))))
|
||||
(list)
|
||||
(cons (first lst) (hk-take-while pred (rest lst)))))
|
||||
|
||||
(define
|
||||
(hk-drop-while pred lst)
|
||||
(if
|
||||
(or (= (len lst) 0) (not (pred (first lst))))
|
||||
lst
|
||||
(hk-drop-while pred (rest lst))))
|
||||
|
||||
(define
|
||||
(hk-zip a b)
|
||||
(if
|
||||
(or (= (len a) 0) (= (len b) 0))
|
||||
(list)
|
||||
(cons (list (first a) (first b)) (hk-zip (rest a) (rest b)))))
|
||||
|
||||
(define
|
||||
(hk-zip-with f a b)
|
||||
(if
|
||||
(or (= (len a) 0) (= (len b) 0))
|
||||
(list)
|
||||
(cons (f (first a) (first b)) (hk-zip-with f (rest a) (rest b)))))
|
||||
|
||||
(define
|
||||
(hk-unzip pairs)
|
||||
(list
|
||||
(map (fn (p) (first p)) pairs)
|
||||
(map (fn (p) (nth p 1)) pairs)))
|
||||
|
||||
(define
|
||||
(hk-elem x lst)
|
||||
(cond
|
||||
((= (len lst) 0) false)
|
||||
((= x (first lst)) true)
|
||||
(else (hk-elem x (rest lst)))))
|
||||
|
||||
(define (hk-not-elem x lst) (not (hk-elem x lst)))
|
||||
|
||||
(define
|
||||
(hk-nub lst)
|
||||
(letrec
|
||||
((go (fn (seen acc items) (if (= (len items) 0) (reverse acc) (let ((h (first items)) (t (rest items))) (if (hk-elem h seen) (go seen acc t) (go (cons h seen) (cons h acc) t)))))))
|
||||
(go (list) (list) lst)))
|
||||
|
||||
(define (hk-sum lst) (reduce + 0 lst))
|
||||
(define (hk-product lst) (reduce * 1 lst))
|
||||
|
||||
(define
|
||||
(hk-maximum lst)
|
||||
(reduce (fn (a b) (if (> a b) a b)) (first lst) (rest lst)))
|
||||
|
||||
(define
|
||||
(hk-minimum lst)
|
||||
(reduce (fn (a b) (if (< a b) a b)) (first lst) (rest lst)))
|
||||
|
||||
(define (hk-concat lsts) (reduce append (list) lsts))
|
||||
|
||||
(define (hk-concat-map f lst) (hk-concat (map f lst)))
|
||||
|
||||
(define hk-sort sort)
|
||||
|
||||
(define
|
||||
(hk-span pred lst)
|
||||
(list (hk-take-while pred lst) (hk-drop-while pred lst)))
|
||||
|
||||
(define (hk-break pred lst) (hk-span (fn (x) (not (pred x))) lst))
|
||||
|
||||
(define
|
||||
(hk-foldl f acc lst)
|
||||
(if
|
||||
(= (len lst) 0)
|
||||
acc
|
||||
(hk-foldl f (f acc (first lst)) (rest lst))))
|
||||
|
||||
(define
|
||||
(hk-foldr f z lst)
|
||||
(if
|
||||
(= (len lst) 0)
|
||||
z
|
||||
(f (first lst) (hk-foldr f z (rest lst)))))
|
||||
|
||||
(define
|
||||
(hk-scanl f acc lst)
|
||||
(if
|
||||
(= (len lst) 0)
|
||||
(list acc)
|
||||
(cons acc (hk-scanl f (f acc (first lst)) (rest lst)))))
|
||||
|
||||
(define
|
||||
(hk-replicate n x)
|
||||
(if (= n 0) (list) (cons x (hk-replicate (- n 1) x))))
|
||||
|
||||
(define
|
||||
(hk-intersperse sep lst)
|
||||
(if
|
||||
(or (= (len lst) 0) (= (len lst) 1))
|
||||
lst
|
||||
(cons (first lst) (cons sep (hk-intersperse sep (rest lst))))))
|
||||
|
||||
;; ===========================================================================
|
||||
;; 7. Maybe / Either ADTs
|
||||
;; ===========================================================================
|
||||
|
||||
(define hk-nothing {:_maybe true :_tag "nothing"})
|
||||
(define (hk-just x) {:_maybe true :value x :_tag "just"})
|
||||
(define (hk-is-nothing? m) (= (get m :_tag) "nothing"))
|
||||
(define (hk-is-just? m) (= (get m :_tag) "just"))
|
||||
(define (hk-from-just m) (get m :value))
|
||||
(define (hk-from-maybe def m) (if (hk-is-nothing? m) def (hk-from-just m)))
|
||||
(define
|
||||
(hk-maybe def f m)
|
||||
(if (hk-is-nothing? m) def (f (hk-from-just m))))
|
||||
|
||||
(define (hk-left x) {:value x :_either true :_tag "left"})
|
||||
(define (hk-right x) {:value x :_either true :_tag "right"})
|
||||
(define (hk-is-left? e) (= (get e :_tag) "left"))
|
||||
(define (hk-is-right? e) (= (get e :_tag) "right"))
|
||||
(define (hk-from-left e) (get e :value))
|
||||
(define (hk-from-right e) (get e :value))
|
||||
(define
|
||||
(hk-either f g e)
|
||||
(if (hk-is-left? e) (f (hk-from-left e)) (g (hk-from-right e))))
|
||||
|
||||
;; ===========================================================================
|
||||
;; 8. Tuples (lists — list->vector unreliable in sx_server)
|
||||
;; ===========================================================================
|
||||
|
||||
(define (hk-pair a b) (list a b))
|
||||
(define hk-fst first)
|
||||
(define (hk-snd t) (nth t 1))
|
||||
|
||||
(define (hk-triple a b c) (list a b c))
|
||||
(define hk-fst3 first)
|
||||
(define (hk-snd3 t) (nth t 1))
|
||||
(define (hk-thd3 t) (nth t 2))
|
||||
|
||||
(define (hk-curry f) (fn (a) (fn (b) (f a b))))
|
||||
(define (hk-uncurry f) (fn (p) (f (hk-fst p) (hk-snd p))))
|
||||
|
||||
;; ===========================================================================
|
||||
;; 9. String helpers (Data.List / Data.Char for strings)
|
||||
;; ===========================================================================
|
||||
|
||||
;; words: split on whitespace
|
||||
(define
|
||||
(hk-words s)
|
||||
(letrec
|
||||
((slen (len s))
|
||||
(skip-ws
|
||||
(fn
|
||||
(i)
|
||||
(if
|
||||
(>= i slen)
|
||||
(list)
|
||||
(let
|
||||
((c (substring s i (+ i 1))))
|
||||
(if
|
||||
(or (= c " ") (= c "\t") (= c "\n"))
|
||||
(skip-ws (+ i 1))
|
||||
(collect-word i (+ i 1)))))))
|
||||
(collect-word
|
||||
(fn
|
||||
(start i)
|
||||
(if
|
||||
(>= i slen)
|
||||
(list (substring s start i))
|
||||
(let
|
||||
((c (substring s i (+ i 1))))
|
||||
(if
|
||||
(or (= c " ") (= c "\t") (= c "\n"))
|
||||
(cons (substring s start i) (skip-ws (+ i 1)))
|
||||
(collect-word start (+ i 1))))))))
|
||||
(skip-ws 0)))
|
||||
|
||||
;; unwords: join with spaces
|
||||
(define
|
||||
(hk-unwords lst)
|
||||
(if
|
||||
(= (len lst) 0)
|
||||
""
|
||||
(reduce (fn (a b) (str a " " b)) (first lst) (rest lst))))
|
||||
|
||||
;; lines: split on newline
|
||||
(define
|
||||
(hk-lines s)
|
||||
(letrec
|
||||
((slen (len s))
|
||||
(go
|
||||
(fn
|
||||
(start i acc)
|
||||
(if
|
||||
(>= i slen)
|
||||
(reverse (cons (substring s start i) acc))
|
||||
(if
|
||||
(= (substring s i (+ i 1)) "\n")
|
||||
(go
|
||||
(+ i 1)
|
||||
(+ i 1)
|
||||
(cons (substring s start i) acc))
|
||||
(go start (+ i 1) acc))))))
|
||||
(if (= slen 0) (list) (go 0 0 (list)))))
|
||||
|
||||
;; unlines: join, each with trailing newline
|
||||
(define (hk-unlines lst) (reduce (fn (a b) (str a b "\n")) "" lst))
|
||||
|
||||
;; isPrefixOf
|
||||
(define
|
||||
(hk-is-prefix-of pre s)
|
||||
(and (<= (len pre) (len s)) (= pre (substring s 0 (len pre)))))
|
||||
|
||||
;; isSuffixOf
|
||||
(define
|
||||
(hk-is-suffix-of suf s)
|
||||
(let
|
||||
((sl (len suf)) (tl (len s)))
|
||||
(and (<= sl tl) (= suf (substring s (- tl sl) tl)))))
|
||||
|
||||
;; isInfixOf — linear scan
|
||||
(define
|
||||
(hk-is-infix-of pat s)
|
||||
(let
|
||||
((plen (len pat)) (slen (len s)))
|
||||
(letrec
|
||||
((go (fn (i) (if (> (+ i plen) slen) false (if (= pat (substring s i (+ i plen))) true (go (+ i 1)))))))
|
||||
(if (= plen 0) true (go 0)))))
|
||||
|
||||
;; ===========================================================================
|
||||
;; 10. Show helper
|
||||
;; ===========================================================================
|
||||
|
||||
(define
|
||||
(hk-show x)
|
||||
(cond
|
||||
((= x nil) "Nothing")
|
||||
((= x true) "True")
|
||||
((= x false) "False")
|
||||
((hk-rational? x) (hk-show-rational x))
|
||||
((integer? x) (str x))
|
||||
((float? x) (str x))
|
||||
((= (type-of x) "string") (str "\"" x "\""))
|
||||
((= (type-of x) "char") (str "'" (str x) "'"))
|
||||
((list? x)
|
||||
(str
|
||||
"["
|
||||
(if
|
||||
(= (len x) 0)
|
||||
""
|
||||
(reduce
|
||||
(fn (a b) (str a "," (hk-show b)))
|
||||
(hk-show (first x))
|
||||
(rest x)))
|
||||
"]"))
|
||||
(else (str x))))
|
||||
@@ -46,7 +46,6 @@ for FILE in "${FILES[@]}"; do
|
||||
cat > "$TMPFILE" <<EPOCHS
|
||||
(epoch 1)
|
||||
(load "lib/haskell/tokenizer.sx")
|
||||
(load "lib/haskell/runtime.sx")
|
||||
(epoch 2)
|
||||
(load "$FILE")
|
||||
(epoch 3)
|
||||
@@ -82,7 +81,6 @@ EPOCHS
|
||||
cat > "$TMPFILE2" <<EPOCHS
|
||||
(epoch 1)
|
||||
(load "lib/haskell/tokenizer.sx")
|
||||
(load "lib/haskell/runtime.sx")
|
||||
(epoch 2)
|
||||
(load "$FILE")
|
||||
(epoch 3)
|
||||
|
||||
@@ -1,451 +0,0 @@
|
||||
;; lib/haskell/tests/runtime.sx — smoke-tests for lib/haskell/runtime.sx
|
||||
;;
|
||||
;; Uses the same hk-test framework as tests/parse.sx.
|
||||
;; Loaded by test.sh after: tokenizer.sx + runtime.sx are pre-loaded.
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Test framework boilerplate (mirrors parse.sx)
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(define hk-test-pass 0)
|
||||
(define hk-test-fail 0)
|
||||
(define hk-test-fails (list))
|
||||
|
||||
(define
|
||||
(hk-test name actual expected)
|
||||
(if
|
||||
(= actual expected)
|
||||
(set! hk-test-pass (+ hk-test-pass 1))
|
||||
(do
|
||||
(set! hk-test-fail (+ hk-test-fail 1))
|
||||
(append! hk-test-fails {:actual actual :expected expected :name name}))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 1. Numeric type class helpers
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(hk-test "is-integer? int" (hk-is-integer? 42) true)
|
||||
(hk-test "is-integer? float" (hk-is-integer? 1.5) false)
|
||||
(hk-test "is-float? float" (hk-is-float? 3.14) true)
|
||||
(hk-test "is-float? int" (hk-is-float? 3) false)
|
||||
(hk-test "is-num? int" (hk-is-num? 10) true)
|
||||
(hk-test "is-num? float" (hk-is-num? 1) true)
|
||||
|
||||
(hk-test "to-float" (hk-to-float 5) 5)
|
||||
(hk-test "to-integer trunc" (hk-to-integer 3.7) 3)
|
||||
|
||||
(hk-test "div pos pos" (hk-div 7 2) 3)
|
||||
(hk-test "div neg pos" (hk-div -7 2) -4)
|
||||
(hk-test "div pos neg" (hk-div 7 -2) -4)
|
||||
(hk-test "div neg neg" (hk-div -7 -2) 3)
|
||||
(hk-test "div exact" (hk-div 6 2) 3)
|
||||
|
||||
(hk-test "mod pos pos" (hk-mod 10 3) 1)
|
||||
(hk-test "mod neg pos" (hk-mod -7 3) 2)
|
||||
(hk-test "rem pos pos" (hk-rem 10 3) 1)
|
||||
(hk-test "rem neg pos" (hk-rem -7 3) -1)
|
||||
|
||||
(hk-test "abs pos" (hk-abs 5) 5)
|
||||
(hk-test "abs neg" (hk-abs -5) 5)
|
||||
(hk-test "signum pos" (hk-signum 42) 1)
|
||||
(hk-test "signum neg" (hk-signum -7) -1)
|
||||
(hk-test "signum zero" (hk-signum 0) 0)
|
||||
|
||||
(hk-test "gcd" (hk-gcd 12 8) 4)
|
||||
(hk-test "lcm" (hk-lcm 4 6) 12)
|
||||
(hk-test "even?" (hk-even? 4) true)
|
||||
(hk-test "even? odd" (hk-even? 3) false)
|
||||
(hk-test "odd?" (hk-odd? 7) true)
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 2. Rational numbers
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(let
|
||||
((r (hk-make-rational 1 2)))
|
||||
(do
|
||||
(hk-test "rational?" (hk-rational? r) true)
|
||||
(hk-test "numerator" (hk-numerator r) 1)
|
||||
(hk-test "denominator" (hk-denominator r) 2)))
|
||||
|
||||
(let
|
||||
((r (hk-make-rational 2 4)))
|
||||
(do
|
||||
(hk-test "rat normalise num" (hk-numerator r) 1)
|
||||
(hk-test "rat normalise den" (hk-denominator r) 2)))
|
||||
|
||||
(let
|
||||
((sum (hk-rational-add (hk-make-rational 1 2) (hk-make-rational 1 3))))
|
||||
(do
|
||||
(hk-test "rat-add num" (hk-numerator sum) 5)
|
||||
(hk-test "rat-add den" (hk-denominator sum) 6)))
|
||||
|
||||
(hk-test
|
||||
"rat-to-float"
|
||||
(hk-rational-to-float (hk-make-rational 1 2))
|
||||
0.5)
|
||||
(hk-test "rational? int" (hk-rational? 42) false)
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 3. Lazy evaluation (promises via SX delay)
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(let
|
||||
((p (delay 42)))
|
||||
(hk-test "force promise" (hk-force p) 42))
|
||||
|
||||
(hk-test "force non-promise" (hk-force 99) 99)
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 4. Char utilities — compare via hk-ord to avoid = on char type
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(hk-test "ord A" (hk-ord (integer->char 65)) 65)
|
||||
(hk-test "chr 65" (hk-ord (hk-chr 65)) 65)
|
||||
(hk-test "is-alpha? A" (hk-is-alpha? (integer->char 65)) true)
|
||||
(hk-test "is-alpha? 0" (hk-is-alpha? (integer->char 48)) false)
|
||||
(hk-test "is-digit? 5" (hk-is-digit? (integer->char 53)) true)
|
||||
(hk-test "is-digit? A" (hk-is-digit? (integer->char 65)) false)
|
||||
(hk-test "is-upper? A" (hk-is-upper? (integer->char 65)) true)
|
||||
(hk-test "is-upper? a" (hk-is-upper? (integer->char 97)) false)
|
||||
(hk-test "is-lower? a" (hk-is-lower? (integer->char 97)) true)
|
||||
(hk-test "is-space? spc" (hk-is-space? (integer->char 32)) true)
|
||||
(hk-test "is-space? A" (hk-is-space? (integer->char 65)) false)
|
||||
(hk-test
|
||||
"to-upper a"
|
||||
(hk-ord (hk-to-upper (integer->char 97)))
|
||||
65)
|
||||
(hk-test
|
||||
"to-lower A"
|
||||
(hk-ord (hk-to-lower (integer->char 65)))
|
||||
97)
|
||||
(hk-test
|
||||
"digit-to-int 0"
|
||||
(hk-digit-to-int (integer->char 48))
|
||||
0)
|
||||
(hk-test
|
||||
"digit-to-int 9"
|
||||
(hk-digit-to-int (integer->char 57))
|
||||
9)
|
||||
(hk-test
|
||||
"digit-to-int a"
|
||||
(hk-digit-to-int (integer->char 97))
|
||||
10)
|
||||
(hk-test
|
||||
"digit-to-int F"
|
||||
(hk-digit-to-int (integer->char 70))
|
||||
15)
|
||||
(hk-test "int-to-digit 0" (hk-ord (hk-int-to-digit 0)) 48)
|
||||
(hk-test "int-to-digit 10" (hk-ord (hk-int-to-digit 10)) 97)
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 5. Data.Set
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(hk-test "set-empty is set?" (hk-set? (hk-set-empty)) true)
|
||||
(hk-test "set-null? empty" (hk-set-null? (hk-set-empty)) true)
|
||||
|
||||
(let
|
||||
((s (hk-set-singleton 42)))
|
||||
(do
|
||||
(hk-test "singleton member" (hk-set-member? 42 s) true)
|
||||
(hk-test "singleton size" (hk-set-size s) 1)))
|
||||
|
||||
(let
|
||||
((s (hk-set-from-list (list 1 2 3))))
|
||||
(do
|
||||
(hk-test "from-list member" (hk-set-member? 2 s) true)
|
||||
(hk-test "from-list absent" (hk-set-member? 9 s) false)
|
||||
(hk-test "from-list size" (hk-set-size s) 3)))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 6. Data.List
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(hk-test "head" (hk-head (list 1 2 3)) 1)
|
||||
(hk-test
|
||||
"tail length"
|
||||
(len (hk-tail (list 1 2 3)))
|
||||
2)
|
||||
(hk-test "null? empty" (hk-null? (list)) true)
|
||||
(hk-test "null? non-empty" (hk-null? (list 1)) false)
|
||||
(hk-test
|
||||
"length"
|
||||
(hk-length (list 1 2 3))
|
||||
3)
|
||||
|
||||
(hk-test
|
||||
"take 2"
|
||||
(hk-take 2 (list 1 2 3))
|
||||
(list 1 2))
|
||||
(hk-test "take 0" (hk-take 0 (list 1 2)) (list))
|
||||
(hk-test
|
||||
"take overflow"
|
||||
(hk-take 5 (list 1 2))
|
||||
(list 1 2))
|
||||
(hk-test
|
||||
"drop 1"
|
||||
(hk-drop 1 (list 1 2 3))
|
||||
(list 2 3))
|
||||
(hk-test
|
||||
"drop 0"
|
||||
(hk-drop 0 (list 1 2))
|
||||
(list 1 2))
|
||||
|
||||
(hk-test
|
||||
"take-while"
|
||||
(hk-take-while
|
||||
(fn (x) (< x 3))
|
||||
(list 1 2 3 4))
|
||||
(list 1 2))
|
||||
(hk-test
|
||||
"drop-while"
|
||||
(hk-drop-while
|
||||
(fn (x) (< x 3))
|
||||
(list 1 2 3 4))
|
||||
(list 3 4))
|
||||
|
||||
(hk-test
|
||||
"zip"
|
||||
(hk-zip (list 1 2) (list 3 4))
|
||||
(list (list 1 3) (list 2 4)))
|
||||
(hk-test
|
||||
"zip uneven"
|
||||
(hk-zip
|
||||
(list 1 2 3)
|
||||
(list 4 5))
|
||||
(list (list 1 4) (list 2 5)))
|
||||
|
||||
(hk-test
|
||||
"zip-with +"
|
||||
(hk-zip-with
|
||||
+
|
||||
(list 1 2 3)
|
||||
(list 10 20 30))
|
||||
(list 11 22 33))
|
||||
|
||||
(hk-test
|
||||
"unzip fst"
|
||||
(first
|
||||
(hk-unzip
|
||||
(list (list 1 3) (list 2 4))))
|
||||
(list 1 2))
|
||||
(hk-test
|
||||
"unzip snd"
|
||||
(nth
|
||||
(hk-unzip
|
||||
(list (list 1 3) (list 2 4)))
|
||||
1)
|
||||
(list 3 4))
|
||||
|
||||
(hk-test
|
||||
"elem hit"
|
||||
(hk-elem 2 (list 1 2 3))
|
||||
true)
|
||||
(hk-test
|
||||
"elem miss"
|
||||
(hk-elem 9 (list 1 2 3))
|
||||
false)
|
||||
(hk-test
|
||||
"not-elem"
|
||||
(hk-not-elem 9 (list 1 2 3))
|
||||
true)
|
||||
|
||||
(hk-test
|
||||
"nub"
|
||||
(hk-nub (list 1 2 1 3 2))
|
||||
(list 1 2 3))
|
||||
|
||||
(hk-test
|
||||
"sum"
|
||||
(hk-sum (list 1 2 3 4))
|
||||
10)
|
||||
(hk-test
|
||||
"product"
|
||||
(hk-product (list 1 2 3 4))
|
||||
24)
|
||||
(hk-test
|
||||
"maximum"
|
||||
(hk-maximum (list 3 1 4 1 5))
|
||||
5)
|
||||
(hk-test
|
||||
"minimum"
|
||||
(hk-minimum (list 3 1 4 1 5))
|
||||
1)
|
||||
|
||||
(hk-test
|
||||
"concat"
|
||||
(hk-concat
|
||||
(list (list 1 2) (list 3 4)))
|
||||
(list 1 2 3 4))
|
||||
(hk-test
|
||||
"concat-map"
|
||||
(hk-concat-map
|
||||
(fn (x) (list x (* x x)))
|
||||
(list 1 2 3))
|
||||
(list 1 1 2 4 3 9))
|
||||
|
||||
(hk-test
|
||||
"sort"
|
||||
(hk-sort (list 3 1 4 1 5))
|
||||
(list 1 1 3 4 5))
|
||||
(hk-test
|
||||
"replicate"
|
||||
(hk-replicate 3 0)
|
||||
(list 0 0 0))
|
||||
(hk-test "replicate 0" (hk-replicate 0 99) (list))
|
||||
|
||||
(hk-test
|
||||
"intersperse"
|
||||
(hk-intersperse 0 (list 1 2 3))
|
||||
(list 1 0 2 0 3))
|
||||
(hk-test
|
||||
"intersperse 1"
|
||||
(hk-intersperse 0 (list 1))
|
||||
(list 1))
|
||||
(hk-test "intersperse empty" (hk-intersperse 0 (list)) (list))
|
||||
|
||||
(hk-test
|
||||
"span"
|
||||
(hk-span
|
||||
(fn (x) (< x 3))
|
||||
(list 1 2 3 4))
|
||||
(list (list 1 2) (list 3 4)))
|
||||
(hk-test
|
||||
"break"
|
||||
(hk-break
|
||||
(fn (x) (>= x 3))
|
||||
(list 1 2 3 4))
|
||||
(list (list 1 2) (list 3 4)))
|
||||
|
||||
(hk-test
|
||||
"foldl"
|
||||
(hk-foldl
|
||||
(fn (a b) (- a b))
|
||||
10
|
||||
(list 1 2 3))
|
||||
4)
|
||||
(hk-test
|
||||
"foldr"
|
||||
(hk-foldr cons (list) (list 1 2 3))
|
||||
(list 1 2 3))
|
||||
|
||||
(hk-test
|
||||
"scanl"
|
||||
(hk-scanl + 0 (list 1 2 3))
|
||||
(list 0 1 3 6))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 7. Maybe / Either
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(hk-test "nothing is-nothing?" (hk-is-nothing? hk-nothing) true)
|
||||
(hk-test "nothing is-just?" (hk-is-just? hk-nothing) false)
|
||||
(hk-test "just is-just?" (hk-is-just? (hk-just 42)) true)
|
||||
(hk-test "just is-nothing?" (hk-is-nothing? (hk-just 42)) false)
|
||||
(hk-test "from-just" (hk-from-just (hk-just 99)) 99)
|
||||
(hk-test
|
||||
"from-maybe nothing"
|
||||
(hk-from-maybe 0 hk-nothing)
|
||||
0)
|
||||
(hk-test
|
||||
"from-maybe just"
|
||||
(hk-from-maybe 0 (hk-just 42))
|
||||
42)
|
||||
(hk-test
|
||||
"maybe nothing"
|
||||
(hk-maybe 0 (fn (x) (* x 2)) hk-nothing)
|
||||
0)
|
||||
(hk-test
|
||||
"maybe just"
|
||||
(hk-maybe 0 (fn (x) (* x 2)) (hk-just 5))
|
||||
10)
|
||||
|
||||
(hk-test "left is-left?" (hk-is-left? (hk-left "e")) true)
|
||||
(hk-test "right is-right?" (hk-is-right? (hk-right 42)) true)
|
||||
(hk-test "from-right" (hk-from-right (hk-right 7)) 7)
|
||||
(hk-test
|
||||
"either left"
|
||||
(hk-either (fn (x) (str "L" x)) (fn (x) (str "R" x)) (hk-left "err"))
|
||||
"Lerr")
|
||||
(hk-test
|
||||
"either right"
|
||||
(hk-either
|
||||
(fn (x) (str "L" x))
|
||||
(fn (x) (str "R" x))
|
||||
(hk-right 42))
|
||||
"R42")
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 8. Tuples
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(hk-test "pair" (hk-pair 1 2) (list 1 2))
|
||||
(hk-test "fst" (hk-fst (hk-pair 3 4)) 3)
|
||||
(hk-test "snd" (hk-snd (hk-pair 3 4)) 4)
|
||||
(hk-test
|
||||
"triple"
|
||||
(hk-triple 1 2 3)
|
||||
(list 1 2 3))
|
||||
(hk-test
|
||||
"fst3"
|
||||
(hk-fst3 (hk-triple 7 8 9))
|
||||
7)
|
||||
(hk-test
|
||||
"thd3"
|
||||
(hk-thd3 (hk-triple 7 8 9))
|
||||
9)
|
||||
|
||||
(hk-test "curry" ((hk-curry +) 3 4) 7)
|
||||
(hk-test
|
||||
"uncurry"
|
||||
((hk-uncurry (fn (a b) (* a b))) (list 3 4))
|
||||
12)
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 9. String helpers
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(hk-test "words" (hk-words "hello world") (list "hello" "world"))
|
||||
(hk-test "words leading ws" (hk-words " foo bar") (list "foo" "bar"))
|
||||
(hk-test "words empty" (hk-words "") (list))
|
||||
(hk-test "unwords" (hk-unwords (list "a" "b" "c")) "a b c")
|
||||
(hk-test "unwords single" (hk-unwords (list "x")) "x")
|
||||
|
||||
(hk-test "lines" (hk-lines "a\nb\nc") (list "a" "b" "c"))
|
||||
(hk-test "lines single" (hk-lines "hello") (list "hello"))
|
||||
(hk-test "unlines" (hk-unlines (list "a" "b")) "a\nb\n")
|
||||
|
||||
(hk-test "is-prefix-of yes" (hk-is-prefix-of "he" "hello") true)
|
||||
(hk-test "is-prefix-of no" (hk-is-prefix-of "wo" "hello") false)
|
||||
(hk-test "is-prefix-of eq" (hk-is-prefix-of "hi" "hi") true)
|
||||
(hk-test "is-prefix-of empty" (hk-is-prefix-of "" "hi") true)
|
||||
|
||||
(hk-test "is-suffix-of yes" (hk-is-suffix-of "lo" "hello") true)
|
||||
(hk-test "is-suffix-of no" (hk-is-suffix-of "he" "hello") false)
|
||||
(hk-test "is-suffix-of empty" (hk-is-suffix-of "" "hi") true)
|
||||
|
||||
(hk-test "is-infix-of yes" (hk-is-infix-of "ell" "hello") true)
|
||||
(hk-test "is-infix-of no" (hk-is-infix-of "xyz" "hello") false)
|
||||
(hk-test "is-infix-of empty" (hk-is-infix-of "" "hello") true)
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 10. Show
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(hk-test "show nil" (hk-show nil) "Nothing")
|
||||
(hk-test "show true" (hk-show true) "True")
|
||||
(hk-test "show false" (hk-show false) "False")
|
||||
(hk-test "show int" (hk-show 42) "42")
|
||||
(hk-test "show string" (hk-show "hi") "\"hi\"")
|
||||
(hk-test
|
||||
"show list"
|
||||
(hk-show (list 1 2 3))
|
||||
"[1,2,3]")
|
||||
(hk-test "show empty list" (hk-show (list)) "[]")
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Summary (required by test.sh — last expression is the return value)
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(list hk-test-pass hk-test-fail)
|
||||
@@ -49,8 +49,6 @@ trap "rm -f $TMPFILE" EXIT
|
||||
echo '(load "lib/js/transpile.sx")'
|
||||
echo '(epoch 5)'
|
||||
echo '(load "lib/js/runtime.sx")'
|
||||
echo '(epoch 6)'
|
||||
echo '(load "lib/js/regex.sx")'
|
||||
|
||||
epoch=100
|
||||
for f in "${FIXTURES[@]}"; do
|
||||
|
||||
943
lib/js/regex.sx
943
lib/js/regex.sx
@@ -1,943 +0,0 @@
|
||||
;; lib/js/regex.sx — pure-SX recursive backtracking regex engine
|
||||
;;
|
||||
;; Installed via (js-regex-platform-override! ...) at load time.
|
||||
;; Covers: character classes (\d\w\s . [abc] [^abc] [a-z]),
|
||||
;; anchors (^ $ \b \B), quantifiers (* + ? {n,m} lazy variants),
|
||||
;; groups (capturing + non-capturing), alternation (a|b),
|
||||
;; flags: i (case-insensitive), g (global), m (multiline).
|
||||
;;
|
||||
;; Architecture:
|
||||
;; 1. rx-parse-pattern — pattern string → compiled node list
|
||||
;; 2. rx-match-nodes — recursive backtracker
|
||||
;; 3. rx-exec / rx-test — public interface
|
||||
;; 4. Install as {:test rx-test :exec rx-exec}
|
||||
|
||||
;; ── Utilities ─────────────────────────────────────────────────────
|
||||
|
||||
(define
|
||||
rx-char-at
|
||||
(fn (s i) (if (and (>= i 0) (< i (len s))) (char-at s i) "")))
|
||||
|
||||
(define
|
||||
rx-digit?
|
||||
(fn
|
||||
(c)
|
||||
(and (not (= c "")) (>= (char-code c) 48) (<= (char-code c) 57))))
|
||||
|
||||
(define
|
||||
rx-word?
|
||||
(fn
|
||||
(c)
|
||||
(and
|
||||
(not (= c ""))
|
||||
(or
|
||||
(and (>= (char-code c) 65) (<= (char-code c) 90))
|
||||
(and (>= (char-code c) 97) (<= (char-code c) 122))
|
||||
(and (>= (char-code c) 48) (<= (char-code c) 57))
|
||||
(= c "_")))))
|
||||
|
||||
(define
|
||||
rx-space?
|
||||
(fn
|
||||
(c)
|
||||
(or (= c " ") (= c "\t") (= c "\n") (= c "\r") (= c "\\f") (= c ""))))
|
||||
|
||||
(define rx-newline? (fn (c) (or (= c "\n") (= c "\r"))))
|
||||
|
||||
(define
|
||||
rx-downcase-char
|
||||
(fn
|
||||
(c)
|
||||
(let
|
||||
((cc (char-code c)))
|
||||
(if (and (>= cc 65) (<= cc 90)) (char-from-code (+ cc 32)) c))))
|
||||
|
||||
(define
|
||||
rx-char-eq?
|
||||
(fn
|
||||
(a b ci?)
|
||||
(if ci? (= (rx-downcase-char a) (rx-downcase-char b)) (= a b))))
|
||||
|
||||
(define
|
||||
rx-parse-int
|
||||
(fn
|
||||
(pat i acc)
|
||||
(let
|
||||
((c (rx-char-at pat i)))
|
||||
(if
|
||||
(rx-digit? c)
|
||||
(rx-parse-int pat (+ i 1) (+ (* acc 10) (- (char-code c) 48)))
|
||||
(list acc i)))))
|
||||
|
||||
(define
|
||||
rx-hex-digit-val
|
||||
(fn
|
||||
(c)
|
||||
(cond
|
||||
((and (>= (char-code c) 48) (<= (char-code c) 57))
|
||||
(- (char-code c) 48))
|
||||
((and (>= (char-code c) 65) (<= (char-code c) 70))
|
||||
(+ 10 (- (char-code c) 65)))
|
||||
((and (>= (char-code c) 97) (<= (char-code c) 102))
|
||||
(+ 10 (- (char-code c) 97)))
|
||||
(else -1))))
|
||||
|
||||
(define
|
||||
rx-parse-hex-n
|
||||
(fn
|
||||
(pat i n acc)
|
||||
(if
|
||||
(= n 0)
|
||||
(list (char-from-code acc) i)
|
||||
(let
|
||||
((v (rx-hex-digit-val (rx-char-at pat i))))
|
||||
(if
|
||||
(< v 0)
|
||||
(list (char-from-code acc) i)
|
||||
(rx-parse-hex-n pat (+ i 1) (- n 1) (+ (* acc 16) v)))))))
|
||||
|
||||
;; ── Pattern compiler ──────────────────────────────────────────────
|
||||
|
||||
;; Node types (stored in dicts with "__t__" key):
|
||||
;; literal : {:__t__ "literal" :__c__ char}
|
||||
;; any : {:__t__ "any"}
|
||||
;; class-d : {:__t__ "class-d" :__neg__ bool}
|
||||
;; class-w : {:__t__ "class-w" :__neg__ bool}
|
||||
;; class-s : {:__t__ "class-s" :__neg__ bool}
|
||||
;; char-class: {:__t__ "char-class" :__neg__ bool :__items__ list}
|
||||
;; anchor-start / anchor-end / anchor-word / anchor-nonword
|
||||
;; quant : {:__t__ "quant" :__node__ n :__min__ m :__max__ mx :__lazy__ bool}
|
||||
;; group : {:__t__ "group" :__idx__ i :__nodes__ list}
|
||||
;; ncgroup : {:__t__ "ncgroup" :__nodes__ list}
|
||||
;; alt : {:__t__ "alt" :__branches__ list-of-node-lists}
|
||||
|
||||
;; parse one escape after `\`, returns (node new-i)
|
||||
(define
|
||||
rx-parse-escape
|
||||
(fn
|
||||
(pat i)
|
||||
(let
|
||||
((c (rx-char-at pat i)))
|
||||
(cond
|
||||
((= c "d") (list (dict "__t__" "class-d" "__neg__" false) (+ i 1)))
|
||||
((= c "D") (list (dict "__t__" "class-d" "__neg__" true) (+ i 1)))
|
||||
((= c "w") (list (dict "__t__" "class-w" "__neg__" false) (+ i 1)))
|
||||
((= c "W") (list (dict "__t__" "class-w" "__neg__" true) (+ i 1)))
|
||||
((= c "s") (list (dict "__t__" "class-s" "__neg__" false) (+ i 1)))
|
||||
((= c "S") (list (dict "__t__" "class-s" "__neg__" true) (+ i 1)))
|
||||
((= c "b") (list (dict "__t__" "anchor-word") (+ i 1)))
|
||||
((= c "B") (list (dict "__t__" "anchor-nonword") (+ i 1)))
|
||||
((= c "n") (list (dict "__t__" "literal" "__c__" "\n") (+ i 1)))
|
||||
((= c "r") (list (dict "__t__" "literal" "__c__" "\r") (+ i 1)))
|
||||
((= c "t") (list (dict "__t__" "literal" "__c__" "\t") (+ i 1)))
|
||||
((= c "f") (list (dict "__t__" "literal" "__c__" "\\f") (+ i 1)))
|
||||
((= c "v") (list (dict "__t__" "literal" "__c__" "") (+ i 1)))
|
||||
((= c "u")
|
||||
(let
|
||||
((res (rx-parse-hex-n pat (+ i 1) 4 0)))
|
||||
(list (dict "__t__" "literal" "__c__" (nth res 0)) (nth res 1))))
|
||||
((= c "x")
|
||||
(let
|
||||
((res (rx-parse-hex-n pat (+ i 1) 2 0)))
|
||||
(list (dict "__t__" "literal" "__c__" (nth res 0)) (nth res 1))))
|
||||
(else (list (dict "__t__" "literal" "__c__" c) (+ i 1)))))))
|
||||
|
||||
;; parse a char-class item inside [...], returns (item new-i)
|
||||
(define
|
||||
rx-parse-class-item
|
||||
(fn
|
||||
(pat i)
|
||||
(let
|
||||
((c (rx-char-at pat i)))
|
||||
(cond
|
||||
((= c "\\")
|
||||
(let
|
||||
((esc (rx-parse-escape pat (+ i 1))))
|
||||
(let
|
||||
((node (nth esc 0)) (ni (nth esc 1)))
|
||||
(let
|
||||
((t (get node "__t__")))
|
||||
(cond
|
||||
((= t "class-d")
|
||||
(list
|
||||
(dict "kind" "class-d" "neg" (get node "__neg__"))
|
||||
ni))
|
||||
((= t "class-w")
|
||||
(list
|
||||
(dict "kind" "class-w" "neg" (get node "__neg__"))
|
||||
ni))
|
||||
((= t "class-s")
|
||||
(list
|
||||
(dict "kind" "class-s" "neg" (get node "__neg__"))
|
||||
ni))
|
||||
(else
|
||||
(let
|
||||
((lc (get node "__c__")))
|
||||
(if
|
||||
(and
|
||||
(= (rx-char-at pat ni) "-")
|
||||
(not (= (rx-char-at pat (+ ni 1)) "]")))
|
||||
(let
|
||||
((hi-c (rx-char-at pat (+ ni 1))))
|
||||
(list
|
||||
(dict "kind" "range" "lo" lc "hi" hi-c)
|
||||
(+ ni 2)))
|
||||
(list (dict "kind" "lit" "c" lc) ni)))))))))
|
||||
(else
|
||||
(if
|
||||
(and
|
||||
(not (= c ""))
|
||||
(= (rx-char-at pat (+ i 1)) "-")
|
||||
(not (= (rx-char-at pat (+ i 2)) "]"))
|
||||
(not (= (rx-char-at pat (+ i 2)) "")))
|
||||
(let
|
||||
((hi-c (rx-char-at pat (+ i 2))))
|
||||
(list (dict "kind" "range" "lo" c "hi" hi-c) (+ i 3)))
|
||||
(list (dict "kind" "lit" "c" c) (+ i 1))))))))
|
||||
|
||||
(define
|
||||
rx-parse-class-items
|
||||
(fn
|
||||
(pat i items)
|
||||
(let
|
||||
((c (rx-char-at pat i)))
|
||||
(if
|
||||
(or (= c "]") (= c ""))
|
||||
(list items i)
|
||||
(let
|
||||
((res (rx-parse-class-item pat i)))
|
||||
(begin
|
||||
(append! items (nth res 0))
|
||||
(rx-parse-class-items pat (nth res 1) items)))))))
|
||||
|
||||
;; parse a sequence until stop-ch or EOF; returns (nodes new-i groups-count)
|
||||
(define
|
||||
rx-parse-seq
|
||||
(fn
|
||||
(pat i stop-ch ds)
|
||||
(let
|
||||
((c (rx-char-at pat i)))
|
||||
(cond
|
||||
((= c "") (list (get ds "nodes") i (get ds "groups")))
|
||||
((= c stop-ch) (list (get ds "nodes") i (get ds "groups")))
|
||||
((= c "|") (rx-parse-alt-rest pat i ds))
|
||||
(else
|
||||
(let
|
||||
((res (rx-parse-atom pat i ds)))
|
||||
(let
|
||||
((node (nth res 0)) (ni (nth res 1)) (ds2 (nth res 2)))
|
||||
(let
|
||||
((qres (rx-parse-quant pat ni node)))
|
||||
(begin
|
||||
(append! (get ds2 "nodes") (nth qres 0))
|
||||
(rx-parse-seq pat (nth qres 1) stop-ch ds2))))))))))
|
||||
|
||||
;; when we hit | inside a sequence, collect all alternatives
|
||||
(define
|
||||
rx-parse-alt-rest
|
||||
(fn
|
||||
(pat i ds)
|
||||
(let
|
||||
((left-branch (get ds "nodes")) (branches (list)))
|
||||
(begin
|
||||
(append! branches left-branch)
|
||||
(rx-parse-alt-branches pat i (get ds "groups") branches)))))
|
||||
|
||||
(define
|
||||
rx-parse-alt-branches
|
||||
(fn
|
||||
(pat i n-groups branches)
|
||||
(let
|
||||
((new-nodes (list)) (ds2 (dict "groups" n-groups "nodes" new-nodes)))
|
||||
(let
|
||||
((res (rx-parse-seq pat (+ i 1) "|" ds2)))
|
||||
(begin
|
||||
(append! branches (nth res 0))
|
||||
(let
|
||||
((ni2 (nth res 1)) (g2 (nth res 2)))
|
||||
(if
|
||||
(= (rx-char-at pat ni2) "|")
|
||||
(rx-parse-alt-branches pat ni2 g2 branches)
|
||||
(list
|
||||
(list (dict "__t__" "alt" "__branches__" branches))
|
||||
ni2
|
||||
g2))))))))
|
||||
|
||||
;; parse quantifier suffix, returns (node new-i)
|
||||
(define
|
||||
rx-parse-quant
|
||||
(fn
|
||||
(pat i node)
|
||||
(let
|
||||
((c (rx-char-at pat i)))
|
||||
(cond
|
||||
((= c "*")
|
||||
(let
|
||||
((lazy? (= (rx-char-at pat (+ i 1)) "?")))
|
||||
(list
|
||||
(dict
|
||||
"__t__"
|
||||
"quant"
|
||||
"__node__"
|
||||
node
|
||||
"__min__"
|
||||
0
|
||||
"__max__"
|
||||
-1
|
||||
"__lazy__"
|
||||
lazy?)
|
||||
(if lazy? (+ i 2) (+ i 1)))))
|
||||
((= c "+")
|
||||
(let
|
||||
((lazy? (= (rx-char-at pat (+ i 1)) "?")))
|
||||
(list
|
||||
(dict
|
||||
"__t__"
|
||||
"quant"
|
||||
"__node__"
|
||||
node
|
||||
"__min__"
|
||||
1
|
||||
"__max__"
|
||||
-1
|
||||
"__lazy__"
|
||||
lazy?)
|
||||
(if lazy? (+ i 2) (+ i 1)))))
|
||||
((= c "?")
|
||||
(let
|
||||
((lazy? (= (rx-char-at pat (+ i 1)) "?")))
|
||||
(list
|
||||
(dict
|
||||
"__t__"
|
||||
"quant"
|
||||
"__node__"
|
||||
node
|
||||
"__min__"
|
||||
0
|
||||
"__max__"
|
||||
1
|
||||
"__lazy__"
|
||||
lazy?)
|
||||
(if lazy? (+ i 2) (+ i 1)))))
|
||||
((= c "{")
|
||||
(let
|
||||
((mres (rx-parse-int pat (+ i 1) 0)))
|
||||
(let
|
||||
((mn (nth mres 0)) (mi (nth mres 1)))
|
||||
(let
|
||||
((sep (rx-char-at pat mi)))
|
||||
(cond
|
||||
((= sep "}")
|
||||
(let
|
||||
((lazy? (= (rx-char-at pat (+ mi 1)) "?")))
|
||||
(list
|
||||
(dict
|
||||
"__t__"
|
||||
"quant"
|
||||
"__node__"
|
||||
node
|
||||
"__min__"
|
||||
mn
|
||||
"__max__"
|
||||
mn
|
||||
"__lazy__"
|
||||
lazy?)
|
||||
(if lazy? (+ mi 2) (+ mi 1)))))
|
||||
((= sep ",")
|
||||
(let
|
||||
((c2 (rx-char-at pat (+ mi 1))))
|
||||
(if
|
||||
(= c2 "}")
|
||||
(let
|
||||
((lazy? (= (rx-char-at pat (+ mi 2)) "?")))
|
||||
(list
|
||||
(dict
|
||||
"__t__"
|
||||
"quant"
|
||||
"__node__"
|
||||
node
|
||||
"__min__"
|
||||
mn
|
||||
"__max__"
|
||||
-1
|
||||
"__lazy__"
|
||||
lazy?)
|
||||
(if lazy? (+ mi 3) (+ mi 2))))
|
||||
(let
|
||||
((mxres (rx-parse-int pat (+ mi 1) 0)))
|
||||
(let
|
||||
((mx (nth mxres 0)) (mxi (nth mxres 1)))
|
||||
(let
|
||||
((lazy? (= (rx-char-at pat (+ mxi 1)) "?")))
|
||||
(list
|
||||
(dict
|
||||
"__t__"
|
||||
"quant"
|
||||
"__node__"
|
||||
node
|
||||
"__min__"
|
||||
mn
|
||||
"__max__"
|
||||
mx
|
||||
"__lazy__"
|
||||
lazy?)
|
||||
(if lazy? (+ mxi 2) (+ mxi 1)))))))))
|
||||
(else (list node i)))))))
|
||||
(else (list node i))))))
|
||||
|
||||
;; parse one atom, returns (node new-i new-ds)
|
||||
(define
|
||||
rx-parse-atom
|
||||
(fn
|
||||
(pat i ds)
|
||||
(let
|
||||
((c (rx-char-at pat i)))
|
||||
(cond
|
||||
((= c ".") (list (dict "__t__" "any") (+ i 1) ds))
|
||||
((= c "^") (list (dict "__t__" "anchor-start") (+ i 1) ds))
|
||||
((= c "$") (list (dict "__t__" "anchor-end") (+ i 1) ds))
|
||||
((= c "\\")
|
||||
(let
|
||||
((esc (rx-parse-escape pat (+ i 1))))
|
||||
(list (nth esc 0) (nth esc 1) ds)))
|
||||
((= c "[")
|
||||
(let
|
||||
((neg? (= (rx-char-at pat (+ i 1)) "^")))
|
||||
(let
|
||||
((start (if neg? (+ i 2) (+ i 1))) (items (list)))
|
||||
(let
|
||||
((res (rx-parse-class-items pat start items)))
|
||||
(let
|
||||
((ci (nth res 1)))
|
||||
(list
|
||||
(dict
|
||||
"__t__"
|
||||
"char-class"
|
||||
"__neg__"
|
||||
neg?
|
||||
"__items__"
|
||||
items)
|
||||
(+ ci 1)
|
||||
ds))))))
|
||||
((= c "(")
|
||||
(let
|
||||
((c2 (rx-char-at pat (+ i 1))))
|
||||
(if
|
||||
(and (= c2 "?") (= (rx-char-at pat (+ i 2)) ":"))
|
||||
(let
|
||||
((inner-nodes (list))
|
||||
(inner-ds
|
||||
(dict "groups" (get ds "groups") "nodes" inner-nodes)))
|
||||
(let
|
||||
((res (rx-parse-seq pat (+ i 3) ")" inner-ds)))
|
||||
(list
|
||||
(dict "__t__" "ncgroup" "__nodes__" (nth res 0))
|
||||
(+ (nth res 1) 1)
|
||||
(dict "groups" (nth res 2) "nodes" (get ds "nodes")))))
|
||||
(let
|
||||
((gidx (+ (get ds "groups") 1)) (inner-nodes (list)))
|
||||
(let
|
||||
((inner-ds (dict "groups" gidx "nodes" inner-nodes)))
|
||||
(let
|
||||
((res (rx-parse-seq pat (+ i 1) ")" inner-ds)))
|
||||
(list
|
||||
(dict
|
||||
"__t__"
|
||||
"group"
|
||||
"__idx__"
|
||||
gidx
|
||||
"__nodes__"
|
||||
(nth res 0))
|
||||
(+ (nth res 1) 1)
|
||||
(dict "groups" (nth res 2) "nodes" (get ds "nodes")))))))))
|
||||
(else (list (dict "__t__" "literal" "__c__" c) (+ i 1) ds))))))
|
||||
|
||||
;; top-level compile
|
||||
(define
|
||||
rx-compile
|
||||
(fn
|
||||
(pattern)
|
||||
(let
|
||||
((nodes (list)) (ds (dict "groups" 0 "nodes" nodes)))
|
||||
(let
|
||||
((res (rx-parse-seq pattern 0 "" ds)))
|
||||
(dict "nodes" (nth res 0) "ngroups" (nth res 2))))))
|
||||
|
||||
;; ── Matcher ───────────────────────────────────────────────────────
|
||||
|
||||
;; Match a char-class item against character c
|
||||
(define
|
||||
rx-item-matches?
|
||||
(fn
|
||||
(item c ci?)
|
||||
(let
|
||||
((kind (get item "kind")))
|
||||
(cond
|
||||
((= kind "lit") (rx-char-eq? c (get item "c") ci?))
|
||||
((= kind "range")
|
||||
(let
|
||||
((lo (if ci? (rx-downcase-char (get item "lo")) (get item "lo")))
|
||||
(hi
|
||||
(if ci? (rx-downcase-char (get item "hi")) (get item "hi")))
|
||||
(dc (if ci? (rx-downcase-char c) c)))
|
||||
(and
|
||||
(>= (char-code dc) (char-code lo))
|
||||
(<= (char-code dc) (char-code hi)))))
|
||||
((= kind "class-d")
|
||||
(let ((m (rx-digit? c))) (if (get item "neg") (not m) m)))
|
||||
((= kind "class-w")
|
||||
(let ((m (rx-word? c))) (if (get item "neg") (not m) m)))
|
||||
((= kind "class-s")
|
||||
(let ((m (rx-space? c))) (if (get item "neg") (not m) m)))
|
||||
(else false)))))
|
||||
|
||||
(define
|
||||
rx-class-items-any?
|
||||
(fn
|
||||
(items c ci?)
|
||||
(if
|
||||
(empty? items)
|
||||
false
|
||||
(if
|
||||
(rx-item-matches? (first items) c ci?)
|
||||
true
|
||||
(rx-class-items-any? (rest items) c ci?)))))
|
||||
|
||||
(define
|
||||
rx-class-matches?
|
||||
(fn
|
||||
(node c ci?)
|
||||
(let
|
||||
((neg? (get node "__neg__")) (items (get node "__items__")))
|
||||
(let
|
||||
((hit (rx-class-items-any? items c ci?)))
|
||||
(if neg? (not hit) hit)))))
|
||||
|
||||
;; Word boundary check
|
||||
(define
|
||||
rx-is-word-boundary?
|
||||
(fn
|
||||
(s i slen)
|
||||
(let
|
||||
((before (if (> i 0) (rx-word? (char-at s (- i 1))) false))
|
||||
(after (if (< i slen) (rx-word? (char-at s i)) false)))
|
||||
(not (= before after)))))
|
||||
|
||||
;; ── Core matcher ──────────────────────────────────────────────────
|
||||
;;
|
||||
;; rx-match-nodes : nodes s i slen ci? mi? groups → end-pos or -1
|
||||
;;
|
||||
;; Matches `nodes` starting at position `i` in string `s`.
|
||||
;; Returns the position after the last character consumed, or -1 on failure.
|
||||
;; Mutates `groups` dict to record captures.
|
||||
|
||||
(define
|
||||
rx-match-nodes
|
||||
(fn
|
||||
(nodes s i slen ci? mi? groups)
|
||||
(if
|
||||
(empty? nodes)
|
||||
i
|
||||
(let
|
||||
((node (first nodes)) (rest-nodes (rest nodes)))
|
||||
(let
|
||||
((t (get node "__t__")))
|
||||
(cond
|
||||
((= t "literal")
|
||||
(if
|
||||
(and
|
||||
(< i slen)
|
||||
(rx-char-eq? (char-at s i) (get node "__c__") ci?))
|
||||
(rx-match-nodes rest-nodes s (+ i 1) slen ci? mi? groups)
|
||||
-1))
|
||||
((= t "any")
|
||||
(if
|
||||
(and (< i slen) (not (rx-newline? (char-at s i))))
|
||||
(rx-match-nodes rest-nodes s (+ i 1) slen ci? mi? groups)
|
||||
-1))
|
||||
((= t "class-d")
|
||||
(let
|
||||
((m (and (< i slen) (rx-digit? (char-at s i)))))
|
||||
(if
|
||||
(if (get node "__neg__") (not m) m)
|
||||
(rx-match-nodes rest-nodes s (+ i 1) slen ci? mi? groups)
|
||||
-1)))
|
||||
((= t "class-w")
|
||||
(let
|
||||
((m (and (< i slen) (rx-word? (char-at s i)))))
|
||||
(if
|
||||
(if (get node "__neg__") (not m) m)
|
||||
(rx-match-nodes rest-nodes s (+ i 1) slen ci? mi? groups)
|
||||
-1)))
|
||||
((= t "class-s")
|
||||
(let
|
||||
((m (and (< i slen) (rx-space? (char-at s i)))))
|
||||
(if
|
||||
(if (get node "__neg__") (not m) m)
|
||||
(rx-match-nodes rest-nodes s (+ i 1) slen ci? mi? groups)
|
||||
-1)))
|
||||
((= t "char-class")
|
||||
(if
|
||||
(and (< i slen) (rx-class-matches? node (char-at s i) ci?))
|
||||
(rx-match-nodes rest-nodes s (+ i 1) slen ci? mi? groups)
|
||||
-1))
|
||||
((= t "anchor-start")
|
||||
(if
|
||||
(or
|
||||
(= i 0)
|
||||
(and mi? (rx-newline? (rx-char-at s (- i 1)))))
|
||||
(rx-match-nodes rest-nodes s i slen ci? mi? groups)
|
||||
-1))
|
||||
((= t "anchor-end")
|
||||
(if
|
||||
(or (= i slen) (and mi? (rx-newline? (rx-char-at s i))))
|
||||
(rx-match-nodes rest-nodes s i slen ci? mi? groups)
|
||||
-1))
|
||||
((= t "anchor-word")
|
||||
(if
|
||||
(rx-is-word-boundary? s i slen)
|
||||
(rx-match-nodes rest-nodes s i slen ci? mi? groups)
|
||||
-1))
|
||||
((= t "anchor-nonword")
|
||||
(if
|
||||
(not (rx-is-word-boundary? s i slen))
|
||||
(rx-match-nodes rest-nodes s i slen ci? mi? groups)
|
||||
-1))
|
||||
((= t "group")
|
||||
(let
|
||||
((gidx (get node "__idx__"))
|
||||
(inner (get node "__nodes__")))
|
||||
(let
|
||||
((g-end (rx-match-nodes inner s i slen ci? mi? groups)))
|
||||
(if
|
||||
(>= g-end 0)
|
||||
(begin
|
||||
(dict-set!
|
||||
groups
|
||||
(js-to-string gidx)
|
||||
(substring s i g-end))
|
||||
(let
|
||||
((final-end (rx-match-nodes rest-nodes s g-end slen ci? mi? groups)))
|
||||
(if
|
||||
(>= final-end 0)
|
||||
final-end
|
||||
(begin
|
||||
(dict-set! groups (js-to-string gidx) nil)
|
||||
-1))))
|
||||
-1))))
|
||||
((= t "ncgroup")
|
||||
(let
|
||||
((inner (get node "__nodes__")))
|
||||
(rx-match-nodes
|
||||
(append inner rest-nodes)
|
||||
s
|
||||
i
|
||||
slen
|
||||
ci?
|
||||
mi?
|
||||
groups)))
|
||||
((= t "alt")
|
||||
(let
|
||||
((branches (get node "__branches__")))
|
||||
(rx-try-branches branches rest-nodes s i slen ci? mi? groups)))
|
||||
((= t "quant")
|
||||
(let
|
||||
((inner-node (get node "__node__"))
|
||||
(mn (get node "__min__"))
|
||||
(mx (get node "__max__"))
|
||||
(lazy? (get node "__lazy__")))
|
||||
(if
|
||||
lazy?
|
||||
(rx-quant-lazy
|
||||
inner-node
|
||||
mn
|
||||
mx
|
||||
rest-nodes
|
||||
s
|
||||
i
|
||||
slen
|
||||
ci?
|
||||
mi?
|
||||
groups
|
||||
0)
|
||||
(rx-quant-greedy
|
||||
inner-node
|
||||
mn
|
||||
mx
|
||||
rest-nodes
|
||||
s
|
||||
i
|
||||
slen
|
||||
ci?
|
||||
mi?
|
||||
groups
|
||||
0))))
|
||||
(else -1)))))))
|
||||
|
||||
(define
|
||||
rx-try-branches
|
||||
(fn
|
||||
(branches rest-nodes s i slen ci? mi? groups)
|
||||
(if
|
||||
(empty? branches)
|
||||
-1
|
||||
(let
|
||||
((res (rx-match-nodes (append (first branches) rest-nodes) s i slen ci? mi? groups)))
|
||||
(if
|
||||
(>= res 0)
|
||||
res
|
||||
(rx-try-branches (rest branches) rest-nodes s i slen ci? mi? groups))))))
|
||||
|
||||
;; Greedy: expand as far as possible, then try rest from the longest match
|
||||
;; Strategy: recurse forward (extend first); only try rest when extension fails
|
||||
(define
|
||||
rx-quant-greedy
|
||||
(fn
|
||||
(inner-node mn mx rest-nodes s i slen ci? mi? groups count)
|
||||
(let
|
||||
((can-extend (and (< i slen) (or (= mx -1) (< count mx)))))
|
||||
(if
|
||||
can-extend
|
||||
(let
|
||||
((ni (rx-match-one inner-node s i slen ci? mi? groups)))
|
||||
(if
|
||||
(>= ni 0)
|
||||
(let
|
||||
((res (rx-quant-greedy inner-node mn mx rest-nodes s ni slen ci? mi? groups (+ count 1))))
|
||||
(if
|
||||
(>= res 0)
|
||||
res
|
||||
(if
|
||||
(>= count mn)
|
||||
(rx-match-nodes rest-nodes s i slen ci? mi? groups)
|
||||
-1)))
|
||||
(if
|
||||
(>= count mn)
|
||||
(rx-match-nodes rest-nodes s i slen ci? mi? groups)
|
||||
-1)))
|
||||
(if
|
||||
(>= count mn)
|
||||
(rx-match-nodes rest-nodes s i slen ci? mi? groups)
|
||||
-1)))))
|
||||
|
||||
;; Lazy: try rest first, extend only if rest fails
|
||||
(define
|
||||
rx-quant-lazy
|
||||
(fn
|
||||
(inner-node mn mx rest-nodes s i slen ci? mi? groups count)
|
||||
(if
|
||||
(>= count mn)
|
||||
(let
|
||||
((res (rx-match-nodes rest-nodes s i slen ci? mi? groups)))
|
||||
(if
|
||||
(>= res 0)
|
||||
res
|
||||
(if
|
||||
(and (< i slen) (or (= mx -1) (< count mx)))
|
||||
(let
|
||||
((ni (rx-match-one inner-node s i slen ci? mi? groups)))
|
||||
(if
|
||||
(>= ni 0)
|
||||
(rx-quant-lazy
|
||||
inner-node
|
||||
mn
|
||||
mx
|
||||
rest-nodes
|
||||
s
|
||||
ni
|
||||
slen
|
||||
ci?
|
||||
mi?
|
||||
groups
|
||||
(+ count 1))
|
||||
-1))
|
||||
-1)))
|
||||
(if
|
||||
(< i slen)
|
||||
(let
|
||||
((ni (rx-match-one inner-node s i slen ci? mi? groups)))
|
||||
(if
|
||||
(>= ni 0)
|
||||
(rx-quant-lazy
|
||||
inner-node
|
||||
mn
|
||||
mx
|
||||
rest-nodes
|
||||
s
|
||||
ni
|
||||
slen
|
||||
ci?
|
||||
mi?
|
||||
groups
|
||||
(+ count 1))
|
||||
-1))
|
||||
-1))))
|
||||
|
||||
;; Match a single node at position i, return new pos or -1
|
||||
(define
|
||||
rx-match-one
|
||||
(fn
|
||||
(node s i slen ci? mi? groups)
|
||||
(rx-match-nodes (list node) s i slen ci? mi? groups)))
|
||||
|
||||
;; ── Engine entry points ───────────────────────────────────────────
|
||||
|
||||
;; Try matching at exactly position i. Returns result dict or nil.
|
||||
(define
|
||||
rx-try-at
|
||||
(fn
|
||||
(compiled s i slen ci? mi?)
|
||||
(let
|
||||
((nodes (get compiled "nodes")) (ngroups (get compiled "ngroups")))
|
||||
(let
|
||||
((groups (dict)))
|
||||
(let
|
||||
((end (rx-match-nodes nodes s i slen ci? mi? groups)))
|
||||
(if
|
||||
(>= end 0)
|
||||
(dict "start" i "end" end "groups" groups "ngroups" ngroups)
|
||||
nil))))))
|
||||
|
||||
;; Find first match scanning from search-start.
|
||||
(define
|
||||
rx-find-from
|
||||
(fn
|
||||
(compiled s search-start slen ci? mi?)
|
||||
(if
|
||||
(> search-start slen)
|
||||
nil
|
||||
(let
|
||||
((res (rx-try-at compiled s search-start slen ci? mi?)))
|
||||
(if
|
||||
res
|
||||
res
|
||||
(rx-find-from compiled s (+ search-start 1) slen ci? mi?))))))
|
||||
|
||||
;; Build exec result dict from raw match result
|
||||
(define
|
||||
rx-build-exec-result
|
||||
(fn
|
||||
(s match-res)
|
||||
(let
|
||||
((start (get match-res "start"))
|
||||
(end (get match-res "end"))
|
||||
(groups (get match-res "groups"))
|
||||
(ngroups (get match-res "ngroups")))
|
||||
(let
|
||||
((matched (substring s start end))
|
||||
(caps (rx-build-captures groups ngroups 1)))
|
||||
(dict "match" matched "index" start "input" s "groups" caps)))))
|
||||
|
||||
(define
|
||||
rx-build-captures
|
||||
(fn
|
||||
(groups ngroups idx)
|
||||
(if
|
||||
(> idx ngroups)
|
||||
(list)
|
||||
(let
|
||||
((cap (get groups (js-to-string idx))))
|
||||
(cons
|
||||
(if (= cap nil) :js-undefined cap)
|
||||
(rx-build-captures groups ngroups (+ idx 1)))))))
|
||||
|
||||
;; ── Public interface ──────────────────────────────────────────────
|
||||
|
||||
;; Lazy compile: build NFA on first use, cache under "__compiled__"
|
||||
(define
|
||||
rx-ensure-compiled!
|
||||
(fn
|
||||
(rx)
|
||||
(if
|
||||
(dict-has? rx "__compiled__")
|
||||
(get rx "__compiled__")
|
||||
(let
|
||||
((c (rx-compile (get rx "source"))))
|
||||
(begin (dict-set! rx "__compiled__" c) c)))))
|
||||
|
||||
(define
|
||||
rx-test
|
||||
(fn
|
||||
(rx s)
|
||||
(let
|
||||
((compiled (rx-ensure-compiled! rx))
|
||||
(ci? (get rx "ignoreCase"))
|
||||
(mi? (get rx "multiline"))
|
||||
(slen (len s)))
|
||||
(let
|
||||
((start (if (get rx "global") (let ((li (get rx "lastIndex"))) (if (number? li) li 0)) 0)))
|
||||
(let
|
||||
((res (rx-find-from compiled s start slen ci? mi?)))
|
||||
(if
|
||||
(get rx "global")
|
||||
(begin
|
||||
(dict-set! rx "lastIndex" (if res (get res "end") 0))
|
||||
(if res true false))
|
||||
(if res true false)))))))
|
||||
|
||||
(define
|
||||
rx-exec
|
||||
(fn
|
||||
(rx s)
|
||||
(let
|
||||
((compiled (rx-ensure-compiled! rx))
|
||||
(ci? (get rx "ignoreCase"))
|
||||
(mi? (get rx "multiline"))
|
||||
(slen (len s)))
|
||||
(let
|
||||
((start (if (get rx "global") (let ((li (get rx "lastIndex"))) (if (number? li) li 0)) 0)))
|
||||
(let
|
||||
((res (rx-find-from compiled s start slen ci? mi?)))
|
||||
(if
|
||||
res
|
||||
(begin
|
||||
(when
|
||||
(get rx "global")
|
||||
(dict-set! rx "lastIndex" (get res "end")))
|
||||
(rx-build-exec-result s res))
|
||||
(begin
|
||||
(when (get rx "global") (dict-set! rx "lastIndex" 0))
|
||||
nil)))))))
|
||||
|
||||
;; match-all for String.prototype.matchAll
|
||||
(define
|
||||
js-regex-match-all
|
||||
(fn
|
||||
(rx s)
|
||||
(let
|
||||
((compiled (rx-ensure-compiled! rx))
|
||||
(ci? (get rx "ignoreCase"))
|
||||
(mi? (get rx "multiline"))
|
||||
(slen (len s))
|
||||
(results (list)))
|
||||
(rx-match-all-loop compiled s 0 slen ci? mi? results))))
|
||||
|
||||
(define
|
||||
rx-match-all-loop
|
||||
(fn
|
||||
(compiled s i slen ci? mi? results)
|
||||
(if
|
||||
(> i slen)
|
||||
results
|
||||
(let
|
||||
((res (rx-find-from compiled s i slen ci? mi?)))
|
||||
(if
|
||||
res
|
||||
(begin
|
||||
(append! results (rx-build-exec-result s res))
|
||||
(let
|
||||
((next (get res "end")))
|
||||
(rx-match-all-loop
|
||||
compiled
|
||||
s
|
||||
(if (= next i) (+ i 1) next)
|
||||
slen
|
||||
ci?
|
||||
mi?
|
||||
results)))
|
||||
results)))))
|
||||
|
||||
;; ── Install platform ──────────────────────────────────────────────
|
||||
|
||||
(js-regex-platform-override! "test" rx-test)
|
||||
(js-regex-platform-override! "exec" rx-exec)
|
||||
@@ -2032,15 +2032,7 @@
|
||||
(&rest args)
|
||||
(cond
|
||||
((= (len args) 0) nil)
|
||||
((js-regex? (nth args 0))
|
||||
(let
|
||||
((rx (nth args 0)))
|
||||
(let
|
||||
((impl (get __js_regex_platform__ "exec")))
|
||||
(if
|
||||
(js-undefined? impl)
|
||||
(js-regex-stub-exec rx s)
|
||||
(impl rx s)))))
|
||||
((js-regex? (nth args 0)) (js-regex-stub-exec (nth args 0) s))
|
||||
(else
|
||||
(let
|
||||
((needle (js-to-string (nth args 0))))
|
||||
@@ -2049,7 +2041,7 @@
|
||||
(if
|
||||
(= idx -1)
|
||||
nil
|
||||
(let ((res (list))) (begin (append! res needle) res)))))))))
|
||||
(let ((res (list))) (append! res needle) res))))))))
|
||||
((= name "at")
|
||||
(fn
|
||||
(i)
|
||||
@@ -2107,20 +2099,6 @@
|
||||
((= name "toWellFormed") (fn () s))
|
||||
(else js-undefined))))
|
||||
|
||||
(define __js_tdz_sentinel__ (dict "__tdz__" true))
|
||||
|
||||
(define js-tdz? (fn (v) (and (dict? v) (dict-has? v "__tdz__"))))
|
||||
|
||||
(define
|
||||
js-tdz-check
|
||||
(fn
|
||||
(name val)
|
||||
(if
|
||||
(js-tdz? val)
|
||||
(raise
|
||||
(TypeError (str "Cannot access '" name "' before initialization")))
|
||||
val)))
|
||||
|
||||
(define
|
||||
js-string-slice
|
||||
(fn
|
||||
|
||||
239
lib/js/stdlib.sx
239
lib/js/stdlib.sx
@@ -1,239 +0,0 @@
|
||||
;; lib/js/stdlib.sx — Phase 22 JS additions
|
||||
;;
|
||||
;; Adds to lib/js/runtime.sx (already loaded):
|
||||
;; 1. Bitwise binary ops (js-bitand/bitor/bitxor/lshift/rshift/urshift/bitnot)
|
||||
;; 2. Map class (arbitrary-key hash map via list of pairs)
|
||||
;; 3. Set class (uniqueness collection via SX make-set)
|
||||
;; 4. RegExp constructor (wraps js-regex-new already in runtime)
|
||||
;; 5. Wires Map / Set / RegExp into js-global
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 1. Bitwise binary ops
|
||||
;; JS coerces operands to 32-bit signed int before applying the op.
|
||||
;; Use truncate (not js-num-to-int) since integer / 0 crashes the evaluator.
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(define
|
||||
(js-bitand a b)
|
||||
(bitwise-and (truncate (js-to-number a)) (truncate (js-to-number b))))
|
||||
|
||||
(define
|
||||
(js-bitor a b)
|
||||
(bitwise-or (truncate (js-to-number a)) (truncate (js-to-number b))))
|
||||
|
||||
(define
|
||||
(js-bitxor a b)
|
||||
(bitwise-xor (truncate (js-to-number a)) (truncate (js-to-number b))))
|
||||
|
||||
;; << : left-shift by (b mod 32) positions
|
||||
(define
|
||||
(js-lshift a b)
|
||||
(arithmetic-shift
|
||||
(truncate (js-to-number a))
|
||||
(modulo (truncate (js-to-number b)) 32)))
|
||||
|
||||
;; >> : arithmetic right-shift (sign-extending)
|
||||
(define
|
||||
(js-rshift a b)
|
||||
(arithmetic-shift
|
||||
(truncate (js-to-number a))
|
||||
(- 0 (modulo (truncate (js-to-number b)) 32))))
|
||||
|
||||
;; >>> : logical right-shift (zero-extending)
|
||||
;; Convert to uint32 first, then divide by 2^n.
|
||||
(define
|
||||
(js-urshift a b)
|
||||
(let
|
||||
((u32 (modulo (truncate (js-to-number a)) 4294967296))
|
||||
(n (modulo (truncate (js-to-number b)) 32)))
|
||||
(quotient u32 (arithmetic-shift 1 n))))
|
||||
|
||||
;; ~ : bitwise NOT — equivalent to -(n+1) in 32-bit signed arithmetic
|
||||
(define (js-bitnot a) (bitwise-not (truncate (js-to-number a))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 2. Map class
|
||||
;; Stored as {:__js_map__ true :size N :_pairs (list (list key val) ...)}
|
||||
;; Mutation via dict-set! on the underlying dict.
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(define
|
||||
(js-map-new)
|
||||
(let
|
||||
((m (dict)))
|
||||
(dict-set! m "__js_map__" true)
|
||||
(dict-set! m "size" 0)
|
||||
(dict-set! m "_pairs" (list))
|
||||
m))
|
||||
|
||||
(define (js-map? v) (and (dict? v) (dict-has? v "__js_map__")))
|
||||
|
||||
;; Linear scan for key using ===; returns index or -1
|
||||
(define
|
||||
(js-map-find-idx pairs k)
|
||||
(letrec
|
||||
((go (fn (ps i) (cond ((= (len ps) 0) -1) ((js-strict-eq (first (first ps)) k) i) (else (go (rest ps) (+ i 1)))))))
|
||||
(go pairs 0)))
|
||||
|
||||
(define
|
||||
(js-map-get m k)
|
||||
(letrec
|
||||
((go (fn (ps) (if (= (len ps) 0) js-undefined (if (js-strict-eq (first (first ps)) k) (nth (first ps) 1) (go (rest ps)))))))
|
||||
(go (get m "_pairs"))))
|
||||
|
||||
;; Replace element at index i in list
|
||||
(define
|
||||
(js-list-set-nth lst i newval)
|
||||
(letrec
|
||||
((go (fn (ps j) (if (= (len ps) 0) (list) (cons (if (= j i) newval (first ps)) (go (rest ps) (+ j 1)))))))
|
||||
(go lst 0)))
|
||||
|
||||
;; Remove element at index i from list
|
||||
(define
|
||||
(js-list-remove-nth lst i)
|
||||
(letrec
|
||||
((go (fn (ps j) (if (= (len ps) 0) (list) (if (= j i) (go (rest ps) (+ j 1)) (cons (first ps) (go (rest ps) (+ j 1))))))))
|
||||
(go lst 0)))
|
||||
|
||||
(define
|
||||
(js-map-set! m k v)
|
||||
(let
|
||||
((pairs (get m "_pairs")) (idx (js-map-find-idx (get m "_pairs") k)))
|
||||
(if
|
||||
(= idx -1)
|
||||
(begin
|
||||
(dict-set! m "_pairs" (append pairs (list (list k v))))
|
||||
(dict-set! m "size" (+ (get m "size") 1)))
|
||||
(dict-set! m "_pairs" (js-list-set-nth pairs idx (list k v)))))
|
||||
m)
|
||||
|
||||
(define
|
||||
(js-map-has m k)
|
||||
(not (= (js-map-find-idx (get m "_pairs") k) -1)))
|
||||
|
||||
(define
|
||||
(js-map-delete! m k)
|
||||
(let
|
||||
((idx (js-map-find-idx (get m "_pairs") k)))
|
||||
(when
|
||||
(not (= idx -1))
|
||||
(dict-set! m "_pairs" (js-list-remove-nth (get m "_pairs") idx))
|
||||
(dict-set! m "size" (- (get m "size") 1))))
|
||||
m)
|
||||
|
||||
(define
|
||||
(js-map-clear! m)
|
||||
(dict-set! m "_pairs" (list))
|
||||
(dict-set! m "size" 0)
|
||||
m)
|
||||
|
||||
(define (js-map-keys m) (map first (get m "_pairs")))
|
||||
(define
|
||||
(js-map-vals m)
|
||||
(map (fn (p) (nth p 1)) (get m "_pairs")))
|
||||
(define (js-map-entries m) (get m "_pairs"))
|
||||
|
||||
(define
|
||||
(js-map-for-each m cb)
|
||||
(for-each
|
||||
(fn (p) (cb (nth p 1) (first p) m))
|
||||
(get m "_pairs"))
|
||||
js-undefined)
|
||||
|
||||
;; Map method dispatch (called from js-object-method-call in runtime)
|
||||
(define
|
||||
(js-map-method m name args)
|
||||
(cond
|
||||
((= name "set")
|
||||
(js-map-set! m (nth args 0) (nth args 1)))
|
||||
((= name "get") (js-map-get m (nth args 0)))
|
||||
((= name "has") (js-map-has m (nth args 0)))
|
||||
((= name "delete") (js-map-delete! m (nth args 0)))
|
||||
((= name "clear") (js-map-clear! m))
|
||||
((= name "keys") (js-map-keys m))
|
||||
((= name "values") (js-map-vals m))
|
||||
((= name "entries") (js-map-entries m))
|
||||
((= name "forEach") (js-map-for-each m (nth args 0)))
|
||||
((= name "toString") "[object Map]")
|
||||
(else js-undefined)))
|
||||
|
||||
(define Map {:__callable__ (fn (&rest args) (let ((m (js-map-new))) (when (and (> (len args) 0) (list? (nth args 0))) (for-each (fn (entry) (js-map-set! m (nth entry 0) (nth entry 1))) (nth args 0))) m)) :prototype {:entries (fn (&rest a) (js-map-entries (js-this))) :delete (fn (&rest a) (js-map-delete! (js-this) (nth a 0))) :get (fn (&rest a) (js-map-get (js-this) (nth a 0))) :values (fn (&rest a) (js-map-vals (js-this))) :toString (fn () "[object Map]") :has (fn (&rest a) (js-map-has (js-this) (nth a 0))) :set (fn (&rest a) (js-map-set! (js-this) (nth a 0) (nth a 1))) :forEach (fn (&rest a) (js-map-for-each (js-this) (nth a 0))) :clear (fn (&rest a) (js-map-clear! (js-this))) :keys (fn (&rest a) (js-map-keys (js-this)))}})
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 3. Set class
|
||||
;; {:__js_set__ true :size N :_set <sx-set>}
|
||||
;; Note: set-member?/set-add!/set-remove! all take (set item) order.
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(define
|
||||
(js-set-new)
|
||||
(let
|
||||
((s (dict)))
|
||||
(dict-set! s "__js_set__" true)
|
||||
(dict-set! s "size" 0)
|
||||
(dict-set! s "_set" (make-set))
|
||||
s))
|
||||
|
||||
(define (js-set? v) (and (dict? v) (dict-has? v "__js_set__")))
|
||||
|
||||
(define
|
||||
(js-set-add! s v)
|
||||
(let
|
||||
((sx (get s "_set")))
|
||||
(when
|
||||
(not (set-member? sx v))
|
||||
(set-add! sx v)
|
||||
(dict-set! s "size" (+ (get s "size") 1))))
|
||||
s)
|
||||
|
||||
(define (js-set-has s v) (set-member? (get s "_set") v))
|
||||
|
||||
(define
|
||||
(js-set-delete! s v)
|
||||
(let
|
||||
((sx (get s "_set")))
|
||||
(when
|
||||
(set-member? sx v)
|
||||
(set-remove! sx v)
|
||||
(dict-set! s "size" (- (get s "size") 1))))
|
||||
s)
|
||||
|
||||
(define
|
||||
(js-set-clear! s)
|
||||
(dict-set! s "_set" (make-set))
|
||||
(dict-set! s "size" 0)
|
||||
s)
|
||||
|
||||
(define (js-set-vals s) (set->list (get s "_set")))
|
||||
|
||||
(define
|
||||
(js-set-for-each s cb)
|
||||
(for-each (fn (v) (cb v v s)) (set->list (get s "_set")))
|
||||
js-undefined)
|
||||
|
||||
(define Set {:__callable__ (fn (&rest args) (let ((s (js-set-new))) (when (and (> (len args) 0) (list? (nth args 0))) (for-each (fn (v) (js-set-add! s v)) (nth args 0))) s)) :prototype {:entries (fn (&rest a) (map (fn (v) (list v v)) (js-set-vals (js-this)))) :delete (fn (&rest a) (js-set-delete! (js-this) (nth a 0))) :values (fn (&rest a) (js-set-vals (js-this))) :add (fn (&rest a) (js-set-add! (js-this) (nth a 0))) :toString (fn () "[object Set]") :has (fn (&rest a) (js-set-has (js-this) (nth a 0))) :forEach (fn (&rest a) (js-set-for-each (js-this) (nth a 0))) :clear (fn (&rest a) (js-set-clear! (js-this))) :keys (fn (&rest a) (js-set-vals (js-this)))}})
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 4. RegExp constructor — callable lambda wrapping js-regex-new
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(define
|
||||
RegExp
|
||||
(fn
|
||||
(&rest args)
|
||||
(cond
|
||||
((= (len args) 0) (js-regex-new "" ""))
|
||||
((= (len args) 1)
|
||||
(js-regex-new (js-to-string (nth args 0)) ""))
|
||||
(else
|
||||
(js-regex-new
|
||||
(js-to-string (nth args 0))
|
||||
(js-to-string (nth args 1)))))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 5. Wire new globals into js-global
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(dict-set! js-global "Map" Map)
|
||||
(dict-set! js-global "Set" Set)
|
||||
(dict-set! js-global "RegExp" RegExp)
|
||||
239
lib/js/test.sh
239
lib/js/test.sh
@@ -33,10 +33,6 @@ cat > "$TMPFILE" << 'EPOCHS'
|
||||
(load "lib/js/transpile.sx")
|
||||
(epoch 5)
|
||||
(load "lib/js/runtime.sx")
|
||||
(epoch 6)
|
||||
(load "lib/js/regex.sx")
|
||||
(epoch 7)
|
||||
(load "lib/js/stdlib.sx")
|
||||
|
||||
;; ── Phase 0: stubs still behave ─────────────────────────────────
|
||||
(epoch 10)
|
||||
@@ -1327,166 +1323,6 @@ cat > "$TMPFILE" << 'EPOCHS'
|
||||
(epoch 3505)
|
||||
(eval "(js-eval \"var a = {length: 3, 0: 10, 1: 20, 2: 30}; var sum = 0; Array.prototype.forEach.call(a, function(x){sum += x;}); sum\")")
|
||||
|
||||
;; ── Phase 12: Regex engine ────────────────────────────────────────
|
||||
;; Platform is installed (test key is a function, not undefined)
|
||||
(epoch 5000)
|
||||
(eval "(js-undefined? (get __js_regex_platform__ \"test\"))")
|
||||
(epoch 5001)
|
||||
(eval "(js-eval \"/foo/.test('hi foo bar')\")")
|
||||
(epoch 5002)
|
||||
(eval "(js-eval \"/foo/.test('hi bar')\")")
|
||||
;; Case-insensitive flag
|
||||
(epoch 5003)
|
||||
(eval "(js-eval \"/FOO/i.test('hello foo world')\")")
|
||||
;; Anchors
|
||||
(epoch 5004)
|
||||
(eval "(js-eval \"/^hello/.test('hello world')\")")
|
||||
(epoch 5005)
|
||||
(eval "(js-eval \"/^hello/.test('say hello')\")")
|
||||
(epoch 5006)
|
||||
(eval "(js-eval \"/world$/.test('hello world')\")")
|
||||
;; Character classes
|
||||
(epoch 5007)
|
||||
(eval "(js-eval \"/\\\\d+/.test('abc 123')\")")
|
||||
(epoch 5008)
|
||||
(eval "(js-eval \"/\\\\w+/.test('hello')\")")
|
||||
(epoch 5009)
|
||||
(eval "(js-eval \"/[abc]/.test('dog')\")")
|
||||
(epoch 5010)
|
||||
(eval "(js-eval \"/[abc]/.test('cat')\")")
|
||||
;; Quantifiers
|
||||
(epoch 5011)
|
||||
(eval "(js-eval \"/a*b/.test('b')\")")
|
||||
(epoch 5012)
|
||||
(eval "(js-eval \"/a+b/.test('b')\")")
|
||||
(epoch 5013)
|
||||
(eval "(js-eval \"/a{2,3}/.test('aa')\")")
|
||||
(epoch 5014)
|
||||
(eval "(js-eval \"/a{2,3}/.test('a')\")")
|
||||
;; Dot
|
||||
(epoch 5015)
|
||||
(eval "(js-eval \"/h.llo/.test('hello')\")")
|
||||
(epoch 5016)
|
||||
(eval "(js-eval \"/h.llo/.test('hllo')\")")
|
||||
;; exec result
|
||||
(epoch 5017)
|
||||
(eval "(js-eval \"var m = /foo(\\\\w+)/.exec('foobar'); m.match\")")
|
||||
(epoch 5018)
|
||||
(eval "(js-eval \"var m = /foo(\\\\w+)/.exec('foobar'); m.index\")")
|
||||
(epoch 5019)
|
||||
(eval "(js-eval \"var m = /foo(\\\\w+)/.exec('foobar'); m.groups[0]\")")
|
||||
;; Alternation
|
||||
(epoch 5020)
|
||||
(eval "(js-eval \"/cat|dog/.test('I have a dog')\")")
|
||||
(epoch 5021)
|
||||
(eval "(js-eval \"/cat|dog/.test('I have a fish')\")")
|
||||
;; Non-capturing group
|
||||
(epoch 5022)
|
||||
(eval "(js-eval \"/(?:foo)+/.test('foofoo')\")")
|
||||
;; Negated char class
|
||||
(epoch 5023)
|
||||
(eval "(js-eval \"/[^abc]/.test('d')\")")
|
||||
(epoch 5024)
|
||||
(eval "(js-eval \"/[^abc]/.test('a')\")")
|
||||
;; Range inside char class
|
||||
(epoch 5025)
|
||||
(eval "(js-eval \"/[a-z]+/.test('hello')\")")
|
||||
;; Word boundary
|
||||
(epoch 5026)
|
||||
(eval "(js-eval \"/\\\\bword\\\\b/.test('a word here')\")")
|
||||
(epoch 5027)
|
||||
(eval "(js-eval \"/\\\\bword\\\\b/.test('password')\")")
|
||||
;; Lazy quantifier
|
||||
(epoch 5028)
|
||||
(eval "(js-eval \"var m = /a+?/.exec('aaa'); m.match\")")
|
||||
;; Global flag exec
|
||||
(epoch 5029)
|
||||
(eval "(js-eval \"var r=/\\\\d+/g; r.exec('a1b2'); r.exec('a1b2').match\")")
|
||||
;; String.prototype.match with regex
|
||||
(epoch 5030)
|
||||
(eval "(js-eval \"'hello world'.match(/\\\\w+/).match\")")
|
||||
;; String.prototype.search
|
||||
(epoch 5031)
|
||||
(eval "(js-eval \"'hello world'.search(/world/)\")")
|
||||
;; String.prototype.replace with regex
|
||||
(epoch 5032)
|
||||
(eval "(js-eval \"'hello world'.replace(/world/, 'there')\")")
|
||||
;; multiline anchor
|
||||
(epoch 5033)
|
||||
(eval "(js-eval \"/^bar/m.test('foo\\nbar')\")")
|
||||
|
||||
;; ── Phase 13: let/const TDZ infrastructure ───────────────────────
|
||||
;; The TDZ sentinel and checker are defined in runtime.sx.
|
||||
;; let/const bindings work normally after initialization.
|
||||
(epoch 5100)
|
||||
(eval "(js-eval \"let x = 5; x\")")
|
||||
(epoch 5101)
|
||||
(eval "(js-eval \"const y = 42; y\")")
|
||||
;; TDZ sentinel exists and is detectable
|
||||
(epoch 5102)
|
||||
(eval "(js-tdz? __js_tdz_sentinel__)")
|
||||
;; js-tdz-check passes through non-sentinel values
|
||||
(epoch 5103)
|
||||
(eval "(js-tdz-check \"x\" 42)")
|
||||
|
||||
;; ── Phase 22: Bitwise ops ────────────────────────────────────────
|
||||
(epoch 6000)
|
||||
(eval "(js-bitand 5 3)")
|
||||
(epoch 6001)
|
||||
(eval "(js-bitor 5 3)")
|
||||
(epoch 6002)
|
||||
(eval "(js-bitxor 5 3)")
|
||||
(epoch 6003)
|
||||
(eval "(js-lshift 1 4)")
|
||||
(epoch 6004)
|
||||
(eval "(js-rshift 32 2)")
|
||||
(epoch 6005)
|
||||
(eval "(js-rshift -8 1)")
|
||||
(epoch 6006)
|
||||
(eval "(js-urshift 4294967292 2)")
|
||||
(epoch 6007)
|
||||
(eval "(js-bitnot 0)")
|
||||
|
||||
;; ── Phase 22: Map ─────────────────────────────────────────────────
|
||||
(epoch 6010)
|
||||
(eval "(js-map? (js-map-new))")
|
||||
(epoch 6011)
|
||||
(eval "(get (js-map-set! (js-map-new) \"k\" 42) \"size\")")
|
||||
(epoch 6012)
|
||||
(eval "(let ((m (js-map-new))) (js-map-set! m \"a\" 1) (js-map-get m \"a\"))")
|
||||
(epoch 6013)
|
||||
(eval "(let ((m (js-map-new))) (js-map-set! m \"x\" 9) (js-map-has m \"x\"))")
|
||||
(epoch 6014)
|
||||
(eval "(let ((m (js-map-new))) (js-map-set! m \"x\" 9) (js-map-has m \"y\"))")
|
||||
(epoch 6015)
|
||||
(eval "(let ((m (js-map-new))) (js-map-set! m \"a\" 1) (js-map-set! m \"b\" 2) (get m \"size\"))")
|
||||
(epoch 6016)
|
||||
(eval "(let ((m (js-map-new))) (js-map-set! m \"a\" 1) (js-map-delete! m \"a\") (get m \"size\"))")
|
||||
(epoch 6017)
|
||||
(eval "(let ((m (js-map-new))) (js-map-set! m \"a\" 1) (js-map-set! m \"a\" 99) (js-map-get m \"a\"))")
|
||||
|
||||
;; ── Phase 22: Set ─────────────────────────────────────────────────
|
||||
(epoch 6020)
|
||||
(eval "(js-set? (js-set-new))")
|
||||
(epoch 6021)
|
||||
(eval "(let ((s (js-set-new))) (js-set-add! s 1) (js-set-has s 1))")
|
||||
(epoch 6022)
|
||||
(eval "(let ((s (js-set-new))) (js-set-add! s 1) (js-set-has s 2))")
|
||||
(epoch 6023)
|
||||
(eval "(let ((s (js-set-new))) (js-set-add! s 1) (js-set-add! s 1) (get s \"size\"))")
|
||||
(epoch 6024)
|
||||
(eval "(let ((s (js-set-new))) (js-set-add! s 1) (js-set-add! s 2) (get s \"size\"))")
|
||||
(epoch 6025)
|
||||
(eval "(let ((s (js-set-new))) (js-set-add! s 1) (js-set-delete! s 1) (get s \"size\"))")
|
||||
|
||||
;; ── Phase 22: RegExp constructor ──────────────────────────────────
|
||||
(epoch 6030)
|
||||
(eval "(js-regex? (RegExp \"ab\" \"i\"))")
|
||||
(epoch 6031)
|
||||
(eval "(get (RegExp \"hello\" \"gi\") \"global\")")
|
||||
(epoch 6032)
|
||||
(eval "(get (RegExp \"foo\" \"i\") \"ignoreCase\")")
|
||||
|
||||
EPOCHS
|
||||
|
||||
|
||||
@@ -2206,81 +2042,6 @@ check 3503 "indexOf.call arrLike" '1'
|
||||
check 3504 "filter.call arrLike" '"2,3"'
|
||||
check 3505 "forEach.call arrLike sum" '60'
|
||||
|
||||
# ── Phase 12: Regex engine ────────────────────────────────────────
|
||||
check 5000 "regex platform installed" 'false'
|
||||
check 5001 "/foo/ matches" 'true'
|
||||
check 5002 "/foo/ no match" 'false'
|
||||
check 5003 "/FOO/i case-insensitive" 'true'
|
||||
check 5004 "/^hello/ anchor match" 'true'
|
||||
check 5005 "/^hello/ anchor no-match" 'false'
|
||||
check 5006 "/world$/ end anchor" 'true'
|
||||
check 5007 "/\\d+/ digit class" 'true'
|
||||
check 5008 "/\\w+/ word class" 'true'
|
||||
check 5009 "/[abc]/ class no-match" 'false'
|
||||
check 5010 "/[abc]/ class match" 'true'
|
||||
check 5011 "/a*b/ zero-or-more" 'true'
|
||||
check 5012 "/a+b/ one-or-more no-match" 'false'
|
||||
check 5013 "/a{2,3}/ quant match" 'true'
|
||||
check 5014 "/a{2,3}/ quant no-match" 'false'
|
||||
check 5015 "dot matches any" 'true'
|
||||
check 5016 "dot requires char" 'false'
|
||||
check 5017 "exec match string" '"foobar"'
|
||||
check 5018 "exec match index" '0'
|
||||
check 5019 "exec capture group" '"bar"'
|
||||
check 5020 "alternation cat|dog match" 'true'
|
||||
check 5021 "alternation cat|dog no-match" 'false'
|
||||
check 5022 "non-capturing group" 'true'
|
||||
check 5023 "negated class match" 'true'
|
||||
check 5024 "negated class no-match" 'false'
|
||||
check 5025 "range [a-z]+" 'true'
|
||||
check 5026 "word boundary match" 'true'
|
||||
check 5027 "word boundary no-match" 'false'
|
||||
check 5028 "lazy quantifier" '"a"'
|
||||
check 5029 "global exec advances" '"2"'
|
||||
check 5030 "String.match regex" '"hello"'
|
||||
check 5031 "String.search regex" '6'
|
||||
check 5032 "String.replace regex" '"hello there"'
|
||||
check 5033 "multiline anchor" 'true'
|
||||
|
||||
# ── Phase 13: let/const TDZ infrastructure ───────────────────────
|
||||
check 5100 "let binding initialized" '5'
|
||||
check 5101 "const binding initialized" '42'
|
||||
check 5102 "TDZ sentinel is detectable" 'true'
|
||||
check 5103 "tdz-check passes non-sentinel" '42'
|
||||
|
||||
# ── Phase 22: Bitwise ops ─────────────────────────────────────────
|
||||
check 6000 "bitand 5&3" '1'
|
||||
check 6001 "bitor 5|3" '7'
|
||||
check 6002 "bitxor 5^3" '6'
|
||||
check 6003 "lshift 1<<4" '16'
|
||||
check 6004 "rshift 32>>2" '8'
|
||||
check 6005 "rshift -8>>1" '-4'
|
||||
check 6006 "urshift >>>" '1073741823'
|
||||
check 6007 "bitnot ~0" '-1'
|
||||
|
||||
# ── Phase 22: Map ─────────────────────────────────────────────────
|
||||
check 6010 "map? new map" 'true'
|
||||
check 6011 "map set→size 1" '1'
|
||||
check 6012 "map get existing" '1'
|
||||
check 6013 "map has key yes" 'true'
|
||||
check 6014 "map has key no" 'false'
|
||||
check 6015 "map size 2 entries" '2'
|
||||
check 6016 "map delete→size 0" '0'
|
||||
check 6017 "map set overwrites" '99'
|
||||
|
||||
# ── Phase 22: Set ─────────────────────────────────────────────────
|
||||
check 6020 "set? new set" 'true'
|
||||
check 6021 "set has after add" 'true'
|
||||
check 6022 "set has absent" 'false'
|
||||
check 6023 "set dedup size" '1'
|
||||
check 6024 "set size 2" '2'
|
||||
check 6025 "set delete→size 0" '0'
|
||||
|
||||
# ── Phase 22: RegExp ──────────────────────────────────────────────
|
||||
check 6030 "RegExp? result" 'true'
|
||||
check 6031 "RegExp global flag" 'true'
|
||||
check 6032 "RegExp ignoreCase" 'true'
|
||||
|
||||
TOTAL=$((PASS + FAIL))
|
||||
if [ $FAIL -eq 0 ]; then
|
||||
echo "✓ $PASS/$TOTAL JS-on-SX tests passed"
|
||||
|
||||
@@ -798,7 +798,6 @@ class ServerSession:
|
||||
self._run_and_collect(3, '(load "lib/js/parser.sx")', timeout=60.0)
|
||||
self._run_and_collect(4, '(load "lib/js/transpile.sx")', timeout=60.0)
|
||||
self._run_and_collect(5, '(load "lib/js/runtime.sx")', timeout=60.0)
|
||||
self._run_and_collect(50, '(load "lib/js/regex.sx")', timeout=60.0)
|
||||
# Preload the stub harness — use precomputed SX cache when available
|
||||
# (huge win: ~15s js-eval HARNESS_STUB → ~0s load precomputed .sx).
|
||||
cache_rel = _harness_cache_rel_path()
|
||||
|
||||
@@ -935,12 +935,12 @@
|
||||
|
||||
(define
|
||||
js-transpile-var
|
||||
(fn (kind decls) (cons (js-sym "begin") (js-vardecl-forms kind decls))))
|
||||
(fn (kind decls) (cons (js-sym "begin") (js-vardecl-forms decls))))
|
||||
|
||||
(define
|
||||
js-vardecl-forms
|
||||
(fn
|
||||
(kind decls)
|
||||
(decls)
|
||||
(cond
|
||||
((empty? decls) (list))
|
||||
(else
|
||||
@@ -953,7 +953,7 @@
|
||||
(js-sym "define")
|
||||
(js-sym (nth d 1))
|
||||
(js-transpile (nth d 2)))
|
||||
(js-vardecl-forms kind (rest decls))))
|
||||
(js-vardecl-forms (rest decls))))
|
||||
((js-tag? d "js-vardecl-obj")
|
||||
(let
|
||||
((names (nth d 1))
|
||||
@@ -964,7 +964,7 @@
|
||||
(js-vardecl-obj-forms
|
||||
names
|
||||
tmp-sym
|
||||
(js-vardecl-forms kind (rest decls))))))
|
||||
(js-vardecl-forms (rest decls))))))
|
||||
((js-tag? d "js-vardecl-arr")
|
||||
(let
|
||||
((names (nth d 1))
|
||||
@@ -976,7 +976,7 @@
|
||||
names
|
||||
tmp-sym
|
||||
0
|
||||
(js-vardecl-forms kind (rest decls))))))
|
||||
(js-vardecl-forms (rest decls))))))
|
||||
(else (error "js-vardecl-forms: unexpected decl"))))))))
|
||||
|
||||
(define
|
||||
|
||||
348
lib/lua/conformance.py
Executable file
348
lib/lua/conformance.py
Executable file
@@ -0,0 +1,348 @@
|
||||
#!/usr/bin/env python3
|
||||
"""lua-conformance — run the PUC-Rio Lua 5.1 test suite against Lua-on-SX.
|
||||
|
||||
Walks lib/lua/lua-tests/*.lua, evaluates each via `lua-eval-ast` on a
|
||||
long-lived sx_server.exe subprocess, classifies pass/fail/timeout per file,
|
||||
and writes lib/lua/scoreboard.{json,md}.
|
||||
|
||||
Modelled on lib/js/test262-runner.py but much simpler: each Lua test file is
|
||||
its own unit (they're self-contained assertion scripts; they pass if they
|
||||
complete without raising). No harness stub, no frontmatter, no worker pool.
|
||||
|
||||
Usage:
|
||||
python3 lib/lua/conformance.py
|
||||
python3 lib/lua/conformance.py --filter locals
|
||||
python3 lib/lua/conformance.py --per-test-timeout 3 -v
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import select
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).resolve().parents[2]
|
||||
SX_SERVER_PRIMARY = REPO / "hosts" / "ocaml" / "_build" / "default" / "bin" / "sx_server.exe"
|
||||
SX_SERVER_FALLBACK = Path("/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe")
|
||||
TESTS_DIR = REPO / "lib" / "lua" / "lua-tests"
|
||||
|
||||
DEFAULT_TIMEOUT = 8.0
|
||||
|
||||
# Files that require facilities we don't (and won't soon) support.
|
||||
# Still classified as skip rather than fail so the scoreboard stays honest.
|
||||
HARDCODED_SKIP = {
|
||||
"all.lua": "driver uses dofile to chain other tests",
|
||||
"api.lua": "requires testC (C debug library)",
|
||||
"checktable.lua": "internal debug helpers",
|
||||
"code.lua": "bytecode inspection via debug library",
|
||||
"db.lua": "debug library",
|
||||
"files.lua": "io library",
|
||||
"gc.lua": "collectgarbage / finalisers",
|
||||
"main.lua": "standalone interpreter driver",
|
||||
}
|
||||
|
||||
RX_OK_INLINE = re.compile(r"^\(ok (\d+) (.*)\)\s*$")
|
||||
RX_OK_LEN = re.compile(r"^\(ok-len (\d+) \d+\)\s*$")
|
||||
RX_ERR = re.compile(r"^\(error (\d+) (.*)\)\s*$")
|
||||
|
||||
|
||||
def pick_sx_server() -> Path:
|
||||
if SX_SERVER_PRIMARY.exists():
|
||||
return SX_SERVER_PRIMARY
|
||||
return SX_SERVER_FALLBACK
|
||||
|
||||
|
||||
def sx_escape_nested(s: str) -> str:
|
||||
"""Two-level escape: (eval "(lua-eval-ast \"<src>\")").
|
||||
|
||||
Outer literal is consumed by `eval` then the inner literal by `lua-eval-ast`.
|
||||
"""
|
||||
inner = (
|
||||
s.replace("\\", "\\\\")
|
||||
.replace('"', '\\"')
|
||||
.replace("\n", "\\n")
|
||||
.replace("\r", "\\r")
|
||||
.replace("\t", "\\t")
|
||||
)
|
||||
return inner.replace("\\", "\\\\").replace('"', '\\"')
|
||||
|
||||
|
||||
def classify_error(msg: str) -> str:
|
||||
m = msg.lower()
|
||||
sym = re.search(r"undefined symbol:\s*\\?\"?([^\"\s)]+)", msg, re.I)
|
||||
if sym:
|
||||
return f"undefined symbol: {sym.group(1).strip(chr(34))}"
|
||||
if "undefined symbol" in m:
|
||||
return "undefined symbol"
|
||||
if "lua: arith" in m:
|
||||
return "arith type error"
|
||||
if "lua-transpile" in m:
|
||||
return "transpile: unsupported node"
|
||||
if "lua-parse" in m:
|
||||
return "parse error"
|
||||
if "lua-tokenize" in m:
|
||||
return "tokenize error"
|
||||
if "unknown node" in m:
|
||||
return "unknown AST node"
|
||||
if "not yet supported" in m:
|
||||
return "not yet supported"
|
||||
if "nth: index out" in m or "nth:" in m:
|
||||
return "nth index error"
|
||||
if "timeout" in m:
|
||||
return "timeout"
|
||||
# Strip SX-side wrapping and trim
|
||||
trimmed = msg.strip('"').strip()
|
||||
return f"other: {trimmed[:80]}"
|
||||
|
||||
|
||||
class Session:
|
||||
def __init__(self, sx_server: Path, timeout: float):
|
||||
self.sx_server = sx_server
|
||||
self.timeout = timeout
|
||||
self.proc: subprocess.Popen | None = None
|
||||
self._buf = b""
|
||||
self._fd = -1
|
||||
|
||||
def start(self) -> None:
|
||||
self.proc = subprocess.Popen(
|
||||
[str(self.sx_server)],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.DEVNULL,
|
||||
cwd=str(REPO),
|
||||
bufsize=0,
|
||||
)
|
||||
self._fd = self.proc.stdout.fileno()
|
||||
self._buf = b""
|
||||
os.set_blocking(self._fd, False)
|
||||
self._wait_for("(ready)", timeout=15.0)
|
||||
self._run(1, '(load "lib/lua/tokenizer.sx")', 60)
|
||||
self._run(2, '(load "lib/lua/parser.sx")', 60)
|
||||
self._run(3, '(load "lib/lua/runtime.sx")', 60)
|
||||
self._run(4, '(load "lib/lua/transpile.sx")', 60)
|
||||
|
||||
def stop(self) -> None:
|
||||
if self.proc is None:
|
||||
return
|
||||
try:
|
||||
self.proc.stdin.close()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
self.proc.terminate()
|
||||
self.proc.wait(timeout=3)
|
||||
except Exception:
|
||||
try:
|
||||
self.proc.kill()
|
||||
except Exception:
|
||||
pass
|
||||
self.proc = None
|
||||
|
||||
def _readline(self, timeout: float) -> str | None:
|
||||
deadline = time.monotonic() + timeout
|
||||
while True:
|
||||
nl = self._buf.find(b"\n")
|
||||
if nl >= 0:
|
||||
line = self._buf[: nl + 1]
|
||||
self._buf = self._buf[nl + 1 :]
|
||||
return line.decode("utf-8", errors="replace")
|
||||
remaining = deadline - time.monotonic()
|
||||
if remaining <= 0:
|
||||
raise TimeoutError("readline timeout")
|
||||
try:
|
||||
rlist, _, _ = select.select([self._fd], [], [], remaining)
|
||||
except (OSError, ValueError):
|
||||
return None
|
||||
if not rlist:
|
||||
raise TimeoutError("readline timeout")
|
||||
try:
|
||||
chunk = os.read(self._fd, 65536)
|
||||
except (BlockingIOError, InterruptedError):
|
||||
continue
|
||||
except OSError:
|
||||
return None
|
||||
if not chunk:
|
||||
if self._buf:
|
||||
rv = self._buf.decode("utf-8", errors="replace")
|
||||
self._buf = b""
|
||||
return rv
|
||||
return None
|
||||
self._buf += chunk
|
||||
|
||||
def _wait_for(self, token: str, timeout: float) -> None:
|
||||
start = time.monotonic()
|
||||
while time.monotonic() - start < timeout:
|
||||
line = self._readline(timeout - (time.monotonic() - start))
|
||||
if line is None:
|
||||
raise RuntimeError("sx_server closed stdout before ready")
|
||||
if token in line:
|
||||
return
|
||||
raise TimeoutError(f"timeout waiting for {token}")
|
||||
|
||||
def _run(self, epoch: int, cmd: str, timeout: float):
|
||||
payload = f"(epoch {epoch})\n{cmd}\n".encode("utf-8")
|
||||
try:
|
||||
self.proc.stdin.write(payload)
|
||||
self.proc.stdin.flush()
|
||||
except (BrokenPipeError, OSError):
|
||||
raise RuntimeError("sx_server stdin closed")
|
||||
deadline = time.monotonic() + timeout
|
||||
while time.monotonic() < deadline:
|
||||
remaining = deadline - time.monotonic()
|
||||
if remaining <= 0:
|
||||
raise TimeoutError(f"epoch {epoch} timeout")
|
||||
line = self._readline(remaining)
|
||||
if line is None:
|
||||
raise RuntimeError("sx_server closed stdout mid-epoch")
|
||||
m = RX_OK_INLINE.match(line)
|
||||
if m and int(m.group(1)) == epoch:
|
||||
return "ok", m.group(2)
|
||||
m = RX_OK_LEN.match(line)
|
||||
if m and int(m.group(1)) == epoch:
|
||||
val = self._readline(deadline - time.monotonic()) or ""
|
||||
return "ok", val.rstrip("\n")
|
||||
m = RX_ERR.match(line)
|
||||
if m and int(m.group(1)) == epoch:
|
||||
return "error", m.group(2)
|
||||
raise TimeoutError(f"epoch {epoch} timeout")
|
||||
|
||||
def run_lua(self, epoch: int, src: str):
|
||||
escaped = sx_escape_nested(src)
|
||||
cmd = f'(eval "(lua-eval-ast \\"{escaped}\\")")'
|
||||
return self._run(epoch, cmd, self.timeout)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--per-test-timeout", type=float, default=DEFAULT_TIMEOUT)
|
||||
ap.add_argument("--filter", type=str, default=None,
|
||||
help="only run tests whose filename contains this substring")
|
||||
ap.add_argument("-v", "--verbose", action="store_true")
|
||||
ap.add_argument("--no-scoreboard", action="store_true",
|
||||
help="do not write scoreboard.{json,md}")
|
||||
args = ap.parse_args()
|
||||
|
||||
sx_server = pick_sx_server()
|
||||
if not sx_server.exists():
|
||||
print(f"ERROR: sx_server not found at {sx_server}", file=sys.stderr)
|
||||
return 1
|
||||
if not TESTS_DIR.exists():
|
||||
print(f"ERROR: no tests dir at {TESTS_DIR}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
tests = sorted(TESTS_DIR.glob("*.lua"))
|
||||
if args.filter:
|
||||
tests = [p for p in tests if args.filter in p.name]
|
||||
if not tests:
|
||||
print("No tests matched.", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
print(f"Running {len(tests)} Lua test file(s)…", file=sys.stderr)
|
||||
session = Session(sx_server, args.per_test_timeout)
|
||||
session.start()
|
||||
|
||||
results = []
|
||||
failure_modes: Counter = Counter()
|
||||
|
||||
try:
|
||||
for i, path in enumerate(tests, start=1):
|
||||
name = path.name
|
||||
skip_reason = HARDCODED_SKIP.get(name)
|
||||
if skip_reason:
|
||||
results.append({"name": name, "status": "skip", "reason": skip_reason, "ms": 0})
|
||||
if args.verbose:
|
||||
print(f" - {name}: SKIP ({skip_reason})")
|
||||
continue
|
||||
|
||||
try:
|
||||
src = path.read_text(encoding="utf-8")
|
||||
except UnicodeDecodeError:
|
||||
src = path.read_text(encoding="latin-1")
|
||||
t0 = time.monotonic()
|
||||
try:
|
||||
kind, payload = session.run_lua(100 + i, src)
|
||||
ms = int((time.monotonic() - t0) * 1000)
|
||||
if kind == "ok":
|
||||
results.append({"name": name, "status": "pass", "reason": "", "ms": ms})
|
||||
if args.verbose:
|
||||
print(f" + {name}: PASS ({ms}ms)")
|
||||
else:
|
||||
reason = classify_error(payload)
|
||||
failure_modes[reason] += 1
|
||||
results.append({"name": name, "status": "fail", "reason": reason, "ms": ms})
|
||||
if args.verbose:
|
||||
print(f" - {name}: FAIL — {reason}")
|
||||
except TimeoutError:
|
||||
ms = int((time.monotonic() - t0) * 1000)
|
||||
failure_modes["timeout"] += 1
|
||||
results.append({"name": name, "status": "timeout", "reason": "per-test timeout",
|
||||
"ms": ms})
|
||||
if args.verbose:
|
||||
print(f" - {name}: TIMEOUT ({ms}ms)")
|
||||
# Restart after a timeout to shed any stuck state.
|
||||
session.stop()
|
||||
session.start()
|
||||
finally:
|
||||
session.stop()
|
||||
|
||||
n_pass = sum(1 for r in results if r["status"] == "pass")
|
||||
n_fail = sum(1 for r in results if r["status"] == "fail")
|
||||
n_timeout = sum(1 for r in results if r["status"] == "timeout")
|
||||
n_skip = sum(1 for r in results if r["status"] == "skip")
|
||||
n_total = len(results)
|
||||
n_runnable = n_total - n_skip
|
||||
pct = (n_pass / n_runnable * 100.0) if n_runnable else 0.0
|
||||
|
||||
print()
|
||||
print(f"Lua-on-SX conformance: {n_pass}/{n_runnable} runnable pass ({pct:.1f}%) "
|
||||
f"fail={n_fail} timeout={n_timeout} skip={n_skip} total={n_total}")
|
||||
if failure_modes:
|
||||
print("Top failure modes:")
|
||||
for mode, count in failure_modes.most_common(10):
|
||||
print(f" {count}x {mode}")
|
||||
|
||||
if not args.no_scoreboard:
|
||||
sb = {
|
||||
"totals": {
|
||||
"pass": n_pass, "fail": n_fail, "timeout": n_timeout,
|
||||
"skip": n_skip, "total": n_total, "runnable": n_runnable,
|
||||
"pass_rate": round(pct, 1),
|
||||
},
|
||||
"top_failure_modes": failure_modes.most_common(20),
|
||||
"results": results,
|
||||
}
|
||||
(REPO / "lib" / "lua" / "scoreboard.json").write_text(
|
||||
json.dumps(sb, indent=2), encoding="utf-8"
|
||||
)
|
||||
md = [
|
||||
"# Lua-on-SX conformance scoreboard",
|
||||
"",
|
||||
f"**Pass rate:** {n_pass}/{n_runnable} runnable ({pct:.1f}%)",
|
||||
f"fail={n_fail} timeout={n_timeout} skip={n_skip} total={n_total}",
|
||||
"",
|
||||
"## Top failure modes",
|
||||
"",
|
||||
]
|
||||
for mode, count in failure_modes.most_common(10):
|
||||
md.append(f"- **{count}x** {mode}")
|
||||
md.extend(["", "## Per-test results", "",
|
||||
"| Test | Status | Reason | ms |",
|
||||
"|---|---|---|---:|"])
|
||||
for r in results:
|
||||
reason = r["reason"] or "-"
|
||||
md.append(f"| {r['name']} | {r['status']} | {reason} | {r['ms']} |")
|
||||
(REPO / "lib" / "lua" / "scoreboard.md").write_text(
|
||||
"\n".join(md) + "\n", encoding="utf-8"
|
||||
)
|
||||
|
||||
return 0 if (n_fail == 0 and n_timeout == 0) else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
13
lib/lua/conformance.sh
Executable file
13
lib/lua/conformance.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
# Lua-on-SX conformance runner — walks lib/lua/lua-tests/*.lua, runs each via
|
||||
# `lua-eval-ast` on a long-lived sx_server.exe subprocess, classifies
|
||||
# pass/fail/timeout, and writes lib/lua/scoreboard.{json,md}.
|
||||
#
|
||||
# Usage:
|
||||
# bash lib/lua/conformance.sh # full suite
|
||||
# bash lib/lua/conformance.sh --filter sort # filter by filename substring
|
||||
# bash lib/lua/conformance.sh -v # per-file verbose
|
||||
|
||||
set -uo pipefail
|
||||
cd "$(git rev-parse --show-toplevel)"
|
||||
exec python3 lib/lua/conformance.py "$@"
|
||||
41
lib/lua/lua-tests/README
Normal file
41
lib/lua/lua-tests/README
Normal file
@@ -0,0 +1,41 @@
|
||||
This tarball contains the official test scripts for Lua 5.1.
|
||||
Unlike Lua itself, these tests do not aim portability, small footprint,
|
||||
or easy of use. (Their main goal is to try to crash Lua.) They are not
|
||||
intended for general use. You are wellcome to use them, but expect to
|
||||
have to "dirt your hands".
|
||||
|
||||
The tarball should expand in the following contents:
|
||||
- several .lua scripts with the tests
|
||||
- a main "all.lua" Lua script that invokes all the other scripts
|
||||
- a subdirectory "libs" with an empty subdirectory "libs/P1",
|
||||
to be used by the scripts
|
||||
- a subdirectory "etc" with some extra files
|
||||
|
||||
To run the tests, do as follows:
|
||||
|
||||
- go to the test directory
|
||||
|
||||
- set LUA_PATH to "?;./?.lua" (or, better yet, set LUA_PATH to "./?.lua;;"
|
||||
and LUA_INIT to "package.path = '?;'..package.path")
|
||||
|
||||
- run "lua all.lua"
|
||||
|
||||
|
||||
--------------------------------------------
|
||||
Internal tests
|
||||
--------------------------------------------
|
||||
|
||||
Some tests need a special library, "testC", that gives access to
|
||||
several internal structures in Lua.
|
||||
This library is only available when Lua is compiled in debug mode.
|
||||
The scripts automatically detect its absence and skip those tests.
|
||||
|
||||
If you want to run these tests, move etc/ltests.c and etc/ltests.h to
|
||||
the directory with the source Lua files, and recompile Lua with
|
||||
the option -DLUA_USER_H='"ltests.h"' (or its equivalent to define
|
||||
LUA_USER_H as the string "ltests.h", including the quotes). This
|
||||
option not only adds the testC library, but it adds several other
|
||||
internal tests as well. After the recompilation, run the tests
|
||||
as before.
|
||||
|
||||
|
||||
137
lib/lua/lua-tests/all.lua
Executable file
137
lib/lua/lua-tests/all.lua
Executable file
@@ -0,0 +1,137 @@
|
||||
#!../lua
|
||||
|
||||
math.randomseed(0)
|
||||
|
||||
collectgarbage("setstepmul", 180)
|
||||
collectgarbage("setpause", 190)
|
||||
|
||||
|
||||
--[=[
|
||||
example of a long [comment],
|
||||
[[spanning several [lines]]]
|
||||
|
||||
]=]
|
||||
|
||||
print("current path:\n " .. string.gsub(package.path, ";", "\n "))
|
||||
|
||||
|
||||
local msgs = {}
|
||||
function Message (m)
|
||||
print(m)
|
||||
msgs[#msgs+1] = string.sub(m, 3, -3)
|
||||
end
|
||||
|
||||
|
||||
local c = os.clock()
|
||||
|
||||
assert(os.setlocale"C")
|
||||
|
||||
local T,print,gcinfo,format,write,assert,type =
|
||||
T,print,gcinfo,string.format,io.write,assert,type
|
||||
|
||||
local function formatmem (m)
|
||||
if m < 1024 then return m
|
||||
else
|
||||
m = m/1024 - m/1024%1
|
||||
if m < 1024 then return m.."K"
|
||||
else
|
||||
m = m/1024 - m/1024%1
|
||||
return m.."M"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local showmem = function ()
|
||||
if not T then
|
||||
print(format(" ---- total memory: %s ----\n", formatmem(gcinfo())))
|
||||
else
|
||||
T.checkmemory()
|
||||
local a,b,c = T.totalmem()
|
||||
local d,e = gcinfo()
|
||||
print(format(
|
||||
"\n ---- total memory: %s (%dK), max use: %s, blocks: %d\n",
|
||||
formatmem(a), d, formatmem(c), b))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- redefine dofile to run files through dump/undump
|
||||
--
|
||||
dofile = function (n)
|
||||
showmem()
|
||||
local f = assert(loadfile(n))
|
||||
local b = string.dump(f)
|
||||
f = assert(loadstring(b))
|
||||
return f()
|
||||
end
|
||||
|
||||
dofile('main.lua')
|
||||
|
||||
do
|
||||
local u = newproxy(true)
|
||||
local newproxy, stderr = newproxy, io.stderr
|
||||
getmetatable(u).__gc = function (o)
|
||||
stderr:write'.'
|
||||
newproxy(o)
|
||||
end
|
||||
end
|
||||
|
||||
local f = assert(loadfile('gc.lua'))
|
||||
f()
|
||||
dofile('db.lua')
|
||||
assert(dofile('calls.lua') == deep and deep)
|
||||
dofile('strings.lua')
|
||||
dofile('literals.lua')
|
||||
assert(dofile('attrib.lua') == 27)
|
||||
assert(dofile('locals.lua') == 5)
|
||||
dofile('constructs.lua')
|
||||
dofile('code.lua')
|
||||
do
|
||||
local f = coroutine.wrap(assert(loadfile('big.lua')))
|
||||
assert(f() == 'b')
|
||||
assert(f() == 'a')
|
||||
end
|
||||
dofile('nextvar.lua')
|
||||
dofile('pm.lua')
|
||||
dofile('api.lua')
|
||||
assert(dofile('events.lua') == 12)
|
||||
dofile('vararg.lua')
|
||||
dofile('closure.lua')
|
||||
dofile('errors.lua')
|
||||
dofile('math.lua')
|
||||
dofile('sort.lua')
|
||||
assert(dofile('verybig.lua') == 10); collectgarbage()
|
||||
dofile('files.lua')
|
||||
|
||||
if #msgs > 0 then
|
||||
print("\ntests not performed:")
|
||||
for i=1,#msgs do
|
||||
print(msgs[i])
|
||||
end
|
||||
print()
|
||||
end
|
||||
|
||||
print("final OK !!!")
|
||||
print('cleaning all!!!!')
|
||||
|
||||
debug.sethook(function (a) assert(type(a) == 'string') end, "cr")
|
||||
|
||||
local _G, collectgarbage, showmem, print, format, clock =
|
||||
_G, collectgarbage, showmem, print, format, os.clock
|
||||
|
||||
local a={}
|
||||
for n in pairs(_G) do a[n] = 1 end
|
||||
a.tostring = nil
|
||||
a.___Glob = nil
|
||||
for n in pairs(a) do _G[n] = nil end
|
||||
|
||||
a = nil
|
||||
collectgarbage()
|
||||
collectgarbage()
|
||||
collectgarbage()
|
||||
collectgarbage()
|
||||
collectgarbage()
|
||||
collectgarbage();showmem()
|
||||
|
||||
print(format("\n\ntotal time: %.2f\n", clock()-c))
|
||||
711
lib/lua/lua-tests/api.lua
Normal file
711
lib/lua/lua-tests/api.lua
Normal file
@@ -0,0 +1,711 @@
|
||||
|
||||
if T==nil then
|
||||
(Message or print)('\a\n >>> testC not active: skipping API tests <<<\n\a')
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
|
||||
function tcheck (t1, t2)
|
||||
table.remove(t1, 1) -- remove code
|
||||
assert(table.getn(t1) == table.getn(t2))
|
||||
for i=1,table.getn(t1) do assert(t1[i] == t2[i]) end
|
||||
end
|
||||
|
||||
function pack(...) return arg end
|
||||
|
||||
|
||||
print('testing C API')
|
||||
|
||||
-- testing allignment
|
||||
a = T.d2s(12458954321123)
|
||||
assert(string.len(a) == 8) -- sizeof(double)
|
||||
assert(T.s2d(a) == 12458954321123)
|
||||
|
||||
a,b,c = T.testC("pushnum 1; pushnum 2; pushnum 3; return 2")
|
||||
assert(a == 2 and b == 3 and not c)
|
||||
|
||||
-- test that all trues are equal
|
||||
a,b,c = T.testC("pushbool 1; pushbool 2; pushbool 0; return 3")
|
||||
assert(a == b and a == true and c == false)
|
||||
a,b,c = T.testC"pushbool 0; pushbool 10; pushnil;\
|
||||
tobool -3; tobool -3; tobool -3; return 3"
|
||||
assert(a==0 and b==1 and c==0)
|
||||
|
||||
|
||||
a,b,c = T.testC("gettop; return 2", 10, 20, 30, 40)
|
||||
assert(a == 40 and b == 5 and not c)
|
||||
|
||||
t = pack(T.testC("settop 5; gettop; return .", 2, 3))
|
||||
tcheck(t, {n=4,2,3})
|
||||
|
||||
t = pack(T.testC("settop 0; settop 15; return 10", 3, 1, 23))
|
||||
assert(t.n == 10 and t[1] == nil and t[10] == nil)
|
||||
|
||||
t = pack(T.testC("remove -2; gettop; return .", 2, 3, 4))
|
||||
tcheck(t, {n=2,2,4})
|
||||
|
||||
t = pack(T.testC("insert -1; gettop; return .", 2, 3))
|
||||
tcheck(t, {n=2,2,3})
|
||||
|
||||
t = pack(T.testC("insert 3; gettop; return .", 2, 3, 4, 5))
|
||||
tcheck(t, {n=4,2,5,3,4})
|
||||
|
||||
t = pack(T.testC("replace 2; gettop; return .", 2, 3, 4, 5))
|
||||
tcheck(t, {n=3,5,3,4})
|
||||
|
||||
t = pack(T.testC("replace -2; gettop; return .", 2, 3, 4, 5))
|
||||
tcheck(t, {n=3,2,3,5})
|
||||
|
||||
t = pack(T.testC("remove 3; gettop; return .", 2, 3, 4, 5))
|
||||
tcheck(t, {n=3,2,4,5})
|
||||
|
||||
t = pack(T.testC("insert 3; pushvalue 3; remove 3; pushvalue 2; remove 2; \
|
||||
insert 2; pushvalue 1; remove 1; insert 1; \
|
||||
insert -2; pushvalue -2; remove -3; gettop; return .",
|
||||
2, 3, 4, 5, 10, 40, 90))
|
||||
tcheck(t, {n=7,2,3,4,5,10,40,90})
|
||||
|
||||
t = pack(T.testC("concat 5; gettop; return .", "alo", 2, 3, "joao", 12))
|
||||
tcheck(t, {n=1,"alo23joao12"})
|
||||
|
||||
-- testing MULTRET
|
||||
t = pack(T.testC("rawcall 2,-1; gettop; return .",
|
||||
function (a,b) return 1,2,3,4,a,b end, "alo", "joao"))
|
||||
tcheck(t, {n=6,1,2,3,4,"alo", "joao"})
|
||||
|
||||
do -- test returning more results than fit in the caller stack
|
||||
local a = {}
|
||||
for i=1,1000 do a[i] = true end; a[999] = 10
|
||||
local b = T.testC([[call 1 -1; pop 1; tostring -1; return 1]], unpack, a)
|
||||
assert(b == "10")
|
||||
end
|
||||
|
||||
|
||||
-- testing lessthan
|
||||
assert(T.testC("lessthan 2 5, return 1", 3, 2, 2, 4, 2, 2))
|
||||
assert(T.testC("lessthan 5 2, return 1", 4, 2, 2, 3, 2, 2))
|
||||
assert(not T.testC("lessthan 2 -3, return 1", "4", "2", "2", "3", "2", "2"))
|
||||
assert(not T.testC("lessthan -3 2, return 1", "3", "2", "2", "4", "2", "2"))
|
||||
|
||||
local b = {__lt = function (a,b) return a[1] < b[1] end}
|
||||
local a1,a3,a4 = setmetatable({1}, b),
|
||||
setmetatable({3}, b),
|
||||
setmetatable({4}, b)
|
||||
assert(T.testC("lessthan 2 5, return 1", a3, 2, 2, a4, 2, 2))
|
||||
assert(T.testC("lessthan 5 -6, return 1", a4, 2, 2, a3, 2, 2))
|
||||
a,b = T.testC("lessthan 5 -6, return 2", a1, 2, 2, a3, 2, 20)
|
||||
assert(a == 20 and b == false)
|
||||
|
||||
|
||||
-- testing lua_is
|
||||
|
||||
function count (x, n)
|
||||
n = n or 2
|
||||
local prog = [[
|
||||
isnumber %d;
|
||||
isstring %d;
|
||||
isfunction %d;
|
||||
iscfunction %d;
|
||||
istable %d;
|
||||
isuserdata %d;
|
||||
isnil %d;
|
||||
isnull %d;
|
||||
return 8
|
||||
]]
|
||||
prog = string.format(prog, n, n, n, n, n, n, n, n)
|
||||
local a,b,c,d,e,f,g,h = T.testC(prog, x)
|
||||
return a+b+c+d+e+f+g+(100*h)
|
||||
end
|
||||
|
||||
assert(count(3) == 2)
|
||||
assert(count('alo') == 1)
|
||||
assert(count('32') == 2)
|
||||
assert(count({}) == 1)
|
||||
assert(count(print) == 2)
|
||||
assert(count(function () end) == 1)
|
||||
assert(count(nil) == 1)
|
||||
assert(count(io.stdin) == 1)
|
||||
assert(count(nil, 15) == 100)
|
||||
|
||||
-- testing lua_to...
|
||||
|
||||
function to (s, x, n)
|
||||
n = n or 2
|
||||
return T.testC(string.format("%s %d; return 1", s, n), x)
|
||||
end
|
||||
|
||||
assert(to("tostring", {}) == nil)
|
||||
assert(to("tostring", "alo") == "alo")
|
||||
assert(to("tostring", 12) == "12")
|
||||
assert(to("tostring", 12, 3) == nil)
|
||||
assert(to("objsize", {}) == 0)
|
||||
assert(to("objsize", "alo\0\0a") == 6)
|
||||
assert(to("objsize", T.newuserdata(0)) == 0)
|
||||
assert(to("objsize", T.newuserdata(101)) == 101)
|
||||
assert(to("objsize", 12) == 2)
|
||||
assert(to("objsize", 12, 3) == 0)
|
||||
assert(to("tonumber", {}) == 0)
|
||||
assert(to("tonumber", "12") == 12)
|
||||
assert(to("tonumber", "s2") == 0)
|
||||
assert(to("tonumber", 1, 20) == 0)
|
||||
a = to("tocfunction", math.deg)
|
||||
assert(a(3) == math.deg(3) and a ~= math.deg)
|
||||
|
||||
|
||||
-- testing errors
|
||||
|
||||
a = T.testC([[
|
||||
loadstring 2; call 0,1;
|
||||
pushvalue 3; insert -2; call 1, 1;
|
||||
call 0, 0;
|
||||
return 1
|
||||
]], "x=150", function (a) assert(a==nil); return 3 end)
|
||||
|
||||
assert(type(a) == 'string' and x == 150)
|
||||
|
||||
function check3(p, ...)
|
||||
assert(arg.n == 3)
|
||||
assert(string.find(arg[3], p))
|
||||
end
|
||||
check3(":1:", T.testC("loadstring 2; gettop; return .", "x="))
|
||||
check3("cannot read", T.testC("loadfile 2; gettop; return .", "."))
|
||||
check3("cannot open xxxx", T.testC("loadfile 2; gettop; return .", "xxxx"))
|
||||
|
||||
-- testing table access
|
||||
|
||||
a = {x=0, y=12}
|
||||
x, y = T.testC("gettable 2; pushvalue 4; gettable 2; return 2",
|
||||
a, 3, "y", 4, "x")
|
||||
assert(x == 0 and y == 12)
|
||||
T.testC("settable -5", a, 3, 4, "x", 15)
|
||||
assert(a.x == 15)
|
||||
a[a] = print
|
||||
x = T.testC("gettable 2; return 1", a) -- table and key are the same object!
|
||||
assert(x == print)
|
||||
T.testC("settable 2", a, "x") -- table and key are the same object!
|
||||
assert(a[a] == "x")
|
||||
|
||||
b = setmetatable({p = a}, {})
|
||||
getmetatable(b).__index = function (t, i) return t.p[i] end
|
||||
k, x = T.testC("gettable 3, return 2", 4, b, 20, 35, "x")
|
||||
assert(x == 15 and k == 35)
|
||||
getmetatable(b).__index = function (t, i) return a[i] end
|
||||
getmetatable(b).__newindex = function (t, i,v ) a[i] = v end
|
||||
y = T.testC("insert 2; gettable -5; return 1", 2, 3, 4, "y", b)
|
||||
assert(y == 12)
|
||||
k = T.testC("settable -5, return 1", b, 3, 4, "x", 16)
|
||||
assert(a.x == 16 and k == 4)
|
||||
a[b] = 'xuxu'
|
||||
y = T.testC("gettable 2, return 1", b)
|
||||
assert(y == 'xuxu')
|
||||
T.testC("settable 2", b, 19)
|
||||
assert(a[b] == 19)
|
||||
|
||||
-- testing next
|
||||
a = {}
|
||||
t = pack(T.testC("next; gettop; return .", a, nil))
|
||||
tcheck(t, {n=1,a})
|
||||
a = {a=3}
|
||||
t = pack(T.testC("next; gettop; return .", a, nil))
|
||||
tcheck(t, {n=3,a,'a',3})
|
||||
t = pack(T.testC("next; pop 1; next; gettop; return .", a, nil))
|
||||
tcheck(t, {n=1,a})
|
||||
|
||||
|
||||
|
||||
-- testing upvalues
|
||||
|
||||
do
|
||||
local A = T.testC[[ pushnum 10; pushnum 20; pushcclosure 2; return 1]]
|
||||
t, b, c = A([[pushvalue U0; pushvalue U1; pushvalue U2; return 3]])
|
||||
assert(b == 10 and c == 20 and type(t) == 'table')
|
||||
a, b = A([[tostring U3; tonumber U4; return 2]])
|
||||
assert(a == nil and b == 0)
|
||||
A([[pushnum 100; pushnum 200; replace U2; replace U1]])
|
||||
b, c = A([[pushvalue U1; pushvalue U2; return 2]])
|
||||
assert(b == 100 and c == 200)
|
||||
A([[replace U2; replace U1]], {x=1}, {x=2})
|
||||
b, c = A([[pushvalue U1; pushvalue U2; return 2]])
|
||||
assert(b.x == 1 and c.x == 2)
|
||||
T.checkmemory()
|
||||
end
|
||||
|
||||
local f = T.testC[[ pushnum 10; pushnum 20; pushcclosure 2; return 1]]
|
||||
assert(T.upvalue(f, 1) == 10 and
|
||||
T.upvalue(f, 2) == 20 and
|
||||
T.upvalue(f, 3) == nil)
|
||||
T.upvalue(f, 2, "xuxu")
|
||||
assert(T.upvalue(f, 2) == "xuxu")
|
||||
|
||||
|
||||
-- testing environments
|
||||
|
||||
assert(T.testC"pushvalue G; return 1" == _G)
|
||||
assert(T.testC"pushvalue E; return 1" == _G)
|
||||
local a = {}
|
||||
T.testC("replace E; return 1", a)
|
||||
assert(T.testC"pushvalue G; return 1" == _G)
|
||||
assert(T.testC"pushvalue E; return 1" == a)
|
||||
assert(debug.getfenv(T.testC) == a)
|
||||
assert(debug.getfenv(T.upvalue) == _G)
|
||||
-- userdata inherit environment
|
||||
local u = T.testC"newuserdata 0; return 1"
|
||||
assert(debug.getfenv(u) == a)
|
||||
-- functions inherit environment
|
||||
u = T.testC"pushcclosure 0; return 1"
|
||||
assert(debug.getfenv(u) == a)
|
||||
debug.setfenv(T.testC, _G)
|
||||
assert(T.testC"pushvalue E; return 1" == _G)
|
||||
|
||||
local b = newproxy()
|
||||
assert(debug.getfenv(b) == _G)
|
||||
assert(debug.setfenv(b, a))
|
||||
assert(debug.getfenv(b) == a)
|
||||
|
||||
|
||||
|
||||
-- testing locks (refs)
|
||||
|
||||
-- reuse of references
|
||||
local i = T.ref{}
|
||||
T.unref(i)
|
||||
assert(T.ref{} == i)
|
||||
|
||||
Arr = {}
|
||||
Lim = 100
|
||||
for i=1,Lim do -- lock many objects
|
||||
Arr[i] = T.ref({})
|
||||
end
|
||||
|
||||
assert(T.ref(nil) == -1 and T.getref(-1) == nil)
|
||||
T.unref(-1); T.unref(-1)
|
||||
|
||||
for i=1,Lim do -- unlock all them
|
||||
T.unref(Arr[i])
|
||||
end
|
||||
|
||||
function printlocks ()
|
||||
local n = T.testC("gettable R; return 1", "n")
|
||||
print("n", n)
|
||||
for i=0,n do
|
||||
print(i, T.testC("gettable R; return 1", i))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
for i=1,Lim do -- lock many objects
|
||||
Arr[i] = T.ref({})
|
||||
end
|
||||
|
||||
for i=1,Lim,2 do -- unlock half of them
|
||||
T.unref(Arr[i])
|
||||
end
|
||||
|
||||
assert(type(T.getref(Arr[2])) == 'table')
|
||||
|
||||
|
||||
assert(T.getref(-1) == nil)
|
||||
|
||||
|
||||
a = T.ref({})
|
||||
|
||||
collectgarbage()
|
||||
|
||||
assert(type(T.getref(a)) == 'table')
|
||||
|
||||
|
||||
-- colect in cl the `val' of all collected userdata
|
||||
tt = {}
|
||||
cl = {n=0}
|
||||
A = nil; B = nil
|
||||
local F
|
||||
F = function (x)
|
||||
local udval = T.udataval(x)
|
||||
table.insert(cl, udval)
|
||||
local d = T.newuserdata(100) -- cria lixo
|
||||
d = nil
|
||||
assert(debug.getmetatable(x).__gc == F)
|
||||
loadstring("table.insert({}, {})")() -- cria mais lixo
|
||||
collectgarbage() -- forca coleta de lixo durante coleta!
|
||||
assert(debug.getmetatable(x).__gc == F) -- coleta anterior nao melou isso?
|
||||
local dummy = {} -- cria lixo durante coleta
|
||||
if A ~= nil then
|
||||
assert(type(A) == "userdata")
|
||||
assert(T.udataval(A) == B)
|
||||
debug.getmetatable(A) -- just acess it
|
||||
end
|
||||
A = x -- ressucita userdata
|
||||
B = udval
|
||||
return 1,2,3
|
||||
end
|
||||
tt.__gc = F
|
||||
|
||||
-- test whether udate collection frees memory in the right time
|
||||
do
|
||||
collectgarbage();
|
||||
collectgarbage();
|
||||
local x = collectgarbage("count");
|
||||
local a = T.newuserdata(5001)
|
||||
assert(T.testC("objsize 2; return 1", a) == 5001)
|
||||
assert(collectgarbage("count") >= x+4)
|
||||
a = nil
|
||||
collectgarbage();
|
||||
assert(collectgarbage("count") <= x+1)
|
||||
-- udata without finalizer
|
||||
x = collectgarbage("count")
|
||||
collectgarbage("stop")
|
||||
for i=1,1000 do newproxy(false) end
|
||||
assert(collectgarbage("count") > x+10)
|
||||
collectgarbage()
|
||||
assert(collectgarbage("count") <= x+1)
|
||||
-- udata with finalizer
|
||||
x = collectgarbage("count")
|
||||
collectgarbage()
|
||||
collectgarbage("stop")
|
||||
a = newproxy(true)
|
||||
getmetatable(a).__gc = function () end
|
||||
for i=1,1000 do newproxy(a) end
|
||||
assert(collectgarbage("count") >= x+10)
|
||||
collectgarbage() -- this collection only calls TM, without freeing memory
|
||||
assert(collectgarbage("count") >= x+10)
|
||||
collectgarbage() -- now frees memory
|
||||
assert(collectgarbage("count") <= x+1)
|
||||
end
|
||||
|
||||
|
||||
collectgarbage("stop")
|
||||
|
||||
-- create 3 userdatas with tag `tt'
|
||||
a = T.newuserdata(0); debug.setmetatable(a, tt); na = T.udataval(a)
|
||||
b = T.newuserdata(0); debug.setmetatable(b, tt); nb = T.udataval(b)
|
||||
c = T.newuserdata(0); debug.setmetatable(c, tt); nc = T.udataval(c)
|
||||
|
||||
-- create userdata without meta table
|
||||
x = T.newuserdata(4)
|
||||
y = T.newuserdata(0)
|
||||
|
||||
assert(debug.getmetatable(x) == nil and debug.getmetatable(y) == nil)
|
||||
|
||||
d=T.ref(a);
|
||||
e=T.ref(b);
|
||||
f=T.ref(c);
|
||||
t = {T.getref(d), T.getref(e), T.getref(f)}
|
||||
assert(t[1] == a and t[2] == b and t[3] == c)
|
||||
|
||||
t=nil; a=nil; c=nil;
|
||||
T.unref(e); T.unref(f)
|
||||
|
||||
collectgarbage()
|
||||
|
||||
-- check that unref objects have been collected
|
||||
assert(table.getn(cl) == 1 and cl[1] == nc)
|
||||
|
||||
x = T.getref(d)
|
||||
assert(type(x) == 'userdata' and debug.getmetatable(x) == tt)
|
||||
x =nil
|
||||
tt.b = b -- create cycle
|
||||
tt=nil -- frees tt for GC
|
||||
A = nil
|
||||
b = nil
|
||||
T.unref(d);
|
||||
n5 = T.newuserdata(0)
|
||||
debug.setmetatable(n5, {__gc=F})
|
||||
n5 = T.udataval(n5)
|
||||
collectgarbage()
|
||||
assert(table.getn(cl) == 4)
|
||||
-- check order of collection
|
||||
assert(cl[2] == n5 and cl[3] == nb and cl[4] == na)
|
||||
|
||||
|
||||
a, na = {}, {}
|
||||
for i=30,1,-1 do
|
||||
a[i] = T.newuserdata(0)
|
||||
debug.setmetatable(a[i], {__gc=F})
|
||||
na[i] = T.udataval(a[i])
|
||||
end
|
||||
cl = {}
|
||||
a = nil; collectgarbage()
|
||||
assert(table.getn(cl) == 30)
|
||||
for i=1,30 do assert(cl[i] == na[i]) end
|
||||
na = nil
|
||||
|
||||
|
||||
for i=2,Lim,2 do -- unlock the other half
|
||||
T.unref(Arr[i])
|
||||
end
|
||||
|
||||
x = T.newuserdata(41); debug.setmetatable(x, {__gc=F})
|
||||
assert(T.testC("objsize 2; return 1", x) == 41)
|
||||
cl = {}
|
||||
a = {[x] = 1}
|
||||
x = T.udataval(x)
|
||||
collectgarbage()
|
||||
-- old `x' cannot be collected (`a' still uses it)
|
||||
assert(table.getn(cl) == 0)
|
||||
for n in pairs(a) do a[n] = nil end
|
||||
collectgarbage()
|
||||
assert(table.getn(cl) == 1 and cl[1] == x) -- old `x' must be collected
|
||||
|
||||
-- testing lua_equal
|
||||
assert(T.testC("equal 2 4; return 1", print, 1, print, 20))
|
||||
assert(T.testC("equal 3 2; return 1", 'alo', "alo"))
|
||||
assert(T.testC("equal 2 3; return 1", nil, nil))
|
||||
assert(not T.testC("equal 2 3; return 1", {}, {}))
|
||||
assert(not T.testC("equal 2 3; return 1"))
|
||||
assert(not T.testC("equal 2 3; return 1", 3))
|
||||
|
||||
-- testing lua_equal with fallbacks
|
||||
do
|
||||
local map = {}
|
||||
local t = {__eq = function (a,b) return map[a] == map[b] end}
|
||||
local function f(x)
|
||||
local u = T.newuserdata(0)
|
||||
debug.setmetatable(u, t)
|
||||
map[u] = x
|
||||
return u
|
||||
end
|
||||
assert(f(10) == f(10))
|
||||
assert(f(10) ~= f(11))
|
||||
assert(T.testC("equal 2 3; return 1", f(10), f(10)))
|
||||
assert(not T.testC("equal 2 3; return 1", f(10), f(20)))
|
||||
t.__eq = nil
|
||||
assert(f(10) ~= f(10))
|
||||
end
|
||||
|
||||
print'+'
|
||||
|
||||
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
do -- testing errors during GC
|
||||
local a = {}
|
||||
for i=1,20 do
|
||||
a[i] = T.newuserdata(i) -- creates several udata
|
||||
end
|
||||
for i=1,20,2 do -- mark half of them to raise error during GC
|
||||
debug.setmetatable(a[i], {__gc = function (x) error("error inside gc") end})
|
||||
end
|
||||
for i=2,20,2 do -- mark the other half to count and to create more garbage
|
||||
debug.setmetatable(a[i], {__gc = function (x) loadstring("A=A+1")() end})
|
||||
end
|
||||
_G.A = 0
|
||||
a = 0
|
||||
while 1 do
|
||||
if xpcall(collectgarbage, function (s) a=a+1 end) then
|
||||
break -- stop if no more errors
|
||||
end
|
||||
end
|
||||
assert(a == 10) -- number of errors
|
||||
assert(A == 10) -- number of normal collections
|
||||
end
|
||||
-------------------------------------------------------------------------
|
||||
-- test for userdata vals
|
||||
do
|
||||
local a = {}; local lim = 30
|
||||
for i=0,lim do a[i] = T.pushuserdata(i) end
|
||||
for i=0,lim do assert(T.udataval(a[i]) == i) end
|
||||
for i=0,lim do assert(T.pushuserdata(i) == a[i]) end
|
||||
for i=0,lim do a[a[i]] = i end
|
||||
for i=0,lim do a[T.pushuserdata(i)] = i end
|
||||
assert(type(tostring(a[1])) == "string")
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
-- testing multiple states
|
||||
T.closestate(T.newstate());
|
||||
L1 = T.newstate()
|
||||
assert(L1)
|
||||
assert(pack(T.doremote(L1, "function f () return 'alo', 3 end; f()")).n == 0)
|
||||
|
||||
a, b = T.doremote(L1, "return f()")
|
||||
assert(a == 'alo' and b == '3')
|
||||
|
||||
T.doremote(L1, "_ERRORMESSAGE = nil")
|
||||
-- error: `sin' is not defined
|
||||
a, b = T.doremote(L1, "return sin(1)")
|
||||
assert(a == nil and b == 2) -- 2 == run-time error
|
||||
|
||||
-- error: syntax error
|
||||
a, b, c = T.doremote(L1, "return a+")
|
||||
assert(a == nil and b == 3 and type(c) == "string") -- 3 == syntax error
|
||||
|
||||
T.loadlib(L1)
|
||||
a, b = T.doremote(L1, [[
|
||||
a = strlibopen()
|
||||
a = packageopen()
|
||||
a = baselibopen(); assert(a == _G and require("_G") == a)
|
||||
a = iolibopen(); assert(type(a.read) == "function")
|
||||
assert(require("io") == a)
|
||||
a = tablibopen(); assert(type(a.insert) == "function")
|
||||
a = dblibopen(); assert(type(a.getlocal) == "function")
|
||||
a = mathlibopen(); assert(type(a.sin) == "function")
|
||||
return string.sub('okinama', 1, 2)
|
||||
]])
|
||||
assert(a == "ok")
|
||||
|
||||
T.closestate(L1);
|
||||
|
||||
L1 = T.newstate()
|
||||
T.loadlib(L1)
|
||||
T.doremote(L1, "a = {}")
|
||||
T.testC(L1, [[pushstring a; gettable G; pushstring x; pushnum 1;
|
||||
settable -3]])
|
||||
assert(T.doremote(L1, "return a.x") == "1")
|
||||
|
||||
T.closestate(L1)
|
||||
|
||||
L1 = nil
|
||||
|
||||
print('+')
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
-- testing memory limits
|
||||
-------------------------------------------------------------------------
|
||||
collectgarbage()
|
||||
T.totalmem(T.totalmem()+5000) -- set low memory limit (+5k)
|
||||
assert(not pcall(loadstring"local a={}; for i=1,100000 do a[i]=i end"))
|
||||
T.totalmem(1000000000) -- restore high limit
|
||||
|
||||
|
||||
local function stack(x) if x>0 then stack(x-1) end end
|
||||
|
||||
-- test memory errors; increase memory limit in small steps, so that
|
||||
-- we get memory errors in different parts of a given task, up to there
|
||||
-- is enough memory to complete the task without errors
|
||||
function testamem (s, f)
|
||||
collectgarbage()
|
||||
stack(10) -- ensure minimum stack size
|
||||
local M = T.totalmem()
|
||||
local oldM = M
|
||||
local a,b = nil
|
||||
while 1 do
|
||||
M = M+3 -- increase memory limit in small steps
|
||||
T.totalmem(M)
|
||||
a, b = pcall(f)
|
||||
if a and b then break end -- stop when no more errors
|
||||
collectgarbage()
|
||||
if not a and not string.find(b, "memory") then -- `real' error?
|
||||
T.totalmem(1000000000) -- restore high limit
|
||||
error(b, 0)
|
||||
end
|
||||
end
|
||||
T.totalmem(1000000000) -- restore high limit
|
||||
print("\nlimit for " .. s .. ": " .. M-oldM)
|
||||
return b
|
||||
end
|
||||
|
||||
|
||||
-- testing memory errors when creating a new state
|
||||
|
||||
b = testamem("state creation", T.newstate)
|
||||
T.closestate(b); -- close new state
|
||||
|
||||
|
||||
-- testing threads
|
||||
|
||||
function expand (n,s)
|
||||
if n==0 then return "" end
|
||||
local e = string.rep("=", n)
|
||||
return string.format("T.doonnewstack([%s[ %s;\n collectgarbage(); %s]%s])\n",
|
||||
e, s, expand(n-1,s), e)
|
||||
end
|
||||
|
||||
G=0; collectgarbage(); a =collectgarbage("count")
|
||||
loadstring(expand(20,"G=G+1"))()
|
||||
assert(G==20); collectgarbage(); -- assert(gcinfo() <= a+1)
|
||||
|
||||
testamem("thread creation", function ()
|
||||
return T.doonnewstack("x=1") == 0 -- try to create thread
|
||||
end)
|
||||
|
||||
|
||||
-- testing memory x compiler
|
||||
|
||||
testamem("loadstring", function ()
|
||||
return loadstring("x=1") -- try to do a loadstring
|
||||
end)
|
||||
|
||||
|
||||
local testprog = [[
|
||||
local function foo () return end
|
||||
local t = {"x"}
|
||||
a = "aaa"
|
||||
for _, v in ipairs(t) do a=a..v end
|
||||
return true
|
||||
]]
|
||||
|
||||
-- testing memory x dofile
|
||||
_G.a = nil
|
||||
local t =os.tmpname()
|
||||
local f = assert(io.open(t, "w"))
|
||||
f:write(testprog)
|
||||
f:close()
|
||||
testamem("dofile", function ()
|
||||
local a = loadfile(t)
|
||||
return a and a()
|
||||
end)
|
||||
assert(os.remove(t))
|
||||
assert(_G.a == "aaax")
|
||||
|
||||
|
||||
-- other generic tests
|
||||
|
||||
testamem("string creation", function ()
|
||||
local a, b = string.gsub("alo alo", "(a)", function (x) return x..'b' end)
|
||||
return (a == 'ablo ablo')
|
||||
end)
|
||||
|
||||
testamem("dump/undump", function ()
|
||||
local a = loadstring(testprog)
|
||||
local b = a and string.dump(a)
|
||||
a = b and loadstring(b)
|
||||
return a and a()
|
||||
end)
|
||||
|
||||
local t = os.tmpname()
|
||||
testamem("file creation", function ()
|
||||
local f = assert(io.open(t, 'w'))
|
||||
assert (not io.open"nomenaoexistente")
|
||||
io.close(f);
|
||||
return not loadfile'nomenaoexistente'
|
||||
end)
|
||||
assert(os.remove(t))
|
||||
|
||||
testamem("table creation", function ()
|
||||
local a, lim = {}, 10
|
||||
for i=1,lim do a[i] = i; a[i..'a'] = {} end
|
||||
return (type(a[lim..'a']) == 'table' and a[lim] == lim)
|
||||
end)
|
||||
|
||||
local a = 1
|
||||
close = nil
|
||||
testamem("closure creation", function ()
|
||||
function close (b,c)
|
||||
return function (x) return a+b+c+x end
|
||||
end
|
||||
return (close(2,3)(4) == 10)
|
||||
end)
|
||||
|
||||
testamem("coroutines", function ()
|
||||
local a = coroutine.wrap(function ()
|
||||
coroutine.yield(string.rep("a", 10))
|
||||
return {}
|
||||
end)
|
||||
assert(string.len(a()) == 10)
|
||||
return a()
|
||||
end)
|
||||
|
||||
print'+'
|
||||
|
||||
-- testing some auxlib functions
|
||||
assert(T.gsub("alo.alo.uhuh.", ".", "//") == "alo//alo//uhuh//")
|
||||
assert(T.gsub("alo.alo.uhuh.", "alo", "//") == "//.//.uhuh.")
|
||||
assert(T.gsub("", "alo", "//") == "")
|
||||
assert(T.gsub("...", ".", "/.") == "/././.")
|
||||
assert(T.gsub("...", "...", "") == "")
|
||||
|
||||
|
||||
print'OK'
|
||||
|
||||
339
lib/lua/lua-tests/attrib.lua
Normal file
339
lib/lua/lua-tests/attrib.lua
Normal file
@@ -0,0 +1,339 @@
|
||||
do --[
|
||||
|
||||
print "testing require"
|
||||
|
||||
assert(require"string" == string)
|
||||
assert(require"math" == math)
|
||||
assert(require"table" == table)
|
||||
assert(require"io" == io)
|
||||
assert(require"os" == os)
|
||||
assert(require"debug" == debug)
|
||||
assert(require"coroutine" == coroutine)
|
||||
|
||||
assert(type(package.path) == "string")
|
||||
assert(type(package.cpath) == "string")
|
||||
assert(type(package.loaded) == "table")
|
||||
assert(type(package.preload) == "table")
|
||||
|
||||
|
||||
local DIR = "libs/"
|
||||
|
||||
local function createfiles (files, preextras, posextras)
|
||||
for n,c in pairs(files) do
|
||||
io.output(DIR..n)
|
||||
io.write(string.format(preextras, n))
|
||||
io.write(c)
|
||||
io.write(string.format(posextras, n))
|
||||
io.close(io.output())
|
||||
end
|
||||
end
|
||||
|
||||
function removefiles (files)
|
||||
for n in pairs(files) do
|
||||
os.remove(DIR..n)
|
||||
end
|
||||
end
|
||||
|
||||
local files = {
|
||||
["A.lua"] = "",
|
||||
["B.lua"] = "assert(...=='B');require 'A'",
|
||||
["A.lc"] = "",
|
||||
["A"] = "",
|
||||
["L"] = "",
|
||||
["XXxX"] = "",
|
||||
["C.lua"] = "package.loaded[...] = 25; require'C'"
|
||||
}
|
||||
|
||||
AA = nil
|
||||
local extras = [[
|
||||
NAME = '%s'
|
||||
REQUIRED = ...
|
||||
return AA]]
|
||||
|
||||
createfiles(files, "", extras)
|
||||
|
||||
|
||||
local oldpath = package.path
|
||||
|
||||
package.path = string.gsub("D/?.lua;D/?.lc;D/?;D/??x?;D/L", "D/", DIR)
|
||||
|
||||
local try = function (p, n, r)
|
||||
NAME = nil
|
||||
local rr = require(p)
|
||||
assert(NAME == n)
|
||||
assert(REQUIRED == p)
|
||||
assert(rr == r)
|
||||
end
|
||||
|
||||
assert(require"C" == 25)
|
||||
assert(require"C" == 25)
|
||||
AA = nil
|
||||
try('B', 'B.lua', true)
|
||||
assert(package.loaded.B)
|
||||
assert(require"B" == true)
|
||||
assert(package.loaded.A)
|
||||
package.loaded.A = nil
|
||||
try('B', nil, true) -- should not reload package
|
||||
try('A', 'A.lua', true)
|
||||
package.loaded.A = nil
|
||||
os.remove(DIR..'A.lua')
|
||||
AA = {}
|
||||
try('A', 'A.lc', AA) -- now must find second option
|
||||
assert(require("A") == AA)
|
||||
AA = false
|
||||
try('K', 'L', false) -- default option
|
||||
try('K', 'L', false) -- default option (should reload it)
|
||||
assert(rawget(_G, "_REQUIREDNAME") == nil)
|
||||
|
||||
AA = "x"
|
||||
try("X", "XXxX", AA)
|
||||
|
||||
|
||||
removefiles(files)
|
||||
|
||||
|
||||
-- testing require of sub-packages
|
||||
|
||||
package.path = string.gsub("D/?.lua;D/?/init.lua", "D/", DIR)
|
||||
|
||||
files = {
|
||||
["P1/init.lua"] = "AA = 10",
|
||||
["P1/xuxu.lua"] = "AA = 20",
|
||||
}
|
||||
|
||||
createfiles(files, "module(..., package.seeall)\n", "")
|
||||
AA = 0
|
||||
|
||||
local m = assert(require"P1")
|
||||
assert(m == P1 and m._NAME == "P1" and AA == 0 and m.AA == 10)
|
||||
assert(require"P1" == P1 and P1 == m)
|
||||
assert(require"P1" == P1)
|
||||
assert(P1._PACKAGE == "")
|
||||
|
||||
local m = assert(require"P1.xuxu")
|
||||
assert(m == P1.xuxu and m._NAME == "P1.xuxu" and AA == 0 and m.AA == 20)
|
||||
assert(require"P1.xuxu" == P1.xuxu and P1.xuxu == m)
|
||||
assert(require"P1.xuxu" == P1.xuxu)
|
||||
assert(require"P1" == P1)
|
||||
assert(P1.xuxu._PACKAGE == "P1.")
|
||||
assert(P1.AA == 10 and P1._PACKAGE == "")
|
||||
assert(P1._G == _G and P1.xuxu._G == _G)
|
||||
|
||||
|
||||
|
||||
removefiles(files)
|
||||
|
||||
|
||||
package.path = ""
|
||||
assert(not pcall(require, "file_does_not_exist"))
|
||||
package.path = "??\0?"
|
||||
assert(not pcall(require, "file_does_not_exist1"))
|
||||
|
||||
package.path = oldpath
|
||||
|
||||
-- check 'require' error message
|
||||
local fname = "file_does_not_exist2"
|
||||
local m, err = pcall(require, fname)
|
||||
for t in string.gmatch(package.path..";"..package.cpath, "[^;]+") do
|
||||
t = string.gsub(t, "?", fname)
|
||||
assert(string.find(err, t, 1, true))
|
||||
end
|
||||
|
||||
|
||||
local function import(...)
|
||||
local f = {...}
|
||||
return function (m)
|
||||
for i=1, #f do m[f[i]] = _G[f[i]] end
|
||||
end
|
||||
end
|
||||
|
||||
local assert, module, package = assert, module, package
|
||||
X = nil; x = 0; assert(_G.x == 0) -- `x' must be a global variable
|
||||
module"X"; x = 1; assert(_M.x == 1)
|
||||
module"X.a.b.c"; x = 2; assert(_M.x == 2)
|
||||
module("X.a.b", package.seeall); x = 3
|
||||
assert(X._NAME == "X" and X.a.b.c._NAME == "X.a.b.c" and X.a.b._NAME == "X.a.b")
|
||||
assert(X._M == X and X.a.b.c._M == X.a.b.c and X.a.b._M == X.a.b)
|
||||
assert(X.x == 1 and X.a.b.c.x == 2 and X.a.b.x == 3)
|
||||
assert(X._PACKAGE == "" and X.a.b.c._PACKAGE == "X.a.b." and
|
||||
X.a.b._PACKAGE == "X.a.")
|
||||
assert(_PACKAGE.."c" == "X.a.c")
|
||||
assert(X.a._NAME == nil and X.a._M == nil)
|
||||
module("X.a", import("X")) ; x = 4
|
||||
assert(X.a._NAME == "X.a" and X.a.x == 4 and X.a._M == X.a)
|
||||
module("X.a.b", package.seeall); assert(x == 3); x = 5
|
||||
assert(_NAME == "X.a.b" and X.a.b.x == 5)
|
||||
|
||||
assert(X._G == nil and X.a._G == nil and X.a.b._G == _G and X.a.b.c._G == nil)
|
||||
|
||||
setfenv(1, _G)
|
||||
assert(x == 0)
|
||||
|
||||
assert(not pcall(module, "x"))
|
||||
assert(not pcall(module, "math.sin"))
|
||||
|
||||
|
||||
-- testing C libraries
|
||||
|
||||
|
||||
local p = "" -- On Mac OS X, redefine this to "_"
|
||||
|
||||
-- assert(loadlib == package.loadlib) -- only for compatibility
|
||||
local f, err, when = package.loadlib("libs/lib1.so", p.."luaopen_lib1")
|
||||
if not f then
|
||||
(Message or print)('\a\n >>> cannot load dynamic library <<<\n\a')
|
||||
print(err, when)
|
||||
else
|
||||
f() -- open library
|
||||
assert(require("lib1") == lib1)
|
||||
collectgarbage()
|
||||
assert(lib1.id("x") == "x")
|
||||
f = assert(package.loadlib("libs/lib1.so", p.."anotherfunc"))
|
||||
assert(f(10, 20) == "1020\n")
|
||||
f, err, when = package.loadlib("libs/lib1.so", p.."xuxu")
|
||||
assert(not f and type(err) == "string" and when == "init")
|
||||
package.cpath = "libs/?.so"
|
||||
require"lib2"
|
||||
assert(lib2.id("x") == "x")
|
||||
local fs = require"lib1.sub"
|
||||
assert(fs == lib1.sub and next(lib1.sub) == nil)
|
||||
module("lib2", package.seeall)
|
||||
f = require"-lib2"
|
||||
assert(f.id("x") == "x" and _M == f and _NAME == "lib2")
|
||||
module("lib1.sub", package.seeall)
|
||||
assert(_M == fs)
|
||||
setfenv(1, _G)
|
||||
|
||||
end
|
||||
f, err, when = package.loadlib("donotexist", p.."xuxu")
|
||||
assert(not f and type(err) == "string" and (when == "open" or when == "absent"))
|
||||
|
||||
|
||||
-- testing preload
|
||||
|
||||
do
|
||||
local p = package
|
||||
package = {}
|
||||
p.preload.pl = function (...)
|
||||
module(...)
|
||||
function xuxu (x) return x+20 end
|
||||
end
|
||||
|
||||
require"pl"
|
||||
assert(require"pl" == pl)
|
||||
assert(pl.xuxu(10) == 30)
|
||||
|
||||
package = p
|
||||
assert(type(package.path) == "string")
|
||||
end
|
||||
|
||||
|
||||
|
||||
end --]
|
||||
|
||||
print('+')
|
||||
|
||||
print("testing assignments, logical operators, and constructors")
|
||||
|
||||
local res, res2 = 27
|
||||
|
||||
a, b = 1, 2+3
|
||||
assert(a==1 and b==5)
|
||||
a={}
|
||||
function f() return 10, 11, 12 end
|
||||
a.x, b, a[1] = 1, 2, f()
|
||||
assert(a.x==1 and b==2 and a[1]==10)
|
||||
a[f()], b, a[f()+3] = f(), a, 'x'
|
||||
assert(a[10] == 10 and b == a and a[13] == 'x')
|
||||
|
||||
do
|
||||
local f = function (n) local x = {}; for i=1,n do x[i]=i end;
|
||||
return unpack(x) end;
|
||||
local a,b,c
|
||||
a,b = 0, f(1)
|
||||
assert(a == 0 and b == 1)
|
||||
A,b = 0, f(1)
|
||||
assert(A == 0 and b == 1)
|
||||
a,b,c = 0,5,f(4)
|
||||
assert(a==0 and b==5 and c==1)
|
||||
a,b,c = 0,5,f(0)
|
||||
assert(a==0 and b==5 and c==nil)
|
||||
end
|
||||
|
||||
|
||||
a, b, c, d = 1 and nil, 1 or nil, (1 and (nil or 1)), 6
|
||||
assert(not a and b and c and d==6)
|
||||
|
||||
d = 20
|
||||
a, b, c, d = f()
|
||||
assert(a==10 and b==11 and c==12 and d==nil)
|
||||
a,b = f(), 1, 2, 3, f()
|
||||
assert(a==10 and b==1)
|
||||
|
||||
assert(a<b == false and a>b == true)
|
||||
assert((10 and 2) == 2)
|
||||
assert((10 or 2) == 10)
|
||||
assert((10 or assert(nil)) == 10)
|
||||
assert(not (nil and assert(nil)))
|
||||
assert((nil or "alo") == "alo")
|
||||
assert((nil and 10) == nil)
|
||||
assert((false and 10) == false)
|
||||
assert((true or 10) == true)
|
||||
assert((false or 10) == 10)
|
||||
assert(false ~= nil)
|
||||
assert(nil ~= false)
|
||||
assert(not nil == true)
|
||||
assert(not not nil == false)
|
||||
assert(not not 1 == true)
|
||||
assert(not not a == true)
|
||||
assert(not not (6 or nil) == true)
|
||||
assert(not not (nil and 56) == false)
|
||||
assert(not not (nil and true) == false)
|
||||
print('+')
|
||||
|
||||
a = {}
|
||||
a[true] = 20
|
||||
a[false] = 10
|
||||
assert(a[1<2] == 20 and a[1>2] == 10)
|
||||
|
||||
function f(a) return a end
|
||||
|
||||
local a = {}
|
||||
for i=3000,-3000,-1 do a[i] = i; end
|
||||
a[10e30] = "alo"; a[true] = 10; a[false] = 20
|
||||
assert(a[10e30] == 'alo' and a[not 1] == 20 and a[10<20] == 10)
|
||||
for i=3000,-3000,-1 do assert(a[i] == i); end
|
||||
a[print] = assert
|
||||
a[f] = print
|
||||
a[a] = a
|
||||
assert(a[a][a][a][a][print] == assert)
|
||||
a[print](a[a[f]] == a[print])
|
||||
a = nil
|
||||
|
||||
a = {10,9,8,7,6,5,4,3,2; [-3]='a', [f]=print, a='a', b='ab'}
|
||||
a, a.x, a.y = a, a[-3]
|
||||
assert(a[1]==10 and a[-3]==a.a and a[f]==print and a.x=='a' and not a.y)
|
||||
a[1], f(a)[2], b, c = {['alo']=assert}, 10, a[1], a[f], 6, 10, 23, f(a), 2
|
||||
a[1].alo(a[2]==10 and b==10 and c==print)
|
||||
|
||||
a[2^31] = 10; a[2^31+1] = 11; a[-2^31] = 12;
|
||||
a[2^32] = 13; a[-2^32] = 14; a[2^32+1] = 15; a[10^33] = 16;
|
||||
|
||||
assert(a[2^31] == 10 and a[2^31+1] == 11 and a[-2^31] == 12 and
|
||||
a[2^32] == 13 and a[-2^32] == 14 and a[2^32+1] == 15 and
|
||||
a[10^33] == 16)
|
||||
|
||||
a = nil
|
||||
|
||||
|
||||
do
|
||||
local a,i,j,b
|
||||
a = {'a', 'b'}; i=1; j=2; b=a
|
||||
i, a[i], a, j, a[j], a[i+j] = j, i, i, b, j, i
|
||||
assert(i == 2 and b[1] == 1 and a == 1 and j == b and b[2] == 2 and
|
||||
b[3] == 1)
|
||||
end
|
||||
|
||||
print('OK')
|
||||
|
||||
return res
|
||||
381
lib/lua/lua-tests/big.lua
Normal file
381
lib/lua/lua-tests/big.lua
Normal file
@@ -0,0 +1,381 @@
|
||||
print "testing string length overflow"
|
||||
|
||||
local longs = string.rep("\0", 2^25)
|
||||
local function catter (i)
|
||||
return assert(loadstring(
|
||||
string.format("return function(a) return a%s end",
|
||||
string.rep("..a", i-1))))()
|
||||
end
|
||||
rep129 = catter(129)
|
||||
local a, b = pcall(rep129, longs)
|
||||
assert(not a and string.find(b, "overflow"))
|
||||
print('+')
|
||||
|
||||
|
||||
require "checktable"
|
||||
|
||||
--[[ lots of empty lines (to force SETLINEW)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
--]]
|
||||
|
||||
|
||||
a,b = nil,nil
|
||||
while not b do
|
||||
if a then
|
||||
b = { -- lots of strings (to force JMPW and PUSHCONSTANTW)
|
||||
"n1", "n2", "n3", "n4", "n5", "n6", "n7", "n8", "n9", "n10",
|
||||
"n11", "n12", "j301", "j302", "j303", "j304", "j305", "j306", "j307", "j308",
|
||||
"j309", "a310", "n311", "n312", "n313", "n314", "n315", "n316", "n317", "n318",
|
||||
"n319", "n320", "n321", "n322", "n323", "n324", "n325", "n326", "n327", "n328",
|
||||
"a329", "n330", "n331", "n332", "n333", "n334", "n335", "n336", "n337", "n338",
|
||||
"n339", "n340", "n341", "z342", "n343", "n344", "n345", "n346", "n347", "n348",
|
||||
"n349", "n350", "n351", "n352", "r353", "n354", "n355", "n356", "n357", "n358",
|
||||
"n359", "n360", "n361", "n362", "n363", "n364", "n365", "n366", "z367", "n368",
|
||||
"n369", "n370", "n371", "n372", "n373", "n374", "n375", "a376", "n377", "n378",
|
||||
"n379", "n380", "n381", "n382", "n383", "n384", "n385", "n386", "n387", "n388",
|
||||
"n389", "n390", "n391", "n392", "n393", "n394", "n395", "n396", "n397", "n398",
|
||||
"n399", "n400", "n13", "n14", "n15", "n16", "n17", "n18", "n19", "n20",
|
||||
"n21", "n22", "n23", "a24", "n25", "n26", "n27", "n28", "n29", "j30",
|
||||
"n31", "n32", "n33", "n34", "n35", "n36", "n37", "n38", "n39", "n40",
|
||||
"n41", "n42", "n43", "n44", "n45", "n46", "n47", "n48", "n49", "n50",
|
||||
"n51", "n52", "n53", "n54", "n55", "n56", "n57", "n58", "n59", "n60",
|
||||
"n61", "n62", "n63", "n64", "n65", "a66", "z67", "n68", "n69", "n70",
|
||||
"n71", "n72", "n73", "n74", "n75", "n76", "n77", "n78", "n79", "n80",
|
||||
"n81", "n82", "n83", "n84", "n85", "n86", "n87", "n88", "n89", "n90",
|
||||
"n91", "n92", "n93", "n94", "n95", "n96", "n97", "n98", "n99", "n100",
|
||||
"n201", "n202", "n203", "n204", "n205", "n206", "n207", "n208", "n209", "n210",
|
||||
"n211", "n212", "n213", "n214", "n215", "n216", "n217", "n218", "n219", "n220",
|
||||
"n221", "n222", "n223", "n224", "n225", "n226", "n227", "n228", "n229", "n230",
|
||||
"n231", "n232", "n233", "n234", "n235", "n236", "n237", "n238", "n239", "a240",
|
||||
"a241", "a242", "a243", "a244", "a245", "a246", "a247", "a248", "a249", "n250",
|
||||
"n251", "n252", "n253", "n254", "n255", "n256", "n257", "n258", "n259", "n260",
|
||||
"n261", "n262", "n263", "n264", "n265", "n266", "n267", "n268", "n269", "n270",
|
||||
"n271", "n272", "n273", "n274", "n275", "n276", "n277", "n278", "n279", "n280",
|
||||
"n281", "n282", "n283", "n284", "n285", "n286", "n287", "n288", "n289", "n290",
|
||||
"n291", "n292", "n293", "n294", "n295", "n296", "n297", "n298", "n299"
|
||||
; x=23}
|
||||
else a = 1 end
|
||||
|
||||
|
||||
end
|
||||
|
||||
assert(b.x == 23)
|
||||
print('+')
|
||||
|
||||
stat(b)
|
||||
|
||||
repeat
|
||||
a = {
|
||||
n1 = 1.5, n2 = 2.5, n3 = 3.5, n4 = 4.5, n5 = 5.5, n6 = 6.5, n7 = 7.5,
|
||||
n8 = 8.5, n9 = 9.5, n10 = 10.5, n11 = 11.5, n12 = 12.5,
|
||||
j301 = 301.5, j302 = 302.5, j303 = 303.5, j304 = 304.5, j305 = 305.5,
|
||||
j306 = 306.5, j307 = 307.5, j308 = 308.5, j309 = 309.5, a310 = 310.5,
|
||||
n311 = 311.5, n312 = 312.5, n313 = 313.5, n314 = 314.5, n315 = 315.5,
|
||||
n316 = 316.5, n317 = 317.5, n318 = 318.5, n319 = 319.5, n320 = 320.5,
|
||||
n321 = 321.5, n322 = 322.5, n323 = 323.5, n324 = 324.5, n325 = 325.5,
|
||||
n326 = 326.5, n327 = 327.5, n328 = 328.5, a329 = 329.5, n330 = 330.5,
|
||||
n331 = 331.5, n332 = 332.5, n333 = 333.5, n334 = 334.5, n335 = 335.5,
|
||||
n336 = 336.5, n337 = 337.5, n338 = 338.5, n339 = 339.5, n340 = 340.5,
|
||||
n341 = 341.5, z342 = 342.5, n343 = 343.5, n344 = 344.5, n345 = 345.5,
|
||||
n346 = 346.5, n347 = 347.5, n348 = 348.5, n349 = 349.5, n350 = 350.5,
|
||||
n351 = 351.5, n352 = 352.5, r353 = 353.5, n354 = 354.5, n355 = 355.5,
|
||||
n356 = 356.5, n357 = 357.5, n358 = 358.5, n359 = 359.5, n360 = 360.5,
|
||||
n361 = 361.5, n362 = 362.5, n363 = 363.5, n364 = 364.5, n365 = 365.5,
|
||||
n366 = 366.5, z367 = 367.5, n368 = 368.5, n369 = 369.5, n370 = 370.5,
|
||||
n371 = 371.5, n372 = 372.5, n373 = 373.5, n374 = 374.5, n375 = 375.5,
|
||||
a376 = 376.5, n377 = 377.5, n378 = 378.5, n379 = 379.5, n380 = 380.5,
|
||||
n381 = 381.5, n382 = 382.5, n383 = 383.5, n384 = 384.5, n385 = 385.5,
|
||||
n386 = 386.5, n387 = 387.5, n388 = 388.5, n389 = 389.5, n390 = 390.5,
|
||||
n391 = 391.5, n392 = 392.5, n393 = 393.5, n394 = 394.5, n395 = 395.5,
|
||||
n396 = 396.5, n397 = 397.5, n398 = 398.5, n399 = 399.5, n400 = 400.5,
|
||||
n13 = 13.5, n14 = 14.5, n15 = 15.5, n16 = 16.5, n17 = 17.5,
|
||||
n18 = 18.5, n19 = 19.5, n20 = 20.5, n21 = 21.5, n22 = 22.5,
|
||||
n23 = 23.5, a24 = 24.5, n25 = 25.5, n26 = 26.5, n27 = 27.5,
|
||||
n28 = 28.5, n29 = 29.5, j30 = 30.5, n31 = 31.5, n32 = 32.5,
|
||||
n33 = 33.5, n34 = 34.5, n35 = 35.5, n36 = 36.5, n37 = 37.5,
|
||||
n38 = 38.5, n39 = 39.5, n40 = 40.5, n41 = 41.5, n42 = 42.5,
|
||||
n43 = 43.5, n44 = 44.5, n45 = 45.5, n46 = 46.5, n47 = 47.5,
|
||||
n48 = 48.5, n49 = 49.5, n50 = 50.5, n51 = 51.5, n52 = 52.5,
|
||||
n53 = 53.5, n54 = 54.5, n55 = 55.5, n56 = 56.5, n57 = 57.5,
|
||||
n58 = 58.5, n59 = 59.5, n60 = 60.5, n61 = 61.5, n62 = 62.5,
|
||||
n63 = 63.5, n64 = 64.5, n65 = 65.5, a66 = 66.5, z67 = 67.5,
|
||||
n68 = 68.5, n69 = 69.5, n70 = 70.5, n71 = 71.5, n72 = 72.5,
|
||||
n73 = 73.5, n74 = 74.5, n75 = 75.5, n76 = 76.5, n77 = 77.5,
|
||||
n78 = 78.5, n79 = 79.5, n80 = 80.5, n81 = 81.5, n82 = 82.5,
|
||||
n83 = 83.5, n84 = 84.5, n85 = 85.5, n86 = 86.5, n87 = 87.5,
|
||||
n88 = 88.5, n89 = 89.5, n90 = 90.5, n91 = 91.5, n92 = 92.5,
|
||||
n93 = 93.5, n94 = 94.5, n95 = 95.5, n96 = 96.5, n97 = 97.5,
|
||||
n98 = 98.5, n99 = 99.5, n100 = 100.5, n201 = 201.5, n202 = 202.5,
|
||||
n203 = 203.5, n204 = 204.5, n205 = 205.5, n206 = 206.5, n207 = 207.5,
|
||||
n208 = 208.5, n209 = 209.5, n210 = 210.5, n211 = 211.5, n212 = 212.5,
|
||||
n213 = 213.5, n214 = 214.5, n215 = 215.5, n216 = 216.5, n217 = 217.5,
|
||||
n218 = 218.5, n219 = 219.5, n220 = 220.5, n221 = 221.5, n222 = 222.5,
|
||||
n223 = 223.5, n224 = 224.5, n225 = 225.5, n226 = 226.5, n227 = 227.5,
|
||||
n228 = 228.5, n229 = 229.5, n230 = 230.5, n231 = 231.5, n232 = 232.5,
|
||||
n233 = 233.5, n234 = 234.5, n235 = 235.5, n236 = 236.5, n237 = 237.5,
|
||||
n238 = 238.5, n239 = 239.5, a240 = 240.5, a241 = 241.5, a242 = 242.5,
|
||||
a243 = 243.5, a244 = 244.5, a245 = 245.5, a246 = 246.5, a247 = 247.5,
|
||||
a248 = 248.5, a249 = 249.5, n250 = 250.5, n251 = 251.5, n252 = 252.5,
|
||||
n253 = 253.5, n254 = 254.5, n255 = 255.5, n256 = 256.5, n257 = 257.5,
|
||||
n258 = 258.5, n259 = 259.5, n260 = 260.5, n261 = 261.5, n262 = 262.5,
|
||||
n263 = 263.5, n264 = 264.5, n265 = 265.5, n266 = 266.5, n267 = 267.5,
|
||||
n268 = 268.5, n269 = 269.5, n270 = 270.5, n271 = 271.5, n272 = 272.5,
|
||||
n273 = 273.5, n274 = 274.5, n275 = 275.5, n276 = 276.5, n277 = 277.5,
|
||||
n278 = 278.5, n279 = 279.5, n280 = 280.5, n281 = 281.5, n282 = 282.5,
|
||||
n283 = 283.5, n284 = 284.5, n285 = 285.5, n286 = 286.5, n287 = 287.5,
|
||||
n288 = 288.5, n289 = 289.5, n290 = 290.5, n291 = 291.5, n292 = 292.5,
|
||||
n293 = 293.5, n294 = 294.5, n295 = 295.5, n296 = 296.5, n297 = 297.5,
|
||||
n298 = 298.5, n299 = 299.5, j300 = 300} or 1
|
||||
until 1
|
||||
|
||||
assert(a.n299 == 299.5)
|
||||
xxx = 1
|
||||
assert(xxx == 1)
|
||||
|
||||
stat(a)
|
||||
|
||||
function a:findfield (f)
|
||||
local i,v = next(self, nil)
|
||||
while i ~= f do
|
||||
if not i then return end
|
||||
i,v = next(self, i)
|
||||
end
|
||||
return v
|
||||
end
|
||||
|
||||
local ii = 0
|
||||
i = 1
|
||||
while b[i] do
|
||||
local r = a:findfield(b[i]);
|
||||
assert(a[b[i]] == r)
|
||||
ii = math.max(ii,i)
|
||||
i = i+1
|
||||
end
|
||||
|
||||
assert(ii == 299)
|
||||
|
||||
function xxxx (x) coroutine.yield('b'); return ii+x end
|
||||
|
||||
assert(xxxx(10) == 309)
|
||||
|
||||
a = nil
|
||||
b = nil
|
||||
a1 = nil
|
||||
|
||||
print("tables with table indices:")
|
||||
i = 1; a={}
|
||||
while i <= 1023 do a[{}] = i; i=i+1 end
|
||||
stat(a)
|
||||
a = nil
|
||||
|
||||
print("tables with function indices:")
|
||||
a={}
|
||||
for i=1,511 do local x; a[function () return x end] = i end
|
||||
stat(a)
|
||||
a = nil
|
||||
|
||||
print'OK'
|
||||
|
||||
return 'a'
|
||||
294
lib/lua/lua-tests/calls.lua
Normal file
294
lib/lua/lua-tests/calls.lua
Normal file
@@ -0,0 +1,294 @@
|
||||
print("testing functions and calls")
|
||||
|
||||
-- get the opportunity to test 'type' too ;)
|
||||
|
||||
assert(type(1<2) == 'boolean')
|
||||
assert(type(true) == 'boolean' and type(false) == 'boolean')
|
||||
assert(type(nil) == 'nil' and type(-3) == 'number' and type'x' == 'string' and
|
||||
type{} == 'table' and type(type) == 'function')
|
||||
|
||||
assert(type(assert) == type(print))
|
||||
f = nil
|
||||
function f (x) return a:x (x) end
|
||||
assert(type(f) == 'function')
|
||||
|
||||
|
||||
-- testing local-function recursion
|
||||
fact = false
|
||||
do
|
||||
local res = 1
|
||||
local function fact (n)
|
||||
if n==0 then return res
|
||||
else return n*fact(n-1)
|
||||
end
|
||||
end
|
||||
assert(fact(5) == 120)
|
||||
end
|
||||
assert(fact == false)
|
||||
|
||||
-- testing declarations
|
||||
a = {i = 10}
|
||||
self = 20
|
||||
function a:x (x) return x+self.i end
|
||||
function a.y (x) return x+self end
|
||||
|
||||
assert(a:x(1)+10 == a.y(1))
|
||||
|
||||
a.t = {i=-100}
|
||||
a["t"].x = function (self, a,b) return self.i+a+b end
|
||||
|
||||
assert(a.t:x(2,3) == -95)
|
||||
|
||||
do
|
||||
local a = {x=0}
|
||||
function a:add (x) self.x, a.y = self.x+x, 20; return self end
|
||||
assert(a:add(10):add(20):add(30).x == 60 and a.y == 20)
|
||||
end
|
||||
|
||||
local a = {b={c={}}}
|
||||
|
||||
function a.b.c.f1 (x) return x+1 end
|
||||
function a.b.c:f2 (x,y) self[x] = y end
|
||||
assert(a.b.c.f1(4) == 5)
|
||||
a.b.c:f2('k', 12); assert(a.b.c.k == 12)
|
||||
|
||||
print('+')
|
||||
|
||||
t = nil -- 'declare' t
|
||||
function f(a,b,c) local d = 'a'; t={a,b,c,d} end
|
||||
|
||||
f( -- this line change must be valid
|
||||
1,2)
|
||||
assert(t[1] == 1 and t[2] == 2 and t[3] == nil and t[4] == 'a')
|
||||
f(1,2, -- this one too
|
||||
3,4)
|
||||
assert(t[1] == 1 and t[2] == 2 and t[3] == 3 and t[4] == 'a')
|
||||
|
||||
function fat(x)
|
||||
if x <= 1 then return 1
|
||||
else return x*loadstring("return fat(" .. x-1 .. ")")()
|
||||
end
|
||||
end
|
||||
|
||||
assert(loadstring "loadstring 'assert(fat(6)==720)' () ")()
|
||||
a = loadstring('return fat(5), 3')
|
||||
a,b = a()
|
||||
assert(a == 120 and b == 3)
|
||||
print('+')
|
||||
|
||||
function err_on_n (n)
|
||||
if n==0 then error(); exit(1);
|
||||
else err_on_n (n-1); exit(1);
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
function dummy (n)
|
||||
if n > 0 then
|
||||
assert(not pcall(err_on_n, n))
|
||||
dummy(n-1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
dummy(10)
|
||||
|
||||
function deep (n)
|
||||
if n>0 then deep(n-1) end
|
||||
end
|
||||
deep(10)
|
||||
deep(200)
|
||||
|
||||
-- testing tail call
|
||||
function deep (n) if n>0 then return deep(n-1) else return 101 end end
|
||||
assert(deep(30000) == 101)
|
||||
a = {}
|
||||
function a:deep (n) if n>0 then return self:deep(n-1) else return 101 end end
|
||||
assert(a:deep(30000) == 101)
|
||||
|
||||
print('+')
|
||||
|
||||
|
||||
a = nil
|
||||
(function (x) a=x end)(23)
|
||||
assert(a == 23 and (function (x) return x*2 end)(20) == 40)
|
||||
|
||||
|
||||
local x,y,z,a
|
||||
a = {}; lim = 2000
|
||||
for i=1, lim do a[i]=i end
|
||||
assert(select(lim, unpack(a)) == lim and select('#', unpack(a)) == lim)
|
||||
x = unpack(a)
|
||||
assert(x == 1)
|
||||
x = {unpack(a)}
|
||||
assert(table.getn(x) == lim and x[1] == 1 and x[lim] == lim)
|
||||
x = {unpack(a, lim-2)}
|
||||
assert(table.getn(x) == 3 and x[1] == lim-2 and x[3] == lim)
|
||||
x = {unpack(a, 10, 6)}
|
||||
assert(next(x) == nil) -- no elements
|
||||
x = {unpack(a, 11, 10)}
|
||||
assert(next(x) == nil) -- no elements
|
||||
x,y = unpack(a, 10, 10)
|
||||
assert(x == 10 and y == nil)
|
||||
x,y,z = unpack(a, 10, 11)
|
||||
assert(x == 10 and y == 11 and z == nil)
|
||||
a,x = unpack{1}
|
||||
assert(a==1 and x==nil)
|
||||
a,x = unpack({1,2}, 1, 1)
|
||||
assert(a==1 and x==nil)
|
||||
|
||||
|
||||
-- testing closures
|
||||
|
||||
-- fixed-point operator
|
||||
Y = function (le)
|
||||
local function a (f)
|
||||
return le(function (x) return f(f)(x) end)
|
||||
end
|
||||
return a(a)
|
||||
end
|
||||
|
||||
|
||||
-- non-recursive factorial
|
||||
|
||||
F = function (f)
|
||||
return function (n)
|
||||
if n == 0 then return 1
|
||||
else return n*f(n-1) end
|
||||
end
|
||||
end
|
||||
|
||||
fat = Y(F)
|
||||
|
||||
assert(fat(0) == 1 and fat(4) == 24 and Y(F)(5)==5*Y(F)(4))
|
||||
|
||||
local function g (z)
|
||||
local function f (a,b,c,d)
|
||||
return function (x,y) return a+b+c+d+a+x+y+z end
|
||||
end
|
||||
return f(z,z+1,z+2,z+3)
|
||||
end
|
||||
|
||||
f = g(10)
|
||||
assert(f(9, 16) == 10+11+12+13+10+9+16+10)
|
||||
|
||||
Y, F, f = nil
|
||||
print('+')
|
||||
|
||||
-- testing multiple returns
|
||||
|
||||
function unlpack (t, i)
|
||||
i = i or 1
|
||||
if (i <= table.getn(t)) then
|
||||
return t[i], unlpack(t, i+1)
|
||||
end
|
||||
end
|
||||
|
||||
function equaltab (t1, t2)
|
||||
assert(table.getn(t1) == table.getn(t2))
|
||||
for i,v1 in ipairs(t1) do
|
||||
assert(v1 == t2[i])
|
||||
end
|
||||
end
|
||||
|
||||
local function pack (...)
|
||||
local x = {...}
|
||||
x.n = select('#', ...)
|
||||
return x
|
||||
end
|
||||
|
||||
function f() return 1,2,30,4 end
|
||||
function ret2 (a,b) return a,b end
|
||||
|
||||
local a,b,c,d = unlpack{1,2,3}
|
||||
assert(a==1 and b==2 and c==3 and d==nil)
|
||||
a = {1,2,3,4,false,10,'alo',false,assert}
|
||||
equaltab(pack(unlpack(a)), a)
|
||||
equaltab(pack(unlpack(a), -1), {1,-1})
|
||||
a,b,c,d = ret2(f()), ret2(f())
|
||||
assert(a==1 and b==1 and c==2 and d==nil)
|
||||
a,b,c,d = unlpack(pack(ret2(f()), ret2(f())))
|
||||
assert(a==1 and b==1 and c==2 and d==nil)
|
||||
a,b,c,d = unlpack(pack(ret2(f()), (ret2(f()))))
|
||||
assert(a==1 and b==1 and c==nil and d==nil)
|
||||
|
||||
a = ret2{ unlpack{1,2,3}, unlpack{3,2,1}, unlpack{"a", "b"}}
|
||||
assert(a[1] == 1 and a[2] == 3 and a[3] == "a" and a[4] == "b")
|
||||
|
||||
|
||||
-- testing calls with 'incorrect' arguments
|
||||
rawget({}, "x", 1)
|
||||
rawset({}, "x", 1, 2)
|
||||
assert(math.sin(1,2) == math.sin(1))
|
||||
table.sort({10,9,8,4,19,23,0,0}, function (a,b) return a<b end, "extra arg")
|
||||
|
||||
|
||||
-- test for generic load
|
||||
x = "-- a comment\0\0\0\n x = 10 + \n23; \
|
||||
local a = function () x = 'hi' end; \
|
||||
return '\0'"
|
||||
local i = 0
|
||||
function read1 (x)
|
||||
return function ()
|
||||
collectgarbage()
|
||||
i=i+1
|
||||
return string.sub(x, i, i)
|
||||
end
|
||||
end
|
||||
|
||||
a = assert(load(read1(x), "modname"))
|
||||
assert(a() == "\0" and _G.x == 33)
|
||||
assert(debug.getinfo(a).source == "modname")
|
||||
|
||||
x = string.dump(loadstring("x = 1; return x"))
|
||||
i = 0
|
||||
a = assert(load(read1(x)))
|
||||
assert(a() == 1 and _G.x == 1)
|
||||
|
||||
i = 0
|
||||
local a, b = load(read1("*a = 123"))
|
||||
assert(not a and type(b) == "string" and i == 2)
|
||||
|
||||
a, b = load(function () error("hhi") end)
|
||||
assert(not a and string.find(b, "hhi"))
|
||||
|
||||
-- test generic load with nested functions
|
||||
x = [[
|
||||
return function (x)
|
||||
return function (y)
|
||||
return function (z)
|
||||
return x+y+z
|
||||
end
|
||||
end
|
||||
end
|
||||
]]
|
||||
|
||||
a = assert(load(read1(x)))
|
||||
assert(a()(2)(3)(10) == 15)
|
||||
|
||||
|
||||
-- test for dump/undump with upvalues
|
||||
local a, b = 20, 30
|
||||
x = loadstring(string.dump(function (x)
|
||||
if x == "set" then a = 10+b; b = b+1 else
|
||||
return a
|
||||
end
|
||||
end))
|
||||
assert(x() == nil)
|
||||
assert(debug.setupvalue(x, 1, "hi") == "a")
|
||||
assert(x() == "hi")
|
||||
assert(debug.setupvalue(x, 2, 13) == "b")
|
||||
assert(not debug.setupvalue(x, 3, 10)) -- only 2 upvalues
|
||||
x("set")
|
||||
assert(x() == 23)
|
||||
x("set")
|
||||
assert(x() == 24)
|
||||
|
||||
|
||||
-- test for bug in parameter adjustment
|
||||
assert((function () return nil end)(4) == nil)
|
||||
assert((function () local a; return a end)(4) == nil)
|
||||
assert((function (a) return a end)() == nil)
|
||||
|
||||
print('OK')
|
||||
return deep
|
||||
77
lib/lua/lua-tests/checktable.lua
Normal file
77
lib/lua/lua-tests/checktable.lua
Normal file
@@ -0,0 +1,77 @@
|
||||
|
||||
assert(rawget(_G, "stat") == nil) -- module not loaded before
|
||||
|
||||
if T == nil then
|
||||
stat = function () print"`querytab' nao ativo" end
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
function checktable (t)
|
||||
local asize, hsize, ff = T.querytab(t)
|
||||
local l = {}
|
||||
for i=0,hsize-1 do
|
||||
local key,val,next = T.querytab(t, i + asize)
|
||||
if key == nil then
|
||||
assert(l[i] == nil and val==nil and next==nil)
|
||||
elseif key == "<undef>" then
|
||||
assert(val==nil)
|
||||
else
|
||||
assert(t[key] == val)
|
||||
local mp = T.hash(key, t)
|
||||
if l[i] then
|
||||
assert(l[i] == mp)
|
||||
elseif mp ~= i then
|
||||
l[i] = mp
|
||||
else -- list head
|
||||
l[mp] = {mp} -- first element
|
||||
while next do
|
||||
assert(ff <= next and next < hsize)
|
||||
if l[next] then assert(l[next] == mp) else l[next] = mp end
|
||||
table.insert(l[mp], next)
|
||||
key,val,next = T.querytab(t, next)
|
||||
assert(key)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
l.asize = asize; l.hsize = hsize; l.ff = ff
|
||||
return l
|
||||
end
|
||||
|
||||
function mostra (t)
|
||||
local asize, hsize, ff = T.querytab(t)
|
||||
print(asize, hsize, ff)
|
||||
print'------'
|
||||
for i=0,asize-1 do
|
||||
local _, v = T.querytab(t, i)
|
||||
print(string.format("[%d] -", i), v)
|
||||
end
|
||||
print'------'
|
||||
for i=0,hsize-1 do
|
||||
print(i, T.querytab(t, i+asize))
|
||||
end
|
||||
print'-------------'
|
||||
end
|
||||
|
||||
function stat (t)
|
||||
t = checktable(t)
|
||||
local nelem, nlist = 0, 0
|
||||
local maxlist = {}
|
||||
for i=0,t.hsize-1 do
|
||||
if type(t[i]) == 'table' then
|
||||
local n = table.getn(t[i])
|
||||
nlist = nlist+1
|
||||
nelem = nelem + n
|
||||
if not maxlist[n] then maxlist[n] = 0 end
|
||||
maxlist[n] = maxlist[n]+1
|
||||
end
|
||||
end
|
||||
print(string.format("hsize=%d elements=%d load=%.2f med.len=%.2f (asize=%d)",
|
||||
t.hsize, nelem, nelem/t.hsize, nelem/nlist, t.asize))
|
||||
for i=1,table.getn(maxlist) do
|
||||
local n = maxlist[i] or 0
|
||||
print(string.format("%5d %10d %.2f%%", i, n, n*100/nlist))
|
||||
end
|
||||
end
|
||||
|
||||
422
lib/lua/lua-tests/closure.lua
Normal file
422
lib/lua/lua-tests/closure.lua
Normal file
@@ -0,0 +1,422 @@
|
||||
print "testing closures and coroutines"
|
||||
|
||||
local A,B = 0,{g=10}
|
||||
function f(x)
|
||||
local a = {}
|
||||
for i=1,1000 do
|
||||
local y = 0
|
||||
do
|
||||
a[i] = function () B.g = B.g+1; y = y+x; return y+A end
|
||||
end
|
||||
end
|
||||
local dummy = function () return a[A] end
|
||||
collectgarbage()
|
||||
A = 1; assert(dummy() == a[1]); A = 0;
|
||||
assert(a[1]() == x)
|
||||
assert(a[3]() == x)
|
||||
collectgarbage()
|
||||
assert(B.g == 12)
|
||||
return a
|
||||
end
|
||||
|
||||
a = f(10)
|
||||
-- force a GC in this level
|
||||
local x = {[1] = {}} -- to detect a GC
|
||||
setmetatable(x, {__mode = 'kv'})
|
||||
while x[1] do -- repeat until GC
|
||||
local a = A..A..A..A -- create garbage
|
||||
A = A+1
|
||||
end
|
||||
assert(a[1]() == 20+A)
|
||||
assert(a[1]() == 30+A)
|
||||
assert(a[2]() == 10+A)
|
||||
collectgarbage()
|
||||
assert(a[2]() == 20+A)
|
||||
assert(a[2]() == 30+A)
|
||||
assert(a[3]() == 20+A)
|
||||
assert(a[8]() == 10+A)
|
||||
assert(getmetatable(x).__mode == 'kv')
|
||||
assert(B.g == 19)
|
||||
|
||||
-- testing closures with 'for' control variable
|
||||
a = {}
|
||||
for i=1,10 do
|
||||
a[i] = {set = function(x) i=x end, get = function () return i end}
|
||||
if i == 3 then break end
|
||||
end
|
||||
assert(a[4] == nil)
|
||||
a[1].set(10)
|
||||
assert(a[2].get() == 2)
|
||||
a[2].set('a')
|
||||
assert(a[3].get() == 3)
|
||||
assert(a[2].get() == 'a')
|
||||
|
||||
a = {}
|
||||
for i, k in pairs{'a', 'b'} do
|
||||
a[i] = {set = function(x, y) i=x; k=y end,
|
||||
get = function () return i, k end}
|
||||
if i == 2 then break end
|
||||
end
|
||||
a[1].set(10, 20)
|
||||
local r,s = a[2].get()
|
||||
assert(r == 2 and s == 'b')
|
||||
r,s = a[1].get()
|
||||
assert(r == 10 and s == 20)
|
||||
a[2].set('a', 'b')
|
||||
r,s = a[2].get()
|
||||
assert(r == "a" and s == "b")
|
||||
|
||||
|
||||
-- testing closures with 'for' control variable x break
|
||||
for i=1,3 do
|
||||
f = function () return i end
|
||||
break
|
||||
end
|
||||
assert(f() == 1)
|
||||
|
||||
for k, v in pairs{"a", "b"} do
|
||||
f = function () return k, v end
|
||||
break
|
||||
end
|
||||
assert(({f()})[1] == 1)
|
||||
assert(({f()})[2] == "a")
|
||||
|
||||
|
||||
-- testing closure x break x return x errors
|
||||
|
||||
local b
|
||||
function f(x)
|
||||
local first = 1
|
||||
while 1 do
|
||||
if x == 3 and not first then return end
|
||||
local a = 'xuxu'
|
||||
b = function (op, y)
|
||||
if op == 'set' then
|
||||
a = x+y
|
||||
else
|
||||
return a
|
||||
end
|
||||
end
|
||||
if x == 1 then do break end
|
||||
elseif x == 2 then return
|
||||
else if x ~= 3 then error() end
|
||||
end
|
||||
first = nil
|
||||
end
|
||||
end
|
||||
|
||||
for i=1,3 do
|
||||
f(i)
|
||||
assert(b('get') == 'xuxu')
|
||||
b('set', 10); assert(b('get') == 10+i)
|
||||
b = nil
|
||||
end
|
||||
|
||||
pcall(f, 4);
|
||||
assert(b('get') == 'xuxu')
|
||||
b('set', 10); assert(b('get') == 14)
|
||||
|
||||
|
||||
local w
|
||||
-- testing multi-level closure
|
||||
function f(x)
|
||||
return function (y)
|
||||
return function (z) return w+x+y+z end
|
||||
end
|
||||
end
|
||||
|
||||
y = f(10)
|
||||
w = 1.345
|
||||
assert(y(20)(30) == 60+w)
|
||||
|
||||
-- testing closures x repeat-until
|
||||
|
||||
local a = {}
|
||||
local i = 1
|
||||
repeat
|
||||
local x = i
|
||||
a[i] = function () i = x+1; return x end
|
||||
until i > 10 or a[i]() ~= x
|
||||
assert(i == 11 and a[1]() == 1 and a[3]() == 3 and i == 4)
|
||||
|
||||
print'+'
|
||||
|
||||
|
||||
-- test for correctly closing upvalues in tail calls of vararg functions
|
||||
local function t ()
|
||||
local function c(a,b) assert(a=="test" and b=="OK") end
|
||||
local function v(f, ...) c("test", f() ~= 1 and "FAILED" or "OK") end
|
||||
local x = 1
|
||||
return v(function() return x end)
|
||||
end
|
||||
t()
|
||||
|
||||
|
||||
-- coroutine tests
|
||||
|
||||
local f
|
||||
|
||||
assert(coroutine.running() == nil)
|
||||
|
||||
|
||||
-- tests for global environment
|
||||
|
||||
local function foo (a)
|
||||
setfenv(0, a)
|
||||
coroutine.yield(getfenv())
|
||||
assert(getfenv(0) == a)
|
||||
assert(getfenv(1) == _G)
|
||||
assert(getfenv(loadstring"") == a)
|
||||
return getfenv()
|
||||
end
|
||||
|
||||
f = coroutine.wrap(foo)
|
||||
local a = {}
|
||||
assert(f(a) == _G)
|
||||
local a,b = pcall(f)
|
||||
assert(a and b == _G)
|
||||
|
||||
|
||||
-- tests for multiple yield/resume arguments
|
||||
|
||||
local function eqtab (t1, t2)
|
||||
assert(table.getn(t1) == table.getn(t2))
|
||||
for i,v in ipairs(t1) do
|
||||
assert(t2[i] == v)
|
||||
end
|
||||
end
|
||||
|
||||
_G.x = nil -- declare x
|
||||
function foo (a, ...)
|
||||
assert(coroutine.running() == f)
|
||||
assert(coroutine.status(f) == "running")
|
||||
local arg = {...}
|
||||
for i=1,table.getn(arg) do
|
||||
_G.x = {coroutine.yield(unpack(arg[i]))}
|
||||
end
|
||||
return unpack(a)
|
||||
end
|
||||
|
||||
f = coroutine.create(foo)
|
||||
assert(type(f) == "thread" and coroutine.status(f) == "suspended")
|
||||
assert(string.find(tostring(f), "thread"))
|
||||
local s,a,b,c,d
|
||||
s,a,b,c,d = coroutine.resume(f, {1,2,3}, {}, {1}, {'a', 'b', 'c'})
|
||||
assert(s and a == nil and coroutine.status(f) == "suspended")
|
||||
s,a,b,c,d = coroutine.resume(f)
|
||||
eqtab(_G.x, {})
|
||||
assert(s and a == 1 and b == nil)
|
||||
s,a,b,c,d = coroutine.resume(f, 1, 2, 3)
|
||||
eqtab(_G.x, {1, 2, 3})
|
||||
assert(s and a == 'a' and b == 'b' and c == 'c' and d == nil)
|
||||
s,a,b,c,d = coroutine.resume(f, "xuxu")
|
||||
eqtab(_G.x, {"xuxu"})
|
||||
assert(s and a == 1 and b == 2 and c == 3 and d == nil)
|
||||
assert(coroutine.status(f) == "dead")
|
||||
s, a = coroutine.resume(f, "xuxu")
|
||||
assert(not s and string.find(a, "dead") and coroutine.status(f) == "dead")
|
||||
|
||||
|
||||
-- yields in tail calls
|
||||
local function foo (i) return coroutine.yield(i) end
|
||||
f = coroutine.wrap(function ()
|
||||
for i=1,10 do
|
||||
assert(foo(i) == _G.x)
|
||||
end
|
||||
return 'a'
|
||||
end)
|
||||
for i=1,10 do _G.x = i; assert(f(i) == i) end
|
||||
_G.x = 'xuxu'; assert(f('xuxu') == 'a')
|
||||
|
||||
-- recursive
|
||||
function pf (n, i)
|
||||
coroutine.yield(n)
|
||||
pf(n*i, i+1)
|
||||
end
|
||||
|
||||
f = coroutine.wrap(pf)
|
||||
local s=1
|
||||
for i=1,10 do
|
||||
assert(f(1, 1) == s)
|
||||
s = s*i
|
||||
end
|
||||
|
||||
-- sieve
|
||||
function gen (n)
|
||||
return coroutine.wrap(function ()
|
||||
for i=2,n do coroutine.yield(i) end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
function filter (p, g)
|
||||
return coroutine.wrap(function ()
|
||||
while 1 do
|
||||
local n = g()
|
||||
if n == nil then return end
|
||||
if math.mod(n, p) ~= 0 then coroutine.yield(n) end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local x = gen(100)
|
||||
local a = {}
|
||||
while 1 do
|
||||
local n = x()
|
||||
if n == nil then break end
|
||||
table.insert(a, n)
|
||||
x = filter(n, x)
|
||||
end
|
||||
|
||||
assert(table.getn(a) == 25 and a[table.getn(a)] == 97)
|
||||
|
||||
|
||||
-- errors in coroutines
|
||||
function foo ()
|
||||
assert(debug.getinfo(1).currentline == debug.getinfo(foo).linedefined + 1)
|
||||
assert(debug.getinfo(2).currentline == debug.getinfo(goo).linedefined)
|
||||
coroutine.yield(3)
|
||||
error(foo)
|
||||
end
|
||||
|
||||
function goo() foo() end
|
||||
x = coroutine.wrap(goo)
|
||||
assert(x() == 3)
|
||||
local a,b = pcall(x)
|
||||
assert(not a and b == foo)
|
||||
|
||||
x = coroutine.create(goo)
|
||||
a,b = coroutine.resume(x)
|
||||
assert(a and b == 3)
|
||||
a,b = coroutine.resume(x)
|
||||
assert(not a and b == foo and coroutine.status(x) == "dead")
|
||||
a,b = coroutine.resume(x)
|
||||
assert(not a and string.find(b, "dead") and coroutine.status(x) == "dead")
|
||||
|
||||
|
||||
-- co-routines x for loop
|
||||
function all (a, n, k)
|
||||
if k == 0 then coroutine.yield(a)
|
||||
else
|
||||
for i=1,n do
|
||||
a[k] = i
|
||||
all(a, n, k-1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local a = 0
|
||||
for t in coroutine.wrap(function () all({}, 5, 4) end) do
|
||||
a = a+1
|
||||
end
|
||||
assert(a == 5^4)
|
||||
|
||||
|
||||
-- access to locals of collected corroutines
|
||||
local C = {}; setmetatable(C, {__mode = "kv"})
|
||||
local x = coroutine.wrap (function ()
|
||||
local a = 10
|
||||
local function f () a = a+10; return a end
|
||||
while true do
|
||||
a = a+1
|
||||
coroutine.yield(f)
|
||||
end
|
||||
end)
|
||||
|
||||
C[1] = x;
|
||||
|
||||
local f = x()
|
||||
assert(f() == 21 and x()() == 32 and x() == f)
|
||||
x = nil
|
||||
collectgarbage()
|
||||
assert(C[1] == nil)
|
||||
assert(f() == 43 and f() == 53)
|
||||
|
||||
|
||||
-- old bug: attempt to resume itself
|
||||
|
||||
function co_func (current_co)
|
||||
assert(coroutine.running() == current_co)
|
||||
assert(coroutine.resume(current_co) == false)
|
||||
assert(coroutine.resume(current_co) == false)
|
||||
return 10
|
||||
end
|
||||
|
||||
local co = coroutine.create(co_func)
|
||||
local a,b = coroutine.resume(co, co)
|
||||
assert(a == true and b == 10)
|
||||
assert(coroutine.resume(co, co) == false)
|
||||
assert(coroutine.resume(co, co) == false)
|
||||
|
||||
-- access to locals of erroneous coroutines
|
||||
local x = coroutine.create (function ()
|
||||
local a = 10
|
||||
_G.f = function () a=a+1; return a end
|
||||
error('x')
|
||||
end)
|
||||
|
||||
assert(not coroutine.resume(x))
|
||||
-- overwrite previous position of local `a'
|
||||
assert(not coroutine.resume(x, 1, 1, 1, 1, 1, 1, 1))
|
||||
assert(_G.f() == 11)
|
||||
assert(_G.f() == 12)
|
||||
|
||||
|
||||
if not T then
|
||||
(Message or print)('\a\n >>> testC not active: skipping yield/hook tests <<<\n\a')
|
||||
else
|
||||
|
||||
local turn
|
||||
|
||||
function fact (t, x)
|
||||
assert(turn == t)
|
||||
if x == 0 then return 1
|
||||
else return x*fact(t, x-1)
|
||||
end
|
||||
end
|
||||
|
||||
local A,B,a,b = 0,0,0,0
|
||||
|
||||
local x = coroutine.create(function ()
|
||||
T.setyhook("", 2)
|
||||
A = fact("A", 10)
|
||||
end)
|
||||
|
||||
local y = coroutine.create(function ()
|
||||
T.setyhook("", 3)
|
||||
B = fact("B", 11)
|
||||
end)
|
||||
|
||||
while A==0 or B==0 do
|
||||
if A==0 then turn = "A"; T.resume(x) end
|
||||
if B==0 then turn = "B"; T.resume(y) end
|
||||
end
|
||||
|
||||
assert(B/A == 11)
|
||||
end
|
||||
|
||||
|
||||
-- leaving a pending coroutine open
|
||||
_X = coroutine.wrap(function ()
|
||||
local a = 10
|
||||
local x = function () a = a+1 end
|
||||
coroutine.yield()
|
||||
end)
|
||||
|
||||
_X()
|
||||
|
||||
|
||||
-- coroutine environments
|
||||
co = coroutine.create(function ()
|
||||
coroutine.yield(getfenv(0))
|
||||
return loadstring("return a")()
|
||||
end)
|
||||
|
||||
a = {a = 15}
|
||||
debug.setfenv(co, a)
|
||||
assert(debug.getfenv(co) == a)
|
||||
assert(select(2, coroutine.resume(co)) == a)
|
||||
assert(select(2, coroutine.resume(co)) == a.a)
|
||||
|
||||
|
||||
print'OK'
|
||||
143
lib/lua/lua-tests/code.lua
Normal file
143
lib/lua/lua-tests/code.lua
Normal file
@@ -0,0 +1,143 @@
|
||||
|
||||
if T==nil then
|
||||
(Message or print)('\a\n >>> testC not active: skipping opcode tests <<<\n\a')
|
||||
return
|
||||
end
|
||||
print "testing code generation and optimizations"
|
||||
|
||||
|
||||
-- this code gave an error for the code checker
|
||||
do
|
||||
local function f (a)
|
||||
for k,v,w in a do end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function check (f, ...)
|
||||
local c = T.listcode(f)
|
||||
for i=1, arg.n do
|
||||
-- print(arg[i], c[i])
|
||||
assert(string.find(c[i], '- '..arg[i]..' *%d'))
|
||||
end
|
||||
assert(c[arg.n+2] == nil)
|
||||
end
|
||||
|
||||
|
||||
function checkequal (a, b)
|
||||
a = T.listcode(a)
|
||||
b = T.listcode(b)
|
||||
for i = 1, table.getn(a) do
|
||||
a[i] = string.gsub(a[i], '%b()', '') -- remove line number
|
||||
b[i] = string.gsub(b[i], '%b()', '') -- remove line number
|
||||
assert(a[i] == b[i])
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- some basic instructions
|
||||
check(function ()
|
||||
(function () end){f()}
|
||||
end, 'CLOSURE', 'NEWTABLE', 'GETGLOBAL', 'CALL', 'SETLIST', 'CALL', 'RETURN')
|
||||
|
||||
|
||||
-- sequence of LOADNILs
|
||||
check(function ()
|
||||
local a,b,c
|
||||
local d; local e;
|
||||
a = nil; d=nil
|
||||
end, 'RETURN')
|
||||
|
||||
|
||||
-- single return
|
||||
check (function (a,b,c) return a end, 'RETURN')
|
||||
|
||||
|
||||
-- infinite loops
|
||||
check(function () while true do local a = -1 end end,
|
||||
'LOADK', 'JMP', 'RETURN')
|
||||
|
||||
check(function () while 1 do local a = -1 end end,
|
||||
'LOADK', 'JMP', 'RETURN')
|
||||
|
||||
check(function () repeat local x = 1 until false end,
|
||||
'LOADK', 'JMP', 'RETURN')
|
||||
|
||||
check(function () repeat local x until nil end,
|
||||
'LOADNIL', 'JMP', 'RETURN')
|
||||
|
||||
check(function () repeat local x = 1 until true end,
|
||||
'LOADK', 'RETURN')
|
||||
|
||||
|
||||
-- concat optimization
|
||||
check(function (a,b,c,d) return a..b..c..d end,
|
||||
'MOVE', 'MOVE', 'MOVE', 'MOVE', 'CONCAT', 'RETURN')
|
||||
|
||||
-- not
|
||||
check(function () return not not nil end, 'LOADBOOL', 'RETURN')
|
||||
check(function () return not not false end, 'LOADBOOL', 'RETURN')
|
||||
check(function () return not not true end, 'LOADBOOL', 'RETURN')
|
||||
check(function () return not not 1 end, 'LOADBOOL', 'RETURN')
|
||||
|
||||
-- direct access to locals
|
||||
check(function ()
|
||||
local a,b,c,d
|
||||
a = b*2
|
||||
c[4], a[b] = -((a + d/-20.5 - a[b]) ^ a.x), b
|
||||
end,
|
||||
'MUL',
|
||||
'DIV', 'ADD', 'GETTABLE', 'SUB', 'GETTABLE', 'POW',
|
||||
'UNM', 'SETTABLE', 'SETTABLE', 'RETURN')
|
||||
|
||||
|
||||
-- direct access to constants
|
||||
check(function ()
|
||||
local a,b
|
||||
a.x = 0
|
||||
a.x = b
|
||||
a[b] = 'y'
|
||||
a = 1 - a
|
||||
b = 1/a
|
||||
b = 5+4
|
||||
a[true] = false
|
||||
end,
|
||||
'SETTABLE', 'SETTABLE', 'SETTABLE', 'SUB', 'DIV', 'LOADK',
|
||||
'SETTABLE', 'RETURN')
|
||||
|
||||
local function f () return -((2^8 + -(-1)) % 8)/2 * 4 - 3 end
|
||||
|
||||
check(f, 'LOADK', 'RETURN')
|
||||
assert(f() == -5)
|
||||
|
||||
check(function ()
|
||||
local a,b,c
|
||||
b[c], a = c, b
|
||||
b[a], a = c, b
|
||||
a, b = c, a
|
||||
a = a
|
||||
end,
|
||||
'MOVE', 'MOVE', 'SETTABLE',
|
||||
'MOVE', 'MOVE', 'MOVE', 'SETTABLE',
|
||||
'MOVE', 'MOVE', 'MOVE',
|
||||
-- no code for a = a
|
||||
'RETURN')
|
||||
|
||||
|
||||
-- x == nil , x ~= nil
|
||||
checkequal(function () if (a==nil) then a=1 end; if a~=nil then a=1 end end,
|
||||
function () if (a==9) then a=1 end; if a~=9 then a=1 end end)
|
||||
|
||||
check(function () if a==nil then a=1 end end,
|
||||
'GETGLOBAL', 'EQ', 'JMP', 'LOADK', 'SETGLOBAL', 'RETURN')
|
||||
|
||||
-- de morgan
|
||||
checkequal(function () local a; if not (a or b) then b=a end end,
|
||||
function () local a; if (not a and not b) then b=a end end)
|
||||
|
||||
checkequal(function (l) local a; return 0 <= a and a <= l end,
|
||||
function (l) local a; return not (not(a >= 0) or not(a <= l)) end)
|
||||
|
||||
|
||||
print 'OK'
|
||||
|
||||
240
lib/lua/lua-tests/constructs.lua
Normal file
240
lib/lua/lua-tests/constructs.lua
Normal file
@@ -0,0 +1,240 @@
|
||||
print "testing syntax"
|
||||
|
||||
-- testing priorities
|
||||
|
||||
assert(2^3^2 == 2^(3^2));
|
||||
assert(2^3*4 == (2^3)*4);
|
||||
assert(2^-2 == 1/4 and -2^- -2 == - - -4);
|
||||
assert(not nil and 2 and not(2>3 or 3<2));
|
||||
assert(-3-1-5 == 0+0-9);
|
||||
assert(-2^2 == -4 and (-2)^2 == 4 and 2*2-3-1 == 0);
|
||||
assert(2*1+3/3 == 3 and 1+2 .. 3*1 == "33");
|
||||
assert(not(2+1 > 3*1) and "a".."b" > "a");
|
||||
|
||||
assert(not ((true or false) and nil))
|
||||
assert( true or false and nil)
|
||||
|
||||
local a,b = 1,nil;
|
||||
assert(-(1 or 2) == -1 and (1 and 2)+(-1.25 or -4) == 0.75);
|
||||
x = ((b or a)+1 == 2 and (10 or a)+1 == 11); assert(x);
|
||||
x = (((2<3) or 1) == true and (2<3 and 4) == 4); assert(x);
|
||||
|
||||
x,y=1,2;
|
||||
assert((x>y) and x or y == 2);
|
||||
x,y=2,1;
|
||||
assert((x>y) and x or y == 2);
|
||||
|
||||
assert(1234567890 == tonumber('1234567890') and 1234567890+1 == 1234567891)
|
||||
|
||||
|
||||
-- silly loops
|
||||
repeat until 1; repeat until true;
|
||||
while false do end; while nil do end;
|
||||
|
||||
do -- test old bug (first name could not be an `upvalue')
|
||||
local a; function f(x) x={a=1}; x={x=1}; x={G=1} end
|
||||
end
|
||||
|
||||
function f (i)
|
||||
if type(i) ~= 'number' then return i,'jojo'; end;
|
||||
if i > 0 then return i, f(i-1); end;
|
||||
end
|
||||
|
||||
x = {f(3), f(5), f(10);};
|
||||
assert(x[1] == 3 and x[2] == 5 and x[3] == 10 and x[4] == 9 and x[12] == 1);
|
||||
assert(x[nil] == nil)
|
||||
x = {f'alo', f'xixi', nil};
|
||||
assert(x[1] == 'alo' and x[2] == 'xixi' and x[3] == nil);
|
||||
x = {f'alo'..'xixi'};
|
||||
assert(x[1] == 'aloxixi')
|
||||
x = {f{}}
|
||||
assert(x[2] == 'jojo' and type(x[1]) == 'table')
|
||||
|
||||
|
||||
local f = function (i)
|
||||
if i < 10 then return 'a';
|
||||
elseif i < 20 then return 'b';
|
||||
elseif i < 30 then return 'c';
|
||||
end;
|
||||
end
|
||||
|
||||
assert(f(3) == 'a' and f(12) == 'b' and f(26) == 'c' and f(100) == nil)
|
||||
|
||||
for i=1,1000 do break; end;
|
||||
n=100;
|
||||
i=3;
|
||||
t = {};
|
||||
a=nil
|
||||
while not a do
|
||||
a=0; for i=1,n do for i=i,1,-1 do a=a+1; t[i]=1; end; end;
|
||||
end
|
||||
assert(a == n*(n+1)/2 and i==3);
|
||||
assert(t[1] and t[n] and not t[0] and not t[n+1])
|
||||
|
||||
function f(b)
|
||||
local x = 1;
|
||||
repeat
|
||||
local a;
|
||||
if b==1 then local b=1; x=10; break
|
||||
elseif b==2 then x=20; break;
|
||||
elseif b==3 then x=30;
|
||||
else local a,b,c,d=math.sin(1); x=x+1;
|
||||
end
|
||||
until x>=12;
|
||||
return x;
|
||||
end;
|
||||
|
||||
assert(f(1) == 10 and f(2) == 20 and f(3) == 30 and f(4)==12)
|
||||
|
||||
|
||||
local f = function (i)
|
||||
if i < 10 then return 'a'
|
||||
elseif i < 20 then return 'b'
|
||||
elseif i < 30 then return 'c'
|
||||
else return 8
|
||||
end
|
||||
end
|
||||
|
||||
assert(f(3) == 'a' and f(12) == 'b' and f(26) == 'c' and f(100) == 8)
|
||||
|
||||
local a, b = nil, 23
|
||||
x = {f(100)*2+3 or a, a or b+2}
|
||||
assert(x[1] == 19 and x[2] == 25)
|
||||
x = {f=2+3 or a, a = b+2}
|
||||
assert(x.f == 5 and x.a == 25)
|
||||
|
||||
a={y=1}
|
||||
x = {a.y}
|
||||
assert(x[1] == 1)
|
||||
|
||||
function f(i)
|
||||
while 1 do
|
||||
if i>0 then i=i-1;
|
||||
else return; end;
|
||||
end;
|
||||
end;
|
||||
|
||||
function g(i)
|
||||
while 1 do
|
||||
if i>0 then i=i-1
|
||||
else return end
|
||||
end
|
||||
end
|
||||
|
||||
f(10); g(10);
|
||||
|
||||
do
|
||||
function f () return 1,2,3; end
|
||||
local a, b, c = f();
|
||||
assert(a==1 and b==2 and c==3)
|
||||
a, b, c = (f());
|
||||
assert(a==1 and b==nil and c==nil)
|
||||
end
|
||||
|
||||
local a,b = 3 and f();
|
||||
assert(a==1 and b==nil)
|
||||
|
||||
function g() f(); return; end;
|
||||
assert(g() == nil)
|
||||
function g() return nil or f() end
|
||||
a,b = g()
|
||||
assert(a==1 and b==nil)
|
||||
|
||||
print'+';
|
||||
|
||||
|
||||
f = [[
|
||||
return function ( a , b , c , d , e )
|
||||
local x = a >= b or c or ( d and e ) or nil
|
||||
return x
|
||||
end , { a = 1 , b = 2 >= 1 , } or { 1 };
|
||||
]]
|
||||
f = string.gsub(f, "%s+", "\n"); -- force a SETLINE between opcodes
|
||||
f,a = loadstring(f)();
|
||||
assert(a.a == 1 and a.b)
|
||||
|
||||
function g (a,b,c,d,e)
|
||||
if not (a>=b or c or d and e or nil) then return 0; else return 1; end;
|
||||
end
|
||||
|
||||
function h (a,b,c,d,e)
|
||||
while (a>=b or c or (d and e) or nil) do return 1; end;
|
||||
return 0;
|
||||
end;
|
||||
|
||||
assert(f(2,1) == true and g(2,1) == 1 and h(2,1) == 1)
|
||||
assert(f(1,2,'a') == 'a' and g(1,2,'a') == 1 and h(1,2,'a') == 1)
|
||||
assert(f(1,2,'a')
|
||||
~= -- force SETLINE before nil
|
||||
nil, "")
|
||||
assert(f(1,2,'a') == 'a' and g(1,2,'a') == 1 and h(1,2,'a') == 1)
|
||||
assert(f(1,2,nil,1,'x') == 'x' and g(1,2,nil,1,'x') == 1 and
|
||||
h(1,2,nil,1,'x') == 1)
|
||||
assert(f(1,2,nil,nil,'x') == nil and g(1,2,nil,nil,'x') == 0 and
|
||||
h(1,2,nil,nil,'x') == 0)
|
||||
assert(f(1,2,nil,1,nil) == nil and g(1,2,nil,1,nil) == 0 and
|
||||
h(1,2,nil,1,nil) == 0)
|
||||
|
||||
assert(1 and 2<3 == true and 2<3 and 'a'<'b' == true)
|
||||
x = 2<3 and not 3; assert(x==false)
|
||||
x = 2<1 or (2>1 and 'a'); assert(x=='a')
|
||||
|
||||
|
||||
do
|
||||
local a; if nil then a=1; else a=2; end; -- this nil comes as PUSHNIL 2
|
||||
assert(a==2)
|
||||
end
|
||||
|
||||
function F(a)
|
||||
assert(debug.getinfo(1, "n").name == 'F')
|
||||
return a,2,3
|
||||
end
|
||||
|
||||
a,b = F(1)~=nil; assert(a == true and b == nil);
|
||||
a,b = F(nil)==nil; assert(a == true and b == nil)
|
||||
|
||||
----------------------------------------------------------------
|
||||
-- creates all combinations of
|
||||
-- [not] ([not] arg op [not] (arg op [not] arg ))
|
||||
-- and tests each one
|
||||
|
||||
function ID(x) return x end
|
||||
|
||||
function f(t, i)
|
||||
local b = t.n
|
||||
local res = math.mod(math.floor(i/c), b)+1
|
||||
c = c*b
|
||||
return t[res]
|
||||
end
|
||||
|
||||
local arg = {" ( 1 < 2 ) ", " ( 1 >= 2 ) ", " F ( ) ", " nil "; n=4}
|
||||
|
||||
local op = {" and ", " or ", " == ", " ~= "; n=4}
|
||||
|
||||
local neg = {" ", " not "; n=2}
|
||||
|
||||
local i = 0
|
||||
repeat
|
||||
c = 1
|
||||
local s = f(neg, i)..'ID('..f(neg, i)..f(arg, i)..f(op, i)..
|
||||
f(neg, i)..'ID('..f(arg, i)..f(op, i)..f(neg, i)..f(arg, i)..'))'
|
||||
local s1 = string.gsub(s, 'ID', '')
|
||||
K,X,NX,WX1,WX2 = nil
|
||||
s = string.format([[
|
||||
local a = %s
|
||||
local b = not %s
|
||||
K = b
|
||||
local xxx;
|
||||
if %s then X = a else X = b end
|
||||
if %s then NX = b else NX = a end
|
||||
while %s do WX1 = a; break end
|
||||
while %s do WX2 = a; break end
|
||||
repeat if (%s) then break end; assert(b) until not(%s)
|
||||
]], s1, s, s1, s, s1, s, s1, s, s)
|
||||
assert(loadstring(s))()
|
||||
assert(X and not NX and not WX1 == K and not WX2 == K)
|
||||
if math.mod(i,4000) == 0 then print('+') end
|
||||
i = i+1
|
||||
until i==c
|
||||
|
||||
print'OK'
|
||||
499
lib/lua/lua-tests/db.lua
Normal file
499
lib/lua/lua-tests/db.lua
Normal file
@@ -0,0 +1,499 @@
|
||||
-- testing debug library
|
||||
|
||||
local function dostring(s) return assert(loadstring(s))() end
|
||||
|
||||
print"testing debug library and debug information"
|
||||
|
||||
do
|
||||
local a=1
|
||||
end
|
||||
|
||||
function test (s, l, p)
|
||||
collectgarbage() -- avoid gc during trace
|
||||
local function f (event, line)
|
||||
assert(event == 'line')
|
||||
local l = table.remove(l, 1)
|
||||
if p then print(l, line) end
|
||||
assert(l == line, "wrong trace!!")
|
||||
end
|
||||
debug.sethook(f,"l"); loadstring(s)(); debug.sethook()
|
||||
assert(table.getn(l) == 0)
|
||||
end
|
||||
|
||||
|
||||
do
|
||||
local a = debug.getinfo(print)
|
||||
assert(a.what == "C" and a.short_src == "[C]")
|
||||
local b = debug.getinfo(test, "SfL")
|
||||
assert(b.name == nil and b.what == "Lua" and b.linedefined == 11 and
|
||||
b.lastlinedefined == b.linedefined + 10 and
|
||||
b.func == test and not string.find(b.short_src, "%["))
|
||||
assert(b.activelines[b.linedefined + 1] and
|
||||
b.activelines[b.lastlinedefined])
|
||||
assert(not b.activelines[b.linedefined] and
|
||||
not b.activelines[b.lastlinedefined + 1])
|
||||
end
|
||||
|
||||
|
||||
-- test file and string names truncation
|
||||
a = "function f () end"
|
||||
local function dostring (s, x) return loadstring(s, x)() end
|
||||
dostring(a)
|
||||
assert(debug.getinfo(f).short_src == string.format('[string "%s"]', a))
|
||||
dostring(a..string.format("; %s\n=1", string.rep('p', 400)))
|
||||
assert(string.find(debug.getinfo(f).short_src, '^%[string [^\n]*%.%.%."%]$'))
|
||||
dostring("\n"..a)
|
||||
assert(debug.getinfo(f).short_src == '[string "..."]')
|
||||
dostring(a, "")
|
||||
assert(debug.getinfo(f).short_src == '[string ""]')
|
||||
dostring(a, "@xuxu")
|
||||
assert(debug.getinfo(f).short_src == "xuxu")
|
||||
dostring(a, "@"..string.rep('p', 1000)..'t')
|
||||
assert(string.find(debug.getinfo(f).short_src, "^%.%.%.p*t$"))
|
||||
dostring(a, "=xuxu")
|
||||
assert(debug.getinfo(f).short_src == "xuxu")
|
||||
dostring(a, string.format("=%s", string.rep('x', 500)))
|
||||
assert(string.find(debug.getinfo(f).short_src, "^x*"))
|
||||
dostring(a, "=")
|
||||
assert(debug.getinfo(f).short_src == "")
|
||||
a = nil; f = nil;
|
||||
|
||||
|
||||
repeat
|
||||
local g = {x = function ()
|
||||
local a = debug.getinfo(2)
|
||||
assert(a.name == 'f' and a.namewhat == 'local')
|
||||
a = debug.getinfo(1)
|
||||
assert(a.name == 'x' and a.namewhat == 'field')
|
||||
return 'xixi'
|
||||
end}
|
||||
local f = function () return 1+1 and (not 1 or g.x()) end
|
||||
assert(f() == 'xixi')
|
||||
g = debug.getinfo(f)
|
||||
assert(g.what == "Lua" and g.func == f and g.namewhat == "" and not g.name)
|
||||
|
||||
function f (x, name) -- local!
|
||||
name = name or 'f'
|
||||
local a = debug.getinfo(1)
|
||||
assert(a.name == name and a.namewhat == 'local')
|
||||
return x
|
||||
end
|
||||
|
||||
-- breaks in different conditions
|
||||
if 3>4 then break end; f()
|
||||
if 3<4 then a=1 else break end; f()
|
||||
while 1 do local x=10; break end; f()
|
||||
local b = 1
|
||||
if 3>4 then return math.sin(1) end; f()
|
||||
a = 3<4; f()
|
||||
a = 3<4 or 1; f()
|
||||
repeat local x=20; if 4>3 then f() else break end; f() until 1
|
||||
g = {}
|
||||
f(g).x = f(2) and f(10)+f(9)
|
||||
assert(g.x == f(19))
|
||||
function g(x) if not x then return 3 end return (x('a', 'x')) end
|
||||
assert(g(f) == 'a')
|
||||
until 1
|
||||
|
||||
test([[if
|
||||
math.sin(1)
|
||||
then
|
||||
a=1
|
||||
else
|
||||
a=2
|
||||
end
|
||||
]], {2,4,7})
|
||||
|
||||
test([[--
|
||||
if nil then
|
||||
a=1
|
||||
else
|
||||
a=2
|
||||
end
|
||||
]], {2,5,6})
|
||||
|
||||
test([[a=1
|
||||
repeat
|
||||
a=a+1
|
||||
until a==3
|
||||
]], {1,3,4,3,4})
|
||||
|
||||
test([[ do
|
||||
return
|
||||
end
|
||||
]], {2})
|
||||
|
||||
test([[local a
|
||||
a=1
|
||||
while a<=3 do
|
||||
a=a+1
|
||||
end
|
||||
]], {2,3,4,3,4,3,4,3,5})
|
||||
|
||||
test([[while math.sin(1) do
|
||||
if math.sin(1)
|
||||
then
|
||||
break
|
||||
end
|
||||
end
|
||||
a=1]], {1,2,4,7})
|
||||
|
||||
test([[for i=1,3 do
|
||||
a=i
|
||||
end
|
||||
]], {1,2,1,2,1,2,1,3})
|
||||
|
||||
test([[for i,v in pairs{'a','b'} do
|
||||
a=i..v
|
||||
end
|
||||
]], {1,2,1,2,1,3})
|
||||
|
||||
test([[for i=1,4 do a=1 end]], {1,1,1,1,1})
|
||||
|
||||
|
||||
|
||||
print'+'
|
||||
|
||||
a = {}; L = nil
|
||||
local glob = 1
|
||||
local oldglob = glob
|
||||
debug.sethook(function (e,l)
|
||||
collectgarbage() -- force GC during a hook
|
||||
local f, m, c = debug.gethook()
|
||||
assert(m == 'crl' and c == 0)
|
||||
if e == "line" then
|
||||
if glob ~= oldglob then
|
||||
L = l-1 -- get the first line where "glob" has changed
|
||||
oldglob = glob
|
||||
end
|
||||
elseif e == "call" then
|
||||
local f = debug.getinfo(2, "f").func
|
||||
a[f] = 1
|
||||
else assert(e == "return")
|
||||
end
|
||||
end, "crl")
|
||||
|
||||
function f(a,b)
|
||||
collectgarbage()
|
||||
local _, x = debug.getlocal(1, 1)
|
||||
local _, y = debug.getlocal(1, 2)
|
||||
assert(x == a and y == b)
|
||||
assert(debug.setlocal(2, 3, "pera") == "AA".."AA")
|
||||
assert(debug.setlocal(2, 4, "maçã") == "B")
|
||||
x = debug.getinfo(2)
|
||||
assert(x.func == g and x.what == "Lua" and x.name == 'g' and
|
||||
x.nups == 0 and string.find(x.source, "^@.*db%.lua"))
|
||||
glob = glob+1
|
||||
assert(debug.getinfo(1, "l").currentline == L+1)
|
||||
assert(debug.getinfo(1, "l").currentline == L+2)
|
||||
end
|
||||
|
||||
function foo()
|
||||
glob = glob+1
|
||||
assert(debug.getinfo(1, "l").currentline == L+1)
|
||||
end; foo() -- set L
|
||||
-- check line counting inside strings and empty lines
|
||||
|
||||
_ = 'alo\
|
||||
alo' .. [[
|
||||
|
||||
]]
|
||||
--[[
|
||||
]]
|
||||
assert(debug.getinfo(1, "l").currentline == L+11) -- check count of lines
|
||||
|
||||
|
||||
function g(...)
|
||||
do local a,b,c; a=math.sin(40); end
|
||||
local feijao
|
||||
local AAAA,B = "xuxu", "mamão"
|
||||
f(AAAA,B)
|
||||
assert(AAAA == "pera" and B == "maçã")
|
||||
do
|
||||
local B = 13
|
||||
local x,y = debug.getlocal(1,5)
|
||||
assert(x == 'B' and y == 13)
|
||||
end
|
||||
end
|
||||
|
||||
g()
|
||||
|
||||
|
||||
assert(a[f] and a[g] and a[assert] and a[debug.getlocal] and not a[print])
|
||||
|
||||
|
||||
-- tests for manipulating non-registered locals (C and Lua temporaries)
|
||||
|
||||
local n, v = debug.getlocal(0, 1)
|
||||
assert(v == 0 and n == "(*temporary)")
|
||||
local n, v = debug.getlocal(0, 2)
|
||||
assert(v == 2 and n == "(*temporary)")
|
||||
assert(not debug.getlocal(0, 3))
|
||||
assert(not debug.getlocal(0, 0))
|
||||
|
||||
function f()
|
||||
assert(select(2, debug.getlocal(2,3)) == 1)
|
||||
assert(not debug.getlocal(2,4))
|
||||
debug.setlocal(2, 3, 10)
|
||||
return 20
|
||||
end
|
||||
|
||||
function g(a,b) return (a+1) + f() end
|
||||
|
||||
assert(g(0,0) == 30)
|
||||
|
||||
|
||||
debug.sethook(nil);
|
||||
assert(debug.gethook() == nil)
|
||||
|
||||
|
||||
-- testing access to function arguments
|
||||
|
||||
X = nil
|
||||
a = {}
|
||||
function a:f (a, b, ...) local c = 13 end
|
||||
debug.sethook(function (e)
|
||||
assert(e == "call")
|
||||
dostring("XX = 12") -- test dostring inside hooks
|
||||
-- testing errors inside hooks
|
||||
assert(not pcall(loadstring("a='joao'+1")))
|
||||
debug.sethook(function (e, l)
|
||||
assert(debug.getinfo(2, "l").currentline == l)
|
||||
local f,m,c = debug.gethook()
|
||||
assert(e == "line")
|
||||
assert(m == 'l' and c == 0)
|
||||
debug.sethook(nil) -- hook is called only once
|
||||
assert(not X) -- check that
|
||||
X = {}; local i = 1
|
||||
local x,y
|
||||
while 1 do
|
||||
x,y = debug.getlocal(2, i)
|
||||
if x==nil then break end
|
||||
X[x] = y
|
||||
i = i+1
|
||||
end
|
||||
end, "l")
|
||||
end, "c")
|
||||
|
||||
a:f(1,2,3,4,5)
|
||||
assert(X.self == a and X.a == 1 and X.b == 2 and X.arg.n == 3 and X.c == nil)
|
||||
assert(XX == 12)
|
||||
assert(debug.gethook() == nil)
|
||||
|
||||
|
||||
-- testing upvalue access
|
||||
local function getupvalues (f)
|
||||
local t = {}
|
||||
local i = 1
|
||||
while true do
|
||||
local name, value = debug.getupvalue(f, i)
|
||||
if not name then break end
|
||||
assert(not t[name])
|
||||
t[name] = value
|
||||
i = i + 1
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
local a,b,c = 1,2,3
|
||||
local function foo1 (a) b = a; return c end
|
||||
local function foo2 (x) a = x; return c+b end
|
||||
assert(debug.getupvalue(foo1, 3) == nil)
|
||||
assert(debug.getupvalue(foo1, 0) == nil)
|
||||
assert(debug.setupvalue(foo1, 3, "xuxu") == nil)
|
||||
local t = getupvalues(foo1)
|
||||
assert(t.a == nil and t.b == 2 and t.c == 3)
|
||||
t = getupvalues(foo2)
|
||||
assert(t.a == 1 and t.b == 2 and t.c == 3)
|
||||
assert(debug.setupvalue(foo1, 1, "xuxu") == "b")
|
||||
assert(({debug.getupvalue(foo2, 3)})[2] == "xuxu")
|
||||
-- cannot manipulate C upvalues from Lua
|
||||
assert(debug.getupvalue(io.read, 1) == nil)
|
||||
assert(debug.setupvalue(io.read, 1, 10) == nil)
|
||||
|
||||
|
||||
-- testing count hooks
|
||||
local a=0
|
||||
debug.sethook(function (e) a=a+1 end, "", 1)
|
||||
a=0; for i=1,1000 do end; assert(1000 < a and a < 1012)
|
||||
debug.sethook(function (e) a=a+1 end, "", 4)
|
||||
a=0; for i=1,1000 do end; assert(250 < a and a < 255)
|
||||
local f,m,c = debug.gethook()
|
||||
assert(m == "" and c == 4)
|
||||
debug.sethook(function (e) a=a+1 end, "", 4000)
|
||||
a=0; for i=1,1000 do end; assert(a == 0)
|
||||
debug.sethook(print, "", 2^24 - 1) -- count upperbound
|
||||
local f,m,c = debug.gethook()
|
||||
assert(({debug.gethook()})[3] == 2^24 - 1)
|
||||
debug.sethook()
|
||||
|
||||
|
||||
-- tests for tail calls
|
||||
local function f (x)
|
||||
if x then
|
||||
assert(debug.getinfo(1, "S").what == "Lua")
|
||||
local tail = debug.getinfo(2)
|
||||
assert(not pcall(getfenv, 3))
|
||||
assert(tail.what == "tail" and tail.short_src == "(tail call)" and
|
||||
tail.linedefined == -1 and tail.func == nil)
|
||||
assert(debug.getinfo(3, "f").func == g1)
|
||||
assert(getfenv(3))
|
||||
assert(debug.getinfo(4, "S").what == "tail")
|
||||
assert(not pcall(getfenv, 5))
|
||||
assert(debug.getinfo(5, "S").what == "main")
|
||||
assert(getfenv(5))
|
||||
print"+"
|
||||
end
|
||||
end
|
||||
|
||||
function g(x) return f(x) end
|
||||
|
||||
function g1(x) g(x) end
|
||||
|
||||
local function h (x) local f=g1; return f(x) end
|
||||
|
||||
h(true)
|
||||
|
||||
local b = {}
|
||||
debug.sethook(function (e) table.insert(b, e) end, "cr")
|
||||
h(false)
|
||||
debug.sethook()
|
||||
local res = {"return", -- first return (from sethook)
|
||||
"call", "call", "call", "call",
|
||||
"return", "tail return", "return", "tail return",
|
||||
"call", -- last call (to sethook)
|
||||
}
|
||||
for _, k in ipairs(res) do assert(k == table.remove(b, 1)) end
|
||||
|
||||
|
||||
lim = 30000
|
||||
local function foo (x)
|
||||
if x==0 then
|
||||
assert(debug.getinfo(lim+2).what == "main")
|
||||
for i=2,lim do assert(debug.getinfo(i, "S").what == "tail") end
|
||||
else return foo(x-1)
|
||||
end
|
||||
end
|
||||
|
||||
foo(lim)
|
||||
|
||||
|
||||
print"+"
|
||||
|
||||
|
||||
-- testing traceback
|
||||
|
||||
assert(debug.traceback(print) == print)
|
||||
assert(debug.traceback(print, 4) == print)
|
||||
assert(string.find(debug.traceback("hi", 4), "^hi\n"))
|
||||
assert(string.find(debug.traceback("hi"), "^hi\n"))
|
||||
assert(not string.find(debug.traceback("hi"), "'traceback'"))
|
||||
assert(string.find(debug.traceback("hi", 0), "'traceback'"))
|
||||
assert(string.find(debug.traceback(), "^stack traceback:\n"))
|
||||
|
||||
-- testing debugging of coroutines
|
||||
|
||||
local function checktraceback (co, p)
|
||||
local tb = debug.traceback(co)
|
||||
local i = 0
|
||||
for l in string.gmatch(tb, "[^\n]+\n?") do
|
||||
assert(i == 0 or string.find(l, p[i]))
|
||||
i = i+1
|
||||
end
|
||||
assert(p[i] == nil)
|
||||
end
|
||||
|
||||
|
||||
local function f (n)
|
||||
if n > 0 then return f(n-1)
|
||||
else coroutine.yield() end
|
||||
end
|
||||
|
||||
local co = coroutine.create(f)
|
||||
coroutine.resume(co, 3)
|
||||
checktraceback(co, {"yield", "db.lua", "tail", "tail", "tail"})
|
||||
|
||||
|
||||
co = coroutine.create(function (x)
|
||||
local a = 1
|
||||
coroutine.yield(debug.getinfo(1, "l"))
|
||||
coroutine.yield(debug.getinfo(1, "l").currentline)
|
||||
return a
|
||||
end)
|
||||
|
||||
local tr = {}
|
||||
local foo = function (e, l) table.insert(tr, l) end
|
||||
debug.sethook(co, foo, "l")
|
||||
|
||||
local _, l = coroutine.resume(co, 10)
|
||||
local x = debug.getinfo(co, 1, "lfLS")
|
||||
assert(x.currentline == l.currentline and x.activelines[x.currentline])
|
||||
assert(type(x.func) == "function")
|
||||
for i=x.linedefined + 1, x.lastlinedefined do
|
||||
assert(x.activelines[i])
|
||||
x.activelines[i] = nil
|
||||
end
|
||||
assert(next(x.activelines) == nil) -- no 'extra' elements
|
||||
assert(debug.getinfo(co, 2) == nil)
|
||||
local a,b = debug.getlocal(co, 1, 1)
|
||||
assert(a == "x" and b == 10)
|
||||
a,b = debug.getlocal(co, 1, 2)
|
||||
assert(a == "a" and b == 1)
|
||||
debug.setlocal(co, 1, 2, "hi")
|
||||
assert(debug.gethook(co) == foo)
|
||||
assert(table.getn(tr) == 2 and
|
||||
tr[1] == l.currentline-1 and tr[2] == l.currentline)
|
||||
|
||||
a,b,c = pcall(coroutine.resume, co)
|
||||
assert(a and b and c == l.currentline+1)
|
||||
checktraceback(co, {"yield", "in function <"})
|
||||
|
||||
a,b = coroutine.resume(co)
|
||||
assert(a and b == "hi")
|
||||
assert(table.getn(tr) == 4 and tr[4] == l.currentline+2)
|
||||
assert(debug.gethook(co) == foo)
|
||||
assert(debug.gethook() == nil)
|
||||
checktraceback(co, {})
|
||||
|
||||
|
||||
-- check traceback of suspended (or dead with error) coroutines
|
||||
|
||||
function f(i) if i==0 then error(i) else coroutine.yield(); f(i-1) end end
|
||||
|
||||
co = coroutine.create(function (x) f(x) end)
|
||||
a, b = coroutine.resume(co, 3)
|
||||
t = {"'yield'", "'f'", "in function <"}
|
||||
while coroutine.status(co) == "suspended" do
|
||||
checktraceback(co, t)
|
||||
a, b = coroutine.resume(co)
|
||||
table.insert(t, 2, "'f'") -- one more recursive call to 'f'
|
||||
end
|
||||
t[1] = "'error'"
|
||||
checktraceback(co, t)
|
||||
|
||||
|
||||
-- test acessing line numbers of a coroutine from a resume inside
|
||||
-- a C function (this is a known bug in Lua 5.0)
|
||||
|
||||
local function g(x)
|
||||
coroutine.yield(x)
|
||||
end
|
||||
|
||||
local function f (i)
|
||||
debug.sethook(function () end, "l")
|
||||
for j=1,1000 do
|
||||
g(i+j)
|
||||
end
|
||||
end
|
||||
|
||||
local co = coroutine.wrap(f)
|
||||
co(10)
|
||||
pcall(co)
|
||||
pcall(co)
|
||||
|
||||
|
||||
assert(type(debug.getregistry()) == "table")
|
||||
|
||||
|
||||
print"OK"
|
||||
|
||||
250
lib/lua/lua-tests/errors.lua
Normal file
250
lib/lua/lua-tests/errors.lua
Normal file
@@ -0,0 +1,250 @@
|
||||
print("testing errors")
|
||||
|
||||
function doit (s)
|
||||
local f, msg = loadstring(s)
|
||||
if f == nil then return msg end
|
||||
local cond, msg = pcall(f)
|
||||
return (not cond) and msg
|
||||
end
|
||||
|
||||
|
||||
function checkmessage (prog, msg)
|
||||
assert(string.find(doit(prog), msg, 1, true))
|
||||
end
|
||||
|
||||
function checksyntax (prog, extra, token, line)
|
||||
local msg = doit(prog)
|
||||
token = string.gsub(token, "(%p)", "%%%1")
|
||||
local pt = string.format([[^%%[string ".*"%%]:%d: .- near '%s'$]],
|
||||
line, token)
|
||||
assert(string.find(msg, pt))
|
||||
assert(string.find(msg, msg, 1, true))
|
||||
end
|
||||
|
||||
|
||||
-- test error message with no extra info
|
||||
assert(doit("error('hi', 0)") == 'hi')
|
||||
|
||||
-- test error message with no info
|
||||
assert(doit("error()") == nil)
|
||||
|
||||
|
||||
-- test common errors/errors that crashed in the past
|
||||
assert(doit("unpack({}, 1, n=2^30)"))
|
||||
assert(doit("a=math.sin()"))
|
||||
assert(not doit("tostring(1)") and doit("tostring()"))
|
||||
assert(doit"tonumber()")
|
||||
assert(doit"repeat until 1; a")
|
||||
checksyntax("break label", "", "label", 1)
|
||||
assert(doit";")
|
||||
assert(doit"a=1;;")
|
||||
assert(doit"return;;")
|
||||
assert(doit"assert(false)")
|
||||
assert(doit"assert(nil)")
|
||||
assert(doit"a=math.sin\n(3)")
|
||||
assert(doit("function a (... , ...) end"))
|
||||
assert(doit("function a (, ...) end"))
|
||||
|
||||
checksyntax([[
|
||||
local a = {4
|
||||
|
||||
]], "'}' expected (to close '{' at line 1)", "<eof>", 3)
|
||||
|
||||
|
||||
-- tests for better error messages
|
||||
|
||||
checkmessage("a=1; bbbb=2; a=math.sin(3)+bbbb(3)", "global 'bbbb'")
|
||||
checkmessage("a=1; local a,bbbb=2,3; a = math.sin(1) and bbbb(3)",
|
||||
"local 'bbbb'")
|
||||
checkmessage("a={}; do local a=1 end a:bbbb(3)", "method 'bbbb'")
|
||||
checkmessage("local a={}; a.bbbb(3)", "field 'bbbb'")
|
||||
assert(not string.find(doit"a={13}; local bbbb=1; a[bbbb](3)", "'bbbb'"))
|
||||
checkmessage("a={13}; local bbbb=1; a[bbbb](3)", "number")
|
||||
|
||||
aaa = nil
|
||||
checkmessage("aaa.bbb:ddd(9)", "global 'aaa'")
|
||||
checkmessage("local aaa={bbb=1}; aaa.bbb:ddd(9)", "field 'bbb'")
|
||||
checkmessage("local aaa={bbb={}}; aaa.bbb:ddd(9)", "method 'ddd'")
|
||||
checkmessage("local a,b,c; (function () a = b+1 end)()", "upvalue 'b'")
|
||||
assert(not doit"local aaa={bbb={ddd=next}}; aaa.bbb:ddd(nil)")
|
||||
|
||||
checkmessage("b=1; local aaa='a'; x=aaa+b", "local 'aaa'")
|
||||
checkmessage("aaa={}; x=3/aaa", "global 'aaa'")
|
||||
checkmessage("aaa='2'; b=nil;x=aaa*b", "global 'b'")
|
||||
checkmessage("aaa={}; x=-aaa", "global 'aaa'")
|
||||
assert(not string.find(doit"aaa={}; x=(aaa or aaa)+(aaa and aaa)", "'aaa'"))
|
||||
assert(not string.find(doit"aaa={}; (aaa or aaa)()", "'aaa'"))
|
||||
|
||||
checkmessage([[aaa=9
|
||||
repeat until 3==3
|
||||
local x=math.sin(math.cos(3))
|
||||
if math.sin(1) == x then return math.sin(1) end -- tail call
|
||||
local a,b = 1, {
|
||||
{x='a'..'b'..'c', y='b', z=x},
|
||||
{1,2,3,4,5} or 3+3<=3+3,
|
||||
3+1>3+1,
|
||||
{d = x and aaa[x or y]}}
|
||||
]], "global 'aaa'")
|
||||
|
||||
checkmessage([[
|
||||
local x,y = {},1
|
||||
if math.sin(1) == 0 then return 3 end -- return
|
||||
x.a()]], "field 'a'")
|
||||
|
||||
checkmessage([[
|
||||
prefix = nil
|
||||
insert = nil
|
||||
while 1 do
|
||||
local a
|
||||
if nil then break end
|
||||
insert(prefix, a)
|
||||
end]], "global 'insert'")
|
||||
|
||||
checkmessage([[ -- tail call
|
||||
return math.sin("a")
|
||||
]], "'sin'")
|
||||
|
||||
checkmessage([[collectgarbage("nooption")]], "invalid option")
|
||||
|
||||
checkmessage([[x = print .. "a"]], "concatenate")
|
||||
|
||||
checkmessage("getmetatable(io.stdin).__gc()", "no value")
|
||||
|
||||
print'+'
|
||||
|
||||
|
||||
-- testing line error
|
||||
|
||||
function lineerror (s)
|
||||
local err,msg = pcall(loadstring(s))
|
||||
local line = string.match(msg, ":(%d+):")
|
||||
return line and line+0
|
||||
end
|
||||
|
||||
assert(lineerror"local a\n for i=1,'a' do \n print(i) \n end" == 2)
|
||||
assert(lineerror"\n local a \n for k,v in 3 \n do \n print(k) \n end" == 3)
|
||||
assert(lineerror"\n\n for k,v in \n 3 \n do \n print(k) \n end" == 4)
|
||||
assert(lineerror"function a.x.y ()\na=a+1\nend" == 1)
|
||||
|
||||
local p = [[
|
||||
function g() f() end
|
||||
function f(x) error('a', X) end
|
||||
g()
|
||||
]]
|
||||
X=3;assert(lineerror(p) == 3)
|
||||
X=0;assert(lineerror(p) == nil)
|
||||
X=1;assert(lineerror(p) == 2)
|
||||
X=2;assert(lineerror(p) == 1)
|
||||
|
||||
lineerror = nil
|
||||
|
||||
C = 0
|
||||
local l = debug.getinfo(1, "l").currentline; function y () C=C+1; y() end
|
||||
|
||||
local function checkstackmessage (m)
|
||||
return (string.find(m, "^.-:%d+: stack overflow"))
|
||||
end
|
||||
assert(checkstackmessage(doit('y()')))
|
||||
assert(checkstackmessage(doit('y()')))
|
||||
assert(checkstackmessage(doit('y()')))
|
||||
-- teste de linhas em erro
|
||||
C = 0
|
||||
local l1
|
||||
local function g()
|
||||
l1 = debug.getinfo(1, "l").currentline; y()
|
||||
end
|
||||
local _, stackmsg = xpcall(g, debug.traceback)
|
||||
local stack = {}
|
||||
for line in string.gmatch(stackmsg, "[^\n]*") do
|
||||
local curr = string.match(line, ":(%d+):")
|
||||
if curr then table.insert(stack, tonumber(curr)) end
|
||||
end
|
||||
local i=1
|
||||
while stack[i] ~= l1 do
|
||||
assert(stack[i] == l)
|
||||
i = i+1
|
||||
end
|
||||
assert(i > 15)
|
||||
|
||||
|
||||
-- error in error handling
|
||||
local res, msg = xpcall(error, error)
|
||||
assert(not res and type(msg) == 'string')
|
||||
|
||||
local function f (x)
|
||||
if x==0 then error('a\n')
|
||||
else
|
||||
local aux = function () return f(x-1) end
|
||||
local a,b = xpcall(aux, aux)
|
||||
return a,b
|
||||
end
|
||||
end
|
||||
f(3)
|
||||
|
||||
-- non string messages
|
||||
function f() error{msg='x'} end
|
||||
res, msg = xpcall(f, function (r) return {msg=r.msg..'y'} end)
|
||||
assert(msg.msg == 'xy')
|
||||
|
||||
print('+')
|
||||
checksyntax("syntax error", "", "error", 1)
|
||||
checksyntax("1.000", "", "1.000", 1)
|
||||
checksyntax("[[a]]", "", "[[a]]", 1)
|
||||
checksyntax("'aa'", "", "'aa'", 1)
|
||||
|
||||
-- test 255 as first char in a chunk
|
||||
checksyntax("\255a = 1", "", "\255", 1)
|
||||
|
||||
doit('I = loadstring("a=9+"); a=3')
|
||||
assert(a==3 and I == nil)
|
||||
print('+')
|
||||
|
||||
lim = 1000
|
||||
if rawget(_G, "_soft") then lim = 100 end
|
||||
for i=1,lim do
|
||||
doit('a = ')
|
||||
doit('a = 4+nil')
|
||||
end
|
||||
|
||||
|
||||
-- testing syntax limits
|
||||
local function testrep (init, rep)
|
||||
local s = "local a; "..init .. string.rep(rep, 400)
|
||||
local a,b = loadstring(s)
|
||||
assert(not a and string.find(b, "syntax levels"))
|
||||
end
|
||||
testrep("a=", "{")
|
||||
testrep("a=", "(")
|
||||
testrep("", "a(")
|
||||
testrep("", "do ")
|
||||
testrep("", "while a do ")
|
||||
testrep("", "if a then else ")
|
||||
testrep("", "function foo () ")
|
||||
testrep("a=", "a..")
|
||||
testrep("a=", "a^")
|
||||
|
||||
|
||||
-- testing other limits
|
||||
-- upvalues
|
||||
local s = "function foo ()\n local "
|
||||
for j = 1,70 do
|
||||
s = s.."a"..j..", "
|
||||
end
|
||||
s = s.."b\n"
|
||||
for j = 1,70 do
|
||||
s = s.."function foo"..j.." ()\n a"..j.."=3\n"
|
||||
end
|
||||
local a,b = loadstring(s)
|
||||
assert(string.find(b, "line 3"))
|
||||
|
||||
-- local variables
|
||||
s = "\nfunction foo ()\n local "
|
||||
for j = 1,300 do
|
||||
s = s.."a"..j..", "
|
||||
end
|
||||
s = s.."b\n"
|
||||
local a,b = loadstring(s)
|
||||
assert(string.find(b, "line 2"))
|
||||
|
||||
|
||||
print('OK')
|
||||
360
lib/lua/lua-tests/events.lua
Normal file
360
lib/lua/lua-tests/events.lua
Normal file
@@ -0,0 +1,360 @@
|
||||
print('testing metatables')
|
||||
|
||||
X = 20; B = 30
|
||||
|
||||
setfenv(1, setmetatable({}, {__index=_G}))
|
||||
|
||||
collectgarbage()
|
||||
|
||||
X = X+10
|
||||
assert(X == 30 and _G.X == 20)
|
||||
B = false
|
||||
assert(B == false)
|
||||
B = nil
|
||||
assert(B == 30)
|
||||
|
||||
assert(getmetatable{} == nil)
|
||||
assert(getmetatable(4) == nil)
|
||||
assert(getmetatable(nil) == nil)
|
||||
a={}; setmetatable(a, {__metatable = "xuxu",
|
||||
__tostring=function(x) return x.name end})
|
||||
assert(getmetatable(a) == "xuxu")
|
||||
assert(tostring(a) == nil)
|
||||
-- cannot change a protected metatable
|
||||
assert(pcall(setmetatable, a, {}) == false)
|
||||
a.name = "gororoba"
|
||||
assert(tostring(a) == "gororoba")
|
||||
|
||||
local a, t = {10,20,30; x="10", y="20"}, {}
|
||||
assert(setmetatable(a,t) == a)
|
||||
assert(getmetatable(a) == t)
|
||||
assert(setmetatable(a,nil) == a)
|
||||
assert(getmetatable(a) == nil)
|
||||
assert(setmetatable(a,t) == a)
|
||||
|
||||
|
||||
function f (t, i, e)
|
||||
assert(not e)
|
||||
local p = rawget(t, "parent")
|
||||
return (p and p[i]+3), "dummy return"
|
||||
end
|
||||
|
||||
t.__index = f
|
||||
|
||||
a.parent = {z=25, x=12, [4] = 24}
|
||||
assert(a[1] == 10 and a.z == 28 and a[4] == 27 and a.x == "10")
|
||||
|
||||
collectgarbage()
|
||||
|
||||
a = setmetatable({}, t)
|
||||
function f(t, i, v) rawset(t, i, v-3) end
|
||||
t.__newindex = f
|
||||
a[1] = 30; a.x = "101"; a[5] = 200
|
||||
assert(a[1] == 27 and a.x == 98 and a[5] == 197)
|
||||
|
||||
|
||||
local c = {}
|
||||
a = setmetatable({}, t)
|
||||
t.__newindex = c
|
||||
a[1] = 10; a[2] = 20; a[3] = 90
|
||||
assert(c[1] == 10 and c[2] == 20 and c[3] == 90)
|
||||
|
||||
|
||||
do
|
||||
local a;
|
||||
a = setmetatable({}, {__index = setmetatable({},
|
||||
{__index = setmetatable({},
|
||||
{__index = function (_,n) return a[n-3]+4, "lixo" end})})})
|
||||
a[0] = 20
|
||||
for i=0,10 do
|
||||
assert(a[i*3] == 20 + i*4)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
do -- newindex
|
||||
local foi
|
||||
local a = {}
|
||||
for i=1,10 do a[i] = 0; a['a'..i] = 0; end
|
||||
setmetatable(a, {__newindex = function (t,k,v) foi=true; rawset(t,k,v) end})
|
||||
foi = false; a[1]=0; assert(not foi)
|
||||
foi = false; a['a1']=0; assert(not foi)
|
||||
foi = false; a['a11']=0; assert(foi)
|
||||
foi = false; a[11]=0; assert(foi)
|
||||
foi = false; a[1]=nil; assert(not foi)
|
||||
foi = false; a[1]=nil; assert(foi)
|
||||
end
|
||||
|
||||
|
||||
function f (t, ...) return t, {...} end
|
||||
t.__call = f
|
||||
|
||||
do
|
||||
local x,y = a(unpack{'a', 1})
|
||||
assert(x==a and y[1]=='a' and y[2]==1 and y[3]==nil)
|
||||
x,y = a()
|
||||
assert(x==a and y[1]==nil)
|
||||
end
|
||||
|
||||
|
||||
local b = setmetatable({}, t)
|
||||
setmetatable(b,t)
|
||||
|
||||
function f(op)
|
||||
return function (...) cap = {[0] = op, ...} ; return (...) end
|
||||
end
|
||||
t.__add = f("add")
|
||||
t.__sub = f("sub")
|
||||
t.__mul = f("mul")
|
||||
t.__div = f("div")
|
||||
t.__mod = f("mod")
|
||||
t.__unm = f("unm")
|
||||
t.__pow = f("pow")
|
||||
|
||||
assert(b+5 == b)
|
||||
assert(cap[0] == "add" and cap[1] == b and cap[2] == 5 and cap[3]==nil)
|
||||
assert(b+'5' == b)
|
||||
assert(cap[0] == "add" and cap[1] == b and cap[2] == '5' and cap[3]==nil)
|
||||
assert(5+b == 5)
|
||||
assert(cap[0] == "add" and cap[1] == 5 and cap[2] == b and cap[3]==nil)
|
||||
assert('5'+b == '5')
|
||||
assert(cap[0] == "add" and cap[1] == '5' and cap[2] == b and cap[3]==nil)
|
||||
b=b-3; assert(getmetatable(b) == t)
|
||||
assert(5-a == 5)
|
||||
assert(cap[0] == "sub" and cap[1] == 5 and cap[2] == a and cap[3]==nil)
|
||||
assert('5'-a == '5')
|
||||
assert(cap[0] == "sub" and cap[1] == '5' and cap[2] == a and cap[3]==nil)
|
||||
assert(a*a == a)
|
||||
assert(cap[0] == "mul" and cap[1] == a and cap[2] == a and cap[3]==nil)
|
||||
assert(a/0 == a)
|
||||
assert(cap[0] == "div" and cap[1] == a and cap[2] == 0 and cap[3]==nil)
|
||||
assert(a%2 == a)
|
||||
assert(cap[0] == "mod" and cap[1] == a and cap[2] == 2 and cap[3]==nil)
|
||||
assert(-a == a)
|
||||
assert(cap[0] == "unm" and cap[1] == a)
|
||||
assert(a^4 == a)
|
||||
assert(cap[0] == "pow" and cap[1] == a and cap[2] == 4 and cap[3]==nil)
|
||||
assert(a^'4' == a)
|
||||
assert(cap[0] == "pow" and cap[1] == a and cap[2] == '4' and cap[3]==nil)
|
||||
assert(4^a == 4)
|
||||
assert(cap[0] == "pow" and cap[1] == 4 and cap[2] == a and cap[3]==nil)
|
||||
assert('4'^a == '4')
|
||||
assert(cap[0] == "pow" and cap[1] == '4' and cap[2] == a and cap[3]==nil)
|
||||
|
||||
|
||||
t = {}
|
||||
t.__lt = function (a,b,c)
|
||||
collectgarbage()
|
||||
assert(c == nil)
|
||||
if type(a) == 'table' then a = a.x end
|
||||
if type(b) == 'table' then b = b.x end
|
||||
return a<b, "dummy"
|
||||
end
|
||||
|
||||
function Op(x) return setmetatable({x=x}, t) end
|
||||
|
||||
local function test ()
|
||||
assert(not(Op(1)<Op(1)) and (Op(1)<Op(2)) and not(Op(2)<Op(1)))
|
||||
assert(not(Op('a')<Op('a')) and (Op('a')<Op('b')) and not(Op('b')<Op('a')))
|
||||
assert((Op(1)<=Op(1)) and (Op(1)<=Op(2)) and not(Op(2)<=Op(1)))
|
||||
assert((Op('a')<=Op('a')) and (Op('a')<=Op('b')) and not(Op('b')<=Op('a')))
|
||||
assert(not(Op(1)>Op(1)) and not(Op(1)>Op(2)) and (Op(2)>Op(1)))
|
||||
assert(not(Op('a')>Op('a')) and not(Op('a')>Op('b')) and (Op('b')>Op('a')))
|
||||
assert((Op(1)>=Op(1)) and not(Op(1)>=Op(2)) and (Op(2)>=Op(1)))
|
||||
assert((Op('a')>=Op('a')) and not(Op('a')>=Op('b')) and (Op('b')>=Op('a')))
|
||||
end
|
||||
|
||||
test()
|
||||
|
||||
t.__le = function (a,b,c)
|
||||
assert(c == nil)
|
||||
if type(a) == 'table' then a = a.x end
|
||||
if type(b) == 'table' then b = b.x end
|
||||
return a<=b, "dummy"
|
||||
end
|
||||
|
||||
test() -- retest comparisons, now using both `lt' and `le'
|
||||
|
||||
|
||||
-- test `partial order'
|
||||
|
||||
local function Set(x)
|
||||
local y = {}
|
||||
for _,k in pairs(x) do y[k] = 1 end
|
||||
return setmetatable(y, t)
|
||||
end
|
||||
|
||||
t.__lt = function (a,b)
|
||||
for k in pairs(a) do
|
||||
if not b[k] then return false end
|
||||
b[k] = nil
|
||||
end
|
||||
return next(b) ~= nil
|
||||
end
|
||||
|
||||
t.__le = nil
|
||||
|
||||
assert(Set{1,2,3} < Set{1,2,3,4})
|
||||
assert(not(Set{1,2,3,4} < Set{1,2,3,4}))
|
||||
assert((Set{1,2,3,4} <= Set{1,2,3,4}))
|
||||
assert((Set{1,2,3,4} >= Set{1,2,3,4}))
|
||||
assert((Set{1,3} <= Set{3,5})) -- wrong!! model needs a `le' method ;-)
|
||||
|
||||
t.__le = function (a,b)
|
||||
for k in pairs(a) do
|
||||
if not b[k] then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
assert(not (Set{1,3} <= Set{3,5})) -- now its OK!
|
||||
assert(not(Set{1,3} <= Set{3,5}))
|
||||
assert(not(Set{1,3} >= Set{3,5}))
|
||||
|
||||
t.__eq = function (a,b)
|
||||
for k in pairs(a) do
|
||||
if not b[k] then return false end
|
||||
b[k] = nil
|
||||
end
|
||||
return next(b) == nil
|
||||
end
|
||||
|
||||
local s = Set{1,3,5}
|
||||
assert(s == Set{3,5,1})
|
||||
assert(not rawequal(s, Set{3,5,1}))
|
||||
assert(rawequal(s, s))
|
||||
assert(Set{1,3,5,1} == Set{3,5,1})
|
||||
assert(Set{1,3,5} ~= Set{3,5,1,6})
|
||||
t[Set{1,3,5}] = 1
|
||||
assert(t[Set{1,3,5}] == nil) -- `__eq' is not valid for table accesses
|
||||
|
||||
|
||||
t.__concat = function (a,b,c)
|
||||
assert(c == nil)
|
||||
if type(a) == 'table' then a = a.val end
|
||||
if type(b) == 'table' then b = b.val end
|
||||
if A then return a..b
|
||||
else
|
||||
return setmetatable({val=a..b}, t)
|
||||
end
|
||||
end
|
||||
|
||||
c = {val="c"}; setmetatable(c, t)
|
||||
d = {val="d"}; setmetatable(d, t)
|
||||
|
||||
A = true
|
||||
assert(c..d == 'cd')
|
||||
assert(0 .."a".."b"..c..d.."e".."f"..(5+3).."g" == "0abcdef8g")
|
||||
|
||||
A = false
|
||||
x = c..d
|
||||
assert(getmetatable(x) == t and x.val == 'cd')
|
||||
x = 0 .."a".."b"..c..d.."e".."f".."g"
|
||||
assert(x.val == "0abcdefg")
|
||||
|
||||
|
||||
-- test comparison compatibilities
|
||||
local t1, t2, c, d
|
||||
t1 = {}; c = {}; setmetatable(c, t1)
|
||||
d = {}
|
||||
t1.__eq = function () return true end
|
||||
t1.__lt = function () return true end
|
||||
assert(c ~= d and not pcall(function () return c < d end))
|
||||
setmetatable(d, t1)
|
||||
assert(c == d and c < d and not(d <= c))
|
||||
t2 = {}
|
||||
t2.__eq = t1.__eq
|
||||
t2.__lt = t1.__lt
|
||||
setmetatable(d, t2)
|
||||
assert(c == d and c < d and not(d <= c))
|
||||
|
||||
|
||||
|
||||
-- test for several levels of calls
|
||||
local i
|
||||
local tt = {
|
||||
__call = function (t, ...)
|
||||
i = i+1
|
||||
if t.f then return t.f(...)
|
||||
else return {...}
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
local a = setmetatable({}, tt)
|
||||
local b = setmetatable({f=a}, tt)
|
||||
local c = setmetatable({f=b}, tt)
|
||||
|
||||
i = 0
|
||||
x = c(3,4,5)
|
||||
assert(i == 3 and x[1] == 3 and x[3] == 5)
|
||||
|
||||
|
||||
assert(_G.X == 20)
|
||||
assert(_G == getfenv(0))
|
||||
|
||||
print'+'
|
||||
|
||||
local _g = _G
|
||||
setfenv(1, setmetatable({}, {__index=function (_,k) return _g[k] end}))
|
||||
|
||||
-- testing proxies
|
||||
assert(getmetatable(newproxy()) == nil)
|
||||
assert(getmetatable(newproxy(false)) == nil)
|
||||
|
||||
local u = newproxy(true)
|
||||
|
||||
getmetatable(u).__newindex = function (u,k,v)
|
||||
getmetatable(u)[k] = v
|
||||
end
|
||||
|
||||
getmetatable(u).__index = function (u,k)
|
||||
return getmetatable(u)[k]
|
||||
end
|
||||
|
||||
for i=1,10 do u[i] = i end
|
||||
for i=1,10 do assert(u[i] == i) end
|
||||
|
||||
local k = newproxy(u)
|
||||
assert(getmetatable(k) == getmetatable(u))
|
||||
|
||||
|
||||
a = {}
|
||||
rawset(a, "x", 1, 2, 3)
|
||||
assert(a.x == 1 and rawget(a, "x", 3) == 1)
|
||||
|
||||
print '+'
|
||||
|
||||
-- testing metatables for basic types
|
||||
mt = {}
|
||||
debug.setmetatable(10, mt)
|
||||
assert(getmetatable(-2) == mt)
|
||||
mt.__index = function (a,b) return a+b end
|
||||
assert((10)[3] == 13)
|
||||
assert((10)["3"] == 13)
|
||||
debug.setmetatable(23, nil)
|
||||
assert(getmetatable(-2) == nil)
|
||||
|
||||
debug.setmetatable(true, mt)
|
||||
assert(getmetatable(false) == mt)
|
||||
mt.__index = function (a,b) return a or b end
|
||||
assert((true)[false] == true)
|
||||
assert((false)[false] == false)
|
||||
debug.setmetatable(false, nil)
|
||||
assert(getmetatable(true) == nil)
|
||||
|
||||
debug.setmetatable(nil, mt)
|
||||
assert(getmetatable(nil) == mt)
|
||||
mt.__add = function (a,b) return (a or 0) + (b or 0) end
|
||||
assert(10 + nil == 10)
|
||||
assert(nil + 23 == 23)
|
||||
assert(nil + nil == 0)
|
||||
debug.setmetatable(nil, nil)
|
||||
assert(getmetatable(nil) == nil)
|
||||
|
||||
debug.setmetatable(nil, {})
|
||||
|
||||
|
||||
print 'OK'
|
||||
|
||||
return 12
|
||||
324
lib/lua/lua-tests/files.lua
Normal file
324
lib/lua/lua-tests/files.lua
Normal file
@@ -0,0 +1,324 @@
|
||||
|
||||
print('testing i/o')
|
||||
|
||||
assert(io.input(io.stdin) == io.stdin)
|
||||
assert(io.output(io.stdout) == io.stdout)
|
||||
|
||||
|
||||
assert(type(io.input()) == "userdata" and io.type(io.output()) == "file")
|
||||
assert(io.type(8) == nil)
|
||||
local a = {}; setmetatable(a, {})
|
||||
assert(io.type(a) == nil)
|
||||
|
||||
local a,b,c = io.open('xuxu_nao_existe')
|
||||
assert(not a and type(b) == "string" and type(c) == "number")
|
||||
|
||||
a,b,c = io.open('/a/b/c/d', 'w')
|
||||
assert(not a and type(b) == "string" and type(c) == "number")
|
||||
|
||||
local file = os.tmpname()
|
||||
local otherfile = os.tmpname()
|
||||
|
||||
assert(os.setlocale('C', 'all'))
|
||||
|
||||
io.input(io.stdin); io.output(io.stdout);
|
||||
|
||||
os.remove(file)
|
||||
assert(loadfile(file) == nil)
|
||||
assert(io.open(file) == nil)
|
||||
io.output(file)
|
||||
assert(io.output() ~= io.stdout)
|
||||
|
||||
assert(io.output():seek() == 0)
|
||||
assert(io.write("alo alo"))
|
||||
assert(io.output():seek() == string.len("alo alo"))
|
||||
assert(io.output():seek("cur", -3) == string.len("alo alo")-3)
|
||||
assert(io.write("joao"))
|
||||
assert(io.output():seek("end") == string.len("alo joao"))
|
||||
|
||||
assert(io.output():seek("set") == 0)
|
||||
|
||||
assert(io.write('"álo"', "{a}\n", "second line\n", "third line \n"))
|
||||
assert(io.write('çfourth_line'))
|
||||
io.output(io.stdout)
|
||||
collectgarbage() -- file should be closed by GC
|
||||
assert(io.input() == io.stdin and rawequal(io.output(), io.stdout))
|
||||
print('+')
|
||||
|
||||
-- test GC for files
|
||||
collectgarbage()
|
||||
for i=1,120 do
|
||||
for i=1,5 do
|
||||
io.input(file)
|
||||
assert(io.open(file, 'r'))
|
||||
io.lines(file)
|
||||
end
|
||||
collectgarbage()
|
||||
end
|
||||
|
||||
assert(os.rename(file, otherfile))
|
||||
assert(os.rename(file, otherfile) == nil)
|
||||
|
||||
io.output(io.open(otherfile, "a"))
|
||||
assert(io.write("\n\n\t\t 3450\n"));
|
||||
io.close()
|
||||
|
||||
-- test line generators
|
||||
assert(os.rename(otherfile, file))
|
||||
io.output(otherfile)
|
||||
local f = io.lines(file)
|
||||
while f() do end;
|
||||
assert(not pcall(f)) -- read lines after EOF
|
||||
assert(not pcall(f)) -- read lines after EOF
|
||||
-- copy from file to otherfile
|
||||
for l in io.lines(file) do io.write(l, "\n") end
|
||||
io.close()
|
||||
-- copy from otherfile back to file
|
||||
local f = assert(io.open(otherfile))
|
||||
assert(io.type(f) == "file")
|
||||
io.output(file)
|
||||
assert(io.output():read() == nil)
|
||||
for l in f:lines() do io.write(l, "\n") end
|
||||
assert(f:close()); io.close()
|
||||
assert(not pcall(io.close, f)) -- error trying to close again
|
||||
assert(tostring(f) == "file (closed)")
|
||||
assert(io.type(f) == "closed file")
|
||||
io.input(file)
|
||||
f = io.open(otherfile):lines()
|
||||
for l in io.lines() do assert(l == f()) end
|
||||
assert(os.remove(otherfile))
|
||||
|
||||
io.input(file)
|
||||
do -- test error returns
|
||||
local a,b,c = io.input():write("xuxu")
|
||||
assert(not a and type(b) == "string" and type(c) == "number")
|
||||
end
|
||||
assert(io.read(0) == "") -- not eof
|
||||
assert(io.read(5, '*l') == '"álo"')
|
||||
assert(io.read(0) == "")
|
||||
assert(io.read() == "second line")
|
||||
local x = io.input():seek()
|
||||
assert(io.read() == "third line ")
|
||||
assert(io.input():seek("set", x))
|
||||
assert(io.read('*l') == "third line ")
|
||||
assert(io.read(1) == "ç")
|
||||
assert(io.read(string.len"fourth_line") == "fourth_line")
|
||||
assert(io.input():seek("cur", -string.len"fourth_line"))
|
||||
assert(io.read() == "fourth_line")
|
||||
assert(io.read() == "") -- empty line
|
||||
assert(io.read('*n') == 3450)
|
||||
assert(io.read(1) == '\n')
|
||||
assert(io.read(0) == nil) -- end of file
|
||||
assert(io.read(1) == nil) -- end of file
|
||||
assert(({io.read(1)})[2] == nil)
|
||||
assert(io.read() == nil) -- end of file
|
||||
assert(({io.read()})[2] == nil)
|
||||
assert(io.read('*n') == nil) -- end of file
|
||||
assert(({io.read('*n')})[2] == nil)
|
||||
assert(io.read('*a') == '') -- end of file (OK for `*a')
|
||||
assert(io.read('*a') == '') -- end of file (OK for `*a')
|
||||
collectgarbage()
|
||||
print('+')
|
||||
io.close(io.input())
|
||||
assert(not pcall(io.read))
|
||||
|
||||
assert(os.remove(file))
|
||||
|
||||
local t = '0123456789'
|
||||
for i=1,12 do t = t..t; end
|
||||
assert(string.len(t) == 10*2^12)
|
||||
|
||||
io.output(file)
|
||||
io.write("alo\n")
|
||||
io.close()
|
||||
assert(not pcall(io.write))
|
||||
local f = io.open(file, "a")
|
||||
io.output(f)
|
||||
collectgarbage()
|
||||
|
||||
assert(io.write(' ' .. t .. ' '))
|
||||
assert(io.write(';', 'end of file\n'))
|
||||
f:flush(); io.flush()
|
||||
f:close()
|
||||
print('+')
|
||||
|
||||
io.input(file)
|
||||
assert(io.read() == "alo")
|
||||
assert(io.read(1) == ' ')
|
||||
assert(io.read(string.len(t)) == t)
|
||||
assert(io.read(1) == ' ')
|
||||
assert(io.read(0))
|
||||
assert(io.read('*a') == ';end of file\n')
|
||||
assert(io.read(0) == nil)
|
||||
assert(io.close(io.input()))
|
||||
|
||||
assert(os.remove(file))
|
||||
print('+')
|
||||
|
||||
local x1 = "string\n\n\\com \"\"''coisas [[estranhas]] ]]'"
|
||||
io.output(file)
|
||||
assert(io.write(string.format("x2 = %q\n-- comment without ending EOS", x1)))
|
||||
io.close()
|
||||
assert(loadfile(file))()
|
||||
assert(x1 == x2)
|
||||
print('+')
|
||||
assert(os.remove(file))
|
||||
assert(os.remove(file) == nil)
|
||||
assert(os.remove(otherfile) == nil)
|
||||
|
||||
io.output(file)
|
||||
assert(io.write("qualquer coisa\n"))
|
||||
assert(io.write("mais qualquer coisa"))
|
||||
io.close()
|
||||
io.output(assert(io.open(otherfile, 'wb')))
|
||||
assert(io.write("outra coisa\0\1\3\0\0\0\0\255\0"))
|
||||
io.close()
|
||||
|
||||
local filehandle = assert(io.open(file, 'r'))
|
||||
local otherfilehandle = assert(io.open(otherfile, 'rb'))
|
||||
assert(filehandle ~= otherfilehandle)
|
||||
assert(type(filehandle) == "userdata")
|
||||
assert(filehandle:read('*l') == "qualquer coisa")
|
||||
io.input(otherfilehandle)
|
||||
assert(io.read(string.len"outra coisa") == "outra coisa")
|
||||
assert(filehandle:read('*l') == "mais qualquer coisa")
|
||||
filehandle:close();
|
||||
assert(type(filehandle) == "userdata")
|
||||
io.input(otherfilehandle)
|
||||
assert(io.read(4) == "\0\1\3\0")
|
||||
assert(io.read(3) == "\0\0\0")
|
||||
assert(io.read(0) == "") -- 255 is not eof
|
||||
assert(io.read(1) == "\255")
|
||||
assert(io.read('*a') == "\0")
|
||||
assert(not io.read(0))
|
||||
assert(otherfilehandle == io.input())
|
||||
otherfilehandle:close()
|
||||
assert(os.remove(file))
|
||||
assert(os.remove(otherfile))
|
||||
collectgarbage()
|
||||
|
||||
io.output(file)
|
||||
io.write[[
|
||||
123.4 -56e-2 not a number
|
||||
second line
|
||||
third line
|
||||
|
||||
and the rest of the file
|
||||
]]
|
||||
io.close()
|
||||
io.input(file)
|
||||
local _,a,b,c,d,e,h,__ = io.read(1, '*n', '*n', '*l', '*l', '*l', '*a', 10)
|
||||
assert(io.close(io.input()))
|
||||
assert(_ == ' ' and __ == nil)
|
||||
assert(type(a) == 'number' and a==123.4 and b==-56e-2)
|
||||
assert(d=='second line' and e=='third line')
|
||||
assert(h==[[
|
||||
|
||||
and the rest of the file
|
||||
]])
|
||||
assert(os.remove(file))
|
||||
collectgarbage()
|
||||
|
||||
-- testing buffers
|
||||
do
|
||||
local f = assert(io.open(file, "w"))
|
||||
local fr = assert(io.open(file, "r"))
|
||||
assert(f:setvbuf("full", 2000))
|
||||
f:write("x")
|
||||
assert(fr:read("*all") == "") -- full buffer; output not written yet
|
||||
f:close()
|
||||
fr:seek("set")
|
||||
assert(fr:read("*all") == "x") -- `close' flushes it
|
||||
f = assert(io.open(file), "w")
|
||||
assert(f:setvbuf("no"))
|
||||
f:write("x")
|
||||
fr:seek("set")
|
||||
assert(fr:read("*all") == "x") -- no buffer; output is ready
|
||||
f:close()
|
||||
f = assert(io.open(file, "a"))
|
||||
assert(f:setvbuf("line"))
|
||||
f:write("x")
|
||||
fr:seek("set", 1)
|
||||
assert(fr:read("*all") == "") -- line buffer; no output without `\n'
|
||||
f:write("a\n")
|
||||
fr:seek("set", 1)
|
||||
assert(fr:read("*all") == "xa\n") -- now we have a whole line
|
||||
f:close(); fr:close()
|
||||
end
|
||||
|
||||
|
||||
-- testing large files (> BUFSIZ)
|
||||
io.output(file)
|
||||
for i=1,5001 do io.write('0123456789123') end
|
||||
io.write('\n12346')
|
||||
io.close()
|
||||
io.input(file)
|
||||
local x = io.read('*a')
|
||||
io.input():seek('set', 0)
|
||||
local y = io.read(30001)..io.read(1005)..io.read(0)..io.read(1)..io.read(100003)
|
||||
assert(x == y and string.len(x) == 5001*13 + 6)
|
||||
io.input():seek('set', 0)
|
||||
y = io.read() -- huge line
|
||||
assert(x == y..'\n'..io.read())
|
||||
assert(io.read() == nil)
|
||||
io.close(io.input())
|
||||
assert(os.remove(file))
|
||||
x = nil; y = nil
|
||||
|
||||
x, y = pcall(io.popen, "ls")
|
||||
if x then
|
||||
assert(y:read("*a"))
|
||||
assert(y:close())
|
||||
else
|
||||
(Message or print)('\a\n >>> popen not available<<<\n\a')
|
||||
end
|
||||
|
||||
print'+'
|
||||
|
||||
local t = os.time()
|
||||
T = os.date("*t", t)
|
||||
loadstring(os.date([[assert(T.year==%Y and T.month==%m and T.day==%d and
|
||||
T.hour==%H and T.min==%M and T.sec==%S and
|
||||
T.wday==%w+1 and T.yday==%j and type(T.isdst) == 'boolean')]], t))()
|
||||
|
||||
assert(os.time(T) == t)
|
||||
|
||||
T = os.date("!*t", t)
|
||||
loadstring(os.date([[!assert(T.year==%Y and T.month==%m and T.day==%d and
|
||||
T.hour==%H and T.min==%M and T.sec==%S and
|
||||
T.wday==%w+1 and T.yday==%j and type(T.isdst) == 'boolean')]], t))()
|
||||
|
||||
do
|
||||
local T = os.date("*t")
|
||||
local t = os.time(T)
|
||||
assert(type(T.isdst) == 'boolean')
|
||||
T.isdst = nil
|
||||
local t1 = os.time(T)
|
||||
assert(t == t1) -- if isdst is absent uses correct default
|
||||
end
|
||||
|
||||
t = os.time(T)
|
||||
T.year = T.year-1;
|
||||
local t1 = os.time(T)
|
||||
-- allow for leap years
|
||||
assert(math.abs(os.difftime(t,t1)/(24*3600) - 365) < 2)
|
||||
|
||||
t = os.time()
|
||||
t1 = os.time(os.date("*t"))
|
||||
assert(os.difftime(t1,t) <= 2)
|
||||
|
||||
local t1 = os.time{year=2000, month=10, day=1, hour=23, min=12, sec=17}
|
||||
local t2 = os.time{year=2000, month=10, day=1, hour=23, min=10, sec=19}
|
||||
assert(os.difftime(t1,t2) == 60*2-2)
|
||||
|
||||
io.output(io.stdout)
|
||||
local d = os.date('%d')
|
||||
local m = os.date('%m')
|
||||
local a = os.date('%Y')
|
||||
local ds = os.date('%w') + 1
|
||||
local h = os.date('%H')
|
||||
local min = os.date('%M')
|
||||
local s = os.date('%S')
|
||||
io.write(string.format('test done on %2.2d/%2.2d/%d', d, m, a))
|
||||
io.write(string.format(', at %2.2d:%2.2d:%2.2d\n', h, min, s))
|
||||
io.write(string.format('%s\n', _VERSION))
|
||||
312
lib/lua/lua-tests/gc.lua
Normal file
312
lib/lua/lua-tests/gc.lua
Normal file
@@ -0,0 +1,312 @@
|
||||
print('testing garbage collection')
|
||||
|
||||
collectgarbage()
|
||||
|
||||
_G["while"] = 234
|
||||
|
||||
limit = 5000
|
||||
|
||||
|
||||
|
||||
contCreate = 0
|
||||
|
||||
print('tables')
|
||||
while contCreate <= limit do
|
||||
local a = {}; a = nil
|
||||
contCreate = contCreate+1
|
||||
end
|
||||
|
||||
a = "a"
|
||||
|
||||
contCreate = 0
|
||||
print('strings')
|
||||
while contCreate <= limit do
|
||||
a = contCreate .. "b";
|
||||
a = string.gsub(a, '(%d%d*)', string.upper)
|
||||
a = "a"
|
||||
contCreate = contCreate+1
|
||||
end
|
||||
|
||||
|
||||
contCreate = 0
|
||||
|
||||
a = {}
|
||||
|
||||
print('functions')
|
||||
function a:test ()
|
||||
while contCreate <= limit do
|
||||
loadstring(string.format("function temp(a) return 'a%d' end", contCreate))()
|
||||
assert(temp() == string.format('a%d', contCreate))
|
||||
contCreate = contCreate+1
|
||||
end
|
||||
end
|
||||
|
||||
a:test()
|
||||
|
||||
-- collection of functions without locals, globals, etc.
|
||||
do local f = function () end end
|
||||
|
||||
|
||||
print("functions with errors")
|
||||
prog = [[
|
||||
do
|
||||
a = 10;
|
||||
function foo(x,y)
|
||||
a = sin(a+0.456-0.23e-12);
|
||||
return function (z) return sin(%x+z) end
|
||||
end
|
||||
local x = function (w) a=a+w; end
|
||||
end
|
||||
]]
|
||||
do
|
||||
local step = 1
|
||||
if rawget(_G, "_soft") then step = 13 end
|
||||
for i=1, string.len(prog), step do
|
||||
for j=i, string.len(prog), step do
|
||||
pcall(loadstring(string.sub(prog, i, j)))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
print('long strings')
|
||||
x = "01234567890123456789012345678901234567890123456789012345678901234567890123456789"
|
||||
assert(string.len(x)==80)
|
||||
s = ''
|
||||
n = 0
|
||||
k = 300
|
||||
while n < k do s = s..x; n=n+1; j=tostring(n) end
|
||||
assert(string.len(s) == k*80)
|
||||
s = string.sub(s, 1, 20000)
|
||||
s, i = string.gsub(s, '(%d%d%d%d)', math.sin)
|
||||
assert(i==20000/4)
|
||||
s = nil
|
||||
x = nil
|
||||
|
||||
assert(_G["while"] == 234)
|
||||
|
||||
|
||||
local bytes = gcinfo()
|
||||
while 1 do
|
||||
local nbytes = gcinfo()
|
||||
if nbytes < bytes then break end -- run until gc
|
||||
bytes = nbytes
|
||||
a = {}
|
||||
end
|
||||
|
||||
|
||||
local function dosteps (siz)
|
||||
collectgarbage()
|
||||
collectgarbage"stop"
|
||||
local a = {}
|
||||
for i=1,100 do a[i] = {{}}; local b = {} end
|
||||
local x = gcinfo()
|
||||
local i = 0
|
||||
repeat
|
||||
i = i+1
|
||||
until collectgarbage("step", siz)
|
||||
assert(gcinfo() < x)
|
||||
return i
|
||||
end
|
||||
|
||||
assert(dosteps(0) > 10)
|
||||
assert(dosteps(6) < dosteps(2))
|
||||
assert(dosteps(10000) == 1)
|
||||
assert(collectgarbage("step", 1000000) == true)
|
||||
assert(collectgarbage("step", 1000000))
|
||||
|
||||
|
||||
do
|
||||
local x = gcinfo()
|
||||
collectgarbage()
|
||||
collectgarbage"stop"
|
||||
repeat
|
||||
local a = {}
|
||||
until gcinfo() > 1000
|
||||
collectgarbage"restart"
|
||||
repeat
|
||||
local a = {}
|
||||
until gcinfo() < 1000
|
||||
end
|
||||
|
||||
lim = 15
|
||||
a = {}
|
||||
-- fill a with `collectable' indices
|
||||
for i=1,lim do a[{}] = i end
|
||||
b = {}
|
||||
for k,v in pairs(a) do b[k]=v end
|
||||
-- remove all indices and collect them
|
||||
for n in pairs(b) do
|
||||
a[n] = nil
|
||||
assert(type(n) == 'table' and next(n) == nil)
|
||||
collectgarbage()
|
||||
end
|
||||
b = nil
|
||||
collectgarbage()
|
||||
for n in pairs(a) do error'cannot be here' end
|
||||
for i=1,lim do a[i] = i end
|
||||
for i=1,lim do assert(a[i] == i) end
|
||||
|
||||
|
||||
print('weak tables')
|
||||
a = {}; setmetatable(a, {__mode = 'k'});
|
||||
-- fill a with some `collectable' indices
|
||||
for i=1,lim do a[{}] = i end
|
||||
-- and some non-collectable ones
|
||||
for i=1,lim do local t={}; a[t]=t end
|
||||
for i=1,lim do a[i] = i end
|
||||
for i=1,lim do local s=string.rep('@', i); a[s] = s..'#' end
|
||||
collectgarbage()
|
||||
local i = 0
|
||||
for k,v in pairs(a) do assert(k==v or k..'#'==v); i=i+1 end
|
||||
assert(i == 3*lim)
|
||||
|
||||
a = {}; setmetatable(a, {__mode = 'v'});
|
||||
a[1] = string.rep('b', 21)
|
||||
collectgarbage()
|
||||
assert(a[1]) -- strings are *values*
|
||||
a[1] = nil
|
||||
-- fill a with some `collectable' values (in both parts of the table)
|
||||
for i=1,lim do a[i] = {} end
|
||||
for i=1,lim do a[i..'x'] = {} end
|
||||
-- and some non-collectable ones
|
||||
for i=1,lim do local t={}; a[t]=t end
|
||||
for i=1,lim do a[i+lim]=i..'x' end
|
||||
collectgarbage()
|
||||
local i = 0
|
||||
for k,v in pairs(a) do assert(k==v or k-lim..'x' == v); i=i+1 end
|
||||
assert(i == 2*lim)
|
||||
|
||||
a = {}; setmetatable(a, {__mode = 'vk'});
|
||||
local x, y, z = {}, {}, {}
|
||||
-- keep only some items
|
||||
a[1], a[2], a[3] = x, y, z
|
||||
a[string.rep('$', 11)] = string.rep('$', 11)
|
||||
-- fill a with some `collectable' values
|
||||
for i=4,lim do a[i] = {} end
|
||||
for i=1,lim do a[{}] = i end
|
||||
for i=1,lim do local t={}; a[t]=t end
|
||||
collectgarbage()
|
||||
assert(next(a) ~= nil)
|
||||
local i = 0
|
||||
for k,v in pairs(a) do
|
||||
assert((k == 1 and v == x) or
|
||||
(k == 2 and v == y) or
|
||||
(k == 3 and v == z) or k==v);
|
||||
i = i+1
|
||||
end
|
||||
assert(i == 4)
|
||||
x,y,z=nil
|
||||
collectgarbage()
|
||||
assert(next(a) == string.rep('$', 11))
|
||||
|
||||
|
||||
-- testing userdata
|
||||
collectgarbage("stop") -- stop collection
|
||||
local u = newproxy(true)
|
||||
local s = 0
|
||||
local a = {[u] = 0}; setmetatable(a, {__mode = 'vk'})
|
||||
for i=1,10 do a[newproxy(u)] = i end
|
||||
for k in pairs(a) do assert(getmetatable(k) == getmetatable(u)) end
|
||||
local a1 = {}; for k,v in pairs(a) do a1[k] = v end
|
||||
for k,v in pairs(a1) do a[v] = k end
|
||||
for i =1,10 do assert(a[i]) end
|
||||
getmetatable(u).a = a1
|
||||
getmetatable(u).u = u
|
||||
do
|
||||
local u = u
|
||||
getmetatable(u).__gc = function (o)
|
||||
assert(a[o] == 10-s)
|
||||
assert(a[10-s] == nil) -- udata already removed from weak table
|
||||
assert(getmetatable(o) == getmetatable(u))
|
||||
assert(getmetatable(o).a[o] == 10-s)
|
||||
s=s+1
|
||||
end
|
||||
end
|
||||
a1, u = nil
|
||||
assert(next(a) ~= nil)
|
||||
collectgarbage()
|
||||
assert(s==11)
|
||||
collectgarbage()
|
||||
assert(next(a) == nil) -- finalized keys are removed in two cycles
|
||||
|
||||
|
||||
-- __gc x weak tables
|
||||
local u = newproxy(true)
|
||||
setmetatable(getmetatable(u), {__mode = "v"})
|
||||
getmetatable(u).__gc = function (o) os.exit(1) end -- cannot happen
|
||||
collectgarbage()
|
||||
|
||||
local u = newproxy(true)
|
||||
local m = getmetatable(u)
|
||||
m.x = {[{0}] = 1; [0] = {1}}; setmetatable(m.x, {__mode = "kv"});
|
||||
m.__gc = function (o)
|
||||
assert(next(getmetatable(o).x) == nil)
|
||||
m = 10
|
||||
end
|
||||
u, m = nil
|
||||
collectgarbage()
|
||||
assert(m==10)
|
||||
|
||||
|
||||
-- errors during collection
|
||||
u = newproxy(true)
|
||||
getmetatable(u).__gc = function () error "!!!" end
|
||||
u = nil
|
||||
assert(not pcall(collectgarbage))
|
||||
|
||||
|
||||
if not rawget(_G, "_soft") then
|
||||
print("deep structures")
|
||||
local a = {}
|
||||
for i = 1,200000 do
|
||||
a = {next = a}
|
||||
end
|
||||
collectgarbage()
|
||||
end
|
||||
|
||||
-- create many threads with self-references and open upvalues
|
||||
local thread_id = 0
|
||||
local threads = {}
|
||||
|
||||
function fn(thread)
|
||||
local x = {}
|
||||
threads[thread_id] = function()
|
||||
thread = x
|
||||
end
|
||||
coroutine.yield()
|
||||
end
|
||||
|
||||
while thread_id < 1000 do
|
||||
local thread = coroutine.create(fn)
|
||||
coroutine.resume(thread, thread)
|
||||
thread_id = thread_id + 1
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- create a userdata to be collected when state is closed
|
||||
do
|
||||
local newproxy,assert,type,print,getmetatable =
|
||||
newproxy,assert,type,print,getmetatable
|
||||
local u = newproxy(true)
|
||||
local tt = getmetatable(u)
|
||||
___Glob = {u} -- avoid udata being collected before program end
|
||||
tt.__gc = function (o)
|
||||
assert(getmetatable(o) == tt)
|
||||
-- create new objects during GC
|
||||
local a = 'xuxu'..(10+3)..'joao', {}
|
||||
___Glob = o -- ressurect object!
|
||||
newproxy(o) -- creates a new one with same metatable
|
||||
print(">>> closing state " .. "<<<\n")
|
||||
end
|
||||
end
|
||||
|
||||
-- create several udata to raise errors when collected while closing state
|
||||
do
|
||||
local u = newproxy(true)
|
||||
getmetatable(u).__gc = function (o) return o + 1 end
|
||||
table.insert(___Glob, u) -- preserve udata until the end
|
||||
for i = 1,10 do table.insert(___Glob, newproxy(u)) end
|
||||
end
|
||||
|
||||
print('OK')
|
||||
176
lib/lua/lua-tests/literals.lua
Normal file
176
lib/lua/lua-tests/literals.lua
Normal file
@@ -0,0 +1,176 @@
|
||||
print('testing scanner')
|
||||
|
||||
local function dostring (x) return assert(loadstring(x))() end
|
||||
|
||||
dostring("x = 'a\0a'")
|
||||
assert(x == 'a\0a' and string.len(x) == 3)
|
||||
|
||||
-- escape sequences
|
||||
assert('\n\"\'\\' == [[
|
||||
|
||||
"'\]])
|
||||
|
||||
assert(string.find("\a\b\f\n\r\t\v", "^%c%c%c%c%c%c%c$"))
|
||||
|
||||
-- assume ASCII just for tests:
|
||||
assert("\09912" == 'c12')
|
||||
assert("\99ab" == 'cab')
|
||||
assert("\099" == '\99')
|
||||
assert("\099\n" == 'c\10')
|
||||
assert('\0\0\0alo' == '\0' .. '\0\0' .. 'alo')
|
||||
|
||||
assert(010 .. 020 .. -030 == "1020-30")
|
||||
|
||||
-- long variable names
|
||||
|
||||
var = string.rep('a', 15000)
|
||||
prog = string.format("%s = 5", var)
|
||||
dostring(prog)
|
||||
assert(_G[var] == 5)
|
||||
var = nil
|
||||
print('+')
|
||||
|
||||
-- escapes --
|
||||
assert("\n\t" == [[
|
||||
|
||||
]])
|
||||
assert([[
|
||||
|
||||
$debug]] == "\n $debug")
|
||||
assert([[ [ ]] ~= [[ ] ]])
|
||||
-- long strings --
|
||||
b = "001234567890123456789012345678901234567891234567890123456789012345678901234567890012345678901234567890123456789012345678912345678901234567890123456789012345678900123456789012345678901234567890123456789123456789012345678901234567890123456789001234567890123456789012345678901234567891234567890123456789012345678901234567890012345678901234567890123456789012345678912345678901234567890123456789012345678900123456789012345678901234567890123456789123456789012345678901234567890123456789001234567890123456789012345678901234567891234567890123456789012345678901234567890012345678901234567890123456789012345678912345678901234567890123456789012345678900123456789012345678901234567890123456789123456789012345678901234567890123456789001234567890123456789012345678901234567891234567890123456789012345678901234567890012345678901234567890123456789012345678912345678901234567890123456789012345678900123456789012345678901234567890123456789123456789012345678901234567890123456789"
|
||||
assert(string.len(b) == 960)
|
||||
prog = [=[
|
||||
print('+')
|
||||
|
||||
a1 = [["isto e' um string com várias 'aspas'"]]
|
||||
a2 = "'aspas'"
|
||||
|
||||
assert(string.find(a1, a2) == 31)
|
||||
print('+')
|
||||
|
||||
a1 = [==[temp = [[um valor qualquer]]; ]==]
|
||||
assert(loadstring(a1))()
|
||||
assert(temp == 'um valor qualquer')
|
||||
-- long strings --
|
||||
b = "001234567890123456789012345678901234567891234567890123456789012345678901234567890012345678901234567890123456789012345678912345678901234567890123456789012345678900123456789012345678901234567890123456789123456789012345678901234567890123456789001234567890123456789012345678901234567891234567890123456789012345678901234567890012345678901234567890123456789012345678912345678901234567890123456789012345678900123456789012345678901234567890123456789123456789012345678901234567890123456789001234567890123456789012345678901234567891234567890123456789012345678901234567890012345678901234567890123456789012345678912345678901234567890123456789012345678900123456789012345678901234567890123456789123456789012345678901234567890123456789001234567890123456789012345678901234567891234567890123456789012345678901234567890012345678901234567890123456789012345678912345678901234567890123456789012345678900123456789012345678901234567890123456789123456789012345678901234567890123456789"
|
||||
assert(string.len(b) == 960)
|
||||
print('+')
|
||||
|
||||
a = [[00123456789012345678901234567890123456789123456789012345678901234567890123456789
|
||||
00123456789012345678901234567890123456789123456789012345678901234567890123456789
|
||||
00123456789012345678901234567890123456789123456789012345678901234567890123456789
|
||||
00123456789012345678901234567890123456789123456789012345678901234567890123456789
|
||||
00123456789012345678901234567890123456789123456789012345678901234567890123456789
|
||||
00123456789012345678901234567890123456789123456789012345678901234567890123456789
|
||||
00123456789012345678901234567890123456789123456789012345678901234567890123456789
|
||||
00123456789012345678901234567890123456789123456789012345678901234567890123456789
|
||||
00123456789012345678901234567890123456789123456789012345678901234567890123456789
|
||||
00123456789012345678901234567890123456789123456789012345678901234567890123456789
|
||||
00123456789012345678901234567890123456789123456789012345678901234567890123456789
|
||||
00123456789012345678901234567890123456789123456789012345678901234567890123456789
|
||||
00123456789012345678901234567890123456789123456789012345678901234567890123456789
|
||||
00123456789012345678901234567890123456789123456789012345678901234567890123456789
|
||||
00123456789012345678901234567890123456789123456789012345678901234567890123456789
|
||||
00123456789012345678901234567890123456789123456789012345678901234567890123456789
|
||||
00123456789012345678901234567890123456789123456789012345678901234567890123456789
|
||||
00123456789012345678901234567890123456789123456789012345678901234567890123456789
|
||||
00123456789012345678901234567890123456789123456789012345678901234567890123456789
|
||||
00123456789012345678901234567890123456789123456789012345678901234567890123456789
|
||||
00123456789012345678901234567890123456789123456789012345678901234567890123456789
|
||||
00123456789012345678901234567890123456789123456789012345678901234567890123456789
|
||||
00123456789012345678901234567890123456789123456789012345678901234567890123456789
|
||||
]]
|
||||
assert(string.len(a) == 1863)
|
||||
assert(string.sub(a, 1, 40) == string.sub(b, 1, 40))
|
||||
x = 1
|
||||
]=]
|
||||
|
||||
print('+')
|
||||
x = nil
|
||||
dostring(prog)
|
||||
assert(x)
|
||||
|
||||
prog = nil
|
||||
a = nil
|
||||
b = nil
|
||||
|
||||
|
||||
-- testing line ends
|
||||
prog = [[
|
||||
a = 1 -- a comment
|
||||
b = 2
|
||||
|
||||
|
||||
x = [=[
|
||||
hi
|
||||
]=]
|
||||
y = "\
|
||||
hello\r\n\
|
||||
"
|
||||
return debug.getinfo(1).currentline
|
||||
]]
|
||||
|
||||
for _, n in pairs{"\n", "\r", "\n\r", "\r\n"} do
|
||||
local prog, nn = string.gsub(prog, "\n", n)
|
||||
assert(dostring(prog) == nn)
|
||||
assert(_G.x == "hi\n" and _G.y == "\nhello\r\n\n")
|
||||
end
|
||||
|
||||
|
||||
-- testing comments and strings with long brackets
|
||||
a = [==[]=]==]
|
||||
assert(a == "]=")
|
||||
|
||||
a = [==[[===[[=[]]=][====[]]===]===]==]
|
||||
assert(a == "[===[[=[]]=][====[]]===]===")
|
||||
|
||||
a = [====[[===[[=[]]=][====[]]===]===]====]
|
||||
assert(a == "[===[[=[]]=][====[]]===]===")
|
||||
|
||||
a = [=[]]]]]]]]]=]
|
||||
assert(a == "]]]]]]]]")
|
||||
|
||||
|
||||
--[===[
|
||||
x y z [==[ blu foo
|
||||
]==
|
||||
]
|
||||
]=]==]
|
||||
error error]=]===]
|
||||
|
||||
-- generate all strings of four of these chars
|
||||
local x = {"=", "[", "]", "\n"}
|
||||
local len = 4
|
||||
local function gen (c, n)
|
||||
if n==0 then coroutine.yield(c)
|
||||
else
|
||||
for _, a in pairs(x) do
|
||||
gen(c..a, n-1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for s in coroutine.wrap(function () gen("", len) end) do
|
||||
assert(s == loadstring("return [====[\n"..s.."]====]")())
|
||||
end
|
||||
|
||||
|
||||
-- testing decimal point locale
|
||||
if os.setlocale("pt_BR") or os.setlocale("ptb") then
|
||||
assert(tonumber("3,4") == 3.4 and tonumber"3.4" == nil)
|
||||
assert(assert(loadstring("return 3.4"))() == 3.4)
|
||||
assert(assert(loadstring("return .4,3"))() == .4)
|
||||
assert(assert(loadstring("return 4."))() == 4.)
|
||||
assert(assert(loadstring("return 4.+.5"))() == 4.5)
|
||||
local a,b = loadstring("return 4.5.")
|
||||
assert(string.find(b, "'4%.5%.'"))
|
||||
assert(os.setlocale("C"))
|
||||
else
|
||||
(Message or print)(
|
||||
'\a\n >>> pt_BR locale not available: skipping decimal point tests <<<\n\a')
|
||||
end
|
||||
|
||||
|
||||
print('OK')
|
||||
127
lib/lua/lua-tests/locals.lua
Normal file
127
lib/lua/lua-tests/locals.lua
Normal file
@@ -0,0 +1,127 @@
|
||||
print('testing local variables plus some extra stuff')
|
||||
|
||||
do
|
||||
local i = 10
|
||||
do local i = 100; assert(i==100) end
|
||||
do local i = 1000; assert(i==1000) end
|
||||
assert(i == 10)
|
||||
if i ~= 10 then
|
||||
local i = 20
|
||||
else
|
||||
local i = 30
|
||||
assert(i == 30)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
f = nil
|
||||
|
||||
local f
|
||||
x = 1
|
||||
|
||||
a = nil
|
||||
loadstring('local a = {}')()
|
||||
assert(type(a) ~= 'table')
|
||||
|
||||
function f (a)
|
||||
local _1, _2, _3, _4, _5
|
||||
local _6, _7, _8, _9, _10
|
||||
local x = 3
|
||||
local b = a
|
||||
local c,d = a,b
|
||||
if (d == b) then
|
||||
local x = 'q'
|
||||
x = b
|
||||
assert(x == 2)
|
||||
else
|
||||
assert(nil)
|
||||
end
|
||||
assert(x == 3)
|
||||
local f = 10
|
||||
end
|
||||
|
||||
local b=10
|
||||
local a; repeat local b; a,b=1,2; assert(a+1==b); until a+b==3
|
||||
|
||||
|
||||
assert(x == 1)
|
||||
|
||||
f(2)
|
||||
assert(type(f) == 'function')
|
||||
|
||||
|
||||
-- testing globals ;-)
|
||||
do
|
||||
local f = {}
|
||||
local _G = _G
|
||||
for i=1,10 do f[i] = function (x) A=A+1; return A, _G.getfenv(x) end end
|
||||
A=10; assert(f[1]() == 11)
|
||||
for i=1,10 do assert(setfenv(f[i], {A=i}) == f[i]) end
|
||||
assert(f[3]() == 4 and A == 11)
|
||||
local a,b = f[8](1)
|
||||
assert(b.A == 9)
|
||||
a,b = f[8](0)
|
||||
assert(b.A == 11) -- `real' global
|
||||
local g
|
||||
local function f () assert(setfenv(2, {a='10'}) == g) end
|
||||
g = function () f(); _G.assert(_G.getfenv(1).a == '10') end
|
||||
g(); assert(getfenv(g).a == '10')
|
||||
end
|
||||
|
||||
-- test for global table of loaded chunks
|
||||
local function foo (s)
|
||||
return loadstring(s)
|
||||
end
|
||||
|
||||
assert(getfenv(foo("")) == _G)
|
||||
local a = {loadstring = loadstring}
|
||||
setfenv(foo, a)
|
||||
assert(getfenv(foo("")) == _G)
|
||||
setfenv(0, a) -- change global environment
|
||||
assert(getfenv(foo("")) == a)
|
||||
setfenv(0, _G)
|
||||
|
||||
|
||||
-- testing limits for special instructions
|
||||
|
||||
local a
|
||||
local p = 4
|
||||
for i=2,31 do
|
||||
for j=-3,3 do
|
||||
assert(loadstring(string.format([[local a=%s;a=a+
|
||||
%s;
|
||||
assert(a
|
||||
==2^%s)]], j, p-j, i))) ()
|
||||
assert(loadstring(string.format([[local a=%s;
|
||||
a=a-%s;
|
||||
assert(a==-2^%s)]], -j, p-j, i))) ()
|
||||
assert(loadstring(string.format([[local a,b=0,%s;
|
||||
a=b-%s;
|
||||
assert(a==-2^%s)]], -j, p-j, i))) ()
|
||||
end
|
||||
p =2*p
|
||||
end
|
||||
|
||||
print'+'
|
||||
|
||||
|
||||
if rawget(_G, "querytab") then
|
||||
-- testing clearing of dead elements from tables
|
||||
collectgarbage("stop") -- stop GC
|
||||
local a = {[{}] = 4, [3] = 0, alo = 1,
|
||||
a1234567890123456789012345678901234567890 = 10}
|
||||
|
||||
local t = querytab(a)
|
||||
|
||||
for k,_ in pairs(a) do a[k] = nil end
|
||||
collectgarbage() -- restore GC and collect dead fiels in `a'
|
||||
for i=0,t-1 do
|
||||
local k = querytab(a, i)
|
||||
assert(k == nil or type(k) == 'number' or k == 'alo')
|
||||
end
|
||||
end
|
||||
|
||||
print('OK')
|
||||
|
||||
return 5,f
|
||||
159
lib/lua/lua-tests/main.lua
Normal file
159
lib/lua/lua-tests/main.lua
Normal file
@@ -0,0 +1,159 @@
|
||||
# testing special comment on first line
|
||||
|
||||
print ("testing lua.c options")
|
||||
|
||||
assert(os.execute() ~= 0) -- machine has a system command
|
||||
|
||||
prog = os.tmpname()
|
||||
otherprog = os.tmpname()
|
||||
out = os.tmpname()
|
||||
|
||||
do
|
||||
local i = 0
|
||||
while arg[i] do i=i-1 end
|
||||
progname = '"'..arg[i+1]..'"'
|
||||
end
|
||||
print(progname)
|
||||
|
||||
local prepfile = function (s, p)
|
||||
p = p or prog
|
||||
io.output(p)
|
||||
io.write(s)
|
||||
assert(io.close())
|
||||
end
|
||||
|
||||
function checkout (s)
|
||||
io.input(out)
|
||||
local t = io.read("*a")
|
||||
io.input():close()
|
||||
assert(os.remove(out))
|
||||
if s ~= t then print(string.format("'%s' - '%s'\n", s, t)) end
|
||||
assert(s == t)
|
||||
return t
|
||||
end
|
||||
|
||||
function auxrun (...)
|
||||
local s = string.format(...)
|
||||
s = string.gsub(s, "lua", progname, 1)
|
||||
return os.execute(s)
|
||||
end
|
||||
|
||||
function RUN (...)
|
||||
assert(auxrun(...) == 0)
|
||||
end
|
||||
|
||||
function NoRun (...)
|
||||
print("\n(the next error is expected by the test)")
|
||||
assert(auxrun(...) ~= 0)
|
||||
end
|
||||
|
||||
-- test 2 files
|
||||
prepfile("print(1); a=2")
|
||||
prepfile("print(a)", otherprog)
|
||||
RUN("lua -l %s -l%s -lstring -l io %s > %s", prog, otherprog, otherprog, out)
|
||||
checkout("1\n2\n2\n")
|
||||
|
||||
local a = [[
|
||||
assert(table.getn(arg) == 3 and arg[1] == 'a' and
|
||||
arg[2] == 'b' and arg[3] == 'c')
|
||||
assert(arg[-1] == '--' and arg[-2] == "-e " and arg[-3] == %s)
|
||||
assert(arg[4] == nil and arg[-4] == nil)
|
||||
local a, b, c = ...
|
||||
assert(... == 'a' and a == 'a' and b == 'b' and c == 'c')
|
||||
]]
|
||||
a = string.format(a, progname)
|
||||
prepfile(a)
|
||||
RUN('lua "-e " -- %s a b c', prog)
|
||||
|
||||
prepfile"assert(arg==nil)"
|
||||
prepfile("assert(arg)", otherprog)
|
||||
RUN("lua -l%s - < %s", prog, otherprog)
|
||||
|
||||
prepfile""
|
||||
RUN("lua - < %s > %s", prog, out)
|
||||
checkout("")
|
||||
|
||||
-- test many arguments
|
||||
prepfile[[print(({...})[30])]]
|
||||
RUN("lua %s %s > %s", prog, string.rep(" a", 30), out)
|
||||
checkout("a\n")
|
||||
|
||||
RUN([[lua "-eprint(1)" -ea=3 -e "print(a)" > %s]], out)
|
||||
checkout("1\n3\n")
|
||||
|
||||
prepfile[[
|
||||
print(
|
||||
1, a
|
||||
)
|
||||
]]
|
||||
RUN("lua - < %s > %s", prog, out)
|
||||
checkout("1\tnil\n")
|
||||
|
||||
prepfile[[
|
||||
= (6*2-6) -- ===
|
||||
a
|
||||
= 10
|
||||
print(a)
|
||||
= a]]
|
||||
RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i < %s > %s]], prog, out)
|
||||
checkout("6\n10\n10\n\n")
|
||||
|
||||
prepfile("a = [[b\nc\nd\ne]]\n=a")
|
||||
print(prog)
|
||||
RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i < %s > %s]], prog, out)
|
||||
checkout("b\nc\nd\ne\n\n")
|
||||
|
||||
prompt = "alo"
|
||||
prepfile[[ --
|
||||
a = 2
|
||||
]]
|
||||
RUN([[lua "-e_PROMPT='%s'" -i < %s > %s]], prompt, prog, out)
|
||||
checkout(string.rep(prompt, 3).."\n")
|
||||
|
||||
s = [=[ --
|
||||
function f ( x )
|
||||
local a = [[
|
||||
xuxu
|
||||
]]
|
||||
local b = "\
|
||||
xuxu\n"
|
||||
if x == 11 then return 1 , 2 end --[[ test multiple returns ]]
|
||||
return x + 1
|
||||
--\\
|
||||
end
|
||||
=( f( 10 ) )
|
||||
assert( a == b )
|
||||
=f( 11 ) ]=]
|
||||
s = string.gsub(s, ' ', '\n\n')
|
||||
prepfile(s)
|
||||
RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i < %s > %s]], prog, out)
|
||||
checkout("11\n1\t2\n\n")
|
||||
|
||||
prepfile[[#comment in 1st line without \n at the end]]
|
||||
RUN("lua %s", prog)
|
||||
|
||||
prepfile("#comment with a binary file\n"..string.dump(loadstring("print(1)")))
|
||||
RUN("lua %s > %s", prog, out)
|
||||
checkout("1\n")
|
||||
|
||||
prepfile("#comment with a binary file\r\n"..string.dump(loadstring("print(1)")))
|
||||
RUN("lua %s > %s", prog, out)
|
||||
checkout("1\n")
|
||||
|
||||
-- close Lua with an open file
|
||||
prepfile(string.format([[io.output(%q); io.write('alo')]], out))
|
||||
RUN("lua %s", prog)
|
||||
checkout('alo')
|
||||
|
||||
assert(os.remove(prog))
|
||||
assert(os.remove(otherprog))
|
||||
assert(not os.remove(out))
|
||||
|
||||
RUN("lua -v")
|
||||
|
||||
NoRun("lua -h")
|
||||
NoRun("lua -e")
|
||||
NoRun("lua -e a")
|
||||
NoRun("lua -f")
|
||||
|
||||
print("OK")
|
||||
208
lib/lua/lua-tests/math.lua
Normal file
208
lib/lua/lua-tests/math.lua
Normal file
@@ -0,0 +1,208 @@
|
||||
print("testing numbers and math lib")
|
||||
|
||||
do
|
||||
local a,b,c = "2", " 3e0 ", " 10 "
|
||||
assert(a+b == 5 and -b == -3 and b+"2" == 5 and "10"-c == 0)
|
||||
assert(type(a) == 'string' and type(b) == 'string' and type(c) == 'string')
|
||||
assert(a == "2" and b == " 3e0 " and c == " 10 " and -c == -" 10 ")
|
||||
assert(c%a == 0 and a^b == 8)
|
||||
end
|
||||
|
||||
|
||||
do
|
||||
local a,b = math.modf(3.5)
|
||||
assert(a == 3 and b == 0.5)
|
||||
assert(math.huge > 10e30)
|
||||
assert(-math.huge < -10e30)
|
||||
end
|
||||
|
||||
function f(...)
|
||||
if select('#', ...) == 1 then
|
||||
return (...)
|
||||
else
|
||||
return "***"
|
||||
end
|
||||
end
|
||||
|
||||
assert(tonumber{} == nil)
|
||||
assert(tonumber'+0.01' == 1/100 and tonumber'+.01' == 0.01 and
|
||||
tonumber'.01' == 0.01 and tonumber'-1.' == -1 and
|
||||
tonumber'+1.' == 1)
|
||||
assert(tonumber'+ 0.01' == nil and tonumber'+.e1' == nil and
|
||||
tonumber'1e' == nil and tonumber'1.0e+' == nil and
|
||||
tonumber'.' == nil)
|
||||
assert(tonumber('-12') == -10-2)
|
||||
assert(tonumber('-1.2e2') == - - -120)
|
||||
assert(f(tonumber('1 a')) == nil)
|
||||
assert(f(tonumber('e1')) == nil)
|
||||
assert(f(tonumber('e 1')) == nil)
|
||||
assert(f(tonumber(' 3.4.5 ')) == nil)
|
||||
assert(f(tonumber('')) == nil)
|
||||
assert(f(tonumber('', 8)) == nil)
|
||||
assert(f(tonumber(' ')) == nil)
|
||||
assert(f(tonumber(' ', 9)) == nil)
|
||||
assert(f(tonumber('99', 8)) == nil)
|
||||
assert(tonumber(' 1010 ', 2) == 10)
|
||||
assert(tonumber('10', 36) == 36)
|
||||
--assert(tonumber('\n -10 \n', 36) == -36)
|
||||
--assert(tonumber('-fFfa', 16) == -(10+(16*(15+(16*(15+(16*15)))))))
|
||||
assert(tonumber('fFfa', 15) == nil)
|
||||
--assert(tonumber(string.rep('1', 42), 2) + 1 == 2^42)
|
||||
assert(tonumber(string.rep('1', 32), 2) + 1 == 2^32)
|
||||
--assert(tonumber('-fffffFFFFF', 16)-1 == -2^40)
|
||||
assert(tonumber('ffffFFFF', 16)+1 == 2^32)
|
||||
|
||||
assert(1.1 == 1.+.1)
|
||||
assert(100.0 == 1E2 and .01 == 1e-2)
|
||||
assert(1111111111111111-1111111111111110== 1000.00e-03)
|
||||
-- 1234567890123456
|
||||
assert(1.1 == '1.'+'.1')
|
||||
assert('1111111111111111'-'1111111111111110' == tonumber" +0.001e+3 \n\t")
|
||||
|
||||
function eq (a,b,limit)
|
||||
if not limit then limit = 10E-10 end
|
||||
return math.abs(a-b) <= limit
|
||||
end
|
||||
|
||||
assert(0.1e-30 > 0.9E-31 and 0.9E30 < 0.1e31)
|
||||
|
||||
assert(0.123456 > 0.123455)
|
||||
|
||||
assert(tonumber('+1.23E30') == 1.23*10^30)
|
||||
|
||||
-- testing order operators
|
||||
assert(not(1<1) and (1<2) and not(2<1))
|
||||
assert(not('a'<'a') and ('a'<'b') and not('b'<'a'))
|
||||
assert((1<=1) and (1<=2) and not(2<=1))
|
||||
assert(('a'<='a') and ('a'<='b') and not('b'<='a'))
|
||||
assert(not(1>1) and not(1>2) and (2>1))
|
||||
assert(not('a'>'a') and not('a'>'b') and ('b'>'a'))
|
||||
assert((1>=1) and not(1>=2) and (2>=1))
|
||||
assert(('a'>='a') and not('a'>='b') and ('b'>='a'))
|
||||
|
||||
-- testing mod operator
|
||||
assert(-4%3 == 2)
|
||||
assert(4%-3 == -2)
|
||||
assert(math.pi - math.pi % 1 == 3)
|
||||
assert(math.pi - math.pi % 0.001 == 3.141)
|
||||
|
||||
local function testbit(a, n)
|
||||
return a/2^n % 2 >= 1
|
||||
end
|
||||
|
||||
assert(eq(math.sin(-9.8)^2 + math.cos(-9.8)^2, 1))
|
||||
assert(eq(math.tan(math.pi/4), 1))
|
||||
assert(eq(math.sin(math.pi/2), 1) and eq(math.cos(math.pi/2), 0))
|
||||
assert(eq(math.atan(1), math.pi/4) and eq(math.acos(0), math.pi/2) and
|
||||
eq(math.asin(1), math.pi/2))
|
||||
assert(eq(math.deg(math.pi/2), 90) and eq(math.rad(90), math.pi/2))
|
||||
assert(math.abs(-10) == 10)
|
||||
assert(eq(math.atan2(1,0), math.pi/2))
|
||||
assert(math.ceil(4.5) == 5.0)
|
||||
assert(math.floor(4.5) == 4.0)
|
||||
assert(math.mod(10,3) == 1)
|
||||
assert(eq(math.sqrt(10)^2, 10))
|
||||
assert(eq(math.log10(2), math.log(2)/math.log(10)))
|
||||
assert(eq(math.exp(0), 1))
|
||||
assert(eq(math.sin(10), math.sin(10%(2*math.pi))))
|
||||
local v,e = math.frexp(math.pi)
|
||||
assert(eq(math.ldexp(v,e), math.pi))
|
||||
|
||||
assert(eq(math.tanh(3.5), math.sinh(3.5)/math.cosh(3.5)))
|
||||
|
||||
assert(tonumber(' 1.3e-2 ') == 1.3e-2)
|
||||
assert(tonumber(' -1.00000000000001 ') == -1.00000000000001)
|
||||
|
||||
-- testing constant limits
|
||||
-- 2^23 = 8388608
|
||||
assert(8388609 + -8388609 == 0)
|
||||
assert(8388608 + -8388608 == 0)
|
||||
assert(8388607 + -8388607 == 0)
|
||||
|
||||
if rawget(_G, "_soft") then return end
|
||||
|
||||
f = io.tmpfile()
|
||||
assert(f)
|
||||
f:write("a = {")
|
||||
i = 1
|
||||
repeat
|
||||
f:write("{", math.sin(i), ", ", math.cos(i), ", ", i/3, "},\n")
|
||||
i=i+1
|
||||
until i > 1000
|
||||
f:write("}")
|
||||
f:seek("set", 0)
|
||||
assert(loadstring(f:read('*a')))()
|
||||
assert(f:close())
|
||||
|
||||
assert(eq(a[300][1], math.sin(300)))
|
||||
assert(eq(a[600][1], math.sin(600)))
|
||||
assert(eq(a[500][2], math.cos(500)))
|
||||
assert(eq(a[800][2], math.cos(800)))
|
||||
assert(eq(a[200][3], 200/3))
|
||||
assert(eq(a[1000][3], 1000/3, 0.001))
|
||||
print('+')
|
||||
|
||||
do -- testing NaN
|
||||
local NaN = 10e500 - 10e400
|
||||
assert(NaN ~= NaN)
|
||||
assert(not (NaN < NaN))
|
||||
assert(not (NaN <= NaN))
|
||||
assert(not (NaN > NaN))
|
||||
assert(not (NaN >= NaN))
|
||||
assert(not (0 < NaN))
|
||||
assert(not (NaN < 0))
|
||||
local a = {}
|
||||
assert(not pcall(function () a[NaN] = 1 end))
|
||||
assert(a[NaN] == nil)
|
||||
a[1] = 1
|
||||
assert(not pcall(function () a[NaN] = 1 end))
|
||||
assert(a[NaN] == nil)
|
||||
end
|
||||
|
||||
require "checktable"
|
||||
stat(a)
|
||||
|
||||
a = nil
|
||||
|
||||
-- testing implicit convertions
|
||||
|
||||
local a,b = '10', '20'
|
||||
assert(a*b == 200 and a+b == 30 and a-b == -10 and a/b == 0.5 and -b == -20)
|
||||
assert(a == '10' and b == '20')
|
||||
|
||||
|
||||
math.randomseed(0)
|
||||
|
||||
local i = 0
|
||||
local Max = 0
|
||||
local Min = 2
|
||||
repeat
|
||||
local t = math.random()
|
||||
Max = math.max(Max, t)
|
||||
Min = math.min(Min, t)
|
||||
i=i+1
|
||||
flag = eq(Max, 1, 0.001) and eq(Min, 0, 0.001)
|
||||
until flag or i>10000
|
||||
assert(0 <= Min and Max<1)
|
||||
assert(flag);
|
||||
|
||||
for i=1,10 do
|
||||
local t = math.random(5)
|
||||
assert(1 <= t and t <= 5)
|
||||
end
|
||||
|
||||
i = 0
|
||||
Max = -200
|
||||
Min = 200
|
||||
repeat
|
||||
local t = math.random(-10,0)
|
||||
Max = math.max(Max, t)
|
||||
Min = math.min(Min, t)
|
||||
i=i+1
|
||||
flag = (Max == 0 and Min == -10)
|
||||
until flag or i>10000
|
||||
assert(-10 <= Min and Max<=0)
|
||||
assert(flag);
|
||||
|
||||
|
||||
print('OK')
|
||||
396
lib/lua/lua-tests/nextvar.lua
Normal file
396
lib/lua/lua-tests/nextvar.lua
Normal file
@@ -0,0 +1,396 @@
|
||||
print('testing tables, next, and for')
|
||||
|
||||
local a = {}
|
||||
|
||||
-- make sure table has lots of space in hash part
|
||||
for i=1,100 do a[i.."+"] = true end
|
||||
for i=1,100 do a[i.."+"] = nil end
|
||||
-- fill hash part with numeric indices testing size operator
|
||||
for i=1,100 do
|
||||
a[i] = true
|
||||
assert(#a == i)
|
||||
end
|
||||
|
||||
|
||||
if T then
|
||||
-- testing table sizes
|
||||
|
||||
local l2 = math.log(2)
|
||||
local function log2 (x) return math.log(x)/l2 end
|
||||
|
||||
local function mp2 (n) -- minimum power of 2 >= n
|
||||
local mp = 2^math.ceil(log2(n))
|
||||
assert(n == 0 or (mp/2 < n and n <= mp))
|
||||
return mp
|
||||
end
|
||||
|
||||
local function fb (n)
|
||||
local r, nn = T.int2fb(n)
|
||||
assert(r < 256)
|
||||
return nn
|
||||
end
|
||||
|
||||
-- test fb function
|
||||
local a = 1
|
||||
local lim = 2^30
|
||||
while a < lim do
|
||||
local n = fb(a)
|
||||
assert(a <= n and n <= a*1.125)
|
||||
a = math.ceil(a*1.3)
|
||||
end
|
||||
|
||||
|
||||
local function check (t, na, nh)
|
||||
local a, h = T.querytab(t)
|
||||
if a ~= na or h ~= nh then
|
||||
print(na, nh, a, h)
|
||||
assert(nil)
|
||||
end
|
||||
end
|
||||
|
||||
-- testing constructor sizes
|
||||
local lim = 40
|
||||
local s = 'return {'
|
||||
for i=1,lim do
|
||||
s = s..i..','
|
||||
local s = s
|
||||
for k=0,lim do
|
||||
local t = loadstring(s..'}')()
|
||||
assert(#t == i)
|
||||
check(t, fb(i), mp2(k))
|
||||
s = string.format('%sa%d=%d,', s, k, k)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- tests with unknown number of elements
|
||||
local a = {}
|
||||
for i=1,lim do a[i] = i end -- build auxiliary table
|
||||
for k=0,lim do
|
||||
local a = {unpack(a,1,k)}
|
||||
assert(#a == k)
|
||||
check(a, k, 0)
|
||||
a = {1,2,3,unpack(a,1,k)}
|
||||
check(a, k+3, 0)
|
||||
assert(#a == k + 3)
|
||||
end
|
||||
|
||||
|
||||
print'+'
|
||||
|
||||
-- testing tables dynamically built
|
||||
local lim = 130
|
||||
local a = {}; a[2] = 1; check(a, 0, 1)
|
||||
a = {}; a[0] = 1; check(a, 0, 1); a[2] = 1; check(a, 0, 2)
|
||||
a = {}; a[0] = 1; a[1] = 1; check(a, 1, 1)
|
||||
a = {}
|
||||
for i = 1,lim do
|
||||
a[i] = 1
|
||||
assert(#a == i)
|
||||
check(a, mp2(i), 0)
|
||||
end
|
||||
|
||||
a = {}
|
||||
for i = 1,lim do
|
||||
a['a'..i] = 1
|
||||
assert(#a == 0)
|
||||
check(a, 0, mp2(i))
|
||||
end
|
||||
|
||||
a = {}
|
||||
for i=1,16 do a[i] = i end
|
||||
check(a, 16, 0)
|
||||
for i=1,11 do a[i] = nil end
|
||||
for i=30,40 do a[i] = nil end -- force a rehash (?)
|
||||
check(a, 0, 8)
|
||||
a[10] = 1
|
||||
for i=30,40 do a[i] = nil end -- force a rehash (?)
|
||||
check(a, 0, 8)
|
||||
for i=1,14 do a[i] = nil end
|
||||
for i=30,50 do a[i] = nil end -- force a rehash (?)
|
||||
check(a, 0, 4)
|
||||
|
||||
-- reverse filling
|
||||
for i=1,lim do
|
||||
local a = {}
|
||||
for i=i,1,-1 do a[i] = i end -- fill in reverse
|
||||
check(a, mp2(i), 0)
|
||||
end
|
||||
|
||||
-- size tests for vararg
|
||||
lim = 35
|
||||
function foo (n, ...)
|
||||
local arg = {...}
|
||||
check(arg, n, 0)
|
||||
assert(select('#', ...) == n)
|
||||
arg[n+1] = true
|
||||
check(arg, mp2(n+1), 0)
|
||||
arg.x = true
|
||||
check(arg, mp2(n+1), 1)
|
||||
end
|
||||
local a = {}
|
||||
for i=1,lim do a[i] = true; foo(i, unpack(a)) end
|
||||
|
||||
end
|
||||
|
||||
|
||||
-- test size operation on empty tables
|
||||
assert(#{} == 0)
|
||||
assert(#{nil} == 0)
|
||||
assert(#{nil, nil} == 0)
|
||||
assert(#{nil, nil, nil} == 0)
|
||||
assert(#{nil, nil, nil, nil} == 0)
|
||||
print'+'
|
||||
|
||||
|
||||
local nofind = {}
|
||||
|
||||
a,b,c = 1,2,3
|
||||
a,b,c = nil
|
||||
|
||||
local function find (name)
|
||||
local n,v
|
||||
while 1 do
|
||||
n,v = next(_G, n)
|
||||
if not n then return nofind end
|
||||
assert(v ~= nil)
|
||||
if n == name then return v end
|
||||
end
|
||||
end
|
||||
|
||||
local function find1 (name)
|
||||
for n,v in pairs(_G) do
|
||||
if n==name then return v end
|
||||
end
|
||||
return nil -- not found
|
||||
end
|
||||
|
||||
do -- create 10000 new global variables
|
||||
for i=1,10000 do _G[i] = i end
|
||||
end
|
||||
|
||||
|
||||
a = {x=90, y=8, z=23}
|
||||
assert(table.foreach(a, function(i,v) if i=='x' then return v end end) == 90)
|
||||
assert(table.foreach(a, function(i,v) if i=='a' then return v end end) == nil)
|
||||
table.foreach({}, error)
|
||||
|
||||
table.foreachi({x=10, y=20}, error)
|
||||
local a = {n = 1}
|
||||
table.foreachi({n=3}, function (i, v)
|
||||
assert(a.n == i and not v)
|
||||
a.n=a.n+1
|
||||
end)
|
||||
a = {10,20,30,nil,50}
|
||||
table.foreachi(a, function (i,v) assert(a[i] == v) end)
|
||||
assert(table.foreachi({'a', 'b', 'c'}, function (i,v)
|
||||
if i==2 then return v end
|
||||
end) == 'b')
|
||||
|
||||
|
||||
assert(print==find("print") and print == find1("print"))
|
||||
assert(_G["print"]==find("print"))
|
||||
assert(assert==find1("assert"))
|
||||
assert(nofind==find("return"))
|
||||
assert(not find1("return"))
|
||||
_G["ret" .. "urn"] = nil
|
||||
assert(nofind==find("return"))
|
||||
_G["xxx"] = 1
|
||||
assert(xxx==find("xxx"))
|
||||
print('+')
|
||||
|
||||
a = {}
|
||||
for i=0,10000 do
|
||||
if math.mod(i,10) ~= 0 then
|
||||
a['x'..i] = i
|
||||
end
|
||||
end
|
||||
|
||||
n = {n=0}
|
||||
for i,v in pairs(a) do
|
||||
n.n = n.n+1
|
||||
assert(i and v and a[i] == v)
|
||||
end
|
||||
assert(n.n == 9000)
|
||||
a = nil
|
||||
|
||||
-- remove those 10000 new global variables
|
||||
for i=1,10000 do _G[i] = nil end
|
||||
|
||||
do -- clear global table
|
||||
local a = {}
|
||||
local preserve = {io = 1, string = 1, debug = 1, os = 1,
|
||||
coroutine = 1, table = 1, math = 1}
|
||||
for n,v in pairs(_G) do a[n]=v end
|
||||
for n,v in pairs(a) do
|
||||
if not preserve[n] and type(v) ~= "function" and
|
||||
not string.find(n, "^[%u_]") then
|
||||
_G[n] = nil
|
||||
end
|
||||
collectgarbage()
|
||||
end
|
||||
end
|
||||
|
||||
local function foo ()
|
||||
local getfenv, setfenv, assert, next =
|
||||
getfenv, setfenv, assert, next
|
||||
local n = {gl1=3}
|
||||
setfenv(foo, n)
|
||||
assert(getfenv(foo) == getfenv(1))
|
||||
assert(getfenv(foo) == n)
|
||||
assert(print == nil and gl1 == 3)
|
||||
gl1 = nil
|
||||
gl = 1
|
||||
assert(n.gl == 1 and next(n, 'gl') == nil)
|
||||
end
|
||||
foo()
|
||||
|
||||
print'+'
|
||||
|
||||
local function checknext (a)
|
||||
local b = {}
|
||||
table.foreach(a, function (k,v) b[k] = v end)
|
||||
for k,v in pairs(b) do assert(a[k] == v) end
|
||||
for k,v in pairs(a) do assert(b[k] == v) end
|
||||
b = {}
|
||||
do local k,v = next(a); while k do b[k] = v; k,v = next(a,k) end end
|
||||
for k,v in pairs(b) do assert(a[k] == v) end
|
||||
for k,v in pairs(a) do assert(b[k] == v) end
|
||||
end
|
||||
|
||||
checknext{1,x=1,y=2,z=3}
|
||||
checknext{1,2,x=1,y=2,z=3}
|
||||
checknext{1,2,3,x=1,y=2,z=3}
|
||||
checknext{1,2,3,4,x=1,y=2,z=3}
|
||||
checknext{1,2,3,4,5,x=1,y=2,z=3}
|
||||
|
||||
assert(table.getn{} == 0)
|
||||
assert(table.getn{[-1] = 2} == 0)
|
||||
assert(table.getn{1,2,3,nil,nil} == 3)
|
||||
for i=0,40 do
|
||||
local a = {}
|
||||
for j=1,i do a[j]=j end
|
||||
assert(table.getn(a) == i)
|
||||
end
|
||||
|
||||
|
||||
assert(table.maxn{} == 0)
|
||||
assert(table.maxn{["1000"] = true} == 0)
|
||||
assert(table.maxn{["1000"] = true, [24.5] = 3} == 24.5)
|
||||
assert(table.maxn{[1000] = true} == 1000)
|
||||
assert(table.maxn{[10] = true, [100*math.pi] = print} == 100*math.pi)
|
||||
|
||||
|
||||
-- int overflow
|
||||
a = {}
|
||||
for i=0,50 do a[math.pow(2,i)] = true end
|
||||
assert(a[table.getn(a)])
|
||||
|
||||
print("+")
|
||||
|
||||
|
||||
-- erasing values
|
||||
local t = {[{1}] = 1, [{2}] = 2, [string.rep("x ", 4)] = 3,
|
||||
[100.3] = 4, [4] = 5}
|
||||
|
||||
local n = 0
|
||||
for k, v in pairs( t ) do
|
||||
n = n+1
|
||||
assert(t[k] == v)
|
||||
t[k] = nil
|
||||
collectgarbage()
|
||||
assert(t[k] == nil)
|
||||
end
|
||||
assert(n == 5)
|
||||
|
||||
|
||||
local function test (a)
|
||||
table.insert(a, 10); table.insert(a, 2, 20);
|
||||
table.insert(a, 1, -1); table.insert(a, 40);
|
||||
table.insert(a, table.getn(a)+1, 50)
|
||||
table.insert(a, 2, -2)
|
||||
assert(table.remove(a,1) == -1)
|
||||
assert(table.remove(a,1) == -2)
|
||||
assert(table.remove(a,1) == 10)
|
||||
assert(table.remove(a,1) == 20)
|
||||
assert(table.remove(a,1) == 40)
|
||||
assert(table.remove(a,1) == 50)
|
||||
assert(table.remove(a,1) == nil)
|
||||
end
|
||||
|
||||
a = {n=0, [-7] = "ban"}
|
||||
test(a)
|
||||
assert(a.n == 0 and a[-7] == "ban")
|
||||
|
||||
a = {[-7] = "ban"};
|
||||
test(a)
|
||||
assert(a.n == nil and table.getn(a) == 0 and a[-7] == "ban")
|
||||
|
||||
|
||||
table.insert(a, 1, 10); table.insert(a, 1, 20); table.insert(a, 1, -1)
|
||||
assert(table.remove(a) == 10)
|
||||
assert(table.remove(a) == 20)
|
||||
assert(table.remove(a) == -1)
|
||||
|
||||
a = {'c', 'd'}
|
||||
table.insert(a, 3, 'a')
|
||||
table.insert(a, 'b')
|
||||
assert(table.remove(a, 1) == 'c')
|
||||
assert(table.remove(a, 1) == 'd')
|
||||
assert(table.remove(a, 1) == 'a')
|
||||
assert(table.remove(a, 1) == 'b')
|
||||
assert(table.getn(a) == 0 and a.n == nil)
|
||||
print("+")
|
||||
|
||||
a = {}
|
||||
for i=1,1000 do
|
||||
a[i] = i; a[i-1] = nil
|
||||
end
|
||||
assert(next(a,nil) == 1000 and next(a,1000) == nil)
|
||||
|
||||
assert(next({}) == nil)
|
||||
assert(next({}, nil) == nil)
|
||||
|
||||
for a,b in pairs{} do error"not here" end
|
||||
for i=1,0 do error'not here' end
|
||||
for i=0,1,-1 do error'not here' end
|
||||
a = nil; for i=1,1 do assert(not a); a=1 end; assert(a)
|
||||
a = nil; for i=1,1,-1 do assert(not a); a=1 end; assert(a)
|
||||
|
||||
a = 0; for i=0, 1, 0.1 do a=a+1 end; assert(a==11)
|
||||
-- precision problems
|
||||
--a = 0; for i=1, 0, -0.01 do a=a+1 end; assert(a==101)
|
||||
a = 0; for i=0, 0.999999999, 0.1 do a=a+1 end; assert(a==10)
|
||||
a = 0; for i=1, 1, 1 do a=a+1 end; assert(a==1)
|
||||
a = 0; for i=1e10, 1e10, -1 do a=a+1 end; assert(a==1)
|
||||
a = 0; for i=1, 0.99999, 1 do a=a+1 end; assert(a==0)
|
||||
a = 0; for i=99999, 1e5, -1 do a=a+1 end; assert(a==0)
|
||||
a = 0; for i=1, 0.99999, -1 do a=a+1 end; assert(a==1)
|
||||
|
||||
-- conversion
|
||||
a = 0; for i="10","1","-2" do a=a+1 end; assert(a==5)
|
||||
|
||||
|
||||
collectgarbage()
|
||||
|
||||
|
||||
-- testing generic 'for'
|
||||
|
||||
local function f (n, p)
|
||||
local t = {}; for i=1,p do t[i] = i*10 end
|
||||
return function (_,n)
|
||||
if n > 0 then
|
||||
n = n-1
|
||||
return n, unpack(t)
|
||||
end
|
||||
end, nil, n
|
||||
end
|
||||
|
||||
local x = 0
|
||||
for n,a,b,c,d in f(5,3) do
|
||||
x = x+1
|
||||
assert(a == 10 and b == 20 and c == 30 and d == nil)
|
||||
end
|
||||
assert(x == 5)
|
||||
|
||||
print"OK"
|
||||
273
lib/lua/lua-tests/pm.lua
Normal file
273
lib/lua/lua-tests/pm.lua
Normal file
@@ -0,0 +1,273 @@
|
||||
print('testing pattern matching')
|
||||
|
||||
function f(s, p)
|
||||
local i,e = string.find(s, p)
|
||||
if i then return string.sub(s, i, e) end
|
||||
end
|
||||
|
||||
function f1(s, p)
|
||||
p = string.gsub(p, "%%([0-9])", function (s) return "%" .. (s+1) end)
|
||||
p = string.gsub(p, "^(^?)", "%1()", 1)
|
||||
p = string.gsub(p, "($?)$", "()%1", 1)
|
||||
local t = {string.match(s, p)}
|
||||
return string.sub(s, t[1], t[#t] - 1)
|
||||
end
|
||||
|
||||
a,b = string.find('', '') -- empty patterns are tricky
|
||||
assert(a == 1 and b == 0);
|
||||
a,b = string.find('alo', '')
|
||||
assert(a == 1 and b == 0)
|
||||
a,b = string.find('a\0o a\0o a\0o', 'a', 1) -- first position
|
||||
assert(a == 1 and b == 1)
|
||||
a,b = string.find('a\0o a\0o a\0o', 'a\0o', 2) -- starts in the midle
|
||||
assert(a == 5 and b == 7)
|
||||
a,b = string.find('a\0o a\0o a\0o', 'a\0o', 9) -- starts in the midle
|
||||
assert(a == 9 and b == 11)
|
||||
a,b = string.find('a\0a\0a\0a\0\0ab', '\0ab', 2); -- finds at the end
|
||||
assert(a == 9 and b == 11);
|
||||
a,b = string.find('a\0a\0a\0a\0\0ab', 'b') -- last position
|
||||
assert(a == 11 and b == 11)
|
||||
assert(string.find('a\0a\0a\0a\0\0ab', 'b\0') == nil) -- check ending
|
||||
assert(string.find('', '\0') == nil)
|
||||
assert(string.find('alo123alo', '12') == 4)
|
||||
assert(string.find('alo123alo', '^12') == nil)
|
||||
|
||||
assert(f('aloALO', '%l*') == 'alo')
|
||||
assert(f('aLo_ALO', '%a*') == 'aLo')
|
||||
|
||||
assert(f('aaab', 'a*') == 'aaa');
|
||||
assert(f('aaa', '^.*$') == 'aaa');
|
||||
assert(f('aaa', 'b*') == '');
|
||||
assert(f('aaa', 'ab*a') == 'aa')
|
||||
assert(f('aba', 'ab*a') == 'aba')
|
||||
assert(f('aaab', 'a+') == 'aaa')
|
||||
assert(f('aaa', '^.+$') == 'aaa')
|
||||
assert(f('aaa', 'b+') == nil)
|
||||
assert(f('aaa', 'ab+a') == nil)
|
||||
assert(f('aba', 'ab+a') == 'aba')
|
||||
assert(f('a$a', '.$') == 'a')
|
||||
assert(f('a$a', '.%$') == 'a$')
|
||||
assert(f('a$a', '.$.') == 'a$a')
|
||||
assert(f('a$a', '$$') == nil)
|
||||
assert(f('a$b', 'a$') == nil)
|
||||
assert(f('a$a', '$') == '')
|
||||
assert(f('', 'b*') == '')
|
||||
assert(f('aaa', 'bb*') == nil)
|
||||
assert(f('aaab', 'a-') == '')
|
||||
assert(f('aaa', '^.-$') == 'aaa')
|
||||
assert(f('aabaaabaaabaaaba', 'b.*b') == 'baaabaaabaaab')
|
||||
assert(f('aabaaabaaabaaaba', 'b.-b') == 'baaab')
|
||||
assert(f('alo xo', '.o$') == 'xo')
|
||||
assert(f(' \n isto é assim', '%S%S*') == 'isto')
|
||||
assert(f(' \n isto é assim', '%S*$') == 'assim')
|
||||
assert(f(' \n isto é assim', '[a-z]*$') == 'assim')
|
||||
assert(f('um caracter ? extra', '[^%sa-z]') == '?')
|
||||
assert(f('', 'a?') == '')
|
||||
assert(f('á', 'á?') == 'á')
|
||||
assert(f('ábl', 'á?b?l?') == 'ábl')
|
||||
assert(f(' ábl', 'á?b?l?') == '')
|
||||
assert(f('aa', '^aa?a?a') == 'aa')
|
||||
assert(f(']]]áb', '[^]]') == 'á')
|
||||
assert(f("0alo alo", "%x*") == "0a")
|
||||
assert(f("alo alo", "%C+") == "alo alo")
|
||||
print('+')
|
||||
|
||||
assert(f1('alo alx 123 b\0o b\0o', '(..*) %1') == "b\0o b\0o")
|
||||
assert(f1('axz123= 4= 4 34', '(.+)=(.*)=%2 %1') == '3= 4= 4 3')
|
||||
assert(f1('=======', '^(=*)=%1$') == '=======')
|
||||
assert(string.match('==========', '^([=]*)=%1$') == nil)
|
||||
|
||||
local function range (i, j)
|
||||
if i <= j then
|
||||
return i, range(i+1, j)
|
||||
end
|
||||
end
|
||||
|
||||
local abc = string.char(range(0, 255));
|
||||
|
||||
assert(string.len(abc) == 256)
|
||||
|
||||
function strset (p)
|
||||
local res = {s=''}
|
||||
string.gsub(abc, p, function (c) res.s = res.s .. c end)
|
||||
return res.s
|
||||
end;
|
||||
|
||||
assert(string.len(strset('[\200-\210]')) == 11)
|
||||
|
||||
assert(strset('[a-z]') == "abcdefghijklmnopqrstuvwxyz")
|
||||
assert(strset('[a-z%d]') == strset('[%da-uu-z]'))
|
||||
assert(strset('[a-]') == "-a")
|
||||
assert(strset('[^%W]') == strset('[%w]'))
|
||||
assert(strset('[]%%]') == '%]')
|
||||
assert(strset('[a%-z]') == '-az')
|
||||
assert(strset('[%^%[%-a%]%-b]') == '-[]^ab')
|
||||
assert(strset('%Z') == strset('[\1-\255]'))
|
||||
assert(strset('.') == strset('[\1-\255%z]'))
|
||||
print('+');
|
||||
|
||||
assert(string.match("alo xyzK", "(%w+)K") == "xyz")
|
||||
assert(string.match("254 K", "(%d*)K") == "")
|
||||
assert(string.match("alo ", "(%w*)$") == "")
|
||||
assert(string.match("alo ", "(%w+)$") == nil)
|
||||
assert(string.find("(álo)", "%(á") == 1)
|
||||
local a, b, c, d, e = string.match("âlo alo", "^(((.).).* (%w*))$")
|
||||
assert(a == 'âlo alo' and b == 'âl' and c == 'â' and d == 'alo' and e == nil)
|
||||
a, b, c, d = string.match('0123456789', '(.+(.?)())')
|
||||
assert(a == '0123456789' and b == '' and c == 11 and d == nil)
|
||||
print('+')
|
||||
|
||||
assert(string.gsub('ülo ülo', 'ü', 'x') == 'xlo xlo')
|
||||
assert(string.gsub('alo úlo ', ' +$', '') == 'alo úlo') -- trim
|
||||
assert(string.gsub(' alo alo ', '^%s*(.-)%s*$', '%1') == 'alo alo') -- double trim
|
||||
assert(string.gsub('alo alo \n 123\n ', '%s+', ' ') == 'alo alo 123 ')
|
||||
t = "abç d"
|
||||
a, b = string.gsub(t, '(.)', '%1@')
|
||||
assert('@'..a == string.gsub(t, '', '@') and b == 5)
|
||||
a, b = string.gsub('abçd', '(.)', '%0@', 2)
|
||||
assert(a == 'a@b@çd' and b == 2)
|
||||
assert(string.gsub('alo alo', '()[al]', '%1') == '12o 56o')
|
||||
assert(string.gsub("abc=xyz", "(%w*)(%p)(%w+)", "%3%2%1-%0") ==
|
||||
"xyz=abc-abc=xyz")
|
||||
assert(string.gsub("abc", "%w", "%1%0") == "aabbcc")
|
||||
assert(string.gsub("abc", "%w+", "%0%1") == "abcabc")
|
||||
assert(string.gsub('áéí', '$', '\0óú') == 'áéí\0óú')
|
||||
assert(string.gsub('', '^', 'r') == 'r')
|
||||
assert(string.gsub('', '$', 'r') == 'r')
|
||||
print('+')
|
||||
|
||||
assert(string.gsub("um (dois) tres (quatro)", "(%(%w+%))", string.upper) ==
|
||||
"um (DOIS) tres (QUATRO)")
|
||||
|
||||
do
|
||||
local function setglobal (n,v) rawset(_G, n, v) end
|
||||
string.gsub("a=roberto,roberto=a", "(%w+)=(%w%w*)", setglobal)
|
||||
assert(_G.a=="roberto" and _G.roberto=="a")
|
||||
end
|
||||
|
||||
function f(a,b) return string.gsub(a,'.',b) end
|
||||
assert(string.gsub("trocar tudo em |teste|b| é |beleza|al|", "|([^|]*)|([^|]*)|", f) ==
|
||||
"trocar tudo em bbbbb é alalalalalal")
|
||||
|
||||
local function dostring (s) return loadstring(s)() or "" end
|
||||
assert(string.gsub("alo $a=1$ novamente $return a$", "$([^$]*)%$", dostring) ==
|
||||
"alo novamente 1")
|
||||
|
||||
x = string.gsub("$x=string.gsub('alo', '.', string.upper)$ assim vai para $return x$",
|
||||
"$([^$]*)%$", dostring)
|
||||
assert(x == ' assim vai para ALO')
|
||||
|
||||
t = {}
|
||||
s = 'a alo jose joao'
|
||||
r = string.gsub(s, '()(%w+)()', function (a,w,b)
|
||||
assert(string.len(w) == b-a);
|
||||
t[a] = b-a;
|
||||
end)
|
||||
assert(s == r and t[1] == 1 and t[3] == 3 and t[7] == 4 and t[13] == 4)
|
||||
|
||||
|
||||
function isbalanced (s)
|
||||
return string.find(string.gsub(s, "%b()", ""), "[()]") == nil
|
||||
end
|
||||
|
||||
assert(isbalanced("(9 ((8))(\0) 7) \0\0 a b ()(c)() a"))
|
||||
assert(not isbalanced("(9 ((8) 7) a b (\0 c) a"))
|
||||
assert(string.gsub("alo 'oi' alo", "%b''", '"') == 'alo " alo')
|
||||
|
||||
|
||||
local t = {"apple", "orange", "lime"; n=0}
|
||||
assert(string.gsub("x and x and x", "x", function () t.n=t.n+1; return t[t.n] end)
|
||||
== "apple and orange and lime")
|
||||
|
||||
t = {n=0}
|
||||
string.gsub("first second word", "%w%w*", function (w) t.n=t.n+1; t[t.n] = w end)
|
||||
assert(t[1] == "first" and t[2] == "second" and t[3] == "word" and t.n == 3)
|
||||
|
||||
t = {n=0}
|
||||
assert(string.gsub("first second word", "%w+",
|
||||
function (w) t.n=t.n+1; t[t.n] = w end, 2) == "first second word")
|
||||
assert(t[1] == "first" and t[2] == "second" and t[3] == nil)
|
||||
|
||||
assert(not pcall(string.gsub, "alo", "(.", print))
|
||||
assert(not pcall(string.gsub, "alo", ".)", print))
|
||||
assert(not pcall(string.gsub, "alo", "(.", {}))
|
||||
assert(not pcall(string.gsub, "alo", "(.)", "%2"))
|
||||
assert(not pcall(string.gsub, "alo", "(%1)", "a"))
|
||||
assert(not pcall(string.gsub, "alo", "(%0)", "a"))
|
||||
|
||||
-- big strings
|
||||
local a = string.rep('a', 300000)
|
||||
assert(string.find(a, '^a*.?$'))
|
||||
assert(not string.find(a, '^a*.?b$'))
|
||||
assert(string.find(a, '^a-.?$'))
|
||||
|
||||
-- deep nest of gsubs
|
||||
function rev (s)
|
||||
return string.gsub(s, "(.)(.+)", function (c,s1) return rev(s1)..c end)
|
||||
end
|
||||
|
||||
local x = string.rep('012345', 10)
|
||||
assert(rev(rev(x)) == x)
|
||||
|
||||
|
||||
-- gsub with tables
|
||||
assert(string.gsub("alo alo", ".", {}) == "alo alo")
|
||||
assert(string.gsub("alo alo", "(.)", {a="AA", l=""}) == "AAo AAo")
|
||||
assert(string.gsub("alo alo", "(.).", {a="AA", l="K"}) == "AAo AAo")
|
||||
assert(string.gsub("alo alo", "((.)(.?))", {al="AA", o=false}) == "AAo AAo")
|
||||
|
||||
assert(string.gsub("alo alo", "().", {2,5,6}) == "256 alo")
|
||||
|
||||
t = {}; setmetatable(t, {__index = function (t,s) return string.upper(s) end})
|
||||
assert(string.gsub("a alo b hi", "%w%w+", t) == "a ALO b HI")
|
||||
|
||||
|
||||
-- tests for gmatch
|
||||
assert(string.gfind == string.gmatch)
|
||||
local a = 0
|
||||
for i in string.gmatch('abcde', '()') do assert(i == a+1); a=i end
|
||||
assert(a==6)
|
||||
|
||||
t = {n=0}
|
||||
for w in string.gmatch("first second word", "%w+") do
|
||||
t.n=t.n+1; t[t.n] = w
|
||||
end
|
||||
assert(t[1] == "first" and t[2] == "second" and t[3] == "word")
|
||||
|
||||
t = {3, 6, 9}
|
||||
for i in string.gmatch ("xuxx uu ppar r", "()(.)%2") do
|
||||
assert(i == table.remove(t, 1))
|
||||
end
|
||||
assert(table.getn(t) == 0)
|
||||
|
||||
t = {}
|
||||
for i,j in string.gmatch("13 14 10 = 11, 15= 16, 22=23", "(%d+)%s*=%s*(%d+)") do
|
||||
t[i] = j
|
||||
end
|
||||
a = 0
|
||||
for k,v in pairs(t) do assert(k+1 == v+0); a=a+1 end
|
||||
assert(a == 3)
|
||||
|
||||
|
||||
-- tests for `%f' (`frontiers')
|
||||
|
||||
assert(string.gsub("aaa aa a aaa a", "%f[%w]a", "x") == "xaa xa x xaa x")
|
||||
assert(string.gsub("[[]] [][] [[[[", "%f[[].", "x") == "x[]] x]x] x[[[")
|
||||
assert(string.gsub("01abc45de3", "%f[%d]", ".") == ".01abc.45de.3")
|
||||
assert(string.gsub("01abc45 de3x", "%f[%D]%w", ".") == "01.bc45 de3.")
|
||||
assert(string.gsub("function", "%f[\1-\255]%w", ".") == ".unction")
|
||||
assert(string.gsub("function", "%f[^\1-\255]", ".") == "function.")
|
||||
|
||||
local i, e = string.find(" alo aalo allo", "%f[%S].-%f[%s].-%f[%S]")
|
||||
assert(i == 2 and e == 5)
|
||||
local k = string.match(" alo aalo allo", "%f[%S](.-%f[%s].-%f[%S])")
|
||||
assert(k == 'alo ')
|
||||
|
||||
local a = {1, 5, 9, 14, 17,}
|
||||
for k in string.gmatch("alo alo th02 is 1hat", "()%f[%w%d]") do
|
||||
assert(table.remove(a, 1) == k)
|
||||
end
|
||||
assert(table.getn(a) == 0)
|
||||
|
||||
|
||||
print('OK')
|
||||
74
lib/lua/lua-tests/sort.lua
Normal file
74
lib/lua/lua-tests/sort.lua
Normal file
@@ -0,0 +1,74 @@
|
||||
print"testing sort"
|
||||
|
||||
|
||||
function check (a, f)
|
||||
f = f or function (x,y) return x<y end;
|
||||
for n=table.getn(a),2,-1 do
|
||||
assert(not f(a[n], a[n-1]))
|
||||
end
|
||||
end
|
||||
|
||||
a = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep",
|
||||
"Oct", "Nov", "Dec"}
|
||||
|
||||
table.sort(a)
|
||||
check(a)
|
||||
|
||||
limit = 30000
|
||||
if rawget(_G, "_soft") then limit = 5000 end
|
||||
|
||||
a = {}
|
||||
for i=1,limit do
|
||||
a[i] = math.random()
|
||||
end
|
||||
|
||||
local x = os.clock()
|
||||
table.sort(a)
|
||||
print(string.format("Sorting %d elements in %.2f sec.", limit, os.clock()-x))
|
||||
check(a)
|
||||
|
||||
x = os.clock()
|
||||
table.sort(a)
|
||||
print(string.format("Re-sorting %d elements in %.2f sec.", limit, os.clock()-x))
|
||||
check(a)
|
||||
|
||||
a = {}
|
||||
for i=1,limit do
|
||||
a[i] = math.random()
|
||||
end
|
||||
|
||||
x = os.clock(); i=0
|
||||
table.sort(a, function(x,y) i=i+1; return y<x end)
|
||||
print(string.format("Invert-sorting other %d elements in %.2f sec., with %i comparisons",
|
||||
limit, os.clock()-x, i))
|
||||
check(a, function(x,y) return y<x end)
|
||||
|
||||
|
||||
table.sort{} -- empty array
|
||||
|
||||
for i=1,limit do a[i] = false end
|
||||
x = os.clock();
|
||||
table.sort(a, function(x,y) return nil end)
|
||||
print(string.format("Sorting %d equal elements in %.2f sec.", limit, os.clock()-x))
|
||||
check(a, function(x,y) return nil end)
|
||||
for i,v in pairs(a) do assert(not v or i=='n' and v==limit) end
|
||||
|
||||
a = {"álo", "\0first :-)", "alo", "then this one", "45", "and a new"}
|
||||
table.sort(a)
|
||||
check(a)
|
||||
|
||||
table.sort(a, function (x, y)
|
||||
loadstring(string.format("a[%q] = ''", x))()
|
||||
collectgarbage()
|
||||
return x<y
|
||||
end)
|
||||
|
||||
|
||||
tt = {__lt = function (a,b) return a.val < b.val end}
|
||||
a = {}
|
||||
for i=1,10 do a[i] = {val=math.random(100)}; setmetatable(a[i], tt); end
|
||||
table.sort(a)
|
||||
check(a, tt.__lt)
|
||||
check(a)
|
||||
|
||||
print"OK"
|
||||
176
lib/lua/lua-tests/strings.lua
Normal file
176
lib/lua/lua-tests/strings.lua
Normal file
@@ -0,0 +1,176 @@
|
||||
print('testing strings and string library')
|
||||
|
||||
assert('alo' < 'alo1')
|
||||
assert('' < 'a')
|
||||
assert('alo\0alo' < 'alo\0b')
|
||||
assert('alo\0alo\0\0' > 'alo\0alo\0')
|
||||
assert('alo' < 'alo\0')
|
||||
assert('alo\0' > 'alo')
|
||||
assert('\0' < '\1')
|
||||
assert('\0\0' < '\0\1')
|
||||
assert('\1\0a\0a' <= '\1\0a\0a')
|
||||
assert(not ('\1\0a\0b' <= '\1\0a\0a'))
|
||||
assert('\0\0\0' < '\0\0\0\0')
|
||||
assert(not('\0\0\0\0' < '\0\0\0'))
|
||||
assert('\0\0\0' <= '\0\0\0\0')
|
||||
assert(not('\0\0\0\0' <= '\0\0\0'))
|
||||
assert('\0\0\0' <= '\0\0\0')
|
||||
assert('\0\0\0' >= '\0\0\0')
|
||||
assert(not ('\0\0b' < '\0\0a\0'))
|
||||
print('+')
|
||||
|
||||
assert(string.sub("123456789",2,4) == "234")
|
||||
assert(string.sub("123456789",7) == "789")
|
||||
assert(string.sub("123456789",7,6) == "")
|
||||
assert(string.sub("123456789",7,7) == "7")
|
||||
assert(string.sub("123456789",0,0) == "")
|
||||
assert(string.sub("123456789",-10,10) == "123456789")
|
||||
assert(string.sub("123456789",1,9) == "123456789")
|
||||
assert(string.sub("123456789",-10,-20) == "")
|
||||
assert(string.sub("123456789",-1) == "9")
|
||||
assert(string.sub("123456789",-4) == "6789")
|
||||
assert(string.sub("123456789",-6, -4) == "456")
|
||||
assert(string.sub("\000123456789",3,5) == "234")
|
||||
assert(("\000123456789"):sub(8) == "789")
|
||||
print('+')
|
||||
|
||||
assert(string.find("123456789", "345") == 3)
|
||||
a,b = string.find("123456789", "345")
|
||||
assert(string.sub("123456789", a, b) == "345")
|
||||
assert(string.find("1234567890123456789", "345", 3) == 3)
|
||||
assert(string.find("1234567890123456789", "345", 4) == 13)
|
||||
assert(string.find("1234567890123456789", "346", 4) == nil)
|
||||
assert(string.find("1234567890123456789", ".45", -9) == 13)
|
||||
assert(string.find("abcdefg", "\0", 5, 1) == nil)
|
||||
assert(string.find("", "") == 1)
|
||||
assert(string.find('', 'aaa', 1) == nil)
|
||||
assert(('alo(.)alo'):find('(.)', 1, 1) == 4)
|
||||
print('+')
|
||||
|
||||
assert(string.len("") == 0)
|
||||
assert(string.len("\0\0\0") == 3)
|
||||
assert(string.len("1234567890") == 10)
|
||||
|
||||
assert(#"" == 0)
|
||||
assert(#"\0\0\0" == 3)
|
||||
assert(#"1234567890" == 10)
|
||||
|
||||
assert(string.byte("a") == 97)
|
||||
assert(string.byte("á") > 127)
|
||||
assert(string.byte(string.char(255)) == 255)
|
||||
assert(string.byte(string.char(0)) == 0)
|
||||
assert(string.byte("\0") == 0)
|
||||
assert(string.byte("\0\0alo\0x", -1) == string.byte('x'))
|
||||
assert(string.byte("ba", 2) == 97)
|
||||
assert(string.byte("\n\n", 2, -1) == 10)
|
||||
assert(string.byte("\n\n", 2, 2) == 10)
|
||||
assert(string.byte("") == nil)
|
||||
assert(string.byte("hi", -3) == nil)
|
||||
assert(string.byte("hi", 3) == nil)
|
||||
assert(string.byte("hi", 9, 10) == nil)
|
||||
assert(string.byte("hi", 2, 1) == nil)
|
||||
assert(string.char() == "")
|
||||
assert(string.char(0, 255, 0) == "\0\255\0")
|
||||
assert(string.char(0, string.byte("á"), 0) == "\0á\0")
|
||||
assert(string.char(string.byte("ál\0óu", 1, -1)) == "ál\0óu")
|
||||
assert(string.char(string.byte("ál\0óu", 1, 0)) == "")
|
||||
assert(string.char(string.byte("ál\0óu", -10, 100)) == "ál\0óu")
|
||||
print('+')
|
||||
|
||||
assert(string.upper("ab\0c") == "AB\0C")
|
||||
assert(string.lower("\0ABCc%$") == "\0abcc%$")
|
||||
assert(string.rep('teste', 0) == '')
|
||||
assert(string.rep('tés\00tê', 2) == 'tés\0têtés\000tê')
|
||||
assert(string.rep('', 10) == '')
|
||||
|
||||
assert(string.reverse"" == "")
|
||||
assert(string.reverse"\0\1\2\3" == "\3\2\1\0")
|
||||
assert(string.reverse"\0001234" == "4321\0")
|
||||
|
||||
for i=0,30 do assert(string.len(string.rep('a', i)) == i) end
|
||||
|
||||
assert(type(tostring(nil)) == 'string')
|
||||
assert(type(tostring(12)) == 'string')
|
||||
assert(''..12 == '12' and type(12 .. '') == 'string')
|
||||
assert(string.find(tostring{}, 'table:'))
|
||||
assert(string.find(tostring(print), 'function:'))
|
||||
assert(tostring(1234567890123) == '1234567890123')
|
||||
assert(#tostring('\0') == 1)
|
||||
assert(tostring(true) == "true")
|
||||
assert(tostring(false) == "false")
|
||||
print('+')
|
||||
|
||||
x = '"ílo"\n\\'
|
||||
assert(string.format('%q%s', x, x) == '"\\"ílo\\"\\\n\\\\""ílo"\n\\')
|
||||
assert(string.format('%q', "\0") == [["\000"]])
|
||||
assert(string.format("\0%c\0%c%x\0", string.byte("á"), string.byte("b"), 140) ==
|
||||
"\0á\0b8c\0")
|
||||
assert(string.format('') == "")
|
||||
assert(string.format("%c",34)..string.format("%c",48)..string.format("%c",90)..string.format("%c",100) ==
|
||||
string.format("%c%c%c%c", 34, 48, 90, 100))
|
||||
assert(string.format("%s\0 is not \0%s", 'not be', 'be') == 'not be\0 is not \0be')
|
||||
assert(string.format("%%%d %010d", 10, 23) == "%10 0000000023")
|
||||
assert(tonumber(string.format("%f", 10.3)) == 10.3)
|
||||
x = string.format('"%-50s"', 'a')
|
||||
assert(#x == 52)
|
||||
assert(string.sub(x, 1, 4) == '"a ')
|
||||
|
||||
assert(string.format("-%.20s.20s", string.rep("%", 2000)) == "-"..string.rep("%", 20)..".20s")
|
||||
assert(string.format('"-%20s.20s"', string.rep("%", 2000)) ==
|
||||
string.format("%q", "-"..string.rep("%", 2000)..".20s"))
|
||||
|
||||
|
||||
-- longest number that can be formated
|
||||
assert(string.len(string.format('%99.99f', -1e308)) >= 100)
|
||||
|
||||
assert(loadstring("return 1\n--comentário sem EOL no final")() == 1)
|
||||
|
||||
|
||||
assert(table.concat{} == "")
|
||||
assert(table.concat({}, 'x') == "")
|
||||
assert(table.concat({'\0', '\0\1', '\0\1\2'}, '.\0.') == "\0.\0.\0\1.\0.\0\1\2")
|
||||
local a = {}; for i=1,3000 do a[i] = "xuxu" end
|
||||
assert(table.concat(a, "123").."123" == string.rep("xuxu123", 3000))
|
||||
assert(table.concat(a, "b", 20, 20) == "xuxu")
|
||||
assert(table.concat(a, "", 20, 21) == "xuxuxuxu")
|
||||
assert(table.concat(a, "", 22, 21) == "")
|
||||
assert(table.concat(a, "3", 2999) == "xuxu3xuxu")
|
||||
|
||||
a = {"a","b","c"}
|
||||
assert(table.concat(a, ",", 1, 0) == "")
|
||||
assert(table.concat(a, ",", 1, 1) == "a")
|
||||
assert(table.concat(a, ",", 1, 2) == "a,b")
|
||||
assert(table.concat(a, ",", 2) == "b,c")
|
||||
assert(table.concat(a, ",", 3) == "c")
|
||||
assert(table.concat(a, ",", 4) == "")
|
||||
|
||||
local locales = { "ptb", "ISO-8859-1", "pt_BR" }
|
||||
local function trylocale (w)
|
||||
for _, l in ipairs(locales) do
|
||||
if os.setlocale(l, w) then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
if not trylocale("collate") then
|
||||
print("locale not supported")
|
||||
else
|
||||
assert("alo" < "álo" and "álo" < "amo")
|
||||
end
|
||||
|
||||
if not trylocale("ctype") then
|
||||
print("locale not supported")
|
||||
else
|
||||
assert(string.gsub("áéíóú", "%a", "x") == "xxxxx")
|
||||
assert(string.gsub("áÁéÉ", "%l", "x") == "xÁxÉ")
|
||||
assert(string.gsub("áÁéÉ", "%u", "x") == "áxéx")
|
||||
assert(string.upper"áÁé{xuxu}ção" == "ÁÁÉ{XUXU}ÇÃO")
|
||||
end
|
||||
|
||||
os.setlocale("C")
|
||||
assert(os.setlocale() == 'C')
|
||||
assert(os.setlocale(nil, "numeric") == 'C')
|
||||
|
||||
print('OK')
|
||||
|
||||
|
||||
126
lib/lua/lua-tests/vararg.lua
Normal file
126
lib/lua/lua-tests/vararg.lua
Normal file
@@ -0,0 +1,126 @@
|
||||
print('testing vararg')
|
||||
|
||||
_G.arg = nil
|
||||
|
||||
function f(a, ...)
|
||||
assert(type(arg) == 'table')
|
||||
assert(type(arg.n) == 'number')
|
||||
for i=1,arg.n do assert(a[i]==arg[i]) end
|
||||
return arg.n
|
||||
end
|
||||
|
||||
function c12 (...)
|
||||
assert(arg == nil)
|
||||
local x = {...}; x.n = table.getn(x)
|
||||
local res = (x.n==2 and x[1] == 1 and x[2] == 2)
|
||||
if res then res = 55 end
|
||||
return res, 2
|
||||
end
|
||||
|
||||
function vararg (...) return arg end
|
||||
|
||||
local call = function (f, args) return f(unpack(args, 1, args.n)) end
|
||||
|
||||
assert(f() == 0)
|
||||
assert(f({1,2,3}, 1, 2, 3) == 3)
|
||||
assert(f({"alo", nil, 45, f, nil}, "alo", nil, 45, f, nil) == 5)
|
||||
|
||||
assert(c12(1,2)==55)
|
||||
a,b = assert(call(c12, {1,2}))
|
||||
assert(a == 55 and b == 2)
|
||||
a = call(c12, {1,2;n=2})
|
||||
assert(a == 55 and b == 2)
|
||||
a = call(c12, {1,2;n=1})
|
||||
assert(not a)
|
||||
assert(c12(1,2,3) == false)
|
||||
local a = vararg(call(next, {_G,nil;n=2}))
|
||||
local b,c = next(_G)
|
||||
assert(a[1] == b and a[2] == c and a.n == 2)
|
||||
a = vararg(call(call, {c12, {1,2}}))
|
||||
assert(a.n == 2 and a[1] == 55 and a[2] == 2)
|
||||
a = call(print, {'+'})
|
||||
assert(a == nil)
|
||||
|
||||
local t = {1, 10}
|
||||
function t:f (...) return self[arg[1]]+arg.n end
|
||||
assert(t:f(1,4) == 3 and t:f(2) == 11)
|
||||
print('+')
|
||||
|
||||
lim = 20
|
||||
local i, a = 1, {}
|
||||
while i <= lim do a[i] = i+0.3; i=i+1 end
|
||||
|
||||
function f(a, b, c, d, ...)
|
||||
local more = {...}
|
||||
assert(a == 1.3 and more[1] == 5.3 and
|
||||
more[lim-4] == lim+0.3 and not more[lim-3])
|
||||
end
|
||||
|
||||
function g(a,b,c)
|
||||
assert(a == 1.3 and b == 2.3 and c == 3.3)
|
||||
end
|
||||
|
||||
call(f, a)
|
||||
call(g, a)
|
||||
|
||||
a = {}
|
||||
i = 1
|
||||
while i <= lim do a[i] = i; i=i+1 end
|
||||
assert(call(math.max, a) == lim)
|
||||
|
||||
print("+")
|
||||
|
||||
|
||||
-- new-style varargs
|
||||
|
||||
function oneless (a, ...) return ... end
|
||||
|
||||
function f (n, a, ...)
|
||||
local b
|
||||
assert(arg == nil)
|
||||
if n == 0 then
|
||||
local b, c, d = ...
|
||||
return a, b, c, d, oneless(oneless(oneless(...)))
|
||||
else
|
||||
n, b, a = n-1, ..., a
|
||||
assert(b == ...)
|
||||
return f(n, a, ...)
|
||||
end
|
||||
end
|
||||
|
||||
a,b,c,d,e = assert(f(10,5,4,3,2,1))
|
||||
assert(a==5 and b==4 and c==3 and d==2 and e==1)
|
||||
|
||||
a,b,c,d,e = f(4)
|
||||
assert(a==nil and b==nil and c==nil and d==nil and e==nil)
|
||||
|
||||
|
||||
-- varargs for main chunks
|
||||
f = loadstring[[ return {...} ]]
|
||||
x = f(2,3)
|
||||
assert(x[1] == 2 and x[2] == 3 and x[3] == nil)
|
||||
|
||||
|
||||
f = loadstring[[
|
||||
local x = {...}
|
||||
for i=1,select('#', ...) do assert(x[i] == select(i, ...)) end
|
||||
assert(x[select('#', ...)+1] == nil)
|
||||
return true
|
||||
]]
|
||||
|
||||
assert(f("a", "b", nil, {}, assert))
|
||||
assert(f())
|
||||
|
||||
a = {select(3, unpack{10,20,30,40})}
|
||||
assert(table.getn(a) == 2 and a[1] == 30 and a[2] == 40)
|
||||
a = {select(1)}
|
||||
assert(next(a) == nil)
|
||||
a = {select(-1, 3, 5, 7)}
|
||||
assert(a[1] == 7 and a[2] == nil)
|
||||
a = {select(-2, 3, 5, 7)}
|
||||
assert(a[1] == 5 and a[2] == 7 and a[3] == nil)
|
||||
pcall(select, 10000)
|
||||
pcall(select, -10000)
|
||||
|
||||
print('OK')
|
||||
|
||||
100
lib/lua/lua-tests/verybig.lua
Normal file
100
lib/lua/lua-tests/verybig.lua
Normal file
@@ -0,0 +1,100 @@
|
||||
if rawget(_G, "_soft") then return 10 end
|
||||
|
||||
print "testing large programs (>64k)"
|
||||
|
||||
-- template to create a very big test file
|
||||
prog = [[$
|
||||
|
||||
local a,b
|
||||
|
||||
b = {$1$
|
||||
b30009 = 65534,
|
||||
b30010 = 65535,
|
||||
b30011 = 65536,
|
||||
b30012 = 65537,
|
||||
b30013 = 16777214,
|
||||
b30014 = 16777215,
|
||||
b30015 = 16777216,
|
||||
b30016 = 16777217,
|
||||
b30017 = 4294967294,
|
||||
b30018 = 4294967295,
|
||||
b30019 = 4294967296,
|
||||
b30020 = 4294967297,
|
||||
b30021 = -65534,
|
||||
b30022 = -65535,
|
||||
b30023 = -65536,
|
||||
b30024 = -4294967297,
|
||||
b30025 = 15012.5,
|
||||
$2$
|
||||
};
|
||||
|
||||
assert(b.a50008 == 25004 and b["a11"] == 5.5)
|
||||
assert(b.a33007 == 16503.5 and b.a50009 == 25004.5)
|
||||
assert(b["b"..30024] == -4294967297)
|
||||
|
||||
function b:xxx (a,b) return a+b end
|
||||
assert(b:xxx(10, 12) == 22) -- pushself with non-constant index
|
||||
b.xxx = nil
|
||||
|
||||
s = 0; n=0
|
||||
for a,b in pairs(b) do s=s+b; n=n+1 end
|
||||
assert(s==13977183656.5 and n==70001)
|
||||
|
||||
require "checktable"
|
||||
stat(b)
|
||||
|
||||
a = nil; b = nil
|
||||
print'+'
|
||||
|
||||
function f(x) b=x end
|
||||
|
||||
a = f{$3$} or 10
|
||||
|
||||
assert(a==10)
|
||||
assert(b[1] == "a10" and b[2] == 5 and b[table.getn(b)-1] == "a50009")
|
||||
|
||||
|
||||
function xxxx (x) return b[x] end
|
||||
|
||||
assert(xxxx(3) == "a11")
|
||||
|
||||
a = nil; b=nil
|
||||
xxxx = nil
|
||||
|
||||
return 10
|
||||
|
||||
]]
|
||||
|
||||
-- functions to fill in the $n$
|
||||
F = {
|
||||
function () -- $1$
|
||||
for i=10,50009 do
|
||||
io.write('a', i, ' = ', 5+((i-10)/2), ',\n')
|
||||
end
|
||||
end,
|
||||
|
||||
function () -- $2$
|
||||
for i=30026,50009 do
|
||||
io.write('b', i, ' = ', 15013+((i-30026)/2), ',\n')
|
||||
end
|
||||
end,
|
||||
|
||||
function () -- $3$
|
||||
for i=10,50009 do
|
||||
io.write('"a', i, '", ', 5+((i-10)/2), ',\n')
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
file = os.tmpname()
|
||||
io.output(file)
|
||||
for s in string.gmatch(prog, "$([^$]+)") do
|
||||
local n = tonumber(s)
|
||||
if not n then io.write(s) else F[n]() end
|
||||
end
|
||||
io.close()
|
||||
result = dofile(file)
|
||||
assert(os.remove(file))
|
||||
print'OK'
|
||||
return result
|
||||
|
||||
@@ -211,6 +211,8 @@
|
||||
(lua-tok-type t)
|
||||
" "
|
||||
(lua-tok-value t))))))))
|
||||
(define parse-pow-chain
|
||||
(fn () (let ((lhs (parse-primary))) (parse-binop-rhs 10 lhs))))
|
||||
(set!
|
||||
parse-unary
|
||||
(fn
|
||||
@@ -228,7 +230,7 @@
|
||||
(begin
|
||||
(advance-tok!)
|
||||
(list (quote lua-unop) "not" (parse-unary))))
|
||||
(else (parse-primary)))))
|
||||
(else (parse-pow-chain)))))
|
||||
(define
|
||||
parse-binop-rhs
|
||||
(fn
|
||||
@@ -279,7 +281,7 @@
|
||||
((at-op? "(")
|
||||
(begin
|
||||
(advance-tok!)
|
||||
(set! base (parse-expr))
|
||||
(set! base (list (quote lua-paren) (parse-expr)))
|
||||
(consume! "op" ")")))
|
||||
(else (error "lua-parse: expected prefixexp")))
|
||||
(define
|
||||
@@ -534,50 +536,73 @@
|
||||
(let
|
||||
((body (parse-block)))
|
||||
(begin (consume! "keyword" "end") (list (quote lua-do) body))))))
|
||||
(define
|
||||
parse-for
|
||||
(fn
|
||||
()
|
||||
(define parse-for-num-rest
|
||||
(fn (name)
|
||||
(begin
|
||||
(consume! "op" "=")
|
||||
(let ((start (parse-expr)))
|
||||
(begin
|
||||
(consume! "op" ",")
|
||||
(let ((stop (parse-expr)) (step nil))
|
||||
(begin
|
||||
(when (at-op? ",")
|
||||
(begin
|
||||
(advance-tok!)
|
||||
(set! step (parse-expr))))
|
||||
(consume! "keyword" "do")
|
||||
(let ((body (parse-block)))
|
||||
(begin
|
||||
(consume! "keyword" "end")
|
||||
(list (quote lua-for-num) name start stop step body))))))))))
|
||||
(define parse-for-in-names
|
||||
(fn (names)
|
||||
(cond
|
||||
((at-op? ",")
|
||||
(begin
|
||||
(advance-tok!)
|
||||
(let ((nt (peek-tok)))
|
||||
(begin
|
||||
(when (not (= (lua-tok-type nt) "ident"))
|
||||
(error "lua-parse: expected name after , in for"))
|
||||
(let ((nm (lua-tok-value nt)))
|
||||
(begin
|
||||
(advance-tok!)
|
||||
(parse-for-in-names (append names (list nm)))))))))
|
||||
(else names))))
|
||||
(define parse-for-in-exps
|
||||
(fn (exps)
|
||||
(cond
|
||||
((at-op? ",")
|
||||
(begin
|
||||
(advance-tok!)
|
||||
(parse-for-in-exps (append exps (list (parse-expr))))))
|
||||
(else exps))))
|
||||
(define parse-for-in-rest
|
||||
(fn (names)
|
||||
(begin
|
||||
(consume! "keyword" "in")
|
||||
(let ((exps (parse-for-in-exps (list (parse-expr)))))
|
||||
(begin
|
||||
(consume! "keyword" "do")
|
||||
(let ((body (parse-block)))
|
||||
(begin
|
||||
(consume! "keyword" "end")
|
||||
(list (quote lua-for-in) names exps body))))))))
|
||||
(define parse-for
|
||||
(fn ()
|
||||
(begin
|
||||
(consume! "keyword" "for")
|
||||
(let
|
||||
((t (peek-tok)))
|
||||
(let ((t (peek-tok)))
|
||||
(begin
|
||||
(when
|
||||
(not (= (lua-tok-type t) "ident"))
|
||||
(when (not (= (lua-tok-type t) "ident"))
|
||||
(error "lua-parse: expected name in for"))
|
||||
(let
|
||||
((name (lua-tok-value t)))
|
||||
(let ((name (lua-tok-value t)))
|
||||
(begin
|
||||
(advance-tok!)
|
||||
(when
|
||||
(not (at-op? "="))
|
||||
(error "lua-parse: only numeric for supported"))
|
||||
(consume! "op" "=")
|
||||
(let
|
||||
((start (parse-expr)))
|
||||
(begin
|
||||
(consume! "op" ",")
|
||||
(let
|
||||
((stop (parse-expr)) (step nil))
|
||||
(begin
|
||||
(when
|
||||
(at-op? ",")
|
||||
(begin
|
||||
(advance-tok!)
|
||||
(set! step (parse-expr))))
|
||||
(consume! "keyword" "do")
|
||||
(let
|
||||
((body (parse-block)))
|
||||
(begin
|
||||
(consume! "keyword" "end")
|
||||
(list
|
||||
(quote lua-for-num)
|
||||
name
|
||||
start
|
||||
stop
|
||||
step
|
||||
body))))))))))))))
|
||||
(cond
|
||||
((at-op? "=") (parse-for-num-rest name))
|
||||
(else
|
||||
(parse-for-in-rest (parse-for-in-names (list name))))))))))))
|
||||
(define
|
||||
parse-funcname
|
||||
(fn
|
||||
@@ -710,6 +735,7 @@
|
||||
(check-tok? "eof" nil)
|
||||
(at-op? ";")))
|
||||
(set! exps (parse-explist)))
|
||||
(when (at-op? ";") (advance-tok!))
|
||||
(list (quote lua-return) exps))))))
|
||||
(define
|
||||
parse-assign-or-call
|
||||
|
||||
2123
lib/lua/runtime.sx
2123
lib/lua/runtime.sx
File diff suppressed because it is too large
Load Diff
179
lib/lua/scoreboard.json
Normal file
179
lib/lua/scoreboard.json
Normal file
@@ -0,0 +1,179 @@
|
||||
{
|
||||
"totals": {
|
||||
"pass": 1,
|
||||
"fail": 9,
|
||||
"timeout": 6,
|
||||
"skip": 8,
|
||||
"total": 24,
|
||||
"runnable": 16,
|
||||
"pass_rate": 6.2
|
||||
},
|
||||
"top_failure_modes": [
|
||||
[
|
||||
"timeout",
|
||||
6
|
||||
],
|
||||
[
|
||||
"other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\",
|
||||
5
|
||||
],
|
||||
[
|
||||
"other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"lua: attempt to call non-functio",
|
||||
2
|
||||
],
|
||||
[
|
||||
"other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"lua: module 'C' not found\\\\\\\"\\",
|
||||
1
|
||||
],
|
||||
[
|
||||
"undefined symbol: fat\\",
|
||||
1
|
||||
]
|
||||
],
|
||||
"results": [
|
||||
{
|
||||
"name": "all.lua",
|
||||
"status": "skip",
|
||||
"reason": "driver uses dofile to chain other tests",
|
||||
"ms": 0
|
||||
},
|
||||
{
|
||||
"name": "api.lua",
|
||||
"status": "skip",
|
||||
"reason": "requires testC (C debug library)",
|
||||
"ms": 0
|
||||
},
|
||||
{
|
||||
"name": "attrib.lua",
|
||||
"status": "fail",
|
||||
"reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"lua: module 'C' not found\\\\\\\"\\",
|
||||
"ms": 6174
|
||||
},
|
||||
{
|
||||
"name": "big.lua",
|
||||
"status": "timeout",
|
||||
"reason": "per-test timeout",
|
||||
"ms": 8007
|
||||
},
|
||||
{
|
||||
"name": "calls.lua",
|
||||
"status": "fail",
|
||||
"reason": "undefined symbol: fat\\",
|
||||
"ms": 5270
|
||||
},
|
||||
{
|
||||
"name": "checktable.lua",
|
||||
"status": "skip",
|
||||
"reason": "internal debug helpers",
|
||||
"ms": 0
|
||||
},
|
||||
{
|
||||
"name": "closure.lua",
|
||||
"status": "timeout",
|
||||
"reason": "per-test timeout",
|
||||
"ms": 8006
|
||||
},
|
||||
{
|
||||
"name": "code.lua",
|
||||
"status": "skip",
|
||||
"reason": "bytecode inspection via debug library",
|
||||
"ms": 0
|
||||
},
|
||||
{
|
||||
"name": "constructs.lua",
|
||||
"status": "timeout",
|
||||
"reason": "per-test timeout",
|
||||
"ms": 8007
|
||||
},
|
||||
{
|
||||
"name": "db.lua",
|
||||
"status": "skip",
|
||||
"reason": "debug library",
|
||||
"ms": 0
|
||||
},
|
||||
{
|
||||
"name": "errors.lua",
|
||||
"status": "fail",
|
||||
"reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\",
|
||||
"ms": 4731
|
||||
},
|
||||
{
|
||||
"name": "events.lua",
|
||||
"status": "timeout",
|
||||
"reason": "per-test timeout",
|
||||
"ms": 8007
|
||||
},
|
||||
{
|
||||
"name": "files.lua",
|
||||
"status": "skip",
|
||||
"reason": "io library",
|
||||
"ms": 0
|
||||
},
|
||||
{
|
||||
"name": "gc.lua",
|
||||
"status": "skip",
|
||||
"reason": "collectgarbage / finalisers",
|
||||
"ms": 0
|
||||
},
|
||||
{
|
||||
"name": "literals.lua",
|
||||
"status": "fail",
|
||||
"reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\",
|
||||
"ms": 1996
|
||||
},
|
||||
{
|
||||
"name": "locals.lua",
|
||||
"status": "fail",
|
||||
"reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"lua: attempt to call non-functio",
|
||||
"ms": 1785
|
||||
},
|
||||
{
|
||||
"name": "main.lua",
|
||||
"status": "skip",
|
||||
"reason": "standalone interpreter driver",
|
||||
"ms": 0
|
||||
},
|
||||
{
|
||||
"name": "math.lua",
|
||||
"status": "fail",
|
||||
"reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"lua: attempt to call non-functio",
|
||||
"ms": 4610
|
||||
},
|
||||
{
|
||||
"name": "nextvar.lua",
|
||||
"status": "timeout",
|
||||
"reason": "per-test timeout",
|
||||
"ms": 8007
|
||||
},
|
||||
{
|
||||
"name": "pm.lua",
|
||||
"status": "fail",
|
||||
"reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\",
|
||||
"ms": 7860
|
||||
},
|
||||
{
|
||||
"name": "sort.lua",
|
||||
"status": "timeout",
|
||||
"reason": "per-test timeout",
|
||||
"ms": 8003
|
||||
},
|
||||
{
|
||||
"name": "strings.lua",
|
||||
"status": "fail",
|
||||
"reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\",
|
||||
"ms": 4636
|
||||
},
|
||||
{
|
||||
"name": "vararg.lua",
|
||||
"status": "fail",
|
||||
"reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\",
|
||||
"ms": 2770
|
||||
},
|
||||
{
|
||||
"name": "verybig.lua",
|
||||
"status": "pass",
|
||||
"reason": "",
|
||||
"ms": 1319
|
||||
}
|
||||
]
|
||||
}
|
||||
41
lib/lua/scoreboard.md
Normal file
41
lib/lua/scoreboard.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Lua-on-SX conformance scoreboard
|
||||
|
||||
**Pass rate:** 1/16 runnable (6.2%)
|
||||
fail=9 timeout=6 skip=8 total=24
|
||||
|
||||
## Top failure modes
|
||||
|
||||
- **6x** timeout
|
||||
- **5x** other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\
|
||||
- **2x** other: Unhandled exception: \"Unhandled exception: \\\"lua: attempt to call non-functio
|
||||
- **1x** other: Unhandled exception: \"Unhandled exception: \\\"lua: module 'C' not found\\\"\
|
||||
- **1x** undefined symbol: fat\
|
||||
|
||||
## Per-test results
|
||||
|
||||
| Test | Status | Reason | ms |
|
||||
|---|---|---|---:|
|
||||
| all.lua | skip | driver uses dofile to chain other tests | 0 |
|
||||
| api.lua | skip | requires testC (C debug library) | 0 |
|
||||
| attrib.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"lua: module 'C' not found\\\"\ | 6174 |
|
||||
| big.lua | timeout | per-test timeout | 8007 |
|
||||
| calls.lua | fail | undefined symbol: fat\ | 5270 |
|
||||
| checktable.lua | skip | internal debug helpers | 0 |
|
||||
| closure.lua | timeout | per-test timeout | 8006 |
|
||||
| code.lua | skip | bytecode inspection via debug library | 0 |
|
||||
| constructs.lua | timeout | per-test timeout | 8007 |
|
||||
| db.lua | skip | debug library | 0 |
|
||||
| errors.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 4731 |
|
||||
| events.lua | timeout | per-test timeout | 8007 |
|
||||
| files.lua | skip | io library | 0 |
|
||||
| gc.lua | skip | collectgarbage / finalisers | 0 |
|
||||
| literals.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 1996 |
|
||||
| locals.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"lua: attempt to call non-functio | 1785 |
|
||||
| main.lua | skip | standalone interpreter driver | 0 |
|
||||
| math.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"lua: attempt to call non-functio | 4610 |
|
||||
| nextvar.lua | timeout | per-test timeout | 8007 |
|
||||
| pm.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 7860 |
|
||||
| sort.lua | timeout | per-test timeout | 8003 |
|
||||
| strings.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 4636 |
|
||||
| vararg.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 2770 |
|
||||
| verybig.lua | pass | - | 1319 |
|
||||
985
lib/lua/test.sh
985
lib/lua/test.sh
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user