Compare commits
162 Commits
bugs/resum
...
loops/mini
| Author | SHA1 | Date | |
|---|---|---|---|
| 96f5809a29 | |||
| 28bd8bb98c | |||
| 1d7400a54a | |||
| 0cb0c1b782 | |||
| 2921aa30b4 | |||
| d1817e026d | |||
| 5c51f5ef8f | |||
| 80ab039ada | |||
| adc8467c78 | |||
| 8644668fc9 | |||
| a6e758664b | |||
| 5d3c248fdd | |||
| f88388b2f9 | |||
| c01ddc2b23 | |||
| 27637aa0f9 | |||
| f2817bb6be | |||
| c71da0e1cf | |||
| 25f709549e | |||
| f8b9bde1a5 | |||
| 2a36e692f4 | |||
| d1e00e2e9e | |||
| de6fd1b183 | |||
| f4a902a6df | |||
| d891831f08 | |||
| 091030f13e | |||
| f5ab66e1a3 | |||
| c51d52dae2 | |||
| 3842496f3b | |||
| 08f4a7babd | |||
| 221c7fef35 | |||
| 363ebc8f04 | |||
| 7ff72cefb2 | |||
| 064ab2900b | |||
| 4f5f8015fb | |||
| c4b6f1fa0f | |||
| 6454603568 | |||
| 4df277803d | |||
| 58d78de32a | |||
| 6bc3c14dac | |||
| eb69039935 | |||
| c04ddd105b | |||
| 136cacbd3f | |||
| 6fc155ddd8 | |||
| d992788a03 | |||
| 4d861575df | |||
| e202c81a0d | |||
| fc14a8063b | |||
| 6ee02db2ab | |||
| 7b6cb64548 | |||
| c2b238635f | |||
| 8c48a0be63 | |||
| 54a58c704e | |||
| ada405b37b | |||
| 99066430fd | |||
| 48835f2d4f | |||
| 16fe22669a | |||
| 2d51a8c4ea | |||
| b4c1253891 | |||
| e7dca2675c | |||
| f00054309d | |||
| cfb43a3cdf | |||
| 186171fec3 | |||
| 9795532f7d | |||
| b89b0def93 | |||
| 428ca79f61 | |||
| bf9fe8b365 | |||
| 2ae848dfe7 | |||
| 33693fc957 | |||
| 3d2a5b1814 | |||
| bc9261e90a | |||
| fd73f3c51b | |||
| b8a0c504bc | |||
| a038d41815 | |||
| d61b355413 | |||
| 43d58e6ca9 | |||
| 240ed90b20 | |||
| f4ab7f2534 | |||
| cae87c1e2c | |||
| 52070e07fc | |||
| 2de6727e83 | |||
| c754a8ee05 | |||
| f43ad04f91 | |||
| 0ba60d6a25 | |||
| f13e03e625 | |||
| 3dae27737c | |||
| f962560652 | |||
| 863e9d93a4 | |||
| 2defa5e739 | |||
| 64157e9e81 | |||
| e0d447e2ce | |||
| 63ad4563cb | |||
| 6915730029 | |||
| a774cd26c1 | |||
| 69a0886214 | |||
| 5f27125f01 | |||
| da27958d67 | |||
| d27622d45e | |||
| b6cf20dac7 | |||
| c8b232d40e | |||
| 251e6e1bab | |||
| 0dd2fa3058 | |||
| 67ff2a3ae8 | |||
| aaabe370d6 | |||
| 637ba4102f | |||
| 7cf8b74d1d | |||
| d473f39b04 | |||
| d5e66474fe | |||
| 64d36fa66e | |||
| dec1cf3fbe | |||
| 52df09655d | |||
| 5a28cf5dd3 | |||
| f480eb943c | |||
| edc7e865b4 | |||
| ca151d7ed5 | |||
| 322eb1d034 | |||
| be820d0337 | |||
| d755caeb9a | |||
| 3e77dd4ded | |||
| 0f13052900 | |||
| e37167a58e | |||
| 49eb22243a | |||
| 20a61de693 | |||
| ed0853f4a0 | |||
| ec26b61cbe | |||
| bee4e0846c | |||
| f591ee17c3 | |||
| 1900726fc9 | |||
| 16167c5d9b | |||
| 84d210b6b3 | |||
| 3628a504db | |||
| 4c71c5a75e | |||
| 9eecbde61e | |||
| 4dbd3a0b34 | |||
| 3d2bdc52b5 | |||
| d570da1dea | |||
| d67e04a9ad | |||
| 4332b4032f | |||
| 3489c9f131 | |||
| c56f400403 | |||
| c63c0d26e8 | |||
| c5ceb9c718 | |||
| e42aec8957 | |||
| ce72070d2a | |||
| 32efdfe4aa | |||
| e06e3ad014 | |||
| ad914b413c | |||
| 7dfa092ed2 | |||
| 03e9df3ecf | |||
| e11fbd6140 | |||
| 248dca5b32 | |||
| 71ad7d2d24 | |||
| c03ba9eccb | |||
| 3c83985841 | |||
| 6a6a94e203 | |||
| be26f77410 | |||
| 2314735431 | |||
| d8cf74fd28 | |||
| a14fe05632 | |||
| 4f4b735958 | |||
| da8ba104a6 | |||
| dbba2fe418 | |||
| c73b696494 |
@@ -355,7 +355,9 @@ let vm_create_closure vm_val frame_val code_val =
|
||||
let f = unwrap_frame frame_val in
|
||||
let uv_count = match code_val with
|
||||
| Dict d -> (match Hashtbl.find_opt d "upvalue-count" with
|
||||
| Some (Number n) -> int_of_float n | _ -> 0)
|
||||
| Some (Integer n) -> n
|
||||
| Some (Number n) -> int_of_float n
|
||||
| _ -> 0)
|
||||
| _ -> 0
|
||||
in
|
||||
let upvalues = Array.init uv_count (fun _ ->
|
||||
|
||||
@@ -715,8 +715,10 @@ let () =
|
||||
| List (Symbol "code" :: rest) ->
|
||||
let d = Hashtbl.create 8 in
|
||||
let rec parse_kv = function
|
||||
| Keyword "arity" :: Number n :: rest -> Hashtbl.replace d "arity" (Number n); parse_kv rest
|
||||
| Keyword "upvalue-count" :: Number n :: rest -> Hashtbl.replace d "upvalue-count" (Number n); parse_kv rest
|
||||
| Keyword "arity" :: (Number _ as n) :: rest -> Hashtbl.replace d "arity" n; parse_kv rest
|
||||
| Keyword "arity" :: (Integer _ as n) :: rest -> Hashtbl.replace d "arity" n; parse_kv rest
|
||||
| Keyword "upvalue-count" :: (Number _ as n) :: rest -> Hashtbl.replace d "upvalue-count" n; parse_kv rest
|
||||
| Keyword "upvalue-count" :: (Integer _ as n) :: rest -> Hashtbl.replace d "upvalue-count" n; parse_kv rest
|
||||
| Keyword "bytecode" :: List nums :: rest ->
|
||||
Hashtbl.replace d "bytecode" (List nums); parse_kv rest
|
||||
| Keyword "constants" :: List consts :: rest ->
|
||||
|
||||
@@ -3124,6 +3124,442 @@ let () =
|
||||
| [String pat] -> List (List.map (fun s -> String s) (glob_paths pat))
|
||||
| _ -> raise (Eval_error "file-glob: (pattern)"));
|
||||
|
||||
(* === File metadata + ops (Phase 5d) === *)
|
||||
let stat_or = function
|
||||
| String path -> (try Some (Unix.stat path) with _ -> None)
|
||||
| _ -> raise (Eval_error "file: path must be a string")
|
||||
in
|
||||
register "file-size" (fun args ->
|
||||
match args with
|
||||
| [v] -> (match stat_or v with Some s -> Integer s.Unix.st_size | None -> Integer 0)
|
||||
| _ -> raise (Eval_error "file-size: (path)"));
|
||||
register "file-mtime" (fun args ->
|
||||
match args with
|
||||
| [v] -> (match stat_or v with Some s -> Integer (int_of_float s.Unix.st_mtime) | None -> Integer 0)
|
||||
| _ -> raise (Eval_error "file-mtime: (path)"));
|
||||
register "file-isfile?" (fun args ->
|
||||
match args with
|
||||
| [v] -> (match stat_or v with Some s -> Bool (s.Unix.st_kind = Unix.S_REG) | None -> Bool false)
|
||||
| _ -> raise (Eval_error "file-isfile?: (path)"));
|
||||
register "file-isdir?" (fun args ->
|
||||
match args with
|
||||
| [v] -> (match stat_or v with Some s -> Bool (s.Unix.st_kind = Unix.S_DIR) | None -> Bool false)
|
||||
| _ -> raise (Eval_error "file-isdir?: (path)"));
|
||||
register "file-readable?" (fun args ->
|
||||
match args with
|
||||
| [String path] ->
|
||||
Bool (try Unix.access path [Unix.R_OK]; true with _ -> false)
|
||||
| _ -> raise (Eval_error "file-readable?: (path)"));
|
||||
register "file-writable?" (fun args ->
|
||||
match args with
|
||||
| [String path] ->
|
||||
Bool (try Unix.access path [Unix.W_OK]; true with _ -> false)
|
||||
| _ -> raise (Eval_error "file-writable?: (path)"));
|
||||
register "file-stat" (fun args ->
|
||||
match args with
|
||||
| [v] ->
|
||||
(match stat_or v with
|
||||
| None -> Nil
|
||||
| Some s ->
|
||||
let d = Hashtbl.create 6 in
|
||||
Hashtbl.replace d "size" (Integer s.Unix.st_size);
|
||||
Hashtbl.replace d "mtime" (Integer (int_of_float s.Unix.st_mtime));
|
||||
Hashtbl.replace d "atime" (Integer (int_of_float s.Unix.st_atime));
|
||||
Hashtbl.replace d "ctime" (Integer (int_of_float s.Unix.st_ctime));
|
||||
Hashtbl.replace d "mode" (Integer s.Unix.st_perm);
|
||||
Hashtbl.replace d "type" (String (match s.Unix.st_kind with
|
||||
| Unix.S_REG -> "file" | Unix.S_DIR -> "directory"
|
||||
| Unix.S_LNK -> "link" | Unix.S_CHR -> "characterSpecial"
|
||||
| Unix.S_BLK -> "blockSpecial" | Unix.S_FIFO -> "fifo"
|
||||
| Unix.S_SOCK -> "socket"));
|
||||
Dict d)
|
||||
| _ -> raise (Eval_error "file-stat: (path)"));
|
||||
register "file-delete" (fun args ->
|
||||
match args with
|
||||
| [String path] ->
|
||||
(try
|
||||
if Sys.is_directory path then Unix.rmdir path
|
||||
else Unix.unlink path
|
||||
with
|
||||
| Unix.Unix_error (Unix.ENOENT, _, _) -> () (* tolerate missing *)
|
||||
| Unix.Unix_error (e, _, _) -> raise (Eval_error ("file-delete: " ^ Unix.error_message e)));
|
||||
Nil
|
||||
| _ -> raise (Eval_error "file-delete: (path)"));
|
||||
register "file-mkdir" (fun args ->
|
||||
match args with
|
||||
| [String path] ->
|
||||
let rec mk p =
|
||||
if p = "" || p = "." || p = "/" then ()
|
||||
else if Sys.file_exists p then ()
|
||||
else begin
|
||||
mk (Filename.dirname p);
|
||||
(try Unix.mkdir p 0o755
|
||||
with Unix.Unix_error (Unix.EEXIST, _, _) -> ())
|
||||
end
|
||||
in
|
||||
(try mk path
|
||||
with Unix.Unix_error (e, _, _) -> raise (Eval_error ("file-mkdir: " ^ Unix.error_message e)));
|
||||
Nil
|
||||
| _ -> raise (Eval_error "file-mkdir: (path)"));
|
||||
register "file-copy" (fun args ->
|
||||
match args with
|
||||
| [String src; String dst] ->
|
||||
(try
|
||||
let ic = open_in_bin src in
|
||||
let oc = open_out_bin dst in
|
||||
let buf = Bytes.create 8192 in
|
||||
let rec loop () =
|
||||
let n = input ic buf 0 (Bytes.length buf) in
|
||||
if n > 0 then (output oc buf 0 n; loop ())
|
||||
in
|
||||
loop ();
|
||||
close_in ic;
|
||||
close_out oc;
|
||||
Nil
|
||||
with
|
||||
| Sys_error msg -> raise (Eval_error ("file-copy: " ^ msg)))
|
||||
| _ -> raise (Eval_error "file-copy: (src dst)"));
|
||||
register "file-rename" (fun args ->
|
||||
match args with
|
||||
| [String src; String dst] ->
|
||||
(try Sys.rename src dst with Sys_error msg -> raise (Eval_error ("file-rename: " ^ msg)));
|
||||
Nil
|
||||
| _ -> raise (Eval_error "file-rename: (src dst)"));
|
||||
|
||||
(* === Channels (random-access + blocking control) === *)
|
||||
let channel_table : (string, Unix.file_descr * string * bool ref * bool ref) Hashtbl.t = Hashtbl.create 16 in
|
||||
let channel_next_id = ref 0 in
|
||||
let parse_open_mode mode =
|
||||
match mode with
|
||||
| "r" -> [Unix.O_RDONLY]
|
||||
| "w" -> [Unix.O_WRONLY; Unix.O_CREAT; Unix.O_TRUNC]
|
||||
| "a" -> [Unix.O_WRONLY; Unix.O_CREAT; Unix.O_APPEND]
|
||||
| "r+" -> [Unix.O_RDWR]
|
||||
| "w+" -> [Unix.O_RDWR; Unix.O_CREAT; Unix.O_TRUNC]
|
||||
| "a+" -> [Unix.O_RDWR; Unix.O_CREAT; Unix.O_APPEND]
|
||||
| _ -> raise (Eval_error ("channel-open: invalid mode " ^ mode))
|
||||
in
|
||||
let chan_get name =
|
||||
match Hashtbl.find_opt channel_table name with
|
||||
| Some c -> c
|
||||
| None -> raise (Eval_error ("channel: no such channel " ^ name))
|
||||
in
|
||||
register "channel-open" (fun args ->
|
||||
match args with
|
||||
| [String path; String mode] ->
|
||||
(try
|
||||
let fd = Unix.openfile path (parse_open_mode mode) 0o644 in
|
||||
let id = !channel_next_id in
|
||||
incr channel_next_id;
|
||||
let name = Printf.sprintf "file%d" id in
|
||||
Hashtbl.replace channel_table name (fd, mode, ref false, ref true);
|
||||
String name
|
||||
with Unix.Unix_error (e, _, _) -> raise (Eval_error ("channel-open: " ^ Unix.error_message e)))
|
||||
| _ -> raise (Eval_error "channel-open: (path mode)"));
|
||||
|
||||
register "channel-close" (fun args ->
|
||||
match args with
|
||||
| [String name] ->
|
||||
let (fd, _, _, _) = chan_get name in
|
||||
(try Unix.close fd with _ -> ());
|
||||
Hashtbl.remove channel_table name;
|
||||
Nil
|
||||
| _ -> raise (Eval_error "channel-close: (channel)"));
|
||||
|
||||
register "channel-read" (fun args ->
|
||||
let (name, max_n) = match args with
|
||||
| [String n] -> (n, -1)
|
||||
| [String n; Integer m] -> (n, m)
|
||||
| [String n; Number m] -> (n, int_of_float m)
|
||||
| _ -> raise (Eval_error "channel-read: (channel ?n?)")
|
||||
in
|
||||
let (fd, _, eof, _) = chan_get name in
|
||||
let chunk = 8192 in
|
||||
let buf = Bytes.create chunk in
|
||||
let buffer = Buffer.create chunk in
|
||||
let total = ref 0 in
|
||||
let stop = ref false in
|
||||
while not !stop do
|
||||
let want = if max_n < 0 then chunk else min chunk (max_n - !total) in
|
||||
if want <= 0 then stop := true
|
||||
else begin
|
||||
try
|
||||
let r = Unix.read fd buf 0 want in
|
||||
if r = 0 then begin eof := true; stop := true end
|
||||
else begin
|
||||
Buffer.add_subbytes buffer buf 0 r;
|
||||
total := !total + r
|
||||
end
|
||||
with
|
||||
| Unix.Unix_error (Unix.EAGAIN, _, _)
|
||||
| Unix.Unix_error (Unix.EWOULDBLOCK, _, _) -> stop := true
|
||||
end
|
||||
done;
|
||||
String (Buffer.contents buffer));
|
||||
|
||||
register "channel-read-line" (fun args ->
|
||||
match args with
|
||||
| [String name] ->
|
||||
let (fd, _, eof, _) = chan_get name in
|
||||
let buf = Buffer.create 80 in
|
||||
let one = Bytes.create 1 in
|
||||
let got_data = ref false in
|
||||
let stop = ref false in
|
||||
while not !stop do
|
||||
try
|
||||
let r = Unix.read fd one 0 1 in
|
||||
if r = 0 then begin eof := true; stop := true end
|
||||
else begin
|
||||
got_data := true;
|
||||
let c = Bytes.get one 0 in
|
||||
if c = '\n' then stop := true
|
||||
else Buffer.add_char buf c
|
||||
end
|
||||
with
|
||||
| Unix.Unix_error (Unix.EAGAIN, _, _)
|
||||
| Unix.Unix_error (Unix.EWOULDBLOCK, _, _) -> stop := true
|
||||
done;
|
||||
if !got_data then String (Buffer.contents buf) else Nil
|
||||
| _ -> raise (Eval_error "channel-read-line: (channel)"));
|
||||
|
||||
register "channel-write" (fun args ->
|
||||
match args with
|
||||
| [String name; String s] ->
|
||||
let (fd, _, _, _) = chan_get name in
|
||||
let b = Bytes.of_string s in
|
||||
let n = Bytes.length b in
|
||||
let written = ref 0 in
|
||||
while !written < n do
|
||||
(try
|
||||
let w = Unix.write fd b !written (n - !written) in
|
||||
written := !written + w
|
||||
with
|
||||
| Unix.Unix_error (Unix.EAGAIN, _, _)
|
||||
| Unix.Unix_error (Unix.EWOULDBLOCK, _, _) ->
|
||||
(* short write — let caller retry *)
|
||||
written := n)
|
||||
done;
|
||||
Nil
|
||||
| _ -> raise (Eval_error "channel-write: (channel string)"));
|
||||
|
||||
register "channel-flush" (fun args ->
|
||||
match args with
|
||||
| [String name] -> let _ = chan_get name in Nil (* no userspace buffer *)
|
||||
| _ -> raise (Eval_error "channel-flush: (channel)"));
|
||||
|
||||
register "channel-seek" (fun args ->
|
||||
let (name, offset, whence) = match args with
|
||||
| [String n; Integer o] -> (n, o, "start")
|
||||
| [String n; Number o] -> (n, int_of_float o, "start")
|
||||
| [String n; Integer o; String w] -> (n, o, w)
|
||||
| [String n; Number o; String w] -> (n, int_of_float o, w)
|
||||
| _ -> raise (Eval_error "channel-seek: (channel offset ?whence?)")
|
||||
in
|
||||
let (fd, _, eof, _) = chan_get name in
|
||||
let cmd = match whence with
|
||||
| "start" -> Unix.SEEK_SET
|
||||
| "current" -> Unix.SEEK_CUR
|
||||
| "end" -> Unix.SEEK_END
|
||||
| _ -> raise (Eval_error ("channel-seek: invalid whence " ^ whence))
|
||||
in
|
||||
let _ = Unix.lseek fd offset cmd in
|
||||
eof := false;
|
||||
Nil);
|
||||
|
||||
register "channel-tell" (fun args ->
|
||||
match args with
|
||||
| [String name] ->
|
||||
let (fd, _, _, _) = chan_get name in
|
||||
Integer (Unix.lseek fd 0 Unix.SEEK_CUR)
|
||||
| _ -> raise (Eval_error "channel-tell: (channel)"));
|
||||
|
||||
register "channel-eof?" (fun args ->
|
||||
match args with
|
||||
| [String name] ->
|
||||
let (_, _, eof, _) = chan_get name in
|
||||
Bool !eof
|
||||
| _ -> raise (Eval_error "channel-eof?: (channel)"));
|
||||
|
||||
register "channel-blocking?" (fun args ->
|
||||
match args with
|
||||
| [String name] ->
|
||||
let (_, _, _, blocking) = chan_get name in
|
||||
Bool !blocking
|
||||
| _ -> raise (Eval_error "channel-blocking?: (channel)"));
|
||||
|
||||
register "channel-set-blocking!" (fun args ->
|
||||
match args with
|
||||
| [String name; Bool b] ->
|
||||
let (fd, _, _, blocking) = chan_get name in
|
||||
blocking := b;
|
||||
(try
|
||||
if b then Unix.clear_nonblock fd
|
||||
else Unix.set_nonblock fd
|
||||
with _ -> ());
|
||||
Nil
|
||||
| _ -> raise (Eval_error "channel-set-blocking!: (channel bool)"));
|
||||
|
||||
(* === Sockets === wrapping Unix.socket/connect/bind/listen/accept *)
|
||||
let resolve_inet_addr host =
|
||||
if host = "" || host = "0.0.0.0" then Unix.inet_addr_any
|
||||
else if host = "localhost" then Unix.inet_addr_loopback
|
||||
else
|
||||
try Unix.inet_addr_of_string host
|
||||
with _ ->
|
||||
try
|
||||
let entry = Unix.gethostbyname host in
|
||||
if Array.length entry.Unix.h_addr_list = 0 then
|
||||
raise (Eval_error ("socket: cannot resolve " ^ host))
|
||||
else entry.Unix.h_addr_list.(0)
|
||||
with Not_found -> raise (Eval_error ("socket: cannot resolve " ^ host))
|
||||
in
|
||||
let port_of v = match v with
|
||||
| Integer n -> n
|
||||
| Number n -> int_of_float n
|
||||
| _ -> raise (Eval_error "socket: port must be a number")
|
||||
in
|
||||
let alloc_chan_name () =
|
||||
let id = !channel_next_id in
|
||||
incr channel_next_id;
|
||||
Printf.sprintf "sock%d" id
|
||||
in
|
||||
|
||||
register "socket-connect" (fun args ->
|
||||
match args with
|
||||
| [String host; port_v] ->
|
||||
let port = port_of port_v in
|
||||
let addr = Unix.ADDR_INET (resolve_inet_addr host, port) in
|
||||
let sock = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
|
||||
(try Unix.connect sock addr
|
||||
with Unix.Unix_error (e, _, _) ->
|
||||
(try Unix.close sock with _ -> ());
|
||||
raise (Eval_error ("socket-connect: " ^ Unix.error_message e)));
|
||||
let name = alloc_chan_name () in
|
||||
Hashtbl.replace channel_table name (sock, "rw", ref false, ref true);
|
||||
String name
|
||||
| _ -> raise (Eval_error "socket-connect: (host port)"));
|
||||
|
||||
(* Non-blocking connect: returns channel immediately. Connection completes
|
||||
when the channel becomes writable; query channel-async-error? after to
|
||||
confirm success or get the error. *)
|
||||
register "socket-connect-async" (fun args ->
|
||||
match args with
|
||||
| [String host; port_v] ->
|
||||
let port = port_of port_v in
|
||||
let addr = Unix.ADDR_INET (resolve_inet_addr host, port) in
|
||||
let sock = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
|
||||
Unix.set_nonblock sock;
|
||||
(try Unix.connect sock addr
|
||||
with
|
||||
| Unix.Unix_error (Unix.EINPROGRESS, _, _)
|
||||
| Unix.Unix_error (Unix.EWOULDBLOCK, _, _) -> ()
|
||||
| Unix.Unix_error (e, _, _) ->
|
||||
(try Unix.close sock with _ -> ());
|
||||
raise (Eval_error ("socket-connect-async: " ^ Unix.error_message e)));
|
||||
let name = alloc_chan_name () in
|
||||
Hashtbl.replace channel_table name (sock, "rw", ref false, ref false);
|
||||
String name
|
||||
| _ -> raise (Eval_error "socket-connect-async: (host port)"));
|
||||
|
||||
(* After a non-blocking connect completes (channel writable), check whether
|
||||
the connect succeeded. Returns "" on success, error message on failure. *)
|
||||
register "channel-async-error" (fun args ->
|
||||
match args with
|
||||
| [String name] ->
|
||||
let (fd, _, _, _) = chan_get name in
|
||||
(try
|
||||
let err = Unix.getsockopt_error fd in
|
||||
match err with
|
||||
| None -> String ""
|
||||
| Some e -> String (Unix.error_message e)
|
||||
with
|
||||
| Unix.Unix_error (e, _, _) -> String (Unix.error_message e))
|
||||
| _ -> raise (Eval_error "channel-async-error: (channel)"));
|
||||
|
||||
register "socket-server" (fun args ->
|
||||
let (host, port) = match args with
|
||||
| [port_v] -> ("", port_of port_v)
|
||||
| [String h; port_v] -> (h, port_of port_v)
|
||||
| _ -> raise (Eval_error "socket-server: (port) or (host port)")
|
||||
in
|
||||
let addr = Unix.ADDR_INET (resolve_inet_addr host, port) in
|
||||
let sock = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
|
||||
Unix.setsockopt sock Unix.SO_REUSEADDR true;
|
||||
(try Unix.bind sock addr
|
||||
with Unix.Unix_error (e, _, _) ->
|
||||
(try Unix.close sock with _ -> ());
|
||||
raise (Eval_error ("socket-server: bind: " ^ Unix.error_message e)));
|
||||
Unix.listen sock 8;
|
||||
let name = alloc_chan_name () in
|
||||
Hashtbl.replace channel_table name (sock, "server", ref false, ref true);
|
||||
String name);
|
||||
|
||||
register "socket-accept" (fun args ->
|
||||
match args with
|
||||
| [String name] ->
|
||||
let (sock, _, _, _) = chan_get name in
|
||||
let (client_sock, client_addr) =
|
||||
try Unix.accept sock
|
||||
with Unix.Unix_error (e, _, _) ->
|
||||
raise (Eval_error ("socket-accept: " ^ Unix.error_message e))
|
||||
in
|
||||
let (host_str, port) = match client_addr with
|
||||
| Unix.ADDR_INET (addr, p) -> (Unix.string_of_inet_addr addr, p)
|
||||
| Unix.ADDR_UNIX path -> (path, 0)
|
||||
in
|
||||
let client_name = alloc_chan_name () in
|
||||
Hashtbl.replace channel_table client_name (client_sock, "rw", ref false, ref true);
|
||||
let d = Hashtbl.create 3 in
|
||||
Hashtbl.replace d "channel" (String client_name);
|
||||
Hashtbl.replace d "host" (String host_str);
|
||||
Hashtbl.replace d "port" (Integer port);
|
||||
Dict d
|
||||
| _ -> raise (Eval_error "socket-accept: (server-channel)"));
|
||||
|
||||
(* io-select-channels: (read-list write-list timeout-ms) → {:readable [...] :writable [...]}
|
||||
timeout-ms < 0 → block indefinitely; 0 → poll. Returns ready channel names. *)
|
||||
register "io-select-channels" (fun args ->
|
||||
let to_ms v = match v with
|
||||
| Integer n -> n
|
||||
| Number n -> int_of_float n
|
||||
| _ -> raise (Eval_error "io-select-channels: timeout must be a number")
|
||||
in
|
||||
let to_list v = match v with
|
||||
| List xs | ListRef { contents = xs } -> xs
|
||||
| Nil -> []
|
||||
| _ -> raise (Eval_error "io-select-channels: expected list")
|
||||
in
|
||||
let chan_name_of v = match v with
|
||||
| String s -> s
|
||||
| _ -> raise (Eval_error "io-select-channels: channel must be a string")
|
||||
in
|
||||
let (read_list, write_list, timeout_ms) = match args with
|
||||
| [r; w; t] -> (to_list r, to_list w, to_ms t)
|
||||
| _ -> raise (Eval_error "io-select-channels: (read-list write-list timeout-ms)")
|
||||
in
|
||||
let read_pairs = List.map (fun v ->
|
||||
let name = chan_name_of v in
|
||||
let (fd, _, _, _) = chan_get name in (name, fd)) read_list in
|
||||
let write_pairs = List.map (fun v ->
|
||||
let name = chan_name_of v in
|
||||
let (fd, _, _, _) = chan_get name in (name, fd)) write_list in
|
||||
let read_fds = List.map snd read_pairs in
|
||||
let write_fds = List.map snd write_pairs in
|
||||
let timeout = if timeout_ms < 0 then -1.0 else float_of_int timeout_ms /. 1000.0 in
|
||||
let (ready_r, ready_w, _) =
|
||||
try Unix.select read_fds write_fds [] timeout
|
||||
with Unix.Unix_error (Unix.EINTR, _, _) -> ([], [], [])
|
||||
in
|
||||
let names_of pairs ready =
|
||||
List.filter_map (fun (n, fd) ->
|
||||
if List.exists (fun rfd -> rfd = fd) ready then Some (String n) else None
|
||||
) pairs
|
||||
in
|
||||
let d = Hashtbl.create 2 in
|
||||
Hashtbl.replace d "readable" (List (names_of read_pairs ready_r));
|
||||
Hashtbl.replace d "writable" (List (names_of write_pairs ready_w));
|
||||
Dict d);
|
||||
|
||||
(* === Clock === *)
|
||||
register "clock-seconds" (fun args ->
|
||||
match args with
|
||||
@@ -3135,11 +3571,8 @@ let () =
|
||||
| [] -> Integer (int_of_float (Unix.gettimeofday () *. 1000.0))
|
||||
| _ -> raise (Eval_error "clock-milliseconds: no args"));
|
||||
|
||||
register "clock-format" (fun args ->
|
||||
match args with
|
||||
| [Integer t] | [Integer t; String _] ->
|
||||
let fmt = (match args with [_; String f] -> f | _ -> "%a %b %e %H:%M:%S %Z %Y") in
|
||||
let tm = Unix.gmtime (float_of_int t) in
|
||||
let format_tm tm tz_label =
|
||||
fun fmt ->
|
||||
let buf = Buffer.create 32 in
|
||||
let n = String.length fmt in
|
||||
let i = ref 0 in
|
||||
@@ -3147,14 +3580,19 @@ let () =
|
||||
if fmt.[!i] = '%' && !i + 1 < n then begin
|
||||
(match fmt.[!i + 1] with
|
||||
| 'Y' -> Buffer.add_string buf (Printf.sprintf "%04d" (1900 + tm.Unix.tm_year))
|
||||
| 'y' -> Buffer.add_string buf (Printf.sprintf "%02d" ((1900 + tm.Unix.tm_year) mod 100))
|
||||
| 'm' -> Buffer.add_string buf (Printf.sprintf "%02d" (tm.Unix.tm_mon + 1))
|
||||
| 'd' -> Buffer.add_string buf (Printf.sprintf "%02d" tm.Unix.tm_mday)
|
||||
| 'e' -> Buffer.add_string buf (Printf.sprintf "%2d" tm.Unix.tm_mday)
|
||||
| 'H' -> Buffer.add_string buf (Printf.sprintf "%02d" tm.Unix.tm_hour)
|
||||
| 'I' -> let h = tm.Unix.tm_hour mod 12 in
|
||||
Buffer.add_string buf (Printf.sprintf "%02d" (if h = 0 then 12 else h))
|
||||
| 'p' -> Buffer.add_string buf (if tm.Unix.tm_hour < 12 then "AM" else "PM")
|
||||
| 'M' -> Buffer.add_string buf (Printf.sprintf "%02d" tm.Unix.tm_min)
|
||||
| 'S' -> Buffer.add_string buf (Printf.sprintf "%02d" tm.Unix.tm_sec)
|
||||
| 'j' -> Buffer.add_string buf (Printf.sprintf "%03d" (tm.Unix.tm_yday + 1))
|
||||
| 'Z' -> Buffer.add_string buf "UTC"
|
||||
| 'w' -> Buffer.add_string buf (string_of_int tm.Unix.tm_wday)
|
||||
| 'Z' -> Buffer.add_string buf tz_label
|
||||
| 'a' -> let days = [|"Sun";"Mon";"Tue";"Wed";"Thu";"Fri";"Sat"|] in
|
||||
Buffer.add_string buf days.(tm.Unix.tm_wday)
|
||||
| 'A' -> let days = [|"Sunday";"Monday";"Tuesday";"Wednesday";"Thursday";"Friday";"Saturday"|] in
|
||||
@@ -3163,6 +3601,7 @@ let () =
|
||||
Buffer.add_string buf mons.(tm.Unix.tm_mon)
|
||||
| 'B' -> let mons = [|"January";"February";"March";"April";"May";"June";"July";"August";"September";"October";"November";"December"|] in
|
||||
Buffer.add_string buf mons.(tm.Unix.tm_mon)
|
||||
| '%' -> Buffer.add_char buf '%'
|
||||
| c -> Buffer.add_char buf '%'; Buffer.add_char buf c);
|
||||
i := !i + 2
|
||||
end else begin
|
||||
@@ -3170,8 +3609,100 @@ let () =
|
||||
incr i
|
||||
end
|
||||
done;
|
||||
String (Buffer.contents buf)
|
||||
| _ -> raise (Eval_error "clock-format: (seconds [format])"));
|
||||
Buffer.contents buf
|
||||
in
|
||||
register "clock-format" (fun args ->
|
||||
let (t, fmt, tz) = match args with
|
||||
| [Integer t] -> (t, "%a %b %e %H:%M:%S %Z %Y", "utc")
|
||||
| [Integer t; String f] -> (t, f, "utc")
|
||||
| [Integer t; String f; String z] -> (t, f, z)
|
||||
| _ -> raise (Eval_error "clock-format: (seconds [format [tz]])")
|
||||
in
|
||||
let tm =
|
||||
if tz = "local" then Unix.localtime (float_of_int t)
|
||||
else Unix.gmtime (float_of_int t)
|
||||
in
|
||||
let label = if tz = "local" then "" else "UTC" in
|
||||
String (format_tm tm label fmt));
|
||||
|
||||
(* clock-scan: parse a date string with format, return seconds.
|
||||
Supports the same format specifiers as clock-format (fixed-width ones).
|
||||
tz: "utc" (default) or "local". *)
|
||||
let timegm (tm : Unix.tm) =
|
||||
let is_leap y = y mod 4 = 0 && (y mod 100 <> 0 || y mod 400 = 0) in
|
||||
let days_in_month = [|31;28;31;30;31;30;31;31;30;31;30;31|] in
|
||||
let year = tm.Unix.tm_year + 1900 in
|
||||
let mon = tm.Unix.tm_mon in
|
||||
let mday = tm.Unix.tm_mday in
|
||||
let total_days = ref 0 in
|
||||
if year >= 1970 then begin
|
||||
for y = 1970 to year - 1 do
|
||||
total_days := !total_days + (if is_leap y then 366 else 365)
|
||||
done
|
||||
end else begin
|
||||
for y = year to 1969 do
|
||||
total_days := !total_days - (if is_leap y then 366 else 365)
|
||||
done
|
||||
end;
|
||||
for m = 0 to mon - 1 do
|
||||
total_days := !total_days + days_in_month.(m);
|
||||
if m = 1 && is_leap year then incr total_days
|
||||
done;
|
||||
total_days := !total_days + mday - 1;
|
||||
!total_days * 86400
|
||||
+ tm.Unix.tm_hour * 3600
|
||||
+ tm.Unix.tm_min * 60
|
||||
+ tm.Unix.tm_sec
|
||||
in
|
||||
register "clock-scan" (fun args ->
|
||||
let (str, fmt, tz) = match args with
|
||||
| [String s; String f] -> (s, f, "utc")
|
||||
| [String s; String f; String z] -> (s, f, z)
|
||||
| _ -> raise (Eval_error "clock-scan: (str fmt [tz])")
|
||||
in
|
||||
let n = String.length fmt and sn = String.length str in
|
||||
let tm = ref { Unix.tm_year = 70; tm_mon = 0; tm_mday = 1;
|
||||
tm_hour = 0; tm_min = 0; tm_sec = 0;
|
||||
tm_wday = 0; tm_yday = 0; tm_isdst = false } in
|
||||
let i = ref 0 and j = ref 0 in
|
||||
let read_n_digits k =
|
||||
let s = ref "" in
|
||||
let cnt = ref 0 in
|
||||
while !cnt < k && !j < sn && str.[!j] >= '0' && str.[!j] <= '9' do
|
||||
s := !s ^ String.make 1 str.[!j];
|
||||
incr j; incr cnt
|
||||
done;
|
||||
if !s = "" then 0 else int_of_string !s
|
||||
in
|
||||
let skip_ws () =
|
||||
while !j < sn && (str.[!j] = ' ' || str.[!j] = '\t') do incr j done
|
||||
in
|
||||
while !i < n do
|
||||
if fmt.[!i] = '%' && !i + 1 < n then begin
|
||||
(match fmt.[!i + 1] with
|
||||
| 'Y' -> tm := { !tm with tm_year = read_n_digits 4 - 1900 }
|
||||
| 'y' -> let y = read_n_digits 2 in
|
||||
tm := { !tm with tm_year = (if y < 70 then 100 + y else y) }
|
||||
| 'm' -> tm := { !tm with tm_mon = read_n_digits 2 - 1 }
|
||||
| 'd' | 'e' -> skip_ws (); tm := { !tm with tm_mday = read_n_digits 2 }
|
||||
| 'H' | 'I' -> tm := { !tm with tm_hour = read_n_digits 2 }
|
||||
| 'M' -> tm := { !tm with tm_min = read_n_digits 2 }
|
||||
| 'S' -> tm := { !tm with tm_sec = read_n_digits 2 }
|
||||
| '%' -> if !j < sn && str.[!j] = '%' then incr j
|
||||
| _ -> () (* unsupported specifier — skip *)
|
||||
);
|
||||
i := !i + 2
|
||||
end else begin
|
||||
if fmt.[!i] = ' ' then skip_ws ()
|
||||
else if !j < sn && str.[!j] = fmt.[!i] then incr j;
|
||||
incr i
|
||||
end
|
||||
done;
|
||||
let secs =
|
||||
if tz = "local" then int_of_float (fst (Unix.mktime !tm))
|
||||
else timegm !tm
|
||||
in
|
||||
Integer secs);
|
||||
|
||||
(* === Env-as-value (Phase 4) === *)
|
||||
|
||||
|
||||
@@ -642,7 +642,9 @@ and run vm =
|
||||
(* Read upvalue descriptors from bytecode *)
|
||||
let uv_count = match code_val with
|
||||
| Dict d -> (match Hashtbl.find_opt d "upvalue-count" with
|
||||
| Some (Number n) -> int_of_float n | _ -> 0)
|
||||
| Some (Integer n) -> n
|
||||
| Some (Number n) -> int_of_float n
|
||||
| _ -> 0)
|
||||
| _ -> 0
|
||||
in
|
||||
let upvalues = Array.init uv_count (fun _ ->
|
||||
@@ -1307,7 +1309,9 @@ let trace_run src globals =
|
||||
let code_val2 = frame.closure.vm_code.vc_constants.(idx) in
|
||||
let uv_count = match code_val2 with
|
||||
| Dict d -> (match Hashtbl.find_opt d "upvalue-count" with
|
||||
| Some (Number n) -> int_of_float n | _ -> 0)
|
||||
| Some (Integer n) -> n
|
||||
| Some (Number n) -> int_of_float n
|
||||
| _ -> 0)
|
||||
| _ -> 0 in
|
||||
let upvalues = Array.init uv_count (fun _ ->
|
||||
let is_local = read_u8 frame in
|
||||
@@ -1428,7 +1432,9 @@ let disassemble (code : vm_code) =
|
||||
if op = 51 && idx < Array.length consts then begin
|
||||
let uv_count = match consts.(idx) with
|
||||
| Dict d -> (match Hashtbl.find_opt d "upvalue-count" with
|
||||
| Some (Number n) -> int_of_float n | _ -> 0)
|
||||
| Some (Integer n) -> n
|
||||
| Some (Number n) -> int_of_float n
|
||||
| _ -> 0)
|
||||
| _ -> 0 in
|
||||
ip := !ip + uv_count * 2
|
||||
end
|
||||
|
||||
@@ -270,7 +270,9 @@ let vm_create_closure vm_val frame_val code_val =
|
||||
let f = unwrap_frame frame_val in
|
||||
let uv_count = match code_val with
|
||||
| Dict d -> (match Hashtbl.find_opt d "upvalue-count" with
|
||||
| Some (Number n) -> int_of_float n | _ -> 0)
|
||||
| Some (Integer n) -> n
|
||||
| Some (Number n) -> int_of_float n
|
||||
| _ -> 0)
|
||||
| _ -> 0
|
||||
in
|
||||
let upvalues = Array.init uv_count (fun _ ->
|
||||
|
||||
@@ -265,7 +265,9 @@ let vm_create_closure vm_val frame_val code_val =
|
||||
let f = unwrap_frame frame_val in
|
||||
let uv_count = match code_val with
|
||||
| Dict d -> (match Hashtbl.find_opt d "upvalue-count" with
|
||||
| Some (Number n) -> int_of_float n | _ -> 0)
|
||||
| Some (Integer n) -> n
|
||||
| Some (Number n) -> int_of_float n
|
||||
| _ -> 0)
|
||||
| _ -> 0
|
||||
in
|
||||
let upvalues = Array.init uv_count (fun _ ->
|
||||
|
||||
116
lib/apl/conformance.sh
Executable file
116
lib/apl/conformance.sh
Executable file
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env bash
|
||||
# lib/apl/conformance.sh — run APL test suites, emit scoreboard.json + scoreboard.md.
|
||||
|
||||
set -uo pipefail
|
||||
cd "$(git rev-parse --show-toplevel)"
|
||||
|
||||
SX_SERVER="${SX_SERVER:-/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe}"
|
||||
if [ ! -x "$SX_SERVER" ]; then
|
||||
SX_SERVER="hosts/ocaml/_build/default/bin/sx_server.exe"
|
||||
fi
|
||||
if [ ! -x "$SX_SERVER" ]; then
|
||||
echo "ERROR: sx_server.exe not found." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SUITES=(structural operators dfn tradfn valence programs system idioms eval-ops pipeline)
|
||||
|
||||
OUT_JSON="lib/apl/scoreboard.json"
|
||||
OUT_MD="lib/apl/scoreboard.md"
|
||||
|
||||
run_suite() {
|
||||
local suite=$1
|
||||
local file="lib/apl/tests/${suite}.sx"
|
||||
local TMP
|
||||
TMP=$(mktemp)
|
||||
cat > "$TMP" << EPOCHS
|
||||
(epoch 1)
|
||||
(load "spec/stdlib.sx")
|
||||
(load "lib/r7rs.sx")
|
||||
(load "lib/apl/runtime.sx")
|
||||
(load "lib/apl/tokenizer.sx")
|
||||
(load "lib/apl/parser.sx")
|
||||
(load "lib/apl/transpile.sx")
|
||||
(epoch 2)
|
||||
(eval "(define apl-test-pass 0)")
|
||||
(eval "(define apl-test-fail 0)")
|
||||
(eval "(define apl-test (fn (name got expected) (if (= got expected) (set! apl-test-pass (+ apl-test-pass 1)) (set! apl-test-fail (+ apl-test-fail 1)))))")
|
||||
(epoch 3)
|
||||
(load "${file}")
|
||||
(epoch 4)
|
||||
(eval "(list apl-test-pass apl-test-fail)")
|
||||
EPOCHS
|
||||
|
||||
local OUTPUT
|
||||
OUTPUT=$(timeout 300 "$SX_SERVER" < "$TMP" 2>/dev/null)
|
||||
rm -f "$TMP"
|
||||
|
||||
local LINE
|
||||
LINE=$(echo "$OUTPUT" | awk '/^\(ok-len 4 / {getline; print; exit}')
|
||||
if [ -z "$LINE" ]; then
|
||||
LINE=$(echo "$OUTPUT" | grep -E '^\(ok 4 \([0-9]+ [0-9]+\)\)' | tail -1 \
|
||||
| sed -E 's/^\(ok 4 //; s/\)$//')
|
||||
fi
|
||||
|
||||
local P F
|
||||
P=$(echo "$LINE" | sed -E 's/^\(([0-9]+) ([0-9]+)\).*/\1/')
|
||||
F=$(echo "$LINE" | sed -E 's/^\(([0-9]+) ([0-9]+)\).*/\2/')
|
||||
P=${P:-0}
|
||||
F=${F:-0}
|
||||
echo "${P} ${F}"
|
||||
}
|
||||
|
||||
declare -A SUITE_PASS
|
||||
declare -A SUITE_FAIL
|
||||
TOTAL_PASS=0
|
||||
TOTAL_FAIL=0
|
||||
|
||||
echo "Running APL conformance suite..." >&2
|
||||
for s in "${SUITES[@]}"; do
|
||||
read -r p f < <(run_suite "$s")
|
||||
SUITE_PASS[$s]=$p
|
||||
SUITE_FAIL[$s]=$f
|
||||
TOTAL_PASS=$((TOTAL_PASS + p))
|
||||
TOTAL_FAIL=$((TOTAL_FAIL + f))
|
||||
printf " %-12s %d/%d\n" "$s" "$p" "$((p+f))" >&2
|
||||
done
|
||||
|
||||
# scoreboard.json
|
||||
{
|
||||
printf '{\n'
|
||||
printf ' "suites": {\n'
|
||||
first=1
|
||||
for s in "${SUITES[@]}"; do
|
||||
if [ $first -eq 0 ]; then printf ',\n'; fi
|
||||
printf ' "%s": {"pass": %d, "fail": %d}' "$s" "${SUITE_PASS[$s]}" "${SUITE_FAIL[$s]}"
|
||||
first=0
|
||||
done
|
||||
printf '\n },\n'
|
||||
printf ' "total_pass": %d,\n' "$TOTAL_PASS"
|
||||
printf ' "total_fail": %d,\n' "$TOTAL_FAIL"
|
||||
printf ' "total": %d\n' "$((TOTAL_PASS + TOTAL_FAIL))"
|
||||
printf '}\n'
|
||||
} > "$OUT_JSON"
|
||||
|
||||
# scoreboard.md
|
||||
{
|
||||
printf '# APL Conformance Scoreboard\n\n'
|
||||
printf '_Generated by `lib/apl/conformance.sh`_\n\n'
|
||||
printf '| Suite | Pass | Fail | Total |\n'
|
||||
printf '|-------|-----:|-----:|------:|\n'
|
||||
for s in "${SUITES[@]}"; do
|
||||
p=${SUITE_PASS[$s]}
|
||||
f=${SUITE_FAIL[$s]}
|
||||
printf '| %s | %d | %d | %d |\n' "$s" "$p" "$f" "$((p+f))"
|
||||
done
|
||||
printf '| **Total** | **%d** | **%d** | **%d** |\n' "$TOTAL_PASS" "$TOTAL_FAIL" "$((TOTAL_PASS + TOTAL_FAIL))"
|
||||
printf '\n'
|
||||
printf '## Notes\n\n'
|
||||
printf '%s\n' '- Suites use the standard `apl-test name got expected` framework loaded against `lib/apl/runtime.sx` + `lib/apl/transpile.sx`.'
|
||||
printf '%s\n' '- `lib/apl/tests/parse.sx` and `lib/apl/tests/scalar.sx` use their own self-contained frameworks and are excluded from this scoreboard.'
|
||||
} > "$OUT_MD"
|
||||
|
||||
echo "Wrote $OUT_JSON and $OUT_MD" >&2
|
||||
echo "Total: $TOTAL_PASS pass, $TOTAL_FAIL fail" >&2
|
||||
|
||||
[ "$TOTAL_FAIL" -eq 0 ]
|
||||
576
lib/apl/parser.sx
Normal file
576
lib/apl/parser.sx
Normal file
@@ -0,0 +1,576 @@
|
||||
; APL Parser — right-to-left expression parser
|
||||
;
|
||||
; Takes a token list (output of apl-tokenize) and produces an AST.
|
||||
; APL evaluates right-to-left with no precedence among functions.
|
||||
; Operators bind to the function immediately to their left in the source.
|
||||
;
|
||||
; AST node types:
|
||||
; (:num n) number literal
|
||||
; (:str s) string literal
|
||||
; (:vec n1 n2 ...) strand (juxtaposed literals)
|
||||
; (:name "x") name reference / alpha / omega
|
||||
; (:assign "x" expr) assignment x←expr
|
||||
; (:monad fn arg) monadic function call
|
||||
; (:dyad fn left right) dyadic function call
|
||||
; (:derived-fn op fn) derived function: f/ f¨ f⍨
|
||||
; (:derived-fn2 "." f g) inner product: f.g
|
||||
; (:outer "∘." fn) outer product: ∘.f
|
||||
; (:fn-glyph "⍳") function reference
|
||||
; (:fn-name "foo") named-function reference (dfn variable)
|
||||
; (:dfn stmt...) {⍺+⍵} anonymous function
|
||||
; (:guard cond expr) cond:expr guard inside dfn
|
||||
; (:program stmt...) multi-statement sequence
|
||||
|
||||
; ============================================================
|
||||
; Glyph classification sets
|
||||
; ============================================================
|
||||
|
||||
(define apl-parse-op-glyphs
|
||||
(list "/" "\\" "¨" "⍨" "∘" "." "⍣" "⍤" "⍥" "@"))
|
||||
|
||||
(define
|
||||
apl-parse-fn-glyphs
|
||||
(list
|
||||
"+"
|
||||
"-"
|
||||
"×"
|
||||
"÷"
|
||||
"*"
|
||||
"⍟"
|
||||
"⌈"
|
||||
"⌊"
|
||||
"|"
|
||||
"!"
|
||||
"?"
|
||||
"○"
|
||||
"~"
|
||||
"<"
|
||||
"≤"
|
||||
"="
|
||||
"≥"
|
||||
">"
|
||||
"≠"
|
||||
"≢"
|
||||
"≡"
|
||||
"∊"
|
||||
"∧"
|
||||
"∨"
|
||||
"⍱"
|
||||
"⍲"
|
||||
","
|
||||
"⍪"
|
||||
"⍴"
|
||||
"⌽"
|
||||
"⊖"
|
||||
"⍉"
|
||||
"↑"
|
||||
"↓"
|
||||
"⊂"
|
||||
"⊃"
|
||||
"⊆"
|
||||
"∪"
|
||||
"∩"
|
||||
"⍳"
|
||||
"⍸"
|
||||
"⌷"
|
||||
"⍋"
|
||||
"⍒"
|
||||
"⊥"
|
||||
"⊤"
|
||||
"⊣"
|
||||
"⊢"
|
||||
"⍎"
|
||||
"⍕"))
|
||||
|
||||
(define apl-quad-fn-names (list "⎕FMT"))
|
||||
|
||||
(define
|
||||
apl-parse-op-glyph?
|
||||
(fn (v) (some (fn (g) (= g v)) apl-parse-op-glyphs)))
|
||||
|
||||
; ============================================================
|
||||
; Token accessors
|
||||
; ============================================================
|
||||
|
||||
(define
|
||||
apl-parse-fn-glyph?
|
||||
(fn (v) (some (fn (g) (= g v)) apl-parse-fn-glyphs)))
|
||||
|
||||
(define tok-type (fn (tok) (get tok :type)))
|
||||
|
||||
(define tok-val (fn (tok) (get tok :value)))
|
||||
|
||||
(define
|
||||
is-op-tok?
|
||||
(fn
|
||||
(tok)
|
||||
(and (= (tok-type tok) :glyph) (apl-parse-op-glyph? (tok-val tok)))))
|
||||
|
||||
; ============================================================
|
||||
; Collect trailing operators starting at index i
|
||||
; Returns {:ops (op ...) :end new-i}
|
||||
; ============================================================
|
||||
|
||||
(define
|
||||
is-fn-tok?
|
||||
(fn
|
||||
(tok)
|
||||
(or
|
||||
(and (= (tok-type tok) :glyph) (apl-parse-fn-glyph? (tok-val tok)))
|
||||
(and
|
||||
(= (tok-type tok) :name)
|
||||
(some (fn (q) (= q (tok-val tok))) apl-quad-fn-names)))))
|
||||
|
||||
(define collect-ops (fn (tokens i) (collect-ops-loop tokens i (list))))
|
||||
|
||||
; ============================================================
|
||||
; Build a derived-fn node by chaining operators left-to-right
|
||||
; (+/¨ → (:derived-fn "¨" (:derived-fn "/" (:fn-glyph "+"))))
|
||||
; ============================================================
|
||||
|
||||
(define
|
||||
collect-ops-loop
|
||||
(fn
|
||||
(tokens i acc)
|
||||
(if
|
||||
(>= i (len tokens))
|
||||
{:end i :ops acc}
|
||||
(let
|
||||
((tok (nth tokens i)))
|
||||
(if
|
||||
(is-op-tok? tok)
|
||||
(collect-ops-loop tokens (+ i 1) (append acc (tok-val tok)))
|
||||
{:end i :ops acc})))))
|
||||
|
||||
; ============================================================
|
||||
; Find matching close bracket/paren/brace
|
||||
; Returns the index of the matching close token
|
||||
; ============================================================
|
||||
|
||||
(define
|
||||
build-derived-fn
|
||||
(fn
|
||||
(fn-node ops)
|
||||
(if
|
||||
(= (len ops) 0)
|
||||
fn-node
|
||||
(build-derived-fn (list :derived-fn (first ops) fn-node) (rest ops)))))
|
||||
|
||||
(define
|
||||
find-matching-close
|
||||
(fn
|
||||
(tokens start open-type close-type)
|
||||
(find-matching-close-loop tokens start open-type close-type 1)))
|
||||
|
||||
; ============================================================
|
||||
; Segment collection: scan tokens left-to-right, building
|
||||
; a list of {:kind "val"/"fn" :node ast} segments.
|
||||
; Operators following function glyphs are merged into
|
||||
; derived-fn nodes during this pass.
|
||||
; ============================================================
|
||||
|
||||
(define
|
||||
find-matching-close-loop
|
||||
(fn
|
||||
(tokens i open-type close-type depth)
|
||||
(if
|
||||
(>= i (len tokens))
|
||||
(len tokens)
|
||||
(let
|
||||
((tt (tok-type (nth tokens i))))
|
||||
(cond
|
||||
((= tt open-type)
|
||||
(find-matching-close-loop
|
||||
tokens
|
||||
(+ i 1)
|
||||
open-type
|
||||
close-type
|
||||
(+ depth 1)))
|
||||
((= tt close-type)
|
||||
(if
|
||||
(= depth 1)
|
||||
i
|
||||
(find-matching-close-loop
|
||||
tokens
|
||||
(+ i 1)
|
||||
open-type
|
||||
close-type
|
||||
(- depth 1))))
|
||||
(true
|
||||
(find-matching-close-loop
|
||||
tokens
|
||||
(+ i 1)
|
||||
open-type
|
||||
close-type
|
||||
depth)))))))
|
||||
|
||||
(define
|
||||
collect-segments
|
||||
(fn (tokens) (collect-segments-loop tokens 0 (list))))
|
||||
|
||||
; ============================================================
|
||||
; Build tree from segment list
|
||||
;
|
||||
; The segments are in left-to-right order.
|
||||
; APL evaluates right-to-left, so the LEFTMOST function is
|
||||
; the outermost (last-evaluated) node.
|
||||
;
|
||||
; Patterns:
|
||||
; [val] → val node
|
||||
; [fn val ...] → (:monad fn (build-tree rest))
|
||||
; [val fn val ...] → (:dyad fn val (build-tree rest))
|
||||
; [val val ...] → (:vec val1 val2 ...) — strand
|
||||
; ============================================================
|
||||
|
||||
; Find the index of the first function segment (returns -1 if none)
|
||||
(define
|
||||
collect-segments-loop
|
||||
(fn
|
||||
(tokens i acc)
|
||||
(if
|
||||
(>= i (len tokens))
|
||||
acc
|
||||
(let
|
||||
((tok (nth tokens i)) (n (len tokens)))
|
||||
(let
|
||||
((tt (tok-type tok)) (tv (tok-val tok)))
|
||||
(cond
|
||||
((or (= tt :diamond) (= tt :newline) (= tt :semi))
|
||||
(collect-segments-loop tokens (+ i 1) acc))
|
||||
((= tt :num)
|
||||
(collect-segments-loop tokens (+ i 1) (append acc {:kind "val" :node (list :num tv)})))
|
||||
((= tt :str)
|
||||
(collect-segments-loop tokens (+ i 1) (append acc {:kind "val" :node (list :str tv)})))
|
||||
((= tt :name)
|
||||
(if
|
||||
(some (fn (q) (= q tv)) apl-quad-fn-names)
|
||||
(let
|
||||
((op-result (collect-ops tokens (+ i 1))))
|
||||
(let
|
||||
((ops (get op-result :ops)) (ni (get op-result :end)))
|
||||
(let
|
||||
((fn-node (build-derived-fn (list :fn-glyph tv) ops)))
|
||||
(collect-segments-loop
|
||||
tokens
|
||||
ni
|
||||
(append acc {:kind "fn" :node fn-node})))))
|
||||
(let
|
||||
((br (maybe-bracket (list :name tv) tokens (+ i 1))))
|
||||
(collect-segments-loop
|
||||
tokens
|
||||
(nth br 1)
|
||||
(append acc {:kind "val" :node (nth br 0)})))))
|
||||
((= tt :lparen)
|
||||
(let
|
||||
((end (find-matching-close tokens (+ i 1) :lparen :rparen)))
|
||||
(let
|
||||
((inner-tokens (slice tokens (+ i 1) end))
|
||||
(after (+ end 1)))
|
||||
(let
|
||||
((br (maybe-bracket (parse-apl-expr inner-tokens) tokens after)))
|
||||
(collect-segments-loop
|
||||
tokens
|
||||
(nth br 1)
|
||||
(append acc {:kind "val" :node (nth br 0)}))))))
|
||||
((= tt :lbrace)
|
||||
(let
|
||||
((end (find-matching-close tokens (+ i 1) :lbrace :rbrace)))
|
||||
(let
|
||||
((inner-tokens (slice tokens (+ i 1) end))
|
||||
(after (+ end 1)))
|
||||
(collect-segments-loop tokens after (append acc {:kind "fn" :node (parse-dfn inner-tokens)})))))
|
||||
((= tt :glyph)
|
||||
(cond
|
||||
((or (= tv "⍺") (= tv "⍵"))
|
||||
(collect-segments-loop
|
||||
tokens
|
||||
(+ i 1)
|
||||
(append acc {:kind "val" :node (list :name tv)})))
|
||||
((= tv "∇")
|
||||
(collect-segments-loop
|
||||
tokens
|
||||
(+ i 1)
|
||||
(append acc {:kind "fn" :node (list :fn-glyph "∇")})))
|
||||
((and (= tv "∘") (< (+ i 1) n) (= (tok-val (nth tokens (+ i 1))) "."))
|
||||
(if
|
||||
(and (< (+ i 2) n) (is-fn-tok? (nth tokens (+ i 2))))
|
||||
(let
|
||||
((fn-tv (tok-val (nth tokens (+ i 2)))))
|
||||
(let
|
||||
((op-result (collect-ops tokens (+ i 3))))
|
||||
(let
|
||||
((ops (get op-result :ops))
|
||||
(ni (get op-result :end)))
|
||||
(let
|
||||
((fn-node (build-derived-fn (list :fn-glyph fn-tv) ops)))
|
||||
(collect-segments-loop
|
||||
tokens
|
||||
ni
|
||||
(append acc {:kind "fn" :node (list :outer "∘." fn-node)}))))))
|
||||
(collect-segments-loop tokens (+ i 1) acc)))
|
||||
((apl-parse-fn-glyph? tv)
|
||||
(let
|
||||
((op-result (collect-ops tokens (+ i 1))))
|
||||
(let
|
||||
((ops (get op-result :ops))
|
||||
(ni (get op-result :end)))
|
||||
(if
|
||||
(and
|
||||
(= (len ops) 1)
|
||||
(= (first ops) ".")
|
||||
(< ni n)
|
||||
(is-fn-tok? (nth tokens ni)))
|
||||
(let
|
||||
((g-tv (tok-val (nth tokens ni))))
|
||||
(let
|
||||
((op-result2 (collect-ops tokens (+ ni 1))))
|
||||
(let
|
||||
((ops2 (get op-result2 :ops))
|
||||
(ni2 (get op-result2 :end)))
|
||||
(let
|
||||
((g-node (build-derived-fn (list :fn-glyph g-tv) ops2)))
|
||||
(collect-segments-loop
|
||||
tokens
|
||||
ni2
|
||||
(append acc {:kind "fn" :node (list :derived-fn2 "." (list :fn-glyph tv) g-node)}))))))
|
||||
(let
|
||||
((fn-node (build-derived-fn (list :fn-glyph tv) ops)))
|
||||
(collect-segments-loop
|
||||
tokens
|
||||
ni
|
||||
(append acc {:kind "fn" :node fn-node})))))))
|
||||
((apl-parse-op-glyph? tv)
|
||||
(collect-segments-loop tokens (+ i 1) acc))
|
||||
(true (collect-segments-loop tokens (+ i 1) acc))))
|
||||
(true (collect-segments-loop tokens (+ i 1) acc))))))))
|
||||
|
||||
(define find-first-fn (fn (segs) (find-first-fn-loop segs 0)))
|
||||
|
||||
; Build an array node from 0..n value segments
|
||||
; If n=1 → return that segment's node
|
||||
; If n>1 → return (:vec node1 node2 ...)
|
||||
(define
|
||||
find-first-fn-loop
|
||||
(fn
|
||||
(segs i)
|
||||
(if
|
||||
(>= i (len segs))
|
||||
-1
|
||||
(if
|
||||
(= (get (nth segs i) :kind) "fn")
|
||||
i
|
||||
(find-first-fn-loop segs (+ i 1))))))
|
||||
|
||||
(define
|
||||
segs-to-array
|
||||
(fn
|
||||
(segs)
|
||||
(if
|
||||
(= (len segs) 1)
|
||||
(get (first segs) :node)
|
||||
(cons :vec (map (fn (s) (get s :node)) segs)))))
|
||||
|
||||
|
||||
; ============================================================
|
||||
; Split token list on statement separators (diamond / newline)
|
||||
; Only splits at depth 0 (ignores separators inside { } or ( ) )
|
||||
; ============================================================
|
||||
|
||||
(define
|
||||
build-tree
|
||||
(fn
|
||||
(segs)
|
||||
(cond
|
||||
((= (len segs) 0) nil)
|
||||
((= (len segs) 1) (get (first segs) :node))
|
||||
((every? (fn (s) (= (get s :kind) "val")) segs)
|
||||
(segs-to-array segs))
|
||||
(true
|
||||
(let
|
||||
((fn-idx (find-first-fn segs)))
|
||||
(cond
|
||||
((= fn-idx -1) (segs-to-array segs))
|
||||
((= fn-idx 0)
|
||||
(list
|
||||
:monad (get (first segs) :node)
|
||||
(build-tree (rest segs))))
|
||||
(true
|
||||
(let
|
||||
((left-segs (slice segs 0 fn-idx))
|
||||
(fn-seg (nth segs fn-idx))
|
||||
(right-segs (slice segs (+ fn-idx 1))))
|
||||
(list
|
||||
:dyad (get fn-seg :node)
|
||||
(segs-to-array left-segs)
|
||||
(build-tree right-segs))))))))))
|
||||
|
||||
(define
|
||||
split-statements
|
||||
(fn (tokens) (split-statements-loop tokens (list) (list) 0)))
|
||||
|
||||
; ============================================================
|
||||
; Parse a dfn body (tokens between { and })
|
||||
; Handles guard expressions: cond : expr
|
||||
; ============================================================
|
||||
|
||||
(define
|
||||
split-statements-loop
|
||||
(fn
|
||||
(tokens current-stmt acc depth)
|
||||
(if
|
||||
(= (len tokens) 0)
|
||||
(if (> (len current-stmt) 0) (append acc (list current-stmt)) acc)
|
||||
(let
|
||||
((tok (first tokens))
|
||||
(rest-toks (rest tokens))
|
||||
(tt (tok-type (first tokens))))
|
||||
(cond
|
||||
((or (= tt :lparen) (= tt :lbrace) (= tt :lbracket))
|
||||
(split-statements-loop
|
||||
rest-toks
|
||||
(append current-stmt tok)
|
||||
acc
|
||||
(+ depth 1)))
|
||||
((or (= tt :rparen) (= tt :rbrace) (= tt :rbracket))
|
||||
(split-statements-loop
|
||||
rest-toks
|
||||
(append current-stmt tok)
|
||||
acc
|
||||
(- depth 1)))
|
||||
((and (> depth 0) (or (= tt :diamond) (= tt :newline)))
|
||||
(split-statements-loop
|
||||
rest-toks
|
||||
(append current-stmt tok)
|
||||
acc
|
||||
depth))
|
||||
((and (= depth 0) (or (= tt :diamond) (= tt :newline)))
|
||||
(if
|
||||
(> (len current-stmt) 0)
|
||||
(split-statements-loop
|
||||
rest-toks
|
||||
(list)
|
||||
(append acc (list current-stmt))
|
||||
depth)
|
||||
(split-statements-loop rest-toks (list) acc depth)))
|
||||
(true
|
||||
(split-statements-loop
|
||||
rest-toks
|
||||
(append current-stmt tok)
|
||||
acc
|
||||
depth)))))))
|
||||
|
||||
(define
|
||||
parse-dfn
|
||||
(fn
|
||||
(tokens)
|
||||
(let
|
||||
((stmt-groups (split-statements tokens)))
|
||||
(let ((stmts (map parse-dfn-stmt stmt-groups))) (cons :dfn stmts)))))
|
||||
|
||||
(define
|
||||
parse-dfn-stmt
|
||||
(fn
|
||||
(tokens)
|
||||
(let
|
||||
((colon-idx (find-top-level-colon tokens 0)))
|
||||
(if
|
||||
(>= colon-idx 0)
|
||||
(let
|
||||
((cond-tokens (slice tokens 0 colon-idx))
|
||||
(body-tokens (slice tokens (+ colon-idx 1))))
|
||||
(list
|
||||
:guard (parse-apl-expr cond-tokens)
|
||||
(parse-apl-expr body-tokens)))
|
||||
(parse-stmt tokens)))))
|
||||
|
||||
(define
|
||||
find-top-level-colon
|
||||
(fn (tokens i) (find-top-level-colon-loop tokens i 0)))
|
||||
|
||||
; ============================================================
|
||||
; Parse a single statement (assignment or expression)
|
||||
; ============================================================
|
||||
|
||||
(define
|
||||
find-top-level-colon-loop
|
||||
(fn
|
||||
(tokens i depth)
|
||||
(if
|
||||
(>= i (len tokens))
|
||||
-1
|
||||
(let
|
||||
((tok (nth tokens i)) (tt (tok-type (nth tokens i))))
|
||||
(cond
|
||||
((or (= tt :lparen) (= tt :lbrace) (= tt :lbracket))
|
||||
(find-top-level-colon-loop tokens (+ i 1) (+ depth 1)))
|
||||
((or (= tt :rparen) (= tt :rbrace) (= tt :rbracket))
|
||||
(find-top-level-colon-loop tokens (+ i 1) (- depth 1)))
|
||||
((and (= tt :colon) (= depth 0)) i)
|
||||
(true (find-top-level-colon-loop tokens (+ i 1) depth)))))))
|
||||
|
||||
; ============================================================
|
||||
; Parse an expression from a flat token list
|
||||
; ============================================================
|
||||
|
||||
(define
|
||||
parse-stmt
|
||||
(fn
|
||||
(tokens)
|
||||
(if
|
||||
(and
|
||||
(>= (len tokens) 2)
|
||||
(= (tok-type (nth tokens 0)) :name)
|
||||
(= (tok-type (nth tokens 1)) :assign))
|
||||
(list
|
||||
:assign (tok-val (nth tokens 0))
|
||||
(parse-apl-expr (slice tokens 2)))
|
||||
(parse-apl-expr tokens))))
|
||||
|
||||
; ============================================================
|
||||
; Main entry point
|
||||
; parse-apl: string → AST
|
||||
; ============================================================
|
||||
|
||||
(define
|
||||
parse-apl-expr
|
||||
(fn
|
||||
(tokens)
|
||||
(let
|
||||
((segs (collect-segments tokens)))
|
||||
(if (= (len segs) 0) nil (build-tree segs)))))
|
||||
|
||||
(define
|
||||
parse-apl
|
||||
(fn
|
||||
(src)
|
||||
(let
|
||||
((tokens (apl-tokenize src)))
|
||||
(let
|
||||
((stmt-groups (split-statements tokens)))
|
||||
(if
|
||||
(= (len stmt-groups) 0)
|
||||
nil
|
||||
(if
|
||||
(= (len stmt-groups) 1)
|
||||
(parse-stmt (first stmt-groups))
|
||||
(cons :program (map parse-stmt stmt-groups))))))))
|
||||
|
||||
(define
|
||||
maybe-bracket
|
||||
(fn
|
||||
(val-node tokens after)
|
||||
(if
|
||||
(and
|
||||
(< after (len tokens))
|
||||
(= (tok-type (nth tokens after)) :lbracket))
|
||||
(let
|
||||
((end (find-matching-close tokens (+ after 1) :lbracket :rbracket)))
|
||||
(let
|
||||
((inner-tokens (slice tokens (+ after 1) end))
|
||||
(next-after (+ end 1)))
|
||||
(let
|
||||
((idx-expr (parse-apl-expr inner-tokens)))
|
||||
(let
|
||||
((indexed (list :dyad (list :fn-glyph "⌷") idx-expr val-node)))
|
||||
(maybe-bracket indexed tokens next-after)))))
|
||||
(list val-node after))))
|
||||
1536
lib/apl/runtime.sx
1536
lib/apl/runtime.sx
File diff suppressed because it is too large
Load Diff
17
lib/apl/scoreboard.json
Normal file
17
lib/apl/scoreboard.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"suites": {
|
||||
"structural": {"pass": 94, "fail": 0},
|
||||
"operators": {"pass": 117, "fail": 0},
|
||||
"dfn": {"pass": 24, "fail": 0},
|
||||
"tradfn": {"pass": 25, "fail": 0},
|
||||
"valence": {"pass": 14, "fail": 0},
|
||||
"programs": {"pass": 45, "fail": 0},
|
||||
"system": {"pass": 13, "fail": 0},
|
||||
"idioms": {"pass": 64, "fail": 0},
|
||||
"eval-ops": {"pass": 14, "fail": 0},
|
||||
"pipeline": {"pass": 40, "fail": 0}
|
||||
},
|
||||
"total_pass": 450,
|
||||
"total_fail": 0,
|
||||
"total": 450
|
||||
}
|
||||
22
lib/apl/scoreboard.md
Normal file
22
lib/apl/scoreboard.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# APL Conformance Scoreboard
|
||||
|
||||
_Generated by `lib/apl/conformance.sh`_
|
||||
|
||||
| Suite | Pass | Fail | Total |
|
||||
|-------|-----:|-----:|------:|
|
||||
| structural | 94 | 0 | 94 |
|
||||
| operators | 117 | 0 | 117 |
|
||||
| dfn | 24 | 0 | 24 |
|
||||
| tradfn | 25 | 0 | 25 |
|
||||
| valence | 14 | 0 | 14 |
|
||||
| programs | 45 | 0 | 45 |
|
||||
| system | 13 | 0 | 13 |
|
||||
| idioms | 64 | 0 | 64 |
|
||||
| eval-ops | 14 | 0 | 14 |
|
||||
| pipeline | 40 | 0 | 40 |
|
||||
| **Total** | **450** | **0** | **450** |
|
||||
|
||||
## Notes
|
||||
|
||||
- Suites use the standard `apl-test name got expected` framework loaded against `lib/apl/runtime.sx` + `lib/apl/transpile.sx`.
|
||||
- `lib/apl/tests/parse.sx` and `lib/apl/tests/scalar.sx` use their own self-contained frameworks and are excluded from this scoreboard.
|
||||
@@ -4,9 +4,9 @@
|
||||
set -uo pipefail
|
||||
cd "$(git rev-parse --show-toplevel)"
|
||||
|
||||
SX_SERVER="${SX_SERVER:-hosts/ocaml/_build/default/bin/sx_server.exe}"
|
||||
SX_SERVER="${SX_SERVER:-/root/rose-ash/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"
|
||||
SX_SERVER="hosts/ocaml/_build/default/bin/sx_server.exe"
|
||||
fi
|
||||
if [ ! -x "$SX_SERVER" ]; then
|
||||
echo "ERROR: sx_server.exe not found."
|
||||
@@ -18,19 +18,37 @@ TMPFILE=$(mktemp); trap "rm -f $TMPFILE" EXIT
|
||||
cat > "$TMPFILE" << 'EPOCHS'
|
||||
(epoch 1)
|
||||
(load "spec/stdlib.sx")
|
||||
(load "lib/r7rs.sx")
|
||||
(load "lib/apl/runtime.sx")
|
||||
(load "lib/apl/tokenizer.sx")
|
||||
(load "lib/apl/parser.sx")
|
||||
(load "lib/apl/transpile.sx")
|
||||
(epoch 2)
|
||||
(load "lib/apl/tests/runtime.sx")
|
||||
(eval "(define apl-test-pass 0)")
|
||||
(eval "(define apl-test-fail 0)")
|
||||
(eval "(define apl-test-fails (list))")
|
||||
(eval "(define apl-test (fn (name got expected) (if (= got expected) (set! apl-test-pass (+ apl-test-pass 1)) (begin (set! apl-test-fail (+ apl-test-fail 1)) (set! apl-test-fails (append apl-test-fails (list {:name name :got got :expected expected})))))))")
|
||||
(epoch 3)
|
||||
(load "lib/apl/tests/structural.sx")
|
||||
(load "lib/apl/tests/operators.sx")
|
||||
(load "lib/apl/tests/dfn.sx")
|
||||
(load "lib/apl/tests/tradfn.sx")
|
||||
(load "lib/apl/tests/valence.sx")
|
||||
(load "lib/apl/tests/programs.sx")
|
||||
(load "lib/apl/tests/system.sx")
|
||||
(load "lib/apl/tests/idioms.sx")
|
||||
(load "lib/apl/tests/eval-ops.sx")
|
||||
(load "lib/apl/tests/pipeline.sx")
|
||||
(epoch 4)
|
||||
(eval "(list apl-test-pass apl-test-fail)")
|
||||
EPOCHS
|
||||
|
||||
OUTPUT=$(timeout 60 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
|
||||
OUTPUT=$(timeout 300 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
|
||||
|
||||
LINE=$(echo "$OUTPUT" | awk '/^\(ok-len 3 / {getline; print; exit}')
|
||||
LINE=$(echo "$OUTPUT" | awk '/^\(ok-len 4 / {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/\)$//')
|
||||
LINE=$(echo "$OUTPUT" | grep -E '^\(ok 4 \([0-9]+ [0-9]+\)\)' | tail -1 \
|
||||
| sed -E 's/^\(ok 4 //; s/\)$//')
|
||||
fi
|
||||
if [ -z "$LINE" ]; then
|
||||
echo "ERROR: could not extract summary"
|
||||
|
||||
227
lib/apl/tests/dfn.sx
Normal file
227
lib/apl/tests/dfn.sx
Normal file
@@ -0,0 +1,227 @@
|
||||
; Tests for apl-eval-ast and apl-call-dfn (manual AST construction).
|
||||
|
||||
(define rv (fn (arr) (get arr :ravel)))
|
||||
(define sh (fn (arr) (get arr :shape)))
|
||||
|
||||
(define mknum (fn (n) (list :num n)))
|
||||
(define mkname (fn (s) (list :name s)))
|
||||
(define mkfg (fn (g) (list :fn-glyph g)))
|
||||
(define mkmon (fn (g a) (list :monad (mkfg g) a)))
|
||||
(define mkdyd (fn (g l r) (list :dyad (mkfg g) l r)))
|
||||
(define mkdfn1 (fn (body) (list :dfn body)))
|
||||
(define mkprog (fn (stmts) (cons :program stmts)))
|
||||
|
||||
(define mkasg (fn (mkname expr) (list :assign mkname expr)))
|
||||
|
||||
(define mkgrd (fn (c e) (list :guard c e)))
|
||||
|
||||
(define mkdfn (fn (stmts) (cons :dfn stmts)))
|
||||
|
||||
(apl-test
|
||||
"eval :num literal"
|
||||
(rv (apl-eval-ast (mknum 42) {}))
|
||||
(list 42))
|
||||
|
||||
(apl-test
|
||||
"eval :num literal shape"
|
||||
(sh (apl-eval-ast (mknum 42) {}))
|
||||
(list))
|
||||
|
||||
(apl-test
|
||||
"eval :dyad +"
|
||||
(rv (apl-eval-ast (mkdyd "+" (mknum 2) (mknum 3)) {}))
|
||||
(list 5))
|
||||
|
||||
(apl-test
|
||||
"eval :dyad ×"
|
||||
(rv (apl-eval-ast (mkdyd "×" (mknum 6) (mknum 7)) {}))
|
||||
(list 42))
|
||||
|
||||
(apl-test
|
||||
"eval :monad - (negate)"
|
||||
(rv (apl-eval-ast (mkmon "-" (mknum 7)) {}))
|
||||
(list -7))
|
||||
|
||||
(apl-test
|
||||
"eval :monad ⌊ (floor)"
|
||||
(rv (apl-eval-ast (mkmon "⌊" (mknum 3)) {}))
|
||||
(list 3))
|
||||
|
||||
(apl-test
|
||||
"eval :name ⍵ from env"
|
||||
(rv (apl-eval-ast (mkname "⍵") {:omega (apl-scalar 99) :alpha nil}))
|
||||
(list 99))
|
||||
|
||||
(apl-test
|
||||
"eval :name ⍺ from env"
|
||||
(rv (apl-eval-ast (mkname "⍺") {:omega nil :alpha (apl-scalar 7)}))
|
||||
(list 7))
|
||||
|
||||
(apl-test
|
||||
"dfn {⍵+1} called monadic"
|
||||
(rv
|
||||
(apl-call-dfn-m
|
||||
(mkdfn1 (mkdyd "+" (mkname "⍵") (mknum 1)))
|
||||
(apl-scalar 5)))
|
||||
(list 6))
|
||||
|
||||
(apl-test
|
||||
"dfn {⍺+⍵} called dyadic"
|
||||
(rv
|
||||
(apl-call-dfn
|
||||
(mkdfn1 (mkdyd "+" (mkname "⍺") (mkname "⍵")))
|
||||
(apl-scalar 4)
|
||||
(apl-scalar 9)))
|
||||
(list 13))
|
||||
|
||||
(apl-test
|
||||
"dfn {⍺×⍵} dyadic on vectors"
|
||||
(rv
|
||||
(apl-call-dfn
|
||||
(mkdfn1 (mkdyd "×" (mkname "⍺") (mkname "⍵")))
|
||||
(make-array (list 3) (list 1 2 3))
|
||||
(make-array (list 3) (list 10 20 30))))
|
||||
(list 10 40 90))
|
||||
|
||||
(apl-test
|
||||
"dfn {-⍵} monadic negate"
|
||||
(rv
|
||||
(apl-call-dfn-m
|
||||
(mkdfn1 (mkmon "-" (mkname "⍵")))
|
||||
(make-array (list 3) (list 1 2 3))))
|
||||
(list -1 -2 -3))
|
||||
|
||||
(apl-test
|
||||
"dfn {⍺-⍵} dyadic subtract scalar"
|
||||
(rv
|
||||
(apl-call-dfn
|
||||
(mkdfn1 (mkdyd "-" (mkname "⍺") (mkname "⍵")))
|
||||
(apl-scalar 10)
|
||||
(apl-scalar 3)))
|
||||
(list 7))
|
||||
|
||||
(apl-test
|
||||
"dfn {⌈⍺,⍵} not used (just verify : missing) — ceiling of right"
|
||||
(rv
|
||||
(apl-call-dfn-m (mkdfn1 (mkmon "⌈" (mkname "⍵"))) (apl-scalar 5)))
|
||||
(list 5))
|
||||
|
||||
(apl-test
|
||||
"dfn nested dyad"
|
||||
(rv
|
||||
(apl-call-dfn
|
||||
(mkdfn1
|
||||
(mkdyd "+" (mkname "⍺") (mkdyd "×" (mkname "⍵") (mknum 2))))
|
||||
(apl-scalar 1)
|
||||
(apl-scalar 3)))
|
||||
(list 7))
|
||||
|
||||
(apl-test
|
||||
"dfn local assign x←⍵+1; ⍺×x"
|
||||
(rv
|
||||
(apl-call-dfn
|
||||
(mkdfn
|
||||
(list
|
||||
(mkasg "x" (mkdyd "+" (mkname "⍵") (mknum 1)))
|
||||
(mkdyd "×" (mkname "⍺") (mkname "x"))))
|
||||
(apl-scalar 3)
|
||||
(apl-scalar 4)))
|
||||
(list 15))
|
||||
|
||||
(apl-test
|
||||
"dfn guard: 0=⍵:99; ⍵×2 (true branch)"
|
||||
(rv
|
||||
(apl-call-dfn-m
|
||||
(mkdfn
|
||||
(list
|
||||
(mkgrd (mkdyd "=" (mknum 0) (mkname "⍵")) (mknum 99))
|
||||
(mkdyd "×" (mkname "⍵") (mknum 2))))
|
||||
(apl-scalar 0)))
|
||||
(list 99))
|
||||
|
||||
(apl-test
|
||||
"dfn guard: 0=⍵:99; ⍵×2 (false branch)"
|
||||
(rv
|
||||
(apl-call-dfn-m
|
||||
(mkdfn
|
||||
(list
|
||||
(mkgrd (mkdyd "=" (mknum 0) (mkname "⍵")) (mknum 99))
|
||||
(mkdyd "×" (mkname "⍵") (mknum 2))))
|
||||
(apl-scalar 5)))
|
||||
(list 10))
|
||||
|
||||
(apl-test
|
||||
"dfn default ⍺←10 used (monadic call)"
|
||||
(rv
|
||||
(apl-call-dfn-m
|
||||
(mkdfn
|
||||
(list
|
||||
(mkasg "⍺" (mknum 10))
|
||||
(mkdyd "+" (mkname "⍺") (mkname "⍵"))))
|
||||
(apl-scalar 5)))
|
||||
(list 15))
|
||||
|
||||
(apl-test
|
||||
"dfn default ⍺←10 ignored when ⍺ given (dyadic call)"
|
||||
(rv
|
||||
(apl-call-dfn
|
||||
(mkdfn
|
||||
(list
|
||||
(mkasg "⍺" (mknum 10))
|
||||
(mkdyd "+" (mkname "⍺") (mkname "⍵"))))
|
||||
(apl-scalar 100)
|
||||
(apl-scalar 5)))
|
||||
(list 105))
|
||||
|
||||
(apl-test
|
||||
"dfn ∇ recursion: factorial via guard"
|
||||
(rv
|
||||
(apl-call-dfn-m
|
||||
(mkdfn
|
||||
(list
|
||||
(mkgrd (mkdyd "=" (mknum 0) (mkname "⍵")) (mknum 1))
|
||||
(mkdyd
|
||||
"×"
|
||||
(mkname "⍵")
|
||||
(mkmon "∇" (mkdyd "-" (mkname "⍵") (mknum 1))))))
|
||||
(apl-scalar 5)))
|
||||
(list 120))
|
||||
|
||||
(apl-test
|
||||
"dfn ∇ recursion: 3 → 6 (factorial)"
|
||||
(rv
|
||||
(apl-call-dfn-m
|
||||
(mkdfn
|
||||
(list
|
||||
(mkgrd (mkdyd "=" (mknum 0) (mkname "⍵")) (mknum 1))
|
||||
(mkdyd
|
||||
"×"
|
||||
(mkname "⍵")
|
||||
(mkmon "∇" (mkdyd "-" (mkname "⍵") (mknum 1))))))
|
||||
(apl-scalar 3)))
|
||||
(list 6))
|
||||
|
||||
(apl-test
|
||||
"dfn local: x←⍵+10; y←x×2; y"
|
||||
(rv
|
||||
(apl-call-dfn-m
|
||||
(mkdfn
|
||||
(list
|
||||
(mkasg "x" (mkdyd "+" (mkname "⍵") (mknum 10)))
|
||||
(mkasg "y" (mkdyd "×" (mkname "x") (mknum 2)))
|
||||
(mkname "y")))
|
||||
(apl-scalar 5)))
|
||||
(list 30))
|
||||
|
||||
(apl-test
|
||||
"dfn first guard wins: many guards"
|
||||
(rv
|
||||
(apl-call-dfn-m
|
||||
(mkdfn
|
||||
(list
|
||||
(mkgrd (mkdyd "=" (mknum 1) (mkname "⍵")) (mknum 100))
|
||||
(mkgrd (mkdyd "=" (mknum 2) (mkname "⍵")) (mknum 200))
|
||||
(mkgrd (mkdyd "=" (mknum 3) (mkname "⍵")) (mknum 300))
|
||||
(mknum 0)))
|
||||
(apl-scalar 2)))
|
||||
(list 200))
|
||||
147
lib/apl/tests/eval-ops.sx
Normal file
147
lib/apl/tests/eval-ops.sx
Normal file
@@ -0,0 +1,147 @@
|
||||
; Tests for operator handling in apl-eval-ast (Phase 7).
|
||||
; Manual AST construction; verifies :derived-fn / :outer / :derived-fn2
|
||||
; route through apl-resolve-monadic / apl-resolve-dyadic correctly.
|
||||
|
||||
(define mkrv (fn (arr) (get arr :ravel)))
|
||||
(define mksh (fn (arr) (get arr :shape)))
|
||||
(define mknum (fn (n) (list :num n)))
|
||||
(define mkfg (fn (g) (list :fn-glyph g)))
|
||||
(define mkmon (fn (g a) (list :monad g a)))
|
||||
(define mkdyd (fn (g l r) (list :dyad g l r)))
|
||||
(define mkder (fn (op f) (list :derived-fn op f)))
|
||||
(define mkdr2 (fn (op f g) (list :derived-fn2 op f g)))
|
||||
(define mkout (fn (f) (list :outer "∘." f)))
|
||||
|
||||
; helper: literal vector AST via :vec (from list of values)
|
||||
(define mkvec (fn (xs) (cons :vec (map (fn (n) (mknum n)) xs))))
|
||||
|
||||
; ---------- monadic operators ----------
|
||||
|
||||
(apl-test
|
||||
"eval-ast +/ ⍳5 → 15"
|
||||
(mkrv
|
||||
(apl-eval-ast
|
||||
(mkmon (mkder "/" (mkfg "+")) (mkmon (mkfg "⍳") (mknum 5)))
|
||||
{}))
|
||||
(list 15))
|
||||
|
||||
(apl-test
|
||||
"eval-ast ×/ ⍳5 → 120"
|
||||
(mkrv
|
||||
(apl-eval-ast
|
||||
(mkmon (mkder "/" (mkfg "×")) (mkmon (mkfg "⍳") (mknum 5)))
|
||||
{}))
|
||||
(list 120))
|
||||
|
||||
(apl-test
|
||||
"eval-ast ⌈/ — max reduce"
|
||||
(mkrv
|
||||
(apl-eval-ast
|
||||
(mkmon (mkder "/" (mkfg "⌈")) (mkvec (list 3 1 4 1 5 9 2 6)))
|
||||
{}))
|
||||
(list 9))
|
||||
|
||||
(apl-test
|
||||
"eval-ast +\\ scan"
|
||||
(mkrv
|
||||
(apl-eval-ast
|
||||
(mkmon (mkder "\\" (mkfg "+")) (mkvec (list 1 2 3 4 5)))
|
||||
{}))
|
||||
(list 1 3 6 10 15))
|
||||
|
||||
(apl-test
|
||||
"eval-ast +⌿ first-axis reduce on vector"
|
||||
(mkrv
|
||||
(apl-eval-ast
|
||||
(mkmon (mkder "⌿" (mkfg "+")) (mkvec (list 1 2 3 4 5)))
|
||||
{}))
|
||||
(list 15))
|
||||
|
||||
(apl-test
|
||||
"eval-ast -¨ each-negate"
|
||||
(mkrv
|
||||
(apl-eval-ast
|
||||
(mkmon (mkder "¨" (mkfg "-")) (mkvec (list 1 2 3 4)))
|
||||
{}))
|
||||
(list -1 -2 -3 -4))
|
||||
|
||||
(apl-test
|
||||
"eval-ast +⍨ commute (double via x+x)"
|
||||
(mkrv
|
||||
(apl-eval-ast (mkmon (mkder "⍨" (mkfg "+")) (mknum 7)) {}))
|
||||
(list 14))
|
||||
|
||||
; ---------- dyadic operators ----------
|
||||
|
||||
(apl-test
|
||||
"eval-ast outer ∘.× — multiplication table"
|
||||
(mkrv
|
||||
(apl-eval-ast
|
||||
(mkdyd
|
||||
(mkout (mkfg "×"))
|
||||
(mkvec (list 1 2 3))
|
||||
(mkvec (list 1 2 3)))
|
||||
{}))
|
||||
(list 1 2 3 2 4 6 3 6 9))
|
||||
|
||||
(apl-test
|
||||
"eval-ast outer ∘.× shape (3 3)"
|
||||
(mksh
|
||||
(apl-eval-ast
|
||||
(mkdyd
|
||||
(mkout (mkfg "×"))
|
||||
(mkvec (list 1 2 3))
|
||||
(mkvec (list 1 2 3)))
|
||||
{}))
|
||||
(list 3 3))
|
||||
|
||||
(apl-test
|
||||
"eval-ast inner +.× — dot product"
|
||||
(mkrv
|
||||
(apl-eval-ast
|
||||
(mkdyd
|
||||
(mkdr2 "." (mkfg "+") (mkfg "×"))
|
||||
(mkvec (list 1 2 3))
|
||||
(mkvec (list 4 5 6)))
|
||||
{}))
|
||||
(list 32))
|
||||
|
||||
(apl-test
|
||||
"eval-ast inner ∧.= equal vectors"
|
||||
(mkrv
|
||||
(apl-eval-ast
|
||||
(mkdyd
|
||||
(mkdr2 "." (mkfg "∧") (mkfg "="))
|
||||
(mkvec (list 1 2 3))
|
||||
(mkvec (list 1 2 3)))
|
||||
{}))
|
||||
(list 1))
|
||||
|
||||
(apl-test
|
||||
"eval-ast each-dyadic +¨"
|
||||
(mkrv
|
||||
(apl-eval-ast
|
||||
(mkdyd
|
||||
(mkder "¨" (mkfg "+"))
|
||||
(mkvec (list 1 2 3))
|
||||
(mkvec (list 10 20 30)))
|
||||
{}))
|
||||
(list 11 22 33))
|
||||
|
||||
(apl-test
|
||||
"eval-ast commute -⍨ (subtract swapped)"
|
||||
(mkrv
|
||||
(apl-eval-ast
|
||||
(mkdyd (mkder "⍨" (mkfg "-")) (mknum 5) (mknum 3))
|
||||
{}))
|
||||
(list -2))
|
||||
|
||||
; ---------- nested operators ----------
|
||||
|
||||
(apl-test
|
||||
"eval-ast +/¨ — sum of each"
|
||||
(mkrv
|
||||
(apl-eval-ast
|
||||
(mkmon (mkder "/" (mkfg "+")) (mkvec (list 10 20 30)))
|
||||
{}))
|
||||
(list 60))
|
||||
359
lib/apl/tests/idioms.sx
Normal file
359
lib/apl/tests/idioms.sx
Normal file
@@ -0,0 +1,359 @@
|
||||
; APL idiom corpus — classic Roger Hui / Phil Last idioms expressed
|
||||
; through our runtime primitives. Each test names the APL one-liner
|
||||
; and verifies the equivalent runtime call.
|
||||
|
||||
(define mkrv (fn (arr) (get arr :ravel)))
|
||||
(define mksh (fn (arr) (get arr :shape)))
|
||||
|
||||
; ---------- reductions ----------
|
||||
|
||||
(apl-test
|
||||
"+/⍵ — sum"
|
||||
(mkrv (apl-reduce apl-add (make-array (list 5) (list 1 2 3 4 5))))
|
||||
(list 15))
|
||||
|
||||
(apl-test
|
||||
"(+/⍵)÷⍴⍵ — mean"
|
||||
(mkrv
|
||||
(apl-div
|
||||
(apl-reduce apl-add (make-array (list 5) (list 1 2 3 4 5)))
|
||||
(apl-scalar 5)))
|
||||
(list 3))
|
||||
|
||||
(apl-test
|
||||
"⌈/⍵ — max"
|
||||
(mkrv (apl-reduce apl-max (make-array (list 6) (list 3 1 4 1 5 9))))
|
||||
(list 9))
|
||||
|
||||
(apl-test
|
||||
"⌊/⍵ — min"
|
||||
(mkrv (apl-reduce apl-min (make-array (list 6) (list 3 1 4 1 5 9))))
|
||||
(list 1))
|
||||
|
||||
(apl-test
|
||||
"(⌈/⍵)-⌊/⍵ — range"
|
||||
(mkrv
|
||||
(apl-sub
|
||||
(apl-reduce apl-max (make-array (list 6) (list 3 1 4 1 5 9)))
|
||||
(apl-reduce apl-min (make-array (list 6) (list 3 1 4 1 5 9)))))
|
||||
(list 8))
|
||||
|
||||
(apl-test
|
||||
"×/⍵ — product"
|
||||
(mkrv (apl-reduce apl-mul (make-array (list 4) (list 1 2 3 4))))
|
||||
(list 24))
|
||||
|
||||
(apl-test
|
||||
"+\\⍵ — running sum"
|
||||
(mkrv (apl-scan apl-add (make-array (list 5) (list 1 2 3 4 5))))
|
||||
(list 1 3 6 10 15))
|
||||
|
||||
; ---------- sort / order ----------
|
||||
|
||||
(apl-test
|
||||
"⍵[⍋⍵] — sort ascending"
|
||||
(mkrv (apl-quicksort (make-array (list 5) (list 3 1 4 1 5))))
|
||||
(list 1 1 3 4 5))
|
||||
|
||||
(apl-test
|
||||
"⌽⍵ — reverse"
|
||||
(mkrv (apl-reverse (make-array (list 5) (list 1 2 3 4 5))))
|
||||
(list 5 4 3 2 1))
|
||||
|
||||
(apl-test
|
||||
"⊃⌽⍵ — last element"
|
||||
(mkrv
|
||||
(apl-disclose (apl-reverse (make-array (list 4) (list 10 20 30 40)))))
|
||||
(list 40))
|
||||
|
||||
(apl-test
|
||||
"1↑⍵ — first element"
|
||||
(mkrv
|
||||
(apl-take (apl-scalar 1) (make-array (list 4) (list 10 20 30 40))))
|
||||
(list 10))
|
||||
|
||||
(apl-test
|
||||
"1↓⍵ — drop first"
|
||||
(mkrv
|
||||
(apl-drop (apl-scalar 1) (make-array (list 4) (list 10 20 30 40))))
|
||||
(list 20 30 40))
|
||||
|
||||
(apl-test
|
||||
"¯1↓⍵ — drop last"
|
||||
(mkrv
|
||||
(apl-drop (apl-scalar -1) (make-array (list 4) (list 10 20 30 40))))
|
||||
(list 10 20 30))
|
||||
|
||||
; ---------- counts / membership ----------
|
||||
|
||||
(apl-test
|
||||
"≢⍵ — tally"
|
||||
(mkrv (apl-tally (make-array (list 7) (list 9 8 7 6 5 4 3))))
|
||||
(list 7))
|
||||
|
||||
(apl-test
|
||||
"+/⍵=v — count occurrences of v"
|
||||
(mkrv
|
||||
(apl-reduce
|
||||
apl-add
|
||||
(apl-eq (make-array (list 7) (list 1 2 3 2 1 3 2)) (apl-scalar 2))))
|
||||
(list 3))
|
||||
|
||||
(apl-test
|
||||
"0=N|M — divisibility test"
|
||||
(mkrv (apl-eq (apl-scalar 0) (apl-mod (apl-scalar 3) (apl-scalar 12))))
|
||||
(list 1))
|
||||
|
||||
; ---------- shape constructors ----------
|
||||
|
||||
(apl-test
|
||||
"N⍴1 — vector of N ones"
|
||||
(mkrv (apl-reshape (apl-scalar 5) (apl-scalar 1)))
|
||||
(list 1 1 1 1 1))
|
||||
|
||||
(apl-test
|
||||
"(N N)⍴0 — N×N zero matrix"
|
||||
(mkrv (apl-reshape (make-array (list 2) (list 3 3)) (apl-scalar 0)))
|
||||
(list 0 0 0 0 0 0 0 0 0))
|
||||
|
||||
(apl-test
|
||||
"⍳∘.=⍳ — N×N identity matrix"
|
||||
(mkrv
|
||||
(apl-outer apl-eq (apl-iota (apl-scalar 3)) (apl-iota (apl-scalar 3))))
|
||||
(list 1 0 0 0 1 0 0 0 1))
|
||||
|
||||
(apl-test
|
||||
"⍳∘.×⍳ — multiplication table"
|
||||
(mkrv
|
||||
(apl-outer apl-mul (apl-iota (apl-scalar 3)) (apl-iota (apl-scalar 3))))
|
||||
(list 1 2 3 2 4 6 3 6 9))
|
||||
|
||||
; ---------- numerical idioms ----------
|
||||
|
||||
(apl-test
|
||||
"+\\⍳N — triangular numbers"
|
||||
(mkrv (apl-scan apl-add (apl-iota (apl-scalar 5))))
|
||||
(list 1 3 6 10 15))
|
||||
|
||||
(apl-test
|
||||
"+/⍳N=N×(N+1)÷2 — sum of 1..N"
|
||||
(mkrv (apl-reduce apl-add (apl-iota (apl-scalar 10))))
|
||||
(list 55))
|
||||
|
||||
(apl-test
|
||||
"×/⍳N — factorial via iota"
|
||||
(mkrv (apl-reduce apl-mul (apl-iota (apl-scalar 5))))
|
||||
(list 120))
|
||||
|
||||
(apl-test
|
||||
"2|⍵ — parity (1=odd)"
|
||||
(mkrv (apl-mod (apl-scalar 2) (make-array (list 5) (list 1 2 3 4 5))))
|
||||
(list 1 0 1 0 1))
|
||||
|
||||
(apl-test
|
||||
"+/2|⍵ — count odd"
|
||||
(mkrv
|
||||
(apl-reduce
|
||||
apl-add
|
||||
(apl-mod (apl-scalar 2) (make-array (list 5) (list 1 2 3 4 5)))))
|
||||
(list 3))
|
||||
|
||||
; ---------- boolean idioms ----------
|
||||
|
||||
(apl-test
|
||||
"∧/⍵ — all-true"
|
||||
(mkrv (apl-reduce apl-and (make-array (list 4) (list 1 1 1 1))))
|
||||
(list 1))
|
||||
|
||||
(apl-test
|
||||
"∧/⍵ — all-true with zero is false"
|
||||
(mkrv (apl-reduce apl-and (make-array (list 4) (list 1 1 0 1))))
|
||||
(list 0))
|
||||
|
||||
(apl-test
|
||||
"∨/⍵ — any-true"
|
||||
(mkrv (apl-reduce apl-or (make-array (list 4) (list 0 0 1 0))))
|
||||
(list 1))
|
||||
|
||||
(apl-test
|
||||
"∨/⍵ — any-true all zero is false"
|
||||
(mkrv (apl-reduce apl-or (make-array (list 4) (list 0 0 0 0))))
|
||||
(list 0))
|
||||
|
||||
; ---------- selection / scaling ----------
|
||||
|
||||
(apl-test
|
||||
"⍵×⍵ — square each"
|
||||
(mkrv
|
||||
(apl-mul
|
||||
(make-array (list 4) (list 1 2 3 4))
|
||||
(make-array (list 4) (list 1 2 3 4))))
|
||||
(list 1 4 9 16))
|
||||
|
||||
(apl-test
|
||||
"+/⍵×⍵ — sum of squares"
|
||||
(mkrv
|
||||
(apl-reduce
|
||||
apl-add
|
||||
(apl-mul
|
||||
(make-array (list 4) (list 1 2 3 4))
|
||||
(make-array (list 4) (list 1 2 3 4)))))
|
||||
(list 30))
|
||||
|
||||
(apl-test
|
||||
"⍵-(+/⍵)÷⍴⍵ — mean-centered"
|
||||
(mkrv
|
||||
(apl-sub
|
||||
(make-array (list 5) (list 2 4 6 8 10))
|
||||
(apl-div
|
||||
(apl-reduce apl-add (make-array (list 5) (list 2 4 6 8 10)))
|
||||
(apl-scalar 5))))
|
||||
(list -4 -2 0 2 4))
|
||||
|
||||
; ---------- shape / structure ----------
|
||||
|
||||
(apl-test
|
||||
",⍵ — ravel"
|
||||
(mkrv (apl-ravel (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list 1 2 3 4 5 6))
|
||||
|
||||
(apl-test
|
||||
"⍴⍴⍵ — rank"
|
||||
(mkrv
|
||||
(apl-shape (apl-shape (make-array (list 2 3) (list 1 2 3 4 5 6)))))
|
||||
(list 2))
|
||||
|
||||
(apl-test
|
||||
"src: +/⍳N → triangular(N)"
|
||||
(mkrv (apl-run "+/⍳100"))
|
||||
(list 5050))
|
||||
|
||||
(apl-test "src: ×/⍳N → N!" (mkrv (apl-run "×/⍳6")) (list 720))
|
||||
|
||||
(apl-test
|
||||
"src: ⌈/V — max"
|
||||
(mkrv (apl-run "⌈/3 1 4 1 5 9 2 6"))
|
||||
(list 9))
|
||||
|
||||
(apl-test
|
||||
"src: ⌊/V — min"
|
||||
(mkrv (apl-run "⌊/3 1 4 1 5 9 2 6"))
|
||||
(list 1))
|
||||
|
||||
(apl-test
|
||||
"src: range = (⌈/V) - ⌊/V"
|
||||
(mkrv (apl-run "(⌈/3 1 4 1 5 9 2 6) - ⌊/3 1 4 1 5 9 2 6"))
|
||||
(list 8))
|
||||
|
||||
(apl-test
|
||||
"src: +\\V — running sum"
|
||||
(mkrv (apl-run "+\\1 2 3 4 5"))
|
||||
(list 1 3 6 10 15))
|
||||
|
||||
(apl-test
|
||||
"src: ×\\V — running product"
|
||||
(mkrv (apl-run "×\\1 2 3 4 5"))
|
||||
(list 1 2 6 24 120))
|
||||
|
||||
(apl-test
|
||||
"src: V × V — squares"
|
||||
(mkrv (apl-run "(⍳5) × ⍳5"))
|
||||
(list 1 4 9 16 25))
|
||||
|
||||
(apl-test
|
||||
"src: +/V × V — sum of squares"
|
||||
(mkrv (apl-run "+/(⍳5) × ⍳5"))
|
||||
(list 55))
|
||||
|
||||
(apl-test "src: ∧/V — all-true" (mkrv (apl-run "∧/1 1 1 1")) (list 1))
|
||||
|
||||
(apl-test "src: ∨/V — any-true" (mkrv (apl-run "∨/0 0 1 0")) (list 1))
|
||||
|
||||
(apl-test "src: 0 = N|M — divides" (mkrv (apl-run "0 = 3 | 12")) (list 1))
|
||||
|
||||
(apl-test
|
||||
"src: 2 | V — parity"
|
||||
(mkrv (apl-run "2 | 1 2 3 4 5 6"))
|
||||
(list 1 0 1 0 1 0))
|
||||
|
||||
(apl-test
|
||||
"src: +/2|V — count odd"
|
||||
(mkrv (apl-run "+/2 | 1 2 3 4 5 6"))
|
||||
(list 3))
|
||||
|
||||
(apl-test "src: ⍴ V" (mkrv (apl-run "⍴ 1 2 3 4 5")) (list 5))
|
||||
|
||||
(apl-test
|
||||
"src: ⍴⍴ M — rank"
|
||||
(mkrv (apl-run "⍴ ⍴ (2 3) ⍴ ⍳6"))
|
||||
(list 2))
|
||||
|
||||
(apl-test
|
||||
"src: N⍴1 — vector of ones"
|
||||
(mkrv (apl-run "5 ⍴ 1"))
|
||||
(list 1 1 1 1 1))
|
||||
|
||||
(apl-test
|
||||
"src: ⍳N ∘.= ⍳N — identity matrix"
|
||||
(mkrv (apl-run "(⍳3) ∘.= ⍳3"))
|
||||
(list 1 0 0 0 1 0 0 0 1))
|
||||
|
||||
(apl-test
|
||||
"src: ⍳N ∘.× ⍳N — multiplication table"
|
||||
(mkrv (apl-run "(⍳3) ∘.× ⍳3"))
|
||||
(list 1 2 3 2 4 6 3 6 9))
|
||||
|
||||
(apl-test
|
||||
"src: V +.× V — dot product"
|
||||
(mkrv (apl-run "1 2 3 +.× 4 5 6"))
|
||||
(list 32))
|
||||
|
||||
(apl-test
|
||||
"src: ∧.= V — vectors equal?"
|
||||
(mkrv (apl-run "1 2 3 ∧.= 1 2 3"))
|
||||
(list 1))
|
||||
|
||||
(apl-test
|
||||
"src: V[1] — first element"
|
||||
(mkrv (apl-run "(10 20 30 40)[1]"))
|
||||
(list 10))
|
||||
|
||||
(apl-test
|
||||
"src: 1↑V — first via take"
|
||||
(mkrv (apl-run "1 ↑ 10 20 30 40"))
|
||||
(list 10))
|
||||
|
||||
(apl-test
|
||||
"src: 1↓V — drop first"
|
||||
(mkrv (apl-run "1 ↓ 10 20 30 40"))
|
||||
(list 20 30 40))
|
||||
|
||||
(apl-test
|
||||
"src: ¯1↓V — drop last"
|
||||
(mkrv (apl-run "¯1 ↓ 10 20 30 40"))
|
||||
(list 10 20 30))
|
||||
|
||||
(apl-test
|
||||
"src: ⌽V — reverse"
|
||||
(mkrv (apl-run "⌽ 1 2 3 4 5"))
|
||||
(list 5 4 3 2 1))
|
||||
|
||||
(apl-test
|
||||
"src: ≢V — tally"
|
||||
(mkrv (apl-run "≢ 9 8 7 6 5 4 3 2 1"))
|
||||
(list 9))
|
||||
|
||||
(apl-test
|
||||
"src: ,M — ravel"
|
||||
(mkrv (apl-run ", (2 3) ⍴ ⍳6"))
|
||||
(list 1 2 3 4 5 6))
|
||||
|
||||
(apl-test
|
||||
"src: A=V — count occurrences"
|
||||
(mkrv (apl-run "+/2 = 1 2 3 2 1 3 2"))
|
||||
(list 3))
|
||||
|
||||
(apl-test
|
||||
"src: ⌈/(V × V) — max squared"
|
||||
(mkrv (apl-run "⌈/(1 2 3 4 5) × 1 2 3 4 5"))
|
||||
(list 25))
|
||||
791
lib/apl/tests/operators.sx
Normal file
791
lib/apl/tests/operators.sx
Normal file
@@ -0,0 +1,791 @@
|
||||
(define rv (fn (arr) (get arr :ravel)))
|
||||
(define sh (fn (arr) (get arr :shape)))
|
||||
|
||||
(apl-test
|
||||
"reduce +/ vector"
|
||||
(rv (apl-reduce apl-add (make-array (list 5) (list 1 2 3 4 5))))
|
||||
(list 15))
|
||||
|
||||
(apl-test
|
||||
"reduce x/ vector"
|
||||
(rv (apl-reduce apl-mul (make-array (list 4) (list 1 2 3 4))))
|
||||
(list 24))
|
||||
|
||||
(apl-test
|
||||
"reduce max/ vector"
|
||||
(rv (apl-reduce apl-max (make-array (list 5) (list 3 1 4 1 5))))
|
||||
(list 5))
|
||||
|
||||
(apl-test
|
||||
"reduce min/ vector"
|
||||
(rv (apl-reduce apl-min (make-array (list 3) (list 3 1 4))))
|
||||
(list 1))
|
||||
|
||||
(apl-test
|
||||
"reduce and/ all true"
|
||||
(rv (apl-reduce apl-and (make-array (list 3) (list 1 1 1))))
|
||||
(list 1))
|
||||
|
||||
(apl-test
|
||||
"reduce or/ with true"
|
||||
(rv (apl-reduce apl-or (make-array (list 3) (list 0 0 1))))
|
||||
(list 1))
|
||||
|
||||
(apl-test
|
||||
"reduce +/ single element"
|
||||
(rv (apl-reduce apl-add (make-array (list 1) (list 42))))
|
||||
(list 42))
|
||||
|
||||
(apl-test
|
||||
"reduce +/ scalar no-op"
|
||||
(rv (apl-reduce apl-add (apl-scalar 7)))
|
||||
(list 7))
|
||||
|
||||
(apl-test
|
||||
"reduce +/ shape is scalar"
|
||||
(sh (apl-reduce apl-add (make-array (list 4) (list 1 2 3 4))))
|
||||
(list))
|
||||
|
||||
(apl-test
|
||||
"reduce +/ matrix row sums shape"
|
||||
(sh (apl-reduce apl-add (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list 2))
|
||||
|
||||
(apl-test
|
||||
"reduce +/ matrix row sums values"
|
||||
(rv (apl-reduce apl-add (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list 6 15))
|
||||
|
||||
(apl-test
|
||||
"reduce max/ matrix row maxima"
|
||||
(rv (apl-reduce apl-max (make-array (list 2 3) (list 3 1 4 1 5 9))))
|
||||
(list 4 9))
|
||||
|
||||
(apl-test
|
||||
"reduce-first +/ vector same as reduce"
|
||||
(rv (apl-reduce-first apl-add (make-array (list 5) (list 1 2 3 4 5))))
|
||||
(list 15))
|
||||
|
||||
(apl-test
|
||||
"reduce-first +/ matrix col sums shape"
|
||||
(sh
|
||||
(apl-reduce-first apl-add (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list 3))
|
||||
|
||||
(apl-test
|
||||
"reduce-first +/ matrix col sums values"
|
||||
(rv
|
||||
(apl-reduce-first apl-add (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list 5 7 9))
|
||||
|
||||
(apl-test
|
||||
"reduce-first max/ matrix col maxima"
|
||||
(rv
|
||||
(apl-reduce-first apl-max (make-array (list 3 2) (list 1 9 2 8 3 7))))
|
||||
(list 3 9))
|
||||
|
||||
(apl-test
|
||||
"scan +\\ vector"
|
||||
(rv (apl-scan apl-add (make-array (list 5) (list 1 2 3 4 5))))
|
||||
(list 1 3 6 10 15))
|
||||
|
||||
(apl-test
|
||||
"scan x\\ vector cumulative product"
|
||||
(rv (apl-scan apl-mul (make-array (list 5) (list 1 2 3 4 5))))
|
||||
(list 1 2 6 24 120))
|
||||
|
||||
(apl-test
|
||||
"scan max\\ vector running max"
|
||||
(rv (apl-scan apl-max (make-array (list 5) (list 3 1 4 1 5))))
|
||||
(list 3 3 4 4 5))
|
||||
|
||||
(apl-test
|
||||
"scan min\\ vector running min"
|
||||
(rv (apl-scan apl-min (make-array (list 5) (list 3 1 4 1 5))))
|
||||
(list 3 1 1 1 1))
|
||||
|
||||
(apl-test
|
||||
"scan +\\ single element"
|
||||
(rv (apl-scan apl-add (make-array (list 1) (list 42))))
|
||||
(list 42))
|
||||
|
||||
(apl-test
|
||||
"scan +\\ scalar no-op"
|
||||
(rv (apl-scan apl-add (apl-scalar 7)))
|
||||
(list 7))
|
||||
|
||||
(apl-test
|
||||
"scan +\\ vector preserves shape"
|
||||
(sh (apl-scan apl-add (make-array (list 5) (list 1 2 3 4 5))))
|
||||
(list 5))
|
||||
|
||||
(apl-test
|
||||
"scan +\\ matrix preserves shape"
|
||||
(sh (apl-scan apl-add (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list 2 3))
|
||||
|
||||
(apl-test
|
||||
"scan +\\ matrix row-wise"
|
||||
(rv (apl-scan apl-add (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list 1 3 6 4 9 15))
|
||||
|
||||
(apl-test
|
||||
"scan max\\ matrix row-wise running max"
|
||||
(rv (apl-scan apl-max (make-array (list 2 3) (list 3 1 4 1 5 9))))
|
||||
(list 3 3 4 1 5 9))
|
||||
|
||||
(apl-test
|
||||
"scan-first +\\ vector same as scan"
|
||||
(rv (apl-scan-first apl-add (make-array (list 5) (list 1 2 3 4 5))))
|
||||
(list 1 3 6 10 15))
|
||||
|
||||
(apl-test
|
||||
"scan-first +\\ scalar no-op"
|
||||
(rv (apl-scan-first apl-add (apl-scalar 9)))
|
||||
(list 9))
|
||||
|
||||
(apl-test
|
||||
"scan-first +\\ matrix preserves shape"
|
||||
(sh (apl-scan-first apl-add (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list 2 3))
|
||||
|
||||
(apl-test
|
||||
"scan-first +\\ matrix col-wise"
|
||||
(rv (apl-scan-first apl-add (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list 1 2 3 5 7 9))
|
||||
|
||||
(apl-test
|
||||
"scan-first max\\ matrix col-wise running max"
|
||||
(rv (apl-scan-first apl-max (make-array (list 3 2) (list 3 1 4 1 5 9))))
|
||||
(list 3 1 4 1 5 9))
|
||||
|
||||
(apl-test
|
||||
"each negate vector"
|
||||
(rv (apl-each apl-neg-m (make-array (list 3) (list 1 2 3))))
|
||||
(list -1 -2 -3))
|
||||
|
||||
(apl-test
|
||||
"each negate vector preserves shape"
|
||||
(sh (apl-each apl-neg-m (make-array (list 3) (list 1 2 3))))
|
||||
(list 3))
|
||||
|
||||
(apl-test
|
||||
"each reciprocal vector"
|
||||
(rv (apl-each apl-recip (make-array (list 3) (list 1 2 4))))
|
||||
(list 1 (/ 1 2) (/ 1 4)))
|
||||
|
||||
(apl-test
|
||||
"each abs vector"
|
||||
(rv (apl-each apl-abs (make-array (list 4) (list -1 2 -3 4))))
|
||||
(list 1 2 3 4))
|
||||
|
||||
(apl-test "each scalar" (rv (apl-each apl-neg-m (apl-scalar 5))) (list -5))
|
||||
|
||||
(apl-test
|
||||
"each scalar shape"
|
||||
(sh (apl-each apl-neg-m (apl-scalar 5)))
|
||||
(list))
|
||||
|
||||
(apl-test
|
||||
"each negate matrix shape"
|
||||
(sh (apl-each apl-neg-m (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list 2 3))
|
||||
|
||||
(apl-test
|
||||
"each negate matrix values"
|
||||
(rv (apl-each apl-neg-m (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list -1 -2 -3 -4 -5 -6))
|
||||
|
||||
(apl-test
|
||||
"each-dyadic scalar+scalar"
|
||||
(rv (apl-each-dyadic apl-add (apl-scalar 3) (apl-scalar 4)))
|
||||
(list 7))
|
||||
|
||||
(apl-test
|
||||
"each-dyadic scalar+vector"
|
||||
(rv
|
||||
(apl-each-dyadic
|
||||
apl-add
|
||||
(apl-scalar 10)
|
||||
(make-array (list 3) (list 1 2 3))))
|
||||
(list 11 12 13))
|
||||
|
||||
(apl-test
|
||||
"each-dyadic vector+scalar"
|
||||
(rv
|
||||
(apl-each-dyadic
|
||||
apl-add
|
||||
(make-array (list 3) (list 1 2 3))
|
||||
(apl-scalar 10)))
|
||||
(list 11 12 13))
|
||||
|
||||
(apl-test
|
||||
"each-dyadic vector+vector"
|
||||
(rv
|
||||
(apl-each-dyadic
|
||||
apl-add
|
||||
(make-array (list 3) (list 1 2 3))
|
||||
(make-array (list 3) (list 10 20 30))))
|
||||
(list 11 22 33))
|
||||
|
||||
(apl-test
|
||||
"each-dyadic mul matrix+matrix shape"
|
||||
(sh
|
||||
(apl-each-dyadic
|
||||
apl-mul
|
||||
(make-array (list 2 2) (list 1 2 3 4))
|
||||
(make-array (list 2 2) (list 5 6 7 8))))
|
||||
(list 2 2))
|
||||
|
||||
(apl-test
|
||||
"each-dyadic mul matrix+matrix values"
|
||||
(rv
|
||||
(apl-each-dyadic
|
||||
apl-mul
|
||||
(make-array (list 2 2) (list 1 2 3 4))
|
||||
(make-array (list 2 2) (list 5 6 7 8))))
|
||||
(list 5 12 21 32))
|
||||
|
||||
(apl-test
|
||||
"outer product mult table values"
|
||||
(rv
|
||||
(apl-outer
|
||||
apl-mul
|
||||
(make-array (list 3) (list 1 2 3))
|
||||
(make-array (list 3) (list 1 2 3))))
|
||||
(list 1 2 3 2 4 6 3 6 9))
|
||||
|
||||
(apl-test
|
||||
"outer product mult table shape"
|
||||
(sh
|
||||
(apl-outer
|
||||
apl-mul
|
||||
(make-array (list 3) (list 1 2 3))
|
||||
(make-array (list 3) (list 1 2 3))))
|
||||
(list 3 3))
|
||||
|
||||
(apl-test
|
||||
"outer product add table values"
|
||||
(rv
|
||||
(apl-outer
|
||||
apl-add
|
||||
(make-array (list 2) (list 1 2))
|
||||
(make-array (list 3) (list 10 20 30))))
|
||||
(list 11 21 31 12 22 32))
|
||||
|
||||
(apl-test
|
||||
"outer product add table shape"
|
||||
(sh
|
||||
(apl-outer
|
||||
apl-add
|
||||
(make-array (list 2) (list 1 2))
|
||||
(make-array (list 3) (list 10 20 30))))
|
||||
(list 2 3))
|
||||
|
||||
(apl-test
|
||||
"outer product scalar+vector shape"
|
||||
(sh
|
||||
(apl-outer apl-mul (apl-scalar 5) (make-array (list 3) (list 1 2 3))))
|
||||
(list 3))
|
||||
|
||||
(apl-test
|
||||
"outer product scalar+vector values"
|
||||
(rv
|
||||
(apl-outer apl-mul (apl-scalar 5) (make-array (list 3) (list 1 2 3))))
|
||||
(list 5 10 15))
|
||||
|
||||
(apl-test
|
||||
"outer product vector+scalar shape"
|
||||
(sh
|
||||
(apl-outer apl-mul (make-array (list 3) (list 1 2 3)) (apl-scalar 10)))
|
||||
(list 3))
|
||||
|
||||
(apl-test
|
||||
"outer product scalar+scalar"
|
||||
(rv (apl-outer apl-mul (apl-scalar 6) (apl-scalar 7)))
|
||||
(list 42))
|
||||
|
||||
(apl-test
|
||||
"outer product scalar+scalar shape"
|
||||
(sh (apl-outer apl-mul (apl-scalar 6) (apl-scalar 7)))
|
||||
(list))
|
||||
|
||||
(apl-test
|
||||
"outer product equality identity matrix values"
|
||||
(rv
|
||||
(apl-outer
|
||||
apl-eq
|
||||
(make-array (list 3) (list 1 2 3))
|
||||
(make-array (list 3) (list 1 2 3))))
|
||||
(list 1 0 0 0 1 0 0 0 1))
|
||||
|
||||
(apl-test
|
||||
"outer product matrix+vector rank doubling shape"
|
||||
(sh
|
||||
(apl-outer
|
||||
apl-add
|
||||
(make-array (list 2 2) (list 1 2 3 4))
|
||||
(make-array (list 3) (list 10 20 30))))
|
||||
(list 2 2 3))
|
||||
|
||||
(apl-test
|
||||
"outer product matrix+vector rank doubling values"
|
||||
(rv
|
||||
(apl-outer
|
||||
apl-add
|
||||
(make-array (list 2 2) (list 1 2 3 4))
|
||||
(make-array (list 3) (list 10 20 30))))
|
||||
(list 11 21 31 12 22 32 13 23 33 14 24 34))
|
||||
|
||||
(apl-test
|
||||
"inner +.× dot product"
|
||||
(rv
|
||||
(apl-inner
|
||||
apl-add
|
||||
apl-mul
|
||||
(make-array (list 3) (list 1 2 3))
|
||||
(make-array (list 3) (list 4 5 6))))
|
||||
(list 32))
|
||||
|
||||
(apl-test
|
||||
"inner +.× dot product shape is scalar"
|
||||
(sh
|
||||
(apl-inner
|
||||
apl-add
|
||||
apl-mul
|
||||
(make-array (list 3) (list 1 2 3))
|
||||
(make-array (list 3) (list 4 5 6))))
|
||||
(list))
|
||||
|
||||
(apl-test
|
||||
"inner +.× matrix multiply 2x3 * 3x2 shape"
|
||||
(sh
|
||||
(apl-inner
|
||||
apl-add
|
||||
apl-mul
|
||||
(make-array (list 2 3) (list 1 2 3 4 5 6))
|
||||
(make-array (list 3 2) (list 7 8 9 10 11 12))))
|
||||
(list 2 2))
|
||||
|
||||
(apl-test
|
||||
"inner +.× matrix multiply 2x3 * 3x2 values"
|
||||
(rv
|
||||
(apl-inner
|
||||
apl-add
|
||||
apl-mul
|
||||
(make-array (list 2 3) (list 1 2 3 4 5 6))
|
||||
(make-array (list 3 2) (list 7 8 9 10 11 12))))
|
||||
(list 58 64 139 154))
|
||||
|
||||
(apl-test
|
||||
"inner +.× identity matrix 2x2"
|
||||
(rv
|
||||
(apl-inner
|
||||
apl-add
|
||||
apl-mul
|
||||
(make-array (list 2 2) (list 1 0 0 1))
|
||||
(make-array (list 2 2) (list 5 6 7 8))))
|
||||
(list 5 6 7 8))
|
||||
|
||||
(apl-test
|
||||
"inner ∧.= equal vectors"
|
||||
(rv
|
||||
(apl-inner
|
||||
apl-and
|
||||
apl-eq
|
||||
(make-array (list 3) (list 1 2 3))
|
||||
(make-array (list 3) (list 1 2 3))))
|
||||
(list 1))
|
||||
|
||||
(apl-test
|
||||
"inner ∧.= unequal vectors"
|
||||
(rv
|
||||
(apl-inner
|
||||
apl-and
|
||||
apl-eq
|
||||
(make-array (list 3) (list 1 2 3))
|
||||
(make-array (list 3) (list 1 9 3))))
|
||||
(list 0))
|
||||
|
||||
(apl-test
|
||||
"inner +.× matrix * vector shape"
|
||||
(sh
|
||||
(apl-inner
|
||||
apl-add
|
||||
apl-mul
|
||||
(make-array (list 2 3) (list 1 2 3 4 5 6))
|
||||
(make-array (list 3) (list 7 8 9))))
|
||||
(list 2))
|
||||
|
||||
(apl-test
|
||||
"inner +.× matrix * vector values"
|
||||
(rv
|
||||
(apl-inner
|
||||
apl-add
|
||||
apl-mul
|
||||
(make-array (list 2 3) (list 1 2 3 4 5 6))
|
||||
(make-array (list 3) (list 7 8 9))))
|
||||
(list 50 122))
|
||||
|
||||
(apl-test
|
||||
"inner +.× vector * matrix shape"
|
||||
(sh
|
||||
(apl-inner
|
||||
apl-add
|
||||
apl-mul
|
||||
(make-array (list 3) (list 1 2 3))
|
||||
(make-array (list 3 2) (list 4 5 6 7 8 9))))
|
||||
(list 2))
|
||||
|
||||
(apl-test
|
||||
"inner +.× vector * matrix values"
|
||||
(rv
|
||||
(apl-inner
|
||||
apl-add
|
||||
apl-mul
|
||||
(make-array (list 3) (list 1 2 3))
|
||||
(make-array (list 3 2) (list 4 5 6 7 8 9))))
|
||||
(list 40 46))
|
||||
|
||||
(apl-test
|
||||
"inner +.× single-element vectors"
|
||||
(rv
|
||||
(apl-inner
|
||||
apl-add
|
||||
apl-mul
|
||||
(make-array (list 1) (list 6))
|
||||
(make-array (list 1) (list 7))))
|
||||
(list 42))
|
||||
|
||||
(apl-test
|
||||
"commute +⍨ scalar doubles"
|
||||
(rv (apl-commute apl-add (apl-scalar 5)))
|
||||
(list 10))
|
||||
|
||||
(apl-test
|
||||
"commute ×⍨ vector squares"
|
||||
(rv (apl-commute apl-mul (make-array (list 4) (list 1 2 3 4))))
|
||||
(list 1 4 9 16))
|
||||
|
||||
(apl-test
|
||||
"commute +⍨ vector doubles"
|
||||
(rv (apl-commute apl-add (make-array (list 3) (list 1 2 3))))
|
||||
(list 2 4 6))
|
||||
|
||||
(apl-test
|
||||
"commute +⍨ shape preserved"
|
||||
(sh (apl-commute apl-add (make-array (list 3) (list 1 2 3))))
|
||||
(list 3))
|
||||
|
||||
(apl-test
|
||||
"commute ×⍨ matrix shape preserved"
|
||||
(sh (apl-commute apl-mul (make-array (list 2 2) (list 1 2 3 4))))
|
||||
(list 2 2))
|
||||
|
||||
(apl-test
|
||||
"commute-dyadic -⍨ swaps subtraction"
|
||||
(rv (apl-commute-dyadic apl-sub (apl-scalar 5) (apl-scalar 3)))
|
||||
(list -2))
|
||||
|
||||
(apl-test
|
||||
"commute-dyadic ÷⍨ swaps division"
|
||||
(rv (apl-commute-dyadic apl-div (apl-scalar 4) (apl-scalar 12)))
|
||||
(list 3))
|
||||
|
||||
(apl-test
|
||||
"commute-dyadic -⍨ on vectors"
|
||||
(rv
|
||||
(apl-commute-dyadic
|
||||
apl-sub
|
||||
(make-array (list 3) (list 10 20 30))
|
||||
(make-array (list 3) (list 1 2 3))))
|
||||
(list -9 -18 -27))
|
||||
|
||||
(apl-test
|
||||
"commute-dyadic +⍨ commutative same result"
|
||||
(rv
|
||||
(apl-commute-dyadic
|
||||
apl-add
|
||||
(make-array (list 3) (list 1 2 3))
|
||||
(make-array (list 3) (list 10 20 30))))
|
||||
(list 11 22 33))
|
||||
|
||||
(apl-test
|
||||
"commute-dyadic ×⍨ commutative same result"
|
||||
(rv
|
||||
(apl-commute-dyadic
|
||||
apl-mul
|
||||
(make-array (list 3) (list 2 3 4))
|
||||
(make-array (list 3) (list 5 6 7))))
|
||||
(list 10 18 28))
|
||||
|
||||
(apl-test
|
||||
"compose -∘| scalar (negative abs)"
|
||||
(rv (apl-compose apl-neg-m apl-abs (apl-scalar -7)))
|
||||
(list -7))
|
||||
|
||||
(apl-test
|
||||
"compose -∘| vector"
|
||||
(rv
|
||||
(apl-compose apl-neg-m apl-abs (make-array (list 4) (list -1 2 -3 4))))
|
||||
(list -1 -2 -3 -4))
|
||||
|
||||
(apl-test
|
||||
"compose ⌊∘- (floor of negate)"
|
||||
(rv (apl-compose apl-floor apl-neg-m (make-array (list 3) (list 1 2 3))))
|
||||
(list -1 -2 -3))
|
||||
|
||||
(apl-test
|
||||
"compose -∘| matrix shape preserved"
|
||||
(sh
|
||||
(apl-compose apl-neg-m apl-abs (make-array (list 2 2) (list -1 2 -3 4))))
|
||||
(list 2 2))
|
||||
|
||||
(apl-test
|
||||
"compose-dyadic +∘- equals subtract scalar"
|
||||
(rv (apl-compose-dyadic apl-add apl-neg-m (apl-scalar 10) (apl-scalar 3)))
|
||||
(list 7))
|
||||
|
||||
(apl-test
|
||||
"compose-dyadic +∘- equals subtract vector"
|
||||
(rv
|
||||
(apl-compose-dyadic
|
||||
apl-add
|
||||
apl-neg-m
|
||||
(make-array (list 3) (list 10 20 30))
|
||||
(make-array (list 3) (list 1 2 3))))
|
||||
(list 9 18 27))
|
||||
|
||||
(apl-test
|
||||
"compose-dyadic -∘| (subtract abs)"
|
||||
(rv (apl-compose-dyadic apl-sub apl-abs (apl-scalar 10) (apl-scalar -3)))
|
||||
(list 7))
|
||||
|
||||
(apl-test
|
||||
"compose-dyadic ×∘- (multiply by negative)"
|
||||
(rv
|
||||
(apl-compose-dyadic
|
||||
apl-mul
|
||||
apl-neg-m
|
||||
(make-array (list 3) (list 2 3 4))
|
||||
(make-array (list 3) (list 1 2 3))))
|
||||
(list -2 -6 -12))
|
||||
|
||||
(apl-test
|
||||
"compose-dyadic shape preserved"
|
||||
(sh
|
||||
(apl-compose-dyadic
|
||||
apl-add
|
||||
apl-neg-m
|
||||
(make-array (list 2 3) (list 1 2 3 4 5 6))
|
||||
(make-array (list 2 3) (list 1 1 1 1 1 1))))
|
||||
(list 2 3))
|
||||
|
||||
(apl-test
|
||||
"power n=0 identity"
|
||||
(rv (apl-power (fn (a) (apl-add a (apl-scalar 1))) 0 (apl-scalar 5)))
|
||||
(list 5))
|
||||
|
||||
(apl-test
|
||||
"power increment by 3"
|
||||
(rv (apl-power (fn (a) (apl-add a (apl-scalar 1))) 3 (apl-scalar 0)))
|
||||
(list 3))
|
||||
|
||||
(apl-test
|
||||
"power double 4 times = 16"
|
||||
(rv (apl-power (fn (a) (apl-mul a (apl-scalar 2))) 4 (apl-scalar 1)))
|
||||
(list 16))
|
||||
|
||||
(apl-test
|
||||
"power on vector +5"
|
||||
(rv
|
||||
(apl-power
|
||||
(fn (a) (apl-add a (apl-scalar 1)))
|
||||
5
|
||||
(make-array (list 3) (list 1 2 3))))
|
||||
(list 6 7 8))
|
||||
|
||||
(apl-test
|
||||
"power on vector preserves shape"
|
||||
(sh
|
||||
(apl-power
|
||||
(fn (a) (apl-add a (apl-scalar 1)))
|
||||
5
|
||||
(make-array (list 3) (list 1 2 3))))
|
||||
(list 3))
|
||||
|
||||
(apl-test
|
||||
"power on matrix"
|
||||
(rv
|
||||
(apl-power
|
||||
(fn (a) (apl-mul a (apl-scalar 3)))
|
||||
2
|
||||
(make-array (list 2 2) (list 1 2 3 4))))
|
||||
(list 9 18 27 36))
|
||||
|
||||
(apl-test
|
||||
"power-fixed identity stops immediately"
|
||||
(rv (apl-power-fixed (fn (a) a) (make-array (list 3) (list 1 2 3))))
|
||||
(list 1 2 3))
|
||||
|
||||
(apl-test
|
||||
"power-fixed floor half scalar to 0"
|
||||
(rv
|
||||
(apl-power-fixed
|
||||
(fn (a) (apl-floor (apl-div a (apl-scalar 2))))
|
||||
(apl-scalar 100)))
|
||||
(list 0))
|
||||
|
||||
(apl-test
|
||||
"power-fixed shape preserved"
|
||||
(sh
|
||||
(apl-power-fixed (fn (a) a) (make-array (list 2 2) (list 1 2 3 4))))
|
||||
(list 2 2))
|
||||
|
||||
(apl-test
|
||||
"rank tally⍤1 row tallies"
|
||||
(rv (apl-rank apl-tally 1 (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list 3 3))
|
||||
|
||||
(apl-test
|
||||
"rank tally⍤1 row tallies shape"
|
||||
(sh (apl-rank apl-tally 1 (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list 2))
|
||||
|
||||
(apl-test
|
||||
"rank neg⍤0 vector scalar cells"
|
||||
(rv (apl-rank apl-neg-m 0 (make-array (list 3) (list 1 2 3))))
|
||||
(list -1 -2 -3))
|
||||
|
||||
(apl-test
|
||||
"rank neg⍤0 vector preserves shape"
|
||||
(sh (apl-rank apl-neg-m 0 (make-array (list 3) (list 1 2 3))))
|
||||
(list 3))
|
||||
|
||||
(apl-test
|
||||
"rank neg⍤1 matrix per-row"
|
||||
(rv (apl-rank apl-neg-m 1 (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list -1 -2 -3 -4 -5 -6))
|
||||
|
||||
(apl-test
|
||||
"rank neg⍤1 matrix preserves shape"
|
||||
(sh (apl-rank apl-neg-m 1 (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list 2 3))
|
||||
|
||||
(apl-test
|
||||
"rank k>=rank fallthrough"
|
||||
(rv (apl-rank apl-tally 5 (make-array (list 4) (list 1 2 3 4))))
|
||||
(list 4))
|
||||
|
||||
(apl-test
|
||||
"rank tally⍤2 whole matrix tally"
|
||||
(rv
|
||||
(apl-rank
|
||||
apl-tally
|
||||
2
|
||||
(make-array (list 3 5) (list 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15))))
|
||||
(list 3))
|
||||
|
||||
(apl-test
|
||||
"rank reverse⍤1 matrix reverse rows"
|
||||
(rv (apl-rank apl-reverse 1 (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list 3 2 1 6 5 4))
|
||||
|
||||
(apl-test
|
||||
"rank tally⍤1 3x4 row tallies"
|
||||
(rv
|
||||
(apl-rank
|
||||
apl-tally
|
||||
1
|
||||
(make-array (list 3 4) (list 1 2 3 4 5 6 7 8 9 10 11 12))))
|
||||
(list 4 4 4))
|
||||
|
||||
(apl-test
|
||||
"at-replace single index"
|
||||
(rv
|
||||
(apl-at-replace
|
||||
(apl-scalar 99)
|
||||
(make-array (list 1) (list 2))
|
||||
(make-array (list 5) (list 1 2 3 4 5))))
|
||||
(list 1 99 3 4 5))
|
||||
|
||||
(apl-test
|
||||
"at-replace multiple indices vector vals"
|
||||
(rv
|
||||
(apl-at-replace
|
||||
(make-array (list 2) (list 99 88))
|
||||
(make-array (list 2) (list 2 4))
|
||||
(make-array (list 5) (list 1 2 3 4 5))))
|
||||
(list 1 99 3 88 5))
|
||||
|
||||
(apl-test
|
||||
"at-replace scalar broadcast"
|
||||
(rv
|
||||
(apl-at-replace
|
||||
(apl-scalar 0)
|
||||
(make-array (list 3) (list 1 3 5))
|
||||
(make-array (list 5) (list 10 20 30 40 50))))
|
||||
(list 0 20 0 40 0))
|
||||
|
||||
(apl-test
|
||||
"at-replace preserves shape"
|
||||
(sh
|
||||
(apl-at-replace
|
||||
(apl-scalar 99)
|
||||
(make-array (list 1) (list 2))
|
||||
(make-array (list 5) (list 1 2 3 4 5))))
|
||||
(list 5))
|
||||
|
||||
(apl-test
|
||||
"at-replace last index"
|
||||
(rv
|
||||
(apl-at-replace
|
||||
(apl-scalar 99)
|
||||
(make-array (list 1) (list 5))
|
||||
(make-array (list 5) (list 1 2 3 4 5))))
|
||||
(list 1 2 3 4 99))
|
||||
|
||||
(apl-test
|
||||
"at-replace on matrix linear-index"
|
||||
(rv
|
||||
(apl-at-replace
|
||||
(apl-scalar 99)
|
||||
(make-array (list 1) (list 3))
|
||||
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list 1 2 99 4 5 6))
|
||||
|
||||
(apl-test
|
||||
"at-apply negate at indices"
|
||||
(rv
|
||||
(apl-at-apply
|
||||
apl-neg-m
|
||||
(make-array (list 3) (list 1 3 5))
|
||||
(make-array (list 5) (list 1 2 3 4 5))))
|
||||
(list -1 2 -3 4 -5))
|
||||
|
||||
(apl-test
|
||||
"at-apply double at index 1"
|
||||
(rv
|
||||
(apl-at-apply
|
||||
(fn (a) (apl-mul a (apl-scalar 2)))
|
||||
(make-array (list 1) (list 1))
|
||||
(make-array (list 2) (list 5 10))))
|
||||
(list 10 10))
|
||||
|
||||
(apl-test
|
||||
"at-apply preserves shape"
|
||||
(sh
|
||||
(apl-at-apply
|
||||
apl-neg-m
|
||||
(make-array (list 2) (list 1 3))
|
||||
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list 2 3))
|
||||
|
||||
(apl-test
|
||||
"at-apply on matrix linear-index"
|
||||
(rv
|
||||
(apl-at-apply
|
||||
apl-neg-m
|
||||
(make-array (list 2) (list 1 6))
|
||||
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list -1 2 3 4 5 -6))
|
||||
340
lib/apl/tests/parse.sx
Normal file
340
lib/apl/tests/parse.sx
Normal file
@@ -0,0 +1,340 @@
|
||||
(define apl-test-count 0)
|
||||
(define apl-test-pass 0)
|
||||
(define apl-test-fails (list))
|
||||
|
||||
(define apl-test
|
||||
(fn (name actual expected)
|
||||
(begin
|
||||
(set! apl-test-count (+ apl-test-count 1))
|
||||
(if (= actual expected)
|
||||
(set! apl-test-pass (+ apl-test-pass 1))
|
||||
(append! apl-test-fails {:name name :actual actual :expected expected})))))
|
||||
|
||||
(define tok-types
|
||||
(fn (src)
|
||||
(map (fn (t) (get t :type)) (apl-tokenize src))))
|
||||
|
||||
(define tok-values
|
||||
(fn (src)
|
||||
(map (fn (t) (get t :value)) (apl-tokenize src))))
|
||||
|
||||
(define tok-count
|
||||
(fn (src)
|
||||
(len (apl-tokenize src))))
|
||||
|
||||
(define tok-type-at
|
||||
(fn (src i)
|
||||
(get (nth (apl-tokenize src) i) :type)))
|
||||
|
||||
(define tok-value-at
|
||||
(fn (src i)
|
||||
(get (nth (apl-tokenize src) i) :value)))
|
||||
|
||||
(apl-test "empty: no tokens" (tok-count "") 0)
|
||||
(apl-test "empty: whitespace only" (tok-count " ") 0)
|
||||
(apl-test "num: zero" (tok-values "0") (list 0))
|
||||
(apl-test "num: positive" (tok-values "42") (list 42))
|
||||
(apl-test "num: large" (tok-values "12345") (list 12345))
|
||||
(apl-test "num: negative" (tok-values "¯5") (list -5))
|
||||
(apl-test "num: negative zero" (tok-values "¯0") (list 0))
|
||||
(apl-test "num: strand count" (tok-count "1 2 3") 3)
|
||||
(apl-test "num: strand types" (tok-types "1 2 3") (list :num :num :num))
|
||||
(apl-test "num: strand values" (tok-values "1 2 3") (list 1 2 3))
|
||||
(apl-test "num: neg in strand" (tok-values "1 ¯2 3") (list 1 -2 3))
|
||||
(apl-test "str: empty" (tok-values "''") (list ""))
|
||||
(apl-test "str: single char" (tok-values "'a'") (list "a"))
|
||||
(apl-test "str: word" (tok-values "'hello'") (list "hello"))
|
||||
(apl-test "str: escaped quote" (tok-values "''''") (list "'"))
|
||||
(apl-test "str: type" (tok-types "'abc'") (list :str))
|
||||
(apl-test "name: simple" (tok-values "foo") (list "foo"))
|
||||
(apl-test "name: type" (tok-types "foo") (list :name))
|
||||
(apl-test "name: mixed case" (tok-values "MyVar") (list "MyVar"))
|
||||
(apl-test "name: with digits" (tok-values "x1") (list "x1"))
|
||||
(apl-test "name: system var" (tok-values "⎕IO") (list "⎕IO"))
|
||||
(apl-test "name: system var type" (tok-types "⎕IO") (list :name))
|
||||
(apl-test "glyph: plus" (tok-types "+") (list :glyph))
|
||||
(apl-test "glyph: plus value" (tok-values "+") (list "+"))
|
||||
(apl-test "glyph: iota" (tok-values "⍳") (list "⍳"))
|
||||
(apl-test "glyph: reduce" (tok-values "+/") (list "+" "/"))
|
||||
(apl-test "glyph: floor" (tok-values "⌊") (list "⌊"))
|
||||
(apl-test "glyph: rho" (tok-values "⍴") (list "⍴"))
|
||||
(apl-test "glyph: alpha omega" (tok-types "⍺ ⍵") (list :glyph :glyph))
|
||||
(apl-test "punct: lparen" (tok-types "(") (list :lparen))
|
||||
(apl-test "punct: rparen" (tok-types ")") (list :rparen))
|
||||
(apl-test "punct: brackets" (tok-types "[42]") (list :lbracket :num :rbracket))
|
||||
(apl-test "punct: braces" (tok-types "{}") (list :lbrace :rbrace))
|
||||
(apl-test "punct: semi" (tok-types ";") (list :semi))
|
||||
(apl-test "assign: arrow" (tok-types "x←1") (list :name :assign :num))
|
||||
(apl-test "diamond: separator" (tok-types "1⋄2") (list :num :diamond :num))
|
||||
(apl-test "newline: emitted" (tok-types "1\n2") (list :num :newline :num))
|
||||
(apl-test "comment: skipped" (tok-count "⍝ ignore me") 0)
|
||||
(apl-test "comment: rest ignored" (tok-count "1 ⍝ note") 1)
|
||||
(apl-test "colon: bare" (tok-types ":") (list :colon))
|
||||
(apl-test "keyword: If" (tok-values ":If") (list ":If"))
|
||||
(apl-test "keyword: type" (tok-types ":While") (list :keyword))
|
||||
(apl-test "keyword: EndFor" (tok-values ":EndFor") (list ":EndFor"))
|
||||
(apl-test "expr: +/ ⍳ 5" (tok-types "+/ ⍳ 5") (list :glyph :glyph :glyph :num))
|
||||
(apl-test "expr: x←42" (tok-count "x←42") 3)
|
||||
(apl-test "expr: dfn body" (tok-types "{⍺+⍵}")
|
||||
(list :lbrace :glyph :glyph :glyph :rbrace))
|
||||
|
||||
(define apl-tokenize-test-summary
|
||||
(str "tokenizer " apl-test-pass "/" apl-test-count
|
||||
(if (= (len apl-test-fails) 0) "" (str " FAILS: " apl-test-fails))))
|
||||
|
||||
; ===========================================================================
|
||||
; Parser tests
|
||||
; ===========================================================================
|
||||
|
||||
; Helper: parse an APL source string and return the AST
|
||||
(define parse
|
||||
(fn (src) (parse-apl src)))
|
||||
|
||||
; Helper: build an expected AST node using keyword-tagged lists
|
||||
(define num-node (fn (n) (list :num n)))
|
||||
(define str-node (fn (s) (list :str s)))
|
||||
(define name-node (fn (n) (list :name n)))
|
||||
(define fn-node (fn (g) (list :fn-glyph g)))
|
||||
(define fn-nm (fn (n) (list :fn-name n)))
|
||||
(define assign-node (fn (nm expr) (list :assign nm expr)))
|
||||
(define monad-node (fn (f a) (list :monad f a)))
|
||||
(define dyad-node (fn (f l r) (list :dyad f l r)))
|
||||
(define derived-fn (fn (op f) (list :derived-fn op f)))
|
||||
(define derived-fn2 (fn (op f g) (list :derived-fn2 op f g)))
|
||||
(define outer-node (fn (f) (list :outer "∘." f)))
|
||||
(define guard-node (fn (c e) (list :guard c e)))
|
||||
|
||||
; ---- numeric literals ----
|
||||
|
||||
(apl-test "parse: num literal"
|
||||
(parse "42")
|
||||
(num-node 42))
|
||||
|
||||
(apl-test "parse: negative num"
|
||||
(parse "¯3")
|
||||
(num-node -3))
|
||||
|
||||
(apl-test "parse: zero"
|
||||
(parse "0")
|
||||
(num-node 0))
|
||||
|
||||
; ---- string literals ----
|
||||
|
||||
(apl-test "parse: str literal"
|
||||
(parse "'hello'")
|
||||
(str-node "hello"))
|
||||
|
||||
(apl-test "parse: empty str"
|
||||
(parse "''")
|
||||
(str-node ""))
|
||||
|
||||
; ---- name reference ----
|
||||
|
||||
(apl-test "parse: name"
|
||||
(parse "x")
|
||||
(name-node "x"))
|
||||
|
||||
(apl-test "parse: system name"
|
||||
(parse "⎕IO")
|
||||
(name-node "⎕IO"))
|
||||
|
||||
; ---- strands (vec nodes) ----
|
||||
|
||||
(apl-test "parse: strand 3 nums"
|
||||
(parse "1 2 3")
|
||||
(list :vec (num-node 1) (num-node 2) (num-node 3)))
|
||||
|
||||
(apl-test "parse: strand 2 nums"
|
||||
(parse "1 2")
|
||||
(list :vec (num-node 1) (num-node 2)))
|
||||
|
||||
(apl-test "parse: strand with negatives"
|
||||
(parse "1 ¯2 3")
|
||||
(list :vec (num-node 1) (num-node -2) (num-node 3)))
|
||||
|
||||
; ---- assignment ----
|
||||
|
||||
(apl-test "parse: assignment"
|
||||
(parse "x←42")
|
||||
(assign-node "x" (num-node 42)))
|
||||
|
||||
(apl-test "parse: assignment with spaces"
|
||||
(parse "x ← 42")
|
||||
(assign-node "x" (num-node 42)))
|
||||
|
||||
(apl-test "parse: assignment of expr"
|
||||
(parse "r←2+3")
|
||||
(assign-node "r" (dyad-node (fn-node "+") (num-node 2) (num-node 3))))
|
||||
|
||||
; ---- monadic functions ----
|
||||
|
||||
(apl-test "parse: monadic iota"
|
||||
(parse "⍳5")
|
||||
(monad-node (fn-node "⍳") (num-node 5)))
|
||||
|
||||
(apl-test "parse: monadic iota with space"
|
||||
(parse "⍳ 5")
|
||||
(monad-node (fn-node "⍳") (num-node 5)))
|
||||
|
||||
(apl-test "parse: monadic negate"
|
||||
(parse "-3")
|
||||
(monad-node (fn-node "-") (num-node 3)))
|
||||
|
||||
(apl-test "parse: monadic floor"
|
||||
(parse "⌊2")
|
||||
(monad-node (fn-node "⌊") (num-node 2)))
|
||||
|
||||
(apl-test "parse: monadic of name"
|
||||
(parse "⍴x")
|
||||
(monad-node (fn-node "⍴") (name-node "x")))
|
||||
|
||||
; ---- dyadic functions ----
|
||||
|
||||
(apl-test "parse: dyadic plus"
|
||||
(parse "2+3")
|
||||
(dyad-node (fn-node "+") (num-node 2) (num-node 3)))
|
||||
|
||||
(apl-test "parse: dyadic times"
|
||||
(parse "2×3")
|
||||
(dyad-node (fn-node "×") (num-node 2) (num-node 3)))
|
||||
|
||||
(apl-test "parse: dyadic with names"
|
||||
(parse "x+y")
|
||||
(dyad-node (fn-node "+") (name-node "x") (name-node "y")))
|
||||
|
||||
; ---- right-to-left evaluation ----
|
||||
|
||||
(apl-test "parse: right-to-left 2×3+4"
|
||||
(parse "2×3+4")
|
||||
(dyad-node (fn-node "×") (num-node 2)
|
||||
(dyad-node (fn-node "+") (num-node 3) (num-node 4))))
|
||||
|
||||
(apl-test "parse: right-to-left chain"
|
||||
(parse "1+2×3-4")
|
||||
(dyad-node (fn-node "+") (num-node 1)
|
||||
(dyad-node (fn-node "×") (num-node 2)
|
||||
(dyad-node (fn-node "-") (num-node 3) (num-node 4)))))
|
||||
|
||||
; ---- parenthesized subexpressions ----
|
||||
|
||||
(apl-test "parse: parens override order"
|
||||
(parse "(2+3)×4")
|
||||
(dyad-node (fn-node "×")
|
||||
(dyad-node (fn-node "+") (num-node 2) (num-node 3))
|
||||
(num-node 4)))
|
||||
|
||||
(apl-test "parse: nested parens"
|
||||
(parse "((2+3))")
|
||||
(dyad-node (fn-node "+") (num-node 2) (num-node 3)))
|
||||
|
||||
(apl-test "parse: paren in dyadic right"
|
||||
(parse "2×(3+4)")
|
||||
(dyad-node (fn-node "×") (num-node 2)
|
||||
(dyad-node (fn-node "+") (num-node 3) (num-node 4))))
|
||||
|
||||
; ---- operators → derived functions ----
|
||||
|
||||
(apl-test "parse: reduce +"
|
||||
(parse "+/x")
|
||||
(monad-node (derived-fn "/" (fn-node "+")) (name-node "x")))
|
||||
|
||||
(apl-test "parse: reduce iota"
|
||||
(parse "+/⍳5")
|
||||
(monad-node (derived-fn "/" (fn-node "+"))
|
||||
(monad-node (fn-node "⍳") (num-node 5))))
|
||||
|
||||
(apl-test "parse: scan"
|
||||
(parse "+\\x")
|
||||
(monad-node (derived-fn "\\" (fn-node "+")) (name-node "x")))
|
||||
|
||||
(apl-test "parse: each"
|
||||
(parse "⍳¨x")
|
||||
(monad-node (derived-fn "¨" (fn-node "⍳")) (name-node "x")))
|
||||
|
||||
(apl-test "parse: commute"
|
||||
(parse "-⍨3")
|
||||
(monad-node (derived-fn "⍨" (fn-node "-")) (num-node 3)))
|
||||
|
||||
(apl-test "parse: stacked ops"
|
||||
(parse "+/¨x")
|
||||
(monad-node (derived-fn "¨" (derived-fn "/" (fn-node "+"))) (name-node "x")))
|
||||
|
||||
; ---- outer product ----
|
||||
|
||||
(apl-test "parse: outer product monadic"
|
||||
(parse "∘.×")
|
||||
(outer-node (fn-node "×")))
|
||||
|
||||
(apl-test "parse: outer product dyadic names"
|
||||
(parse "x ∘.× y")
|
||||
(dyad-node (outer-node (fn-node "×")) (name-node "x") (name-node "y")))
|
||||
|
||||
(apl-test "parse: outer product dyadic strands"
|
||||
(parse "1 2 3 ∘.× 4 5 6")
|
||||
(dyad-node (outer-node (fn-node "×"))
|
||||
(list :vec (num-node 1) (num-node 2) (num-node 3))
|
||||
(list :vec (num-node 4) (num-node 5) (num-node 6))))
|
||||
|
||||
; ---- inner product ----
|
||||
|
||||
(apl-test "parse: inner product"
|
||||
(parse "+.×")
|
||||
(derived-fn2 "." (fn-node "+") (fn-node "×")))
|
||||
|
||||
(apl-test "parse: inner product applied"
|
||||
(parse "a +.× b")
|
||||
(dyad-node (derived-fn2 "." (fn-node "+") (fn-node "×"))
|
||||
(name-node "a") (name-node "b")))
|
||||
|
||||
; ---- dfn (anonymous function) ----
|
||||
|
||||
(apl-test "parse: simple dfn"
|
||||
(parse "{⍺+⍵}")
|
||||
(list :dfn (dyad-node (fn-node "+") (name-node "⍺") (name-node "⍵"))))
|
||||
|
||||
(apl-test "parse: monadic dfn"
|
||||
(parse "{⍵×2}")
|
||||
(list :dfn (dyad-node (fn-node "×") (name-node "⍵") (num-node 2))))
|
||||
|
||||
(apl-test "parse: dfn self-ref"
|
||||
(parse "{⍵≤1:1 ⋄ ⍵×∇ ⍵-1}")
|
||||
(list :dfn
|
||||
(guard-node (dyad-node (fn-node "≤") (name-node "⍵") (num-node 1)) (num-node 1))
|
||||
(dyad-node (fn-node "×") (name-node "⍵")
|
||||
(monad-node (fn-node "∇") (dyad-node (fn-node "-") (name-node "⍵") (num-node 1))))))
|
||||
|
||||
; ---- dfn applied ----
|
||||
|
||||
(apl-test "parse: dfn as function"
|
||||
(parse "{⍺+⍵} 3")
|
||||
(monad-node
|
||||
(list :dfn (dyad-node (fn-node "+") (name-node "⍺") (name-node "⍵")))
|
||||
(num-node 3)))
|
||||
|
||||
; ---- multi-statement ----
|
||||
|
||||
(apl-test "parse: diamond separator"
|
||||
(let ((result (parse "x←1 ⋄ x+2")))
|
||||
(= (first result) :program))
|
||||
true)
|
||||
|
||||
(apl-test "parse: diamond first stmt"
|
||||
(let ((result (parse "x←1 ⋄ x+2")))
|
||||
(nth result 1))
|
||||
(assign-node "x" (num-node 1)))
|
||||
|
||||
(apl-test "parse: diamond second stmt"
|
||||
(let ((result (parse "x←1 ⋄ x+2")))
|
||||
(nth result 2))
|
||||
(dyad-node (fn-node "+") (name-node "x") (num-node 2)))
|
||||
|
||||
; ---- combined summary ----
|
||||
|
||||
(define apl-parse-test-count (- apl-test-count 46))
|
||||
(define apl-parse-test-pass (- apl-test-pass 46))
|
||||
|
||||
(define apl-test-summary
|
||||
(str
|
||||
"tokenizer 46/46 | "
|
||||
"parser " apl-parse-test-pass "/" apl-parse-test-count
|
||||
(if (= (len apl-test-fails) 0) "" (str " FAILS: " apl-test-fails))))
|
||||
180
lib/apl/tests/pipeline.sx
Normal file
180
lib/apl/tests/pipeline.sx
Normal file
@@ -0,0 +1,180 @@
|
||||
; End-to-end pipeline tests: source string → tokenize → parse → eval-ast → array.
|
||||
; Verifies the full stack as a single function call (apl-run).
|
||||
|
||||
(define mkrv (fn (arr) (get arr :ravel)))
|
||||
(define mksh (fn (arr) (get arr :shape)))
|
||||
|
||||
; ---------- scalars ----------
|
||||
|
||||
(apl-test "apl-run \"42\" → scalar 42" (mkrv (apl-run "42")) (list 42))
|
||||
|
||||
(apl-test "apl-run \"¯7\" → scalar -7" (mkrv (apl-run "¯7")) (list -7))
|
||||
|
||||
; ---------- strands ----------
|
||||
|
||||
(apl-test
|
||||
"apl-run \"1 2 3\" → vector"
|
||||
(mkrv (apl-run "1 2 3"))
|
||||
(list 1 2 3))
|
||||
|
||||
(apl-test "apl-run \"1 2 3\" shape" (mksh (apl-run "1 2 3")) (list 3))
|
||||
|
||||
; ---------- dyadic arithmetic ----------
|
||||
|
||||
(apl-test "apl-run \"2 + 3\" → 5" (mkrv (apl-run "2 + 3")) (list 5))
|
||||
|
||||
(apl-run "2 × 3 + 4") ; right-to-left
|
||||
|
||||
(apl-test
|
||||
"apl-run \"2 × 3 + 4\" → 14 (right-to-left)"
|
||||
(mkrv (apl-run "2 × 3 + 4"))
|
||||
(list 14))
|
||||
|
||||
(apl-test
|
||||
"apl-run \"1 2 3 + 4 5 6\" → 5 7 9"
|
||||
(mkrv (apl-run "1 2 3 + 4 5 6"))
|
||||
(list 5 7 9))
|
||||
|
||||
(apl-test
|
||||
"apl-run \"3 × 1 2 3 4\" → scalar broadcast"
|
||||
(mkrv (apl-run "3 × 1 2 3 4"))
|
||||
(list 3 6 9 12))
|
||||
|
||||
; ---------- monadic primitives ----------
|
||||
|
||||
(apl-test
|
||||
"apl-run \"⍳5\" → 1..5"
|
||||
(mkrv (apl-run "⍳5"))
|
||||
(list 1 2 3 4 5))
|
||||
|
||||
(apl-test
|
||||
"apl-run \"-3\" → -3 (monadic negate)"
|
||||
(mkrv (apl-run "-3"))
|
||||
(list -3))
|
||||
|
||||
(apl-test
|
||||
"apl-run \"⌈/ 1 3 9 5 7\" → 9 (max-reduce)"
|
||||
(mkrv (apl-run "⌈/ 1 3 9 5 7"))
|
||||
(list 9))
|
||||
|
||||
(apl-test
|
||||
"apl-run \"⌊/ 4 7 2 9 1 3\" → 1 (min-reduce)"
|
||||
(mkrv (apl-run "⌊/ 4 7 2 9 1 3"))
|
||||
(list 1))
|
||||
|
||||
; ---------- operators ----------
|
||||
|
||||
(apl-test "apl-run \"+/⍳5\" → 15" (mkrv (apl-run "+/⍳5")) (list 15))
|
||||
|
||||
(apl-test "apl-run \"×/⍳5\" → 120" (mkrv (apl-run "×/⍳5")) (list 120))
|
||||
|
||||
(apl-test
|
||||
"apl-run \"⌈/3 1 4 1 5 9 2\" → 9"
|
||||
(mkrv (apl-run "⌈/3 1 4 1 5 9 2"))
|
||||
(list 9))
|
||||
|
||||
(apl-test
|
||||
"apl-run \"+\\\\⍳5\" → triangular numbers"
|
||||
(mkrv (apl-run "+\\⍳5"))
|
||||
(list 1 3 6 10 15))
|
||||
|
||||
; ---------- outer / inner products ----------
|
||||
|
||||
(apl-test
|
||||
"apl-run \"1 2 3 ∘.× 1 2 3\" → mult table values"
|
||||
(mkrv (apl-run "1 2 3 ∘.× 1 2 3"))
|
||||
(list 1 2 3 2 4 6 3 6 9))
|
||||
|
||||
(apl-test
|
||||
"apl-run \"1 2 3 +.× 4 5 6\" → dot product 32"
|
||||
(mkrv (apl-run "1 2 3 +.× 4 5 6"))
|
||||
(list 32))
|
||||
|
||||
; ---------- shape ----------
|
||||
|
||||
(apl-test
|
||||
"apl-run \"⍴ 1 2 3 4 5\" → 5"
|
||||
(mkrv (apl-run "⍴ 1 2 3 4 5"))
|
||||
(list 5))
|
||||
|
||||
(apl-test "apl-run \"⍴⍳10\" → 10" (mkrv (apl-run "⍴⍳10")) (list 10))
|
||||
|
||||
; ---------- comparison ----------
|
||||
|
||||
(apl-test "apl-run \"3 < 5\" → 1" (mkrv (apl-run "3 < 5")) (list 1))
|
||||
|
||||
(apl-test "apl-run \"5 = 5\" → 1" (mkrv (apl-run "5 = 5")) (list 1))
|
||||
|
||||
(apl-test
|
||||
"apl-run \"1 2 3 = 1 0 3\" → 1 0 1"
|
||||
(mkrv (apl-run "1 2 3 = 1 0 3"))
|
||||
(list 1 0 1))
|
||||
|
||||
; ---------- famous one-liners ----------
|
||||
|
||||
(apl-test
|
||||
"apl-run \"+/(⍳10)\" → sum 1..10 = 55"
|
||||
(mkrv (apl-run "+/(⍳10)"))
|
||||
(list 55))
|
||||
|
||||
(apl-test
|
||||
"apl-run \"×/⍳10\" → 10! = 3628800"
|
||||
(mkrv (apl-run "×/⍳10"))
|
||||
(list 3628800))
|
||||
|
||||
(apl-test "apl-run \"⎕IO\" → 1" (mkrv (apl-run "⎕IO")) (list 1))
|
||||
|
||||
(apl-test "apl-run \"⎕ML\" → 1" (mkrv (apl-run "⎕ML")) (list 1))
|
||||
|
||||
(apl-test "apl-run \"⎕FR\" → 1248" (mkrv (apl-run "⎕FR")) (list 1248))
|
||||
|
||||
(apl-test "apl-run \"⎕TS\" shape (7)" (mksh (apl-run "⎕TS")) (list 7))
|
||||
|
||||
(apl-test "apl-run \"⎕FMT 42\" → \"42\"" (apl-run "⎕FMT 42") "42")
|
||||
|
||||
(apl-test
|
||||
"apl-run \"⎕FMT 1 2 3\" → \"1 2 3\""
|
||||
(apl-run "⎕FMT 1 2 3")
|
||||
"1 2 3")
|
||||
|
||||
(apl-test
|
||||
"apl-run \"⎕FMT ⍳5\" → \"1 2 3 4 5\""
|
||||
(apl-run "⎕FMT ⍳5")
|
||||
"1 2 3 4 5")
|
||||
|
||||
(apl-test "apl-run \"⎕IO + 4\" → 5" (mkrv (apl-run "⎕IO + 4")) (list 5))
|
||||
|
||||
(apl-test
|
||||
"apl-run \"(10 20 30 40 50)[3]\" → 30"
|
||||
(mkrv (apl-run "(10 20 30 40 50)[3]"))
|
||||
(list 30))
|
||||
|
||||
(apl-test
|
||||
"apl-run \"(⍳10)[5]\" → 5"
|
||||
(mkrv (apl-run "(⍳10)[5]"))
|
||||
(list 5))
|
||||
|
||||
(apl-test
|
||||
"apl-run \"A ← 100 200 300 ⋄ A[2]\" → 200"
|
||||
(mkrv (apl-run "A ← 100 200 300 ⋄ A[2]"))
|
||||
(list 200))
|
||||
|
||||
(apl-test
|
||||
"apl-run \"V ← ⍳10 ⋄ V[3]\" → 3"
|
||||
(mkrv (apl-run "V ← ⍳10 ⋄ V[3]"))
|
||||
(list 3))
|
||||
|
||||
(apl-test
|
||||
"apl-run \"(10 20 30)[1]\" → 10 (1-indexed)"
|
||||
(mkrv (apl-run "(10 20 30)[1]"))
|
||||
(list 10))
|
||||
|
||||
(apl-test
|
||||
"apl-run \"V ← 10 20 30 40 50 ⋄ V[3] + 1\" → 31"
|
||||
(mkrv (apl-run "V ← 10 20 30 40 50 ⋄ V[3] + 1"))
|
||||
(list 31))
|
||||
|
||||
(apl-test
|
||||
"apl-run \"(⍳5)[3] × 7\" → 21"
|
||||
(mkrv (apl-run "(⍳5)[3] × 7"))
|
||||
(list 21))
|
||||
304
lib/apl/tests/programs.sx
Normal file
304
lib/apl/tests/programs.sx
Normal file
@@ -0,0 +1,304 @@
|
||||
; Tests for classic APL programs (lib/apl/tests/programs/*.apl).
|
||||
; Programs are showcase APL source; runtime impl is in lib/apl/runtime.sx.
|
||||
|
||||
(define mkrv (fn (arr) (get arr :ravel)))
|
||||
(define mksh (fn (arr) (get arr :shape)))
|
||||
|
||||
; ===== primes (Sieve of Eratosthenes) =====
|
||||
|
||||
(apl-test "primes 1 → empty" (mkrv (apl-primes 1)) (list))
|
||||
|
||||
(apl-test "primes 2 → just 2" (mkrv (apl-primes 2)) (list 2))
|
||||
|
||||
(apl-test "primes 10 → 2 3 5 7" (mkrv (apl-primes 10)) (list 2 3 5 7))
|
||||
|
||||
(apl-test
|
||||
"primes 20 → 2 3 5 7 11 13 17 19"
|
||||
(mkrv (apl-primes 20))
|
||||
(list 2 3 5 7 11 13 17 19))
|
||||
|
||||
(apl-test
|
||||
"primes 30"
|
||||
(mkrv (apl-primes 30))
|
||||
(list 2 3 5 7 11 13 17 19 23 29))
|
||||
|
||||
(apl-test
|
||||
"primes 50"
|
||||
(mkrv (apl-primes 50))
|
||||
(list 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47))
|
||||
|
||||
(apl-test "primes 7 length" (first (mksh (apl-primes 7))) 4)
|
||||
|
||||
(apl-test "primes 100 has 25 primes" (first (mksh (apl-primes 100))) 25)
|
||||
|
||||
; ===== compress helper sanity =====
|
||||
|
||||
(apl-test
|
||||
"compress 1 0 1 0 1 / 10 20 30 40 50"
|
||||
(mkrv
|
||||
(apl-compress
|
||||
(make-array (list 5) (list 1 0 1 0 1))
|
||||
(make-array (list 5) (list 10 20 30 40 50))))
|
||||
(list 10 30 50))
|
||||
|
||||
(apl-test
|
||||
"compress all-zero mask → empty"
|
||||
(mkrv
|
||||
(apl-compress
|
||||
(make-array (list 3) (list 0 0 0))
|
||||
(make-array (list 3) (list 1 2 3))))
|
||||
(list))
|
||||
|
||||
(apl-test
|
||||
"compress all-one mask → full vector"
|
||||
(mkrv
|
||||
(apl-compress
|
||||
(make-array (list 3) (list 1 1 1))
|
||||
(make-array (list 3) (list 1 2 3))))
|
||||
(list 1 2 3))
|
||||
|
||||
(apl-test
|
||||
"life: empty 5x5 stays empty"
|
||||
(mkrv
|
||||
(apl-life-step
|
||||
(make-array
|
||||
(list 5 5)
|
||||
(list 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0))))
|
||||
(list 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0))
|
||||
|
||||
(apl-test
|
||||
"life: horizontal blinker → vertical blinker"
|
||||
(mkrv
|
||||
(apl-life-step
|
||||
(make-array
|
||||
(list 5 5)
|
||||
(list 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0))))
|
||||
(list 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0))
|
||||
|
||||
(apl-test
|
||||
"life: vertical blinker → horizontal blinker"
|
||||
(mkrv
|
||||
(apl-life-step
|
||||
(make-array
|
||||
(list 5 5)
|
||||
(list 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0))))
|
||||
(list 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0))
|
||||
|
||||
(apl-test
|
||||
"life: blinker has period 2"
|
||||
(mkrv
|
||||
(apl-life-step
|
||||
(apl-life-step
|
||||
(make-array
|
||||
(list 5 5)
|
||||
(list 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0)))))
|
||||
(list 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0))
|
||||
|
||||
(apl-test
|
||||
"life: 2x2 block stable on 5x5"
|
||||
(mkrv
|
||||
(apl-life-step
|
||||
(make-array
|
||||
(list 5 5)
|
||||
(list 0 0 0 0 0 0 1 1 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0))))
|
||||
(list 0 0 0 0 0 0 1 1 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0))
|
||||
|
||||
(apl-test
|
||||
"life: shape preserved"
|
||||
(mksh
|
||||
(apl-life-step
|
||||
(make-array
|
||||
(list 5 5)
|
||||
(list 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0))))
|
||||
(list 5 5))
|
||||
|
||||
(apl-test
|
||||
"life: glider on 6x6 advances"
|
||||
(mkrv
|
||||
(apl-life-step
|
||||
(make-array
|
||||
(list 6 6)
|
||||
(list
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
1
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
1
|
||||
0
|
||||
0
|
||||
0
|
||||
1
|
||||
1
|
||||
1
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0))))
|
||||
(list
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
1
|
||||
0
|
||||
1
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
1
|
||||
1
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
1
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0))
|
||||
|
||||
(apl-test
|
||||
"mandelbrot c=0 stays bounded"
|
||||
(mkrv (apl-mandelbrot-1d (make-array (list 1) (list 0)) 100))
|
||||
(list 100))
|
||||
|
||||
(apl-test
|
||||
"mandelbrot c=-1 cycle bounded"
|
||||
(mkrv (apl-mandelbrot-1d (make-array (list 1) (list -1)) 100))
|
||||
(list 100))
|
||||
|
||||
(apl-test
|
||||
"mandelbrot c=-2 boundary stays bounded"
|
||||
(mkrv (apl-mandelbrot-1d (make-array (list 1) (list -2)) 100))
|
||||
(list 100))
|
||||
|
||||
(apl-test
|
||||
"mandelbrot c=0.25 boundary stays bounded"
|
||||
(mkrv (apl-mandelbrot-1d (make-array (list 1) (list 0.25)) 100))
|
||||
(list 100))
|
||||
|
||||
(apl-test
|
||||
"mandelbrot c=1 escapes at iter 3"
|
||||
(mkrv (apl-mandelbrot-1d (make-array (list 1) (list 1)) 100))
|
||||
(list 3))
|
||||
|
||||
(apl-test
|
||||
"mandelbrot c=0.5 escapes at iter 5"
|
||||
(mkrv (apl-mandelbrot-1d (make-array (list 1) (list 0.5)) 100))
|
||||
(list 5))
|
||||
|
||||
(apl-test
|
||||
"mandelbrot batched grid (rank-polymorphic)"
|
||||
(mkrv (apl-mandelbrot-1d (make-array (list 5) (list -2 -1 0 1 2)) 10))
|
||||
(list 10 10 10 3 2))
|
||||
|
||||
(apl-test
|
||||
"mandelbrot batched preserves shape"
|
||||
(mksh (apl-mandelbrot-1d (make-array (list 5) (list -2 -1 0 1 2)) 10))
|
||||
(list 5))
|
||||
|
||||
(apl-test
|
||||
"mandelbrot c=-1.5 stays bounded"
|
||||
(mkrv (apl-mandelbrot-1d (make-array (list 1) (list -1.5)) 100))
|
||||
(list 100))
|
||||
|
||||
(apl-test "queens 1 → 1 solution" (mkrv (apl-queens 1)) (list 1))
|
||||
|
||||
(apl-test "queens 2 → 0 solutions" (mkrv (apl-queens 2)) (list 0))
|
||||
|
||||
(apl-test "queens 3 → 0 solutions" (mkrv (apl-queens 3)) (list 0))
|
||||
|
||||
(apl-test "queens 4 → 2 solutions" (mkrv (apl-queens 4)) (list 2))
|
||||
|
||||
(apl-test "queens 5 → 10 solutions" (mkrv (apl-queens 5)) (list 10))
|
||||
|
||||
(apl-test "queens 6 → 4 solutions" (mkrv (apl-queens 6)) (list 4))
|
||||
|
||||
(apl-test "queens 7 → 40 solutions" (mkrv (apl-queens 7)) (list 40))
|
||||
|
||||
(apl-test "permutations of 3 has 6" (len (apl-permutations 3)) 6)
|
||||
|
||||
(apl-test "permutations of 4 has 24" (len (apl-permutations 4)) 24)
|
||||
|
||||
(apl-test
|
||||
"quicksort empty"
|
||||
(mkrv (apl-quicksort (make-array (list 0) (list))))
|
||||
(list))
|
||||
|
||||
(apl-test
|
||||
"quicksort single"
|
||||
(mkrv (apl-quicksort (make-array (list 1) (list 42))))
|
||||
(list 42))
|
||||
|
||||
(apl-test
|
||||
"quicksort already sorted"
|
||||
(mkrv (apl-quicksort (make-array (list 5) (list 1 2 3 4 5))))
|
||||
(list 1 2 3 4 5))
|
||||
|
||||
(apl-test
|
||||
"quicksort reverse sorted"
|
||||
(mkrv (apl-quicksort (make-array (list 5) (list 5 4 3 2 1))))
|
||||
(list 1 2 3 4 5))
|
||||
|
||||
(apl-test
|
||||
"quicksort with duplicates"
|
||||
(mkrv (apl-quicksort (make-array (list 7) (list 3 1 4 1 5 9 2))))
|
||||
(list 1 1 2 3 4 5 9))
|
||||
|
||||
(apl-test
|
||||
"quicksort all equal"
|
||||
(mkrv (apl-quicksort (make-array (list 5) (list 7 7 7 7 7))))
|
||||
(list 7 7 7 7 7))
|
||||
|
||||
(apl-test
|
||||
"quicksort negatives"
|
||||
(mkrv (apl-quicksort (make-array (list 5) (list -3 1 -1 2 0))))
|
||||
(list -3 -1 0 1 2))
|
||||
|
||||
(apl-test
|
||||
"quicksort 11-element pi"
|
||||
(mkrv
|
||||
(apl-quicksort (make-array (list 11) (list 3 1 4 1 5 9 2 6 5 3 5))))
|
||||
(list 1 1 2 3 3 4 5 5 5 6 9))
|
||||
|
||||
(apl-test
|
||||
"quicksort preserves length"
|
||||
(first
|
||||
(mksh (apl-quicksort (make-array (list 7) (list 3 1 4 1 5 9 2)))))
|
||||
7)
|
||||
22
lib/apl/tests/programs/life.apl
Normal file
22
lib/apl/tests/programs/life.apl
Normal file
@@ -0,0 +1,22 @@
|
||||
⍝ Conway's Game of Life — toroidal one-liner
|
||||
⍝
|
||||
⍝ The classic Roger Hui formulation:
|
||||
⍝ life ← {⊃1 ⍵ ∨.∧ 3 4 = +/ +/ ¯1 0 1 ∘.⊖ ¯1 0 1 ⌽¨ ⊂⍵}
|
||||
⍝
|
||||
⍝ Read right-to-left:
|
||||
⍝ ⊂⍵ : enclose the board (so it's a single scalar item)
|
||||
⍝ ¯1 0 1 ⌽¨ ⊂⍵ : produce 3 horizontally-shifted copies
|
||||
⍝ ¯1 0 1 ∘.⊖ … : outer-product with vertical shifts → 3×3 = 9 shifts
|
||||
⍝ +/ +/ … : sum the 9 boards element-wise → neighbor-count + self
|
||||
⍝ 3 4 = … : boolean — count is exactly 3 or exactly 4
|
||||
⍝ 1 ⍵ ∨.∧ … : "alive next" iff (count=3) or (alive AND count=4)
|
||||
⍝ ⊃ … : disclose back to a 2D board
|
||||
⍝
|
||||
⍝ Rules in plain language:
|
||||
⍝ - dead cell + 3 live neighbors → born
|
||||
⍝ - live cell + 2 or 3 live neighbors → survives
|
||||
⍝ - all else → dies
|
||||
⍝
|
||||
⍝ Toroidal: edges wrap (rotate is cyclic).
|
||||
|
||||
life ← {⊃1 ⍵ ∨.∧ 3 4 = +/ +/ ¯1 0 1 ∘.⊖ ¯1 0 1 ⌽¨ ⊂⍵}
|
||||
29
lib/apl/tests/programs/mandelbrot.apl
Normal file
29
lib/apl/tests/programs/mandelbrot.apl
Normal file
@@ -0,0 +1,29 @@
|
||||
⍝ Mandelbrot — real-axis subset
|
||||
⍝
|
||||
⍝ For complex c, the Mandelbrot set is { c : |z_n| stays bounded } where
|
||||
⍝ z_0 = 0, z_{n+1} = z_n² + c.
|
||||
⍝ Restricting c (and z) to ℝ gives the segment c ∈ [-2, 1/4]
|
||||
⍝ where the iteration stays bounded.
|
||||
⍝
|
||||
⍝ Rank-polymorphic batched-iteration form:
|
||||
⍝ mandelbrot ← {⍵ ⍵⍵ ⍺⍺ +,(⍺⍺ × ⍺⍺) }
|
||||
⍝
|
||||
⍝ Pseudocode (as we don't have ⎕ system fns yet):
|
||||
⍝ z ← 0×c ⍝ start at zero
|
||||
⍝ alive ← 1+0×c ⍝ all "still in"
|
||||
⍝ for k iterations:
|
||||
⍝ alive ← alive ∧ 4 ≥ z×z ⍝ still bounded?
|
||||
⍝ z ← alive × c + z×z ⍝ freeze escaped via mask
|
||||
⍝ count ← count + alive ⍝ tally surviving iters
|
||||
⍝
|
||||
⍝ Examples (count after 100 iterations):
|
||||
⍝ c=0 : 100 (z stays at 0)
|
||||
⍝ c=-1 : 100 (cycles 0,-1,0,-1,...)
|
||||
⍝ c=-2 : 100 (settles at 2 — boundary)
|
||||
⍝ c=0.25 : 100 (boundary — converges to 0.5)
|
||||
⍝ c=0.5 : 5 (escapes by iteration 6)
|
||||
⍝ c=1 : 3 (escapes quickly)
|
||||
⍝
|
||||
⍝ Real-axis Mandelbrot set: bounded for c ∈ [-2, 0.25].
|
||||
|
||||
mandelbrot ← {z←alive←count←0×⍵ ⋄ {alive←alive∧4≥z×z ⋄ z←alive×⍵+z×z ⋄ count+←alive}⍣⍺⊢⍵}
|
||||
18
lib/apl/tests/programs/n-queens.apl
Normal file
18
lib/apl/tests/programs/n-queens.apl
Normal file
@@ -0,0 +1,18 @@
|
||||
⍝ N-Queens — count solutions to placing N non-attacking queens on N×N
|
||||
⍝
|
||||
⍝ A solution is encoded as a permutation P of 1..N where P[i] is the
|
||||
⍝ column of the queen in row i. Rows and columns are then automatically
|
||||
⍝ unique (it's a permutation). We must additionally rule out queens
|
||||
⍝ sharing a diagonal: |i-j| = |P[i]-P[j]| for any pair.
|
||||
⍝
|
||||
⍝ Backtracking via reduce — the classic Roger Hui style:
|
||||
⍝ queens ← {≢{⍵,¨⍨↓(0=∊(¨⍳⍴⍵)≠.+|⍵)/⍳⍴⍵}/(⍳⍵)⍴⊂⍳⍵}
|
||||
⍝
|
||||
⍝ Plain reading:
|
||||
⍝ permute 1..N, keep those where no two queens share a diagonal.
|
||||
⍝
|
||||
⍝ Known solution counts (OEIS A000170):
|
||||
⍝ N 1 2 3 4 5 6 7 8 9 10
|
||||
⍝ q(N) 1 0 0 2 10 4 40 92 352 724
|
||||
|
||||
queens ← {≢({(i j)←⍺⍵ ⋄ (|i-j)≠|(P[i])-(P[j])}⌿permutations ⍵)}
|
||||
16
lib/apl/tests/programs/primes.apl
Normal file
16
lib/apl/tests/programs/primes.apl
Normal file
@@ -0,0 +1,16 @@
|
||||
⍝ Sieve of Eratosthenes — the classic APL one-liner
|
||||
⍝ primes ← (2=+⌿0=A∘.|A)/A←⍳N
|
||||
⍝
|
||||
⍝ Read right-to-left:
|
||||
⍝ A ← ⍳N : A is 1..N
|
||||
⍝ A∘.|A : outer-product residue table — M[i,j] = A[j] mod A[i]
|
||||
⍝ 0=... : boolean — true where A[i] divides A[j]
|
||||
⍝ +⌿... : column sums — count of divisors per A[j]
|
||||
⍝ 2=... : true for numbers with exactly 2 divisors (1 and self) → primes
|
||||
⍝ .../A : compress — select A[j] where mask[j] is true
|
||||
⍝
|
||||
⍝ Examples:
|
||||
⍝ primes 10 → 2 3 5 7
|
||||
⍝ primes 30 → 2 3 5 7 11 13 17 19 23 29
|
||||
|
||||
primes ← {(2=+⌿0=⍵∘.|⍵)/⍵←⍳⍵}
|
||||
25
lib/apl/tests/programs/quicksort.apl
Normal file
25
lib/apl/tests/programs/quicksort.apl
Normal file
@@ -0,0 +1,25 @@
|
||||
⍝ Quicksort — the classic Roger Hui one-liner
|
||||
⍝
|
||||
⍝ Q ← {1≥≢⍵:⍵ ⋄ (∇⍵⌿⍨⍵<p),(p=⍵)/⍵,∇⍵⌿⍨⍵>p←⍵⌷⍨?≢⍵}
|
||||
⍝
|
||||
⍝ Read right-to-left:
|
||||
⍝ ?≢⍵ : pick a random index in 1..length
|
||||
⍝ ⍵⌷⍨… : take that element as pivot p
|
||||
⍝ ⍵>p : boolean — elements greater than pivot
|
||||
⍝ ∇⍵⌿⍨… : recursively sort the > partition
|
||||
⍝ (p=⍵)/⍵ : keep elements equal to pivot
|
||||
⍝ ⍵<p : boolean — elements less than pivot
|
||||
⍝ ∇⍵⌿⍨… : recursively sort the < partition
|
||||
⍝ , : catenate ⟨less⟩ ⟨equal⟩ ⟨greater⟩
|
||||
⍝ 1≥≢⍵:⍵ : guard — base case for length ≤ 1
|
||||
⍝
|
||||
⍝ Stability: not stable on duplicates (but eq-class is preserved as a block).
|
||||
⍝ Worst case O(N²) on already-sorted input with deterministic pivot;
|
||||
⍝ randomized pivot selection gives expected O(N log N).
|
||||
⍝
|
||||
⍝ Examples:
|
||||
⍝ Q 3 1 4 1 5 9 2 6 5 3 5 → 1 1 2 3 3 4 5 5 5 6 9
|
||||
⍝ Q ⍳0 → ⍬ (empty)
|
||||
⍝ Q ,42 → 42
|
||||
|
||||
quicksort ← {1≥≢⍵:⍵ ⋄ p←⍵⌷⍨?≢⍵ ⋄ (∇⍵⌿⍨⍵<p),(p=⍵)/⍵,∇⍵⌿⍨⍵>p}
|
||||
369
lib/apl/tests/scalar.sx
Normal file
369
lib/apl/tests/scalar.sx
Normal file
@@ -0,0 +1,369 @@
|
||||
; APL scalar primitives test suite
|
||||
; Requires: lib/apl/runtime.sx
|
||||
|
||||
; ============================================================
|
||||
; Test framework
|
||||
; ============================================================
|
||||
|
||||
(define apl-rt-count 0)
|
||||
(define apl-rt-pass 0)
|
||||
(define apl-rt-fails (list))
|
||||
|
||||
; Element-wise list comparison (handles both List and ListRef)
|
||||
(define
|
||||
lists-eq
|
||||
(fn
|
||||
(a b)
|
||||
(if
|
||||
(and (= (len a) 0) (= (len b) 0))
|
||||
true
|
||||
(if
|
||||
(not (= (len a) (len b)))
|
||||
false
|
||||
(if
|
||||
(not (= (first a) (first b)))
|
||||
false
|
||||
(lists-eq (rest a) (rest b)))))))
|
||||
|
||||
(define
|
||||
apl-rt-test
|
||||
(fn
|
||||
(name actual expected)
|
||||
(begin
|
||||
(set! apl-rt-count (+ apl-rt-count 1))
|
||||
(if
|
||||
(equal? actual expected)
|
||||
(set! apl-rt-pass (+ apl-rt-pass 1))
|
||||
(append! apl-rt-fails {:actual actual :expected expected :name name})))))
|
||||
|
||||
; Test that a ravel equals a plain list (handles ListRef vs List)
|
||||
(define
|
||||
ravel-test
|
||||
(fn
|
||||
(name arr expected-list)
|
||||
(begin
|
||||
(set! apl-rt-count (+ apl-rt-count 1))
|
||||
(let
|
||||
((actual (get arr :ravel)))
|
||||
(if
|
||||
(lists-eq actual expected-list)
|
||||
(set! apl-rt-pass (+ apl-rt-pass 1))
|
||||
(append! apl-rt-fails {:actual actual :expected expected-list :name name}))))))
|
||||
|
||||
; Test a scalar ravel value (single-element list)
|
||||
(define
|
||||
scalar-test
|
||||
(fn (name arr expected-val) (ravel-test name arr (list expected-val))))
|
||||
|
||||
; ============================================================
|
||||
; Array constructor tests
|
||||
; ============================================================
|
||||
|
||||
(apl-rt-test
|
||||
"scalar: shape is empty list"
|
||||
(get (apl-scalar 5) :shape)
|
||||
(list))
|
||||
|
||||
(apl-rt-test
|
||||
"scalar: ravel has one element"
|
||||
(get (apl-scalar 5) :ravel)
|
||||
(list 5))
|
||||
|
||||
(apl-rt-test "scalar: rank 0" (array-rank (apl-scalar 5)) 0)
|
||||
|
||||
(apl-rt-test "scalar? returns true for scalar" (scalar? (apl-scalar 5)) true)
|
||||
|
||||
(apl-rt-test "scalar: zero" (get (apl-scalar 0) :ravel) (list 0))
|
||||
|
||||
(apl-rt-test
|
||||
"vector: shape is (3)"
|
||||
(get (apl-vector (list 1 2 3)) :shape)
|
||||
(list 3))
|
||||
|
||||
(apl-rt-test
|
||||
"vector: ravel matches input"
|
||||
(get (apl-vector (list 1 2 3)) :ravel)
|
||||
(list 1 2 3))
|
||||
|
||||
(apl-rt-test "vector: rank 1" (array-rank (apl-vector (list 1 2 3))) 1)
|
||||
|
||||
(apl-rt-test
|
||||
"scalar? returns false for vector"
|
||||
(scalar? (apl-vector (list 1 2 3)))
|
||||
false)
|
||||
|
||||
(apl-rt-test
|
||||
"make-array: rank 2"
|
||||
(array-rank (make-array (list 2 3) (list 1 2 3 4 5 6)))
|
||||
2)
|
||||
|
||||
(apl-rt-test
|
||||
"make-array: shape"
|
||||
(get (make-array (list 2 3) (list 1 2 3 4 5 6)) :shape)
|
||||
(list 2 3))
|
||||
|
||||
(apl-rt-test
|
||||
"array-ref: first element"
|
||||
(array-ref (apl-vector (list 10 20 30)) 0)
|
||||
10)
|
||||
|
||||
(apl-rt-test
|
||||
"array-ref: last element"
|
||||
(array-ref (apl-vector (list 10 20 30)) 2)
|
||||
30)
|
||||
|
||||
(apl-rt-test "enclose: wraps in rank-0" (scalar? (enclose 42)) true)
|
||||
|
||||
(apl-rt-test
|
||||
"enclose: ravel contains value"
|
||||
(get (enclose 42) :ravel)
|
||||
(list 42))
|
||||
|
||||
(apl-rt-test "disclose: unwraps rank-0" (disclose (enclose 42)) 42)
|
||||
|
||||
; ============================================================
|
||||
; Shape primitive tests
|
||||
; ============================================================
|
||||
|
||||
(ravel-test "⍴ scalar: returns empty" (apl-shape (apl-scalar 5)) (list))
|
||||
|
||||
(ravel-test
|
||||
"⍴ vector: returns (3)"
|
||||
(apl-shape (apl-vector (list 1 2 3)))
|
||||
(list 3))
|
||||
|
||||
(ravel-test
|
||||
"⍴ matrix: returns (2 3)"
|
||||
(apl-shape (make-array (list 2 3) (list 1 2 3 4 5 6)))
|
||||
(list 2 3))
|
||||
|
||||
(ravel-test
|
||||
", ravel scalar: vector of 1"
|
||||
(apl-ravel (apl-scalar 5))
|
||||
(list 5))
|
||||
|
||||
(apl-rt-test
|
||||
", ravel vector: same elements"
|
||||
(get (apl-ravel (apl-vector (list 1 2 3))) :ravel)
|
||||
(list 1 2 3))
|
||||
|
||||
(apl-rt-test
|
||||
", ravel matrix: all elements"
|
||||
(get (apl-ravel (make-array (list 2 3) (list 1 2 3 4 5 6))) :ravel)
|
||||
(list 1 2 3 4 5 6))
|
||||
|
||||
(scalar-test "≢ tally scalar: 1" (apl-tally (apl-scalar 5)) 1)
|
||||
|
||||
(scalar-test
|
||||
"≢ tally vector: first dimension"
|
||||
(apl-tally (apl-vector (list 1 2 3)))
|
||||
3)
|
||||
|
||||
(scalar-test
|
||||
"≢ tally matrix: first dimension"
|
||||
(apl-tally (make-array (list 2 3) (list 1 2 3 4 5 6)))
|
||||
2)
|
||||
|
||||
(scalar-test
|
||||
"≡ depth flat vector: 0"
|
||||
(apl-depth (apl-vector (list 1 2 3)))
|
||||
0)
|
||||
|
||||
(scalar-test "≡ depth scalar: 0" (apl-depth (apl-scalar 5)) 0)
|
||||
|
||||
(scalar-test
|
||||
"≡ depth nested (enclose in vector): 1"
|
||||
(apl-depth (enclose (apl-vector (list 1 2 3))))
|
||||
1)
|
||||
|
||||
; ============================================================
|
||||
; ⍳ iota tests
|
||||
; ============================================================
|
||||
|
||||
(apl-rt-test
|
||||
"⍳5 shape is (5)"
|
||||
(get (apl-iota (apl-scalar 5)) :shape)
|
||||
(list 5))
|
||||
|
||||
(ravel-test "⍳5 ravel is 1..5" (apl-iota (apl-scalar 5)) (list 1 2 3 4 5))
|
||||
|
||||
(ravel-test "⍳1 ravel is (1)" (apl-iota (apl-scalar 1)) (list 1))
|
||||
|
||||
(ravel-test "⍳0 ravel is empty" (apl-iota (apl-scalar 0)) (list))
|
||||
|
||||
(apl-rt-test "apl-io is 1" apl-io 1)
|
||||
|
||||
; ============================================================
|
||||
; Arithmetic broadcast tests
|
||||
; ============================================================
|
||||
|
||||
(scalar-test
|
||||
"+ scalar scalar: 3+4=7"
|
||||
(apl-add (apl-scalar 3) (apl-scalar 4))
|
||||
7)
|
||||
|
||||
(ravel-test
|
||||
"+ vector scalar: +10"
|
||||
(apl-add (apl-vector (list 1 2 3)) (apl-scalar 10))
|
||||
(list 11 12 13))
|
||||
|
||||
(ravel-test
|
||||
"+ scalar vector: 10+"
|
||||
(apl-add (apl-scalar 10) (apl-vector (list 1 2 3)))
|
||||
(list 11 12 13))
|
||||
|
||||
(ravel-test
|
||||
"+ vector vector"
|
||||
(apl-add (apl-vector (list 1 2 3)) (apl-vector (list 4 5 6)))
|
||||
(list 5 7 9))
|
||||
|
||||
(scalar-test "- negate monadic" (apl-neg-m (apl-scalar 5)) -5)
|
||||
|
||||
(scalar-test "- dyadic 10-3=7" (apl-sub (apl-scalar 10) (apl-scalar 3)) 7)
|
||||
|
||||
(scalar-test "× signum positive" (apl-signum (apl-scalar 7)) 1)
|
||||
|
||||
(scalar-test "× signum negative" (apl-signum (apl-scalar -3)) -1)
|
||||
|
||||
(scalar-test "× signum zero" (apl-signum (apl-scalar 0)) 0)
|
||||
|
||||
(scalar-test "× dyadic 3×4=12" (apl-mul (apl-scalar 3) (apl-scalar 4)) 12)
|
||||
|
||||
(scalar-test "÷ reciprocal 1÷4=0.25" (apl-recip (apl-scalar 4)) 0.25)
|
||||
|
||||
(scalar-test
|
||||
"÷ dyadic 10÷4=2.5"
|
||||
(apl-div (apl-scalar 10) (apl-scalar 4))
|
||||
2.5)
|
||||
|
||||
(scalar-test "⌈ ceiling 2.3→3" (apl-ceil (apl-scalar 2.3)) 3)
|
||||
|
||||
(scalar-test "⌈ max 3 5 → 5" (apl-max (apl-scalar 3) (apl-scalar 5)) 5)
|
||||
|
||||
(scalar-test "⌊ floor 2.7→2" (apl-floor (apl-scalar 2.7)) 2)
|
||||
|
||||
(scalar-test "⌊ min 3 5 → 3" (apl-min (apl-scalar 3) (apl-scalar 5)) 3)
|
||||
|
||||
(scalar-test "* exp monadic e^0=1" (apl-exp (apl-scalar 0)) 1)
|
||||
|
||||
(scalar-test
|
||||
"* pow dyadic 2^10=1024"
|
||||
(apl-pow (apl-scalar 2) (apl-scalar 10))
|
||||
1024)
|
||||
|
||||
(scalar-test "⍟ ln 1=0" (apl-ln (apl-scalar 1)) 0)
|
||||
|
||||
(scalar-test "| abs positive" (apl-abs (apl-scalar 5)) 5)
|
||||
|
||||
(scalar-test "| abs negative" (apl-abs (apl-scalar -5)) 5)
|
||||
|
||||
(scalar-test "| mod 3|7=1" (apl-mod (apl-scalar 3) (apl-scalar 7)) 1)
|
||||
|
||||
(scalar-test "! factorial 5!=120" (apl-fact (apl-scalar 5)) 120)
|
||||
|
||||
(scalar-test "! factorial 0!=1" (apl-fact (apl-scalar 0)) 1)
|
||||
|
||||
(scalar-test
|
||||
"! binomial 4 choose 2 = 6"
|
||||
(apl-binomial (apl-scalar 4) (apl-scalar 2))
|
||||
6)
|
||||
|
||||
(scalar-test "○ pi×0=0" (apl-pi-times (apl-scalar 0)) 0)
|
||||
|
||||
(scalar-test "○ trig sin(0)=0" (apl-trig (apl-scalar 1) (apl-scalar 0)) 0)
|
||||
|
||||
(scalar-test "○ trig cos(0)=1" (apl-trig (apl-scalar 2) (apl-scalar 0)) 1)
|
||||
|
||||
; ============================================================
|
||||
; Comparison tests
|
||||
; ============================================================
|
||||
|
||||
(scalar-test "< less: 3<5 → 1" (apl-lt (apl-scalar 3) (apl-scalar 5)) 1)
|
||||
|
||||
(scalar-test "< less: 5<3 → 0" (apl-lt (apl-scalar 5) (apl-scalar 3)) 0)
|
||||
|
||||
(scalar-test
|
||||
"≤ le equal: 3≤3 → 1"
|
||||
(apl-le (apl-scalar 3) (apl-scalar 3))
|
||||
1)
|
||||
|
||||
(scalar-test "= eq: 5=5 → 1" (apl-eq (apl-scalar 5) (apl-scalar 5)) 1)
|
||||
|
||||
(scalar-test "= ne: 5=6 → 0" (apl-eq (apl-scalar 5) (apl-scalar 6)) 0)
|
||||
|
||||
(scalar-test "≥ ge: 5≥3 → 1" (apl-ge (apl-scalar 5) (apl-scalar 3)) 1)
|
||||
|
||||
(scalar-test "> gt: 5>3 → 1" (apl-gt (apl-scalar 5) (apl-scalar 3)) 1)
|
||||
|
||||
(scalar-test "≠ ne: 5≠3 → 1" (apl-ne (apl-scalar 5) (apl-scalar 3)) 1)
|
||||
|
||||
(ravel-test
|
||||
"comparison vector broadcast: 1 2 3 < 2 → 1 0 0"
|
||||
(apl-lt (apl-vector (list 1 2 3)) (apl-scalar 2))
|
||||
(list 1 0 0))
|
||||
|
||||
; ============================================================
|
||||
; Logical tests
|
||||
; ============================================================
|
||||
|
||||
(scalar-test "~ not 0 → 1" (apl-not (apl-scalar 0)) 1)
|
||||
|
||||
(scalar-test "~ not 1 → 0" (apl-not (apl-scalar 1)) 0)
|
||||
|
||||
(ravel-test
|
||||
"~ not vector: 1 0 1 0 → 0 1 0 1"
|
||||
(apl-not (apl-vector (list 1 0 1 0)))
|
||||
(list 0 1 0 1))
|
||||
|
||||
(scalar-test
|
||||
"∧ and 1∧1 → 1"
|
||||
(apl-and (apl-scalar 1) (apl-scalar 1))
|
||||
1)
|
||||
|
||||
(scalar-test
|
||||
"∧ and 1∧0 → 0"
|
||||
(apl-and (apl-scalar 1) (apl-scalar 0))
|
||||
0)
|
||||
|
||||
(scalar-test "∨ or 0∨1 → 1" (apl-or (apl-scalar 0) (apl-scalar 1)) 1)
|
||||
|
||||
(scalar-test "∨ or 0∨0 → 0" (apl-or (apl-scalar 0) (apl-scalar 0)) 0)
|
||||
|
||||
(scalar-test
|
||||
"⍱ nor 0⍱0 → 1"
|
||||
(apl-nor (apl-scalar 0) (apl-scalar 0))
|
||||
1)
|
||||
|
||||
(scalar-test
|
||||
"⍱ nor 1⍱0 → 0"
|
||||
(apl-nor (apl-scalar 1) (apl-scalar 0))
|
||||
0)
|
||||
|
||||
(scalar-test
|
||||
"⍲ nand 1⍲1 → 0"
|
||||
(apl-nand (apl-scalar 1) (apl-scalar 1))
|
||||
0)
|
||||
|
||||
(scalar-test
|
||||
"⍲ nand 1⍲0 → 1"
|
||||
(apl-nand (apl-scalar 1) (apl-scalar 0))
|
||||
1)
|
||||
|
||||
; ============================================================
|
||||
; plus-m identity test
|
||||
; ============================================================
|
||||
|
||||
(scalar-test "+ monadic identity: +5 → 5" (apl-plus-m (apl-scalar 5)) 5)
|
||||
|
||||
; ============================================================
|
||||
; Summary
|
||||
; ============================================================
|
||||
|
||||
(define
|
||||
apl-scalar-summary
|
||||
(str
|
||||
"scalar "
|
||||
apl-rt-pass
|
||||
"/"
|
||||
apl-rt-count
|
||||
(if (= (len apl-rt-fails) 0) "" (str " FAILS: " apl-rt-fails))))
|
||||
608
lib/apl/tests/structural.sx
Normal file
608
lib/apl/tests/structural.sx
Normal file
@@ -0,0 +1,608 @@
|
||||
;; lib/apl/tests/structural.sx — Phase 3: structural primitives
|
||||
;; Tests for: apl-reshape, apl-ravel, apl-transpose, apl-transpose-dyadic
|
||||
;; Loaded after runtime.sx; shares apl-test / apl-test-pass / apl-test-fail.
|
||||
|
||||
(define rv (fn (arr) (get arr :ravel)))
|
||||
(define sh (fn (arr) (get arr :shape)))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 1. Ravel (monadic ,)
|
||||
;; ---------------------------------------------------------------------------
|
||||
(apl-test "ravel scalar" (rv (apl-ravel (apl-scalar 5))) (list 5))
|
||||
|
||||
(apl-test
|
||||
"ravel vector"
|
||||
(rv (apl-ravel (make-array (list 3) (list 1 2 3))))
|
||||
(list 1 2 3))
|
||||
|
||||
(apl-test
|
||||
"ravel matrix"
|
||||
(rv (apl-ravel (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list 1 2 3 4 5 6))
|
||||
|
||||
(apl-test
|
||||
"ravel shape is rank-1"
|
||||
(sh (apl-ravel (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list 6))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 2. Reshape (dyadic ⍴)
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(apl-test
|
||||
"reshape 2x3 ravel"
|
||||
(rv
|
||||
(apl-reshape
|
||||
(make-array (list 2) (list 2 3))
|
||||
(make-array (list 6) (list 1 2 3 4 5 6))))
|
||||
(list 1 2 3 4 5 6))
|
||||
|
||||
(apl-test
|
||||
"reshape 2x3 shape"
|
||||
(sh
|
||||
(apl-reshape
|
||||
(make-array (list 2) (list 2 3))
|
||||
(make-array (list 6) (list 1 2 3 4 5 6))))
|
||||
(list 2 3))
|
||||
|
||||
(apl-test
|
||||
"reshape cycle 6 from 1 2"
|
||||
(rv
|
||||
(apl-reshape
|
||||
(make-array (list 1) (list 6))
|
||||
(make-array (list 2) (list 1 2))))
|
||||
(list 1 2 1 2 1 2))
|
||||
|
||||
(apl-test
|
||||
"reshape cycle 2x3 from 1 2"
|
||||
(rv
|
||||
(apl-reshape
|
||||
(make-array (list 2) (list 2 3))
|
||||
(make-array (list 2) (list 1 2))))
|
||||
(list 1 2 1 2 1 2))
|
||||
|
||||
(apl-test
|
||||
"reshape scalar fill"
|
||||
(rv (apl-reshape (make-array (list 1) (list 4)) (apl-scalar 7)))
|
||||
(list 7 7 7 7))
|
||||
|
||||
(apl-test
|
||||
"reshape truncate"
|
||||
(rv
|
||||
(apl-reshape
|
||||
(make-array (list 1) (list 3))
|
||||
(make-array (list 6) (list 10 20 30 40 50 60))))
|
||||
(list 10 20 30))
|
||||
|
||||
(apl-test
|
||||
"reshape matrix to vector"
|
||||
(sh
|
||||
(apl-reshape
|
||||
(make-array (list 1) (list 6))
|
||||
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list 6))
|
||||
|
||||
(apl-test
|
||||
"reshape 2x2x3"
|
||||
(sh
|
||||
(apl-reshape
|
||||
(make-array (list 3) (list 2 2 3))
|
||||
(make-array (list 12) (range 1 13))))
|
||||
(list 2 2 3))
|
||||
|
||||
(apl-test
|
||||
"reshape to empty"
|
||||
(rv
|
||||
(apl-reshape
|
||||
(make-array (list 1) (list 0))
|
||||
(make-array (list 3) (list 1 2 3))))
|
||||
(list))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 3. Monadic transpose (⍉)
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(apl-test
|
||||
"transpose scalar shape"
|
||||
(sh (apl-transpose (apl-scalar 99)))
|
||||
(list))
|
||||
|
||||
(apl-test
|
||||
"transpose scalar ravel"
|
||||
(rv (apl-transpose (apl-scalar 99)))
|
||||
(list 99))
|
||||
|
||||
(apl-test
|
||||
"transpose vector shape"
|
||||
(sh (apl-transpose (make-array (list 3) (list 3 1 4))))
|
||||
(list 3))
|
||||
|
||||
(apl-test
|
||||
"transpose vector ravel"
|
||||
(rv (apl-transpose (make-array (list 3) (list 3 1 4))))
|
||||
(list 3 1 4))
|
||||
|
||||
(apl-test
|
||||
"transpose 2x3 shape"
|
||||
(sh (apl-transpose (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list 3 2))
|
||||
|
||||
(apl-test
|
||||
"transpose 2x3 ravel"
|
||||
(rv (apl-transpose (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list 1 4 2 5 3 6))
|
||||
|
||||
(apl-test
|
||||
"transpose 3x3"
|
||||
(rv (apl-transpose (make-array (list 3 3) (list 1 2 3 4 5 6 7 8 9))))
|
||||
(list 1 4 7 2 5 8 3 6 9))
|
||||
|
||||
(apl-test
|
||||
"transpose 1x4 shape"
|
||||
(sh (apl-transpose (make-array (list 1 4) (list 1 2 3 4))))
|
||||
(list 4 1))
|
||||
|
||||
(apl-test
|
||||
"transpose twice identity"
|
||||
(rv
|
||||
(apl-transpose
|
||||
(apl-transpose (make-array (list 2 3) (list 1 2 3 4 5 6)))))
|
||||
(list 1 2 3 4 5 6))
|
||||
|
||||
(apl-test
|
||||
"transpose 3d shape"
|
||||
(sh (apl-transpose (make-array (list 2 3 4) (range 0 24))))
|
||||
(list 4 3 2))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; 4. Dyadic transpose (perm⍉arr)
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(apl-test
|
||||
"dyadic-transpose identity"
|
||||
(rv
|
||||
(apl-transpose-dyadic
|
||||
(make-array (list 2) (list 1 2))
|
||||
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list 1 2 3 4 5 6))
|
||||
|
||||
(apl-test
|
||||
"dyadic-transpose swap 2x3"
|
||||
(rv
|
||||
(apl-transpose-dyadic
|
||||
(make-array (list 2) (list 2 1))
|
||||
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list 1 4 2 5 3 6))
|
||||
|
||||
(apl-test
|
||||
"dyadic-transpose swap shape"
|
||||
(sh
|
||||
(apl-transpose-dyadic
|
||||
(make-array (list 2) (list 2 1))
|
||||
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list 3 2))
|
||||
|
||||
(apl-test
|
||||
"dyadic-transpose 3d shape"
|
||||
(sh
|
||||
(apl-transpose-dyadic
|
||||
(make-array (list 3) (list 2 1 3))
|
||||
(make-array (list 2 3 4) (range 0 24))))
|
||||
(list 3 2 4))
|
||||
|
||||
(apl-test
|
||||
"take 3 from front"
|
||||
(rv (apl-take (apl-scalar 3) (make-array (list 5) (list 1 2 3 4 5))))
|
||||
(list 1 2 3))
|
||||
|
||||
(apl-test
|
||||
"take 0"
|
||||
(rv (apl-take (apl-scalar 0) (make-array (list 5) (list 1 2 3 4 5))))
|
||||
(list))
|
||||
|
||||
(apl-test
|
||||
"take -2 from back"
|
||||
(rv (apl-take (apl-scalar -2) (make-array (list 5) (list 1 2 3 4 5))))
|
||||
(list 4 5))
|
||||
|
||||
(apl-test
|
||||
"take over-take pads with 0"
|
||||
(rv (apl-take (apl-scalar 7) (make-array (list 5) (list 1 2 3 4 5))))
|
||||
(list 1 2 3 4 5 0 0))
|
||||
|
||||
(apl-test
|
||||
"take matrix 1 row 2 cols shape"
|
||||
(sh
|
||||
(apl-take
|
||||
(make-array (list 2) (list 1 2))
|
||||
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list 1 2))
|
||||
|
||||
(apl-test
|
||||
"take matrix 1 row 2 cols ravel"
|
||||
(rv
|
||||
(apl-take
|
||||
(make-array (list 2) (list 1 2))
|
||||
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list 1 2))
|
||||
|
||||
(apl-test
|
||||
"take matrix negative row"
|
||||
(rv
|
||||
(apl-take
|
||||
(make-array (list 2) (list -1 3))
|
||||
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list 4 5 6))
|
||||
|
||||
(apl-test
|
||||
"drop 2 from front"
|
||||
(rv (apl-drop (apl-scalar 2) (make-array (list 5) (list 1 2 3 4 5))))
|
||||
(list 3 4 5))
|
||||
|
||||
(apl-test
|
||||
"drop -2 from back"
|
||||
(rv (apl-drop (apl-scalar -2) (make-array (list 5) (list 1 2 3 4 5))))
|
||||
(list 1 2 3))
|
||||
|
||||
(apl-test
|
||||
"drop all"
|
||||
(rv (apl-drop (apl-scalar 5) (make-array (list 5) (list 1 2 3 4 5))))
|
||||
(list))
|
||||
|
||||
(apl-test
|
||||
"drop 0"
|
||||
(rv (apl-drop (apl-scalar 0) (make-array (list 5) (list 1 2 3 4 5))))
|
||||
(list 1 2 3 4 5))
|
||||
|
||||
(apl-test
|
||||
"drop matrix 1 row shape"
|
||||
(sh
|
||||
(apl-drop
|
||||
(make-array (list 2) (list 1 0))
|
||||
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list 1 3))
|
||||
|
||||
(apl-test
|
||||
"drop matrix 1 row ravel"
|
||||
(rv
|
||||
(apl-drop
|
||||
(make-array (list 2) (list 1 0))
|
||||
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list 4 5 6))
|
||||
|
||||
(apl-test
|
||||
"reverse vector"
|
||||
(rv (apl-reverse (make-array (list 5) (list 1 2 3 4 5))))
|
||||
(list 5 4 3 2 1))
|
||||
|
||||
(apl-test
|
||||
"reverse scalar identity"
|
||||
(rv (apl-reverse (apl-scalar 42)))
|
||||
(list 42))
|
||||
|
||||
(apl-test
|
||||
"reverse matrix last axis"
|
||||
(rv (apl-reverse (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list 3 2 1 6 5 4))
|
||||
|
||||
(apl-test
|
||||
"reverse-first matrix"
|
||||
(rv (apl-reverse-first (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list 4 5 6 1 2 3))
|
||||
|
||||
(apl-test
|
||||
"reverse-first vector identity"
|
||||
(rv (apl-reverse-first (make-array (list 4) (list 1 2 3 4))))
|
||||
(list 4 3 2 1))
|
||||
|
||||
(apl-test
|
||||
"rotate vector left by 2"
|
||||
(rv (apl-rotate (apl-scalar 2) (make-array (list 5) (list 1 2 3 4 5))))
|
||||
(list 3 4 5 1 2))
|
||||
|
||||
(apl-test
|
||||
"rotate vector right by 1 (negative)"
|
||||
(rv (apl-rotate (apl-scalar -1) (make-array (list 5) (list 1 2 3 4 5))))
|
||||
(list 5 1 2 3 4))
|
||||
|
||||
(apl-test
|
||||
"rotate by 0 is identity"
|
||||
(rv (apl-rotate (apl-scalar 0) (make-array (list 5) (list 1 2 3 4 5))))
|
||||
(list 1 2 3 4 5))
|
||||
|
||||
(apl-test
|
||||
"rotate matrix last axis"
|
||||
(rv
|
||||
(apl-rotate (apl-scalar 1) (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list 2 3 1 5 6 4))
|
||||
|
||||
(apl-test
|
||||
"rotate-first matrix"
|
||||
(rv
|
||||
(apl-rotate-first
|
||||
(apl-scalar 1)
|
||||
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list 4 5 6 1 2 3))
|
||||
|
||||
(apl-test
|
||||
"cat v,v ravel"
|
||||
(rv
|
||||
(apl-catenate
|
||||
(make-array (list 3) (list 1 2 3))
|
||||
(make-array (list 2) (list 4 5))))
|
||||
(list 1 2 3 4 5))
|
||||
|
||||
(apl-test
|
||||
"cat v,v shape"
|
||||
(sh
|
||||
(apl-catenate
|
||||
(make-array (list 3) (list 1 2 3))
|
||||
(make-array (list 2) (list 4 5))))
|
||||
(list 5))
|
||||
|
||||
(apl-test
|
||||
"cat scalar,v"
|
||||
(rv (apl-catenate (apl-scalar 99) (make-array (list 3) (list 1 2 3))))
|
||||
(list 99 1 2 3))
|
||||
|
||||
(apl-test
|
||||
"cat v,scalar"
|
||||
(rv (apl-catenate (make-array (list 3) (list 1 2 3)) (apl-scalar 99)))
|
||||
(list 1 2 3 99))
|
||||
|
||||
(apl-test
|
||||
"cat matrix last-axis shape"
|
||||
(sh
|
||||
(apl-catenate
|
||||
(make-array (list 2 3) (list 1 2 3 4 5 6))
|
||||
(make-array (list 2 2) (list 7 8 9 10))))
|
||||
(list 2 5))
|
||||
|
||||
(apl-test
|
||||
"cat matrix last-axis ravel"
|
||||
(rv
|
||||
(apl-catenate
|
||||
(make-array (list 2 3) (list 1 2 3 4 5 6))
|
||||
(make-array (list 2 2) (list 7 8 9 10))))
|
||||
(list 1 2 3 7 8 4 5 6 9 10))
|
||||
|
||||
(apl-test
|
||||
"cat-first v,v shape"
|
||||
(sh
|
||||
(apl-catenate-first
|
||||
(make-array (list 3) (list 1 2 3))
|
||||
(make-array (list 2) (list 4 5))))
|
||||
(list 5))
|
||||
|
||||
(apl-test
|
||||
"cat-first matrix shape"
|
||||
(sh
|
||||
(apl-catenate-first
|
||||
(make-array (list 2 3) (list 1 2 3 4 5 6))
|
||||
(make-array (list 3 3) (list 11 12 13 14 15 16 17 18 19))))
|
||||
(list 5 3))
|
||||
|
||||
(apl-test
|
||||
"cat-first matrix ravel"
|
||||
(rv
|
||||
(apl-catenate-first
|
||||
(make-array (list 2 3) (list 1 2 3 4 5 6))
|
||||
(make-array (list 3 3) (list 11 12 13 14 15 16 17 18 19))))
|
||||
(list 1 2 3 4 5 6 11 12 13 14 15 16 17 18 19))
|
||||
|
||||
(apl-test
|
||||
"squad scalar into vector"
|
||||
(rv
|
||||
(apl-squad (apl-scalar 2) (make-array (list 5) (list 10 20 30 40 50))))
|
||||
(list 20))
|
||||
|
||||
(apl-test
|
||||
"squad first element"
|
||||
(rv (apl-squad (apl-scalar 1) (make-array (list 3) (list 10 20 30))))
|
||||
(list 10))
|
||||
|
||||
(apl-test
|
||||
"squad last element"
|
||||
(rv
|
||||
(apl-squad (apl-scalar 5) (make-array (list 5) (list 10 20 30 40 50))))
|
||||
(list 50))
|
||||
|
||||
(apl-test
|
||||
"squad fully specified matrix element"
|
||||
(rv
|
||||
(apl-squad
|
||||
(make-array (list 2) (list 2 3))
|
||||
(make-array (list 3 4) (list 1 2 3 4 5 6 7 8 9 10 11 12))))
|
||||
(list 7))
|
||||
|
||||
(apl-test
|
||||
"squad partial row of matrix shape"
|
||||
(sh
|
||||
(apl-squad
|
||||
(apl-scalar 2)
|
||||
(make-array (list 3 4) (list 1 2 3 4 5 6 7 8 9 10 11 12))))
|
||||
(list 4))
|
||||
|
||||
(apl-test
|
||||
"squad partial row of matrix ravel"
|
||||
(rv
|
||||
(apl-squad
|
||||
(apl-scalar 2)
|
||||
(make-array (list 3 4) (list 1 2 3 4 5 6 7 8 9 10 11 12))))
|
||||
(list 5 6 7 8))
|
||||
|
||||
(apl-test
|
||||
"squad partial 3d slice shape"
|
||||
(sh (apl-squad (apl-scalar 1) (make-array (list 2 3 4) (range 1 25))))
|
||||
(list 3 4))
|
||||
|
||||
(apl-test
|
||||
"grade-up basic"
|
||||
(rv (apl-grade-up (make-array (list 5) (list 3 1 4 1 5))))
|
||||
(list 2 4 1 3 5))
|
||||
|
||||
(apl-test
|
||||
"grade-up shape"
|
||||
(sh (apl-grade-up (make-array (list 4) (list 4 1 3 2))))
|
||||
(list 4))
|
||||
|
||||
(apl-test
|
||||
"grade-up no duplicates"
|
||||
(rv (apl-grade-up (make-array (list 4) (list 4 1 3 2))))
|
||||
(list 2 4 3 1))
|
||||
|
||||
(apl-test
|
||||
"grade-up already sorted"
|
||||
(rv (apl-grade-up (make-array (list 3) (list 1 2 3))))
|
||||
(list 1 2 3))
|
||||
|
||||
(apl-test
|
||||
"grade-up reverse sorted"
|
||||
(rv (apl-grade-up (make-array (list 3) (list 3 2 1))))
|
||||
(list 3 2 1))
|
||||
|
||||
(apl-test
|
||||
"grade-down basic"
|
||||
(rv (apl-grade-down (make-array (list 5) (list 3 1 4 1 5))))
|
||||
(list 5 3 1 2 4))
|
||||
|
||||
(apl-test
|
||||
"grade-down no duplicates"
|
||||
(rv (apl-grade-down (make-array (list 4) (list 4 1 3 2))))
|
||||
(list 1 3 4 2))
|
||||
|
||||
(apl-test
|
||||
"grade-up single element"
|
||||
(rv (apl-grade-up (make-array (list 1) (list 42))))
|
||||
(list 1))
|
||||
|
||||
(apl-test
|
||||
"enclose shape is scalar"
|
||||
(sh (apl-enclose (make-array (list 3) (list 1 2 3))))
|
||||
(list))
|
||||
|
||||
(apl-test
|
||||
"enclose ravel length is 1"
|
||||
(len (rv (apl-enclose (make-array (list 3) (list 1 2 3)))))
|
||||
1)
|
||||
|
||||
(apl-test
|
||||
"enclose inner ravel"
|
||||
(rv (first (rv (apl-enclose (make-array (list 3) (list 1 2 3))))))
|
||||
(list 1 2 3))
|
||||
|
||||
(apl-test
|
||||
"disclose of enclose round-trips ravel"
|
||||
(rv (apl-disclose (apl-enclose (make-array (list 3) (list 10 20 30)))))
|
||||
(list 10 20 30))
|
||||
|
||||
(apl-test
|
||||
"disclose of enclose round-trips shape"
|
||||
(sh (apl-disclose (apl-enclose (make-array (list 3) (list 10 20 30)))))
|
||||
(list 3))
|
||||
|
||||
(apl-test
|
||||
"disclose scalar ravel"
|
||||
(rv (apl-disclose (apl-scalar 42)))
|
||||
(list 42))
|
||||
|
||||
(apl-test
|
||||
"disclose vector ravel"
|
||||
(rv (apl-disclose (make-array (list 3) (list 5 6 7))))
|
||||
(list 5))
|
||||
|
||||
(apl-test
|
||||
"disclose matrix returns first row"
|
||||
(rv (apl-disclose (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||
(list 1 2 3))
|
||||
|
||||
(apl-test
|
||||
"member basic"
|
||||
(rv
|
||||
(apl-member
|
||||
(make-array (list 3) (list 1 2 3))
|
||||
(make-array (list 2) (list 2 3))))
|
||||
(list 0 1 1))
|
||||
|
||||
(apl-test
|
||||
"member all absent"
|
||||
(rv
|
||||
(apl-member
|
||||
(make-array (list 3) (list 4 5 6))
|
||||
(make-array (list 3) (list 1 2 3))))
|
||||
(list 0 0 0))
|
||||
|
||||
(apl-test
|
||||
"member scalar"
|
||||
(rv (apl-member (apl-scalar 5) (make-array (list 3) (list 1 5 9))))
|
||||
(list 1))
|
||||
|
||||
(apl-test
|
||||
"member shape preserved"
|
||||
(sh
|
||||
(apl-member
|
||||
(make-array (list 2 3) (list 1 2 3 4 5 6))
|
||||
(make-array (list 3) (list 1 3 5))))
|
||||
(list 2 3))
|
||||
|
||||
(apl-test
|
||||
"member matrix ravel"
|
||||
(rv
|
||||
(apl-member
|
||||
(make-array (list 2 3) (list 1 2 3 4 5 6))
|
||||
(make-array (list 3) (list 1 3 5))))
|
||||
(list 1 0 1 0 1 0))
|
||||
|
||||
(apl-test
|
||||
"index-of basic"
|
||||
(rv
|
||||
(apl-index-of
|
||||
(make-array (list 4) (list 10 20 30 40))
|
||||
(make-array (list 3) (list 20 40 10))))
|
||||
(list 2 4 1))
|
||||
|
||||
(apl-test
|
||||
"index-of not-found"
|
||||
(rv
|
||||
(apl-index-of
|
||||
(make-array (list 3) (list 1 2 3))
|
||||
(make-array (list 2) (list 5 2))))
|
||||
(list 4 2))
|
||||
|
||||
(apl-test
|
||||
"index-of scalar right"
|
||||
(rv
|
||||
(apl-index-of (make-array (list 3) (list 10 20 30)) (apl-scalar 20)))
|
||||
(list 2))
|
||||
|
||||
(apl-test
|
||||
"without basic"
|
||||
(rv
|
||||
(apl-without
|
||||
(make-array (list 5) (list 1 2 3 4 5))
|
||||
(make-array (list 2) (list 2 4))))
|
||||
(list 1 3 5))
|
||||
|
||||
(apl-test
|
||||
"without shape"
|
||||
(sh
|
||||
(apl-without
|
||||
(make-array (list 5) (list 1 2 3 4 5))
|
||||
(make-array (list 2) (list 2 4))))
|
||||
(list 3))
|
||||
|
||||
(apl-test
|
||||
"without nothing removed"
|
||||
(rv
|
||||
(apl-without
|
||||
(make-array (list 3) (list 1 2 3))
|
||||
(make-array (list 3) (list 4 5 6))))
|
||||
(list 1 2 3))
|
||||
|
||||
(apl-test
|
||||
"without all removed"
|
||||
(rv
|
||||
(apl-without
|
||||
(make-array (list 3) (list 1 2 3))
|
||||
(make-array (list 3) (list 1 2 3))))
|
||||
(list))
|
||||
48
lib/apl/tests/system.sx
Normal file
48
lib/apl/tests/system.sx
Normal file
@@ -0,0 +1,48 @@
|
||||
; Tests for APL ⎕ system functions.
|
||||
|
||||
(define mkrv (fn (arr) (get arr :ravel)))
|
||||
(define mksh (fn (arr) (get arr :shape)))
|
||||
|
||||
(apl-test "⎕IO returns 1" (mkrv (apl-quad-io)) (list 1))
|
||||
|
||||
(apl-test "⎕ML returns 1" (mkrv (apl-quad-ml)) (list 1))
|
||||
|
||||
(apl-test "⎕FR returns 1248" (mkrv (apl-quad-fr)) (list 1248))
|
||||
|
||||
(apl-test "⎕TS shape is 7" (mksh (apl-quad-ts)) (list 7))
|
||||
|
||||
(apl-test "⎕TS year is 1970 default" (first (mkrv (apl-quad-ts))) 1970)
|
||||
|
||||
(apl-test "⎕FMT scalar 42" (apl-quad-fmt (apl-scalar 42)) "42")
|
||||
|
||||
(apl-test "⎕FMT scalar negative" (apl-quad-fmt (apl-scalar -7)) "-7")
|
||||
|
||||
(apl-test
|
||||
"⎕FMT empty vector"
|
||||
(apl-quad-fmt (make-array (list 0) (list)))
|
||||
"")
|
||||
|
||||
(apl-test
|
||||
"⎕FMT singleton vector"
|
||||
(apl-quad-fmt (make-array (list 1) (list 42)))
|
||||
"42")
|
||||
|
||||
(apl-test
|
||||
"⎕FMT vector"
|
||||
(apl-quad-fmt (make-array (list 5) (list 1 2 3 4 5)))
|
||||
"1 2 3 4 5")
|
||||
|
||||
(apl-test
|
||||
"⎕FMT matrix 2x3"
|
||||
(apl-quad-fmt (make-array (list 2 3) (list 1 2 3 4 5 6)))
|
||||
"1 2 3\n4 5 6\n")
|
||||
|
||||
(apl-test
|
||||
"⎕← (print) returns its arg"
|
||||
(mkrv (apl-quad-print (apl-scalar 99)))
|
||||
(list 99))
|
||||
|
||||
(apl-test
|
||||
"⎕← preserves shape"
|
||||
(mksh (apl-quad-print (make-array (list 3) (list 1 2 3))))
|
||||
(list 3))
|
||||
156
lib/apl/tests/tradfn.sx
Normal file
156
lib/apl/tests/tradfn.sx
Normal file
@@ -0,0 +1,156 @@
|
||||
; Tests for apl-call-tradfn (manual structure construction).
|
||||
|
||||
(define mkrv (fn (arr) (get arr :ravel)))
|
||||
(define mksh (fn (arr) (get arr :shape)))
|
||||
(define mknum (fn (n) (list :num n)))
|
||||
(define mknm (fn (s) (list :name s)))
|
||||
(define mkfg (fn (g) (list :fn-glyph g)))
|
||||
(define mkmon (fn (g a) (list :monad (mkfg g) a)))
|
||||
(define mkdyd (fn (g l r) (list :dyad (mkfg g) l r)))
|
||||
(define mkasg (fn (n e) (list :assign n e)))
|
||||
(define mkbr (fn (e) (list :branch e)))
|
||||
|
||||
(define mkif (fn (c t e) (list :if c t e)))
|
||||
|
||||
(define mkwhile (fn (c b) (list :while c b)))
|
||||
|
||||
(define mkfor (fn (v i b) (list :for v i b)))
|
||||
|
||||
(define mksel (fn (v cs d) (list :select v cs d)))
|
||||
|
||||
(define mktrap (fn (codes t c) (list :trap codes t c)))
|
||||
|
||||
(define mkthr (fn (code msg) (list :throw code msg)))
|
||||
|
||||
(apl-test
|
||||
"tradfn R←L+W simple add"
|
||||
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkasg "R" (mkdyd "+" (mknm "L") (mknm "W")))) :alpha "L"} (apl-scalar 5) (apl-scalar 7)))
|
||||
(list 12))
|
||||
|
||||
(apl-test
|
||||
"tradfn R←L×W"
|
||||
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkasg "R" (mkdyd "×" (mknm "L") (mknm "W")))) :alpha "L"} (apl-scalar 6) (apl-scalar 7)))
|
||||
(list 42))
|
||||
|
||||
(apl-test
|
||||
"tradfn monadic R←-W"
|
||||
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkasg "R" (mkmon "-" (mknm "W")))) :alpha nil} nil (apl-scalar 9)))
|
||||
(list -9))
|
||||
|
||||
(apl-test
|
||||
"tradfn →0 exits early"
|
||||
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkasg "R" (mknm "W")) (mkbr (mknum 0)) (mkasg "R" (mknum 999))) :alpha nil} nil (apl-scalar 7)))
|
||||
(list 7))
|
||||
|
||||
(apl-test
|
||||
"tradfn branch to line 3 skips line 2"
|
||||
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkbr (mknum 3)) (mkasg "R" (mknum 999)) (mkasg "R" (mknum 42))) :alpha nil} nil (apl-scalar 0)))
|
||||
(list 42))
|
||||
|
||||
(apl-test
|
||||
"tradfn local var t←W+1; R←t×2"
|
||||
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkasg "t" (mkdyd "+" (mknm "W") (mknum 1))) (mkasg "R" (mkdyd "×" (mknm "t") (mknum 2)))) :alpha nil} nil (apl-scalar 5)))
|
||||
(list 12))
|
||||
|
||||
(apl-test
|
||||
"tradfn vector args"
|
||||
(mkrv
|
||||
(apl-call-tradfn
|
||||
{:result "R" :omega "W" :stmts (list (mkasg "R" (mkdyd "+" (mknm "L") (mknm "W")))) :alpha "L"}
|
||||
(make-array (list 3) (list 1 2 3))
|
||||
(make-array (list 3) (list 10 20 30))))
|
||||
(list 11 22 33))
|
||||
|
||||
(apl-test
|
||||
"tradfn unset result returns nil"
|
||||
(apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkbr (mknum 0))) :alpha nil} nil (apl-scalar 5))
|
||||
nil)
|
||||
|
||||
(apl-test
|
||||
"tradfn run-off end returns result"
|
||||
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkasg "R" (mkdyd "×" (mknm "W") (mknum 3)))) :alpha nil} nil (apl-scalar 7)))
|
||||
(list 21))
|
||||
|
||||
(apl-test
|
||||
"tradfn loop sum 1+2+...+5 via branch"
|
||||
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkasg "i" (mknum 1)) (mkasg "R" (mknum 0)) (mkasg "R" (mkdyd "+" (mknm "R") (mknm "i"))) (mkasg "i" (mkdyd "+" (mknm "i") (mknum 1))) (mkbr (mkdyd "×" (mkdyd "≤" (mknm "i") (mknm "W")) (mknum 3)))) :alpha nil} nil (apl-scalar 5)))
|
||||
(list 15))
|
||||
|
||||
(apl-test
|
||||
"tradfn :If true branch"
|
||||
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkif (mkdyd ">" (mknm "W") (mknum 0)) (list (mkasg "R" (mknum 1))) (list (mkasg "R" (mknum 0))))) :alpha nil} nil (apl-scalar 5)))
|
||||
(list 1))
|
||||
|
||||
(apl-test
|
||||
"tradfn :If false branch"
|
||||
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkif (mkdyd ">" (mknm "W") (mknum 100)) (list (mkasg "R" (mknum 1))) (list (mkasg "R" (mknum 0))))) :alpha nil} nil (apl-scalar 5)))
|
||||
(list 0))
|
||||
|
||||
(apl-test
|
||||
"tradfn :While sum 1..N"
|
||||
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkasg "i" (mknum 1)) (mkasg "R" (mknum 0)) (mkwhile (mkdyd "≤" (mknm "i") (mknm "W")) (list (mkasg "R" (mkdyd "+" (mknm "R") (mknm "i"))) (mkasg "i" (mkdyd "+" (mknm "i") (mknum 1)))))) :alpha nil} nil (apl-scalar 10)))
|
||||
(list 55))
|
||||
|
||||
(apl-test
|
||||
"tradfn :For sum elements"
|
||||
(mkrv
|
||||
(apl-call-tradfn
|
||||
{:result "R" :omega "W" :stmts (list (mkasg "R" (mknum 0)) (mkfor "x" (mknm "W") (list (mkasg "R" (mkdyd "+" (mknm "R") (mknm "x")))))) :alpha nil}
|
||||
nil
|
||||
(make-array (list 4) (list 10 20 30 40))))
|
||||
(list 100))
|
||||
|
||||
(apl-test
|
||||
"tradfn :For with empty vector"
|
||||
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkasg "R" (mknum 99)) (mkfor "x" (mknm "W") (list (mkasg "R" (mkdyd "+" (mknm "R") (mknm "x")))))) :alpha nil} nil (make-array (list 0) (list))))
|
||||
(list 99))
|
||||
|
||||
(apl-test
|
||||
"tradfn :Select dispatch hit"
|
||||
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mksel (mknm "W") (list (list (mknum 1) (mkasg "R" (mknum 100))) (list (mknum 2) (mkasg "R" (mknum 200))) (list (mknum 3) (mkasg "R" (mknum 300)))) (list (mkasg "R" (mknum 0))))) :alpha nil} nil (apl-scalar 2)))
|
||||
(list 200))
|
||||
|
||||
(apl-test
|
||||
"tradfn :Select default block"
|
||||
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mksel (mknm "W") (list (list (mknum 1) (mkasg "R" (mknum 100))) (list (mknum 2) (mkasg "R" (mknum 200)))) (list (mkasg "R" (mknum -1))))) :alpha nil} nil (apl-scalar 99)))
|
||||
(list -1))
|
||||
|
||||
(apl-test
|
||||
"tradfn nested :If"
|
||||
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkif (mkdyd ">" (mknm "W") (mknum 0)) (list (mkif (mkdyd ">" (mknm "W") (mknum 10)) (list (mkasg "R" (mknum 2))) (list (mkasg "R" (mknum 1))))) (list (mkasg "R" (mknum 0))))) :alpha nil} nil (apl-scalar 5)))
|
||||
(list 1))
|
||||
|
||||
(apl-test
|
||||
"tradfn :If assigns persist outside"
|
||||
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkasg "R" (mknum 0)) (mkif (mkdyd ">" (mknm "W") (mknum 0)) (list (mkasg "R" (mknum 42))) (list)) (mkasg "R" (mkdyd "+" (mknm "R") (mknum 1)))) :alpha nil} nil (apl-scalar 5)))
|
||||
(list 43))
|
||||
|
||||
(apl-test
|
||||
"tradfn :For factorial 1..5"
|
||||
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkasg "R" (mknum 1)) (mkfor "x" (mkmon "⍳" (mknm "W")) (list (mkasg "R" (mkdyd "×" (mknm "R") (mknm "x")))))) :alpha nil} nil (apl-scalar 5)))
|
||||
(list 120))
|
||||
|
||||
(apl-test
|
||||
"tradfn :Trap normal flow (no error)"
|
||||
(mkrv (apl-call-tradfn {:result "R" :omega nil :stmts (list (mktrap (list 0) (list (mkasg "R" (mknum 99))) (list (mkasg "R" (mknum -1))))) :alpha nil} nil nil))
|
||||
(list 99))
|
||||
|
||||
(apl-test
|
||||
"tradfn :Trap catches matching code"
|
||||
(mkrv (apl-call-tradfn {:result "R" :omega nil :stmts (list (mktrap (list 5) (list (mkthr 5 "boom")) (list (mkasg "R" (mknum 42))))) :alpha nil} nil nil))
|
||||
(list 42))
|
||||
|
||||
(apl-test
|
||||
"tradfn :Trap catch-all (code 0)"
|
||||
(mkrv (apl-call-tradfn {:result "R" :omega nil :stmts (list (mktrap (list 0) (list (mkthr 99 "any")) (list (mkasg "R" (mknum 1))))) :alpha nil} nil nil))
|
||||
(list 1))
|
||||
|
||||
(apl-test
|
||||
"tradfn :Trap catches one of many codes"
|
||||
(mkrv (apl-call-tradfn {:result "R" :omega nil :stmts (list (mktrap (list 1 2 3) (list (mkthr 2 "two")) (list (mkasg "R" (mknum 22))))) :alpha nil} nil nil))
|
||||
(list 22))
|
||||
|
||||
(apl-test
|
||||
"tradfn :Trap continues to next stmt after catch"
|
||||
(mkrv (apl-call-tradfn {:result "R" :omega nil :stmts (list (mktrap (list 7) (list (mkthr 7 "c")) (list (mkasg "R" (mknum 10)))) (mkasg "R" (mkdyd "+" (mknm "R") (mknum 5)))) :alpha nil} nil nil))
|
||||
(list 15))
|
||||
81
lib/apl/tests/valence.sx
Normal file
81
lib/apl/tests/valence.sx
Normal file
@@ -0,0 +1,81 @@
|
||||
; Tests for valence detection (apl-dfn-valence, apl-tradfn-valence)
|
||||
; and unified dispatch (apl-call).
|
||||
|
||||
(define mkrv (fn (arr) (get arr :ravel)))
|
||||
(define mknum (fn (n) (list :num n)))
|
||||
(define mknm (fn (s) (list :name s)))
|
||||
(define mkfg (fn (g) (list :fn-glyph g)))
|
||||
(define mkmon (fn (g a) (list :monad (mkfg g) a)))
|
||||
(define mkdyd (fn (g l r) (list :dyad (mkfg g) l r)))
|
||||
(define mkasg (fn (n e) (list :assign n e)))
|
||||
(define mkdfn (fn (stmts) (cons :dfn stmts)))
|
||||
|
||||
(apl-test
|
||||
"dfn-valence niladic body=42"
|
||||
(apl-dfn-valence (mkdfn (list (mknum 42))))
|
||||
:niladic)
|
||||
|
||||
(apl-test
|
||||
"dfn-valence monadic body=⍵+1"
|
||||
(apl-dfn-valence (mkdfn (list (mkdyd "+" (mknm "⍵") (mknum 1)))))
|
||||
:monadic)
|
||||
|
||||
(apl-test
|
||||
"dfn-valence dyadic body=⍺+⍵"
|
||||
(apl-dfn-valence (mkdfn (list (mkdyd "+" (mknm "⍺") (mknm "⍵")))))
|
||||
:dyadic)
|
||||
|
||||
(apl-test
|
||||
"dfn-valence dyadic mentions ⍺ via local"
|
||||
(apl-dfn-valence (mkdfn (list (mkasg "x" (mknm "⍺")) (mknm "x"))))
|
||||
:dyadic)
|
||||
|
||||
(apl-test
|
||||
"dfn-valence dyadic deep nest"
|
||||
(apl-dfn-valence
|
||||
(mkdfn (list (mkmon "-" (mkdyd "×" (mknm "⍺") (mknm "⍵"))))))
|
||||
:dyadic)
|
||||
|
||||
(apl-test "tradfn-valence niladic" (apl-tradfn-valence {:result "R" :omega nil :stmts (list) :alpha nil}) :niladic)
|
||||
|
||||
(apl-test "tradfn-valence monadic" (apl-tradfn-valence {:result "R" :omega "W" :stmts (list) :alpha nil}) :monadic)
|
||||
|
||||
(apl-test "tradfn-valence dyadic" (apl-tradfn-valence {:result "R" :omega "W" :stmts (list) :alpha "L"}) :dyadic)
|
||||
|
||||
(apl-test
|
||||
"apl-call dfn niladic"
|
||||
(mkrv (apl-call (mkdfn (list (mknum 42))) nil nil))
|
||||
(list 42))
|
||||
|
||||
(apl-test
|
||||
"apl-call dfn monadic"
|
||||
(mkrv
|
||||
(apl-call
|
||||
(mkdfn (list (mkdyd "+" (mknm "⍵") (mknum 1))))
|
||||
nil
|
||||
(apl-scalar 5)))
|
||||
(list 6))
|
||||
|
||||
(apl-test
|
||||
"apl-call dfn dyadic"
|
||||
(mkrv
|
||||
(apl-call
|
||||
(mkdfn (list (mkdyd "+" (mknm "⍺") (mknm "⍵"))))
|
||||
(apl-scalar 3)
|
||||
(apl-scalar 4)))
|
||||
(list 7))
|
||||
|
||||
(apl-test
|
||||
"apl-call tradfn dyadic"
|
||||
(mkrv (apl-call {:result "R" :omega "W" :stmts (list (mkasg "R" (mkdyd "×" (mknm "L") (mknm "W")))) :alpha "L"} (apl-scalar 6) (apl-scalar 7)))
|
||||
(list 42))
|
||||
|
||||
(apl-test
|
||||
"apl-call tradfn monadic"
|
||||
(mkrv (apl-call {:result "R" :omega "W" :stmts (list (mkasg "R" (mkmon "-" (mknm "W")))) :alpha nil} nil (apl-scalar 9)))
|
||||
(list -9))
|
||||
|
||||
(apl-test
|
||||
"apl-call tradfn niladic returns nil result"
|
||||
(apl-call {:result "R" :omega nil :stmts (list) :alpha nil} nil nil)
|
||||
nil)
|
||||
168
lib/apl/tokenizer.sx
Normal file
168
lib/apl/tokenizer.sx
Normal file
@@ -0,0 +1,168 @@
|
||||
(define apl-glyph-set
|
||||
(list "+" "-" "×" "÷" "*" "⍟" "⌈" "⌊" "|" "!" "?" "○" "~" "<" "≤" "=" "≥" ">" "≠"
|
||||
"≢" "≡" "∊" "∧" "∨" "⍱" "⍲" "," "⍪" "⍴" "⌽" "⊖" "⍉" "↑" "↓" "⊂" "⊃" "⊆"
|
||||
"∪" "∩" "⍳" "⍸" "⌷" "⍋" "⍒" "⊥" "⊤" "⊣" "⊢" "⍎" "⍕"
|
||||
"⍺" "⍵" "∇" "/" "\\" "¨" "⍨" "∘" "." "⍣" "⍤" "⍥" "@" "¯"))
|
||||
|
||||
(define apl-glyph?
|
||||
(fn (ch)
|
||||
(some (fn (g) (= g ch)) apl-glyph-set)))
|
||||
|
||||
(define apl-digit?
|
||||
(fn (ch)
|
||||
(and (string? ch) (>= ch "0") (<= ch "9"))))
|
||||
|
||||
(define apl-alpha?
|
||||
(fn (ch)
|
||||
(and (string? ch)
|
||||
(or (and (>= ch "a") (<= ch "z"))
|
||||
(and (>= ch "A") (<= ch "Z"))
|
||||
(= ch "_")))))
|
||||
|
||||
(define apl-tokenize
|
||||
(fn (source)
|
||||
(let ((pos 0)
|
||||
(src-len (len source))
|
||||
(tokens (list)))
|
||||
|
||||
(define tok-push!
|
||||
(fn (type value)
|
||||
(append! tokens {:type type :value value})))
|
||||
|
||||
(define cur-sw?
|
||||
(fn (ch)
|
||||
(and (< pos src-len) (starts-with? (slice source pos) ch))))
|
||||
|
||||
(define cur-byte
|
||||
(fn ()
|
||||
(if (< pos src-len) (nth source pos) nil)))
|
||||
|
||||
(define advance!
|
||||
(fn ()
|
||||
(set! pos (+ pos 1))))
|
||||
|
||||
(define consume!
|
||||
(fn (ch)
|
||||
(set! pos (+ pos (len ch)))))
|
||||
|
||||
(define find-glyph
|
||||
(fn ()
|
||||
(let ((rem (slice source pos)))
|
||||
(let ((matches (filter (fn (g) (starts-with? rem g)) apl-glyph-set)))
|
||||
(if (> (len matches) 0) (first matches) nil)))))
|
||||
|
||||
(define read-digits!
|
||||
(fn (acc)
|
||||
(if (and (< pos src-len) (apl-digit? (cur-byte)))
|
||||
(let ((ch (cur-byte)))
|
||||
(begin
|
||||
(advance!)
|
||||
(read-digits! (str acc ch))))
|
||||
acc)))
|
||||
|
||||
(define read-ident-cont!
|
||||
(fn ()
|
||||
(when (and (< pos src-len)
|
||||
(let ((ch (cur-byte)))
|
||||
(or (apl-alpha? ch) (apl-digit? ch))))
|
||||
(begin
|
||||
(advance!)
|
||||
(read-ident-cont!)))))
|
||||
|
||||
(define read-string!
|
||||
(fn (acc)
|
||||
(cond
|
||||
((>= pos src-len) acc)
|
||||
((cur-sw? "'")
|
||||
(if (and (< (+ pos 1) src-len) (cur-sw? "'"))
|
||||
(begin
|
||||
(advance!)
|
||||
(advance!)
|
||||
(read-string! (str acc "'")))
|
||||
(begin (advance!) acc)))
|
||||
(true
|
||||
(let ((ch (cur-byte)))
|
||||
(begin
|
||||
(advance!)
|
||||
(read-string! (str acc ch))))))))
|
||||
|
||||
(define skip-line!
|
||||
(fn ()
|
||||
(when (and (< pos src-len) (not (cur-sw? "\n")))
|
||||
(begin
|
||||
(advance!)
|
||||
(skip-line!)))))
|
||||
|
||||
(define scan!
|
||||
(fn ()
|
||||
(when (< pos src-len)
|
||||
(let ((ch (cur-byte)))
|
||||
(cond
|
||||
((or (= ch " ") (= ch "\t") (= ch "\r"))
|
||||
(begin (advance!) (scan!)))
|
||||
((= ch "\n")
|
||||
(begin (advance!) (tok-push! :newline nil) (scan!)))
|
||||
((cur-sw? "⍝")
|
||||
(begin (skip-line!) (scan!)))
|
||||
((cur-sw? "⋄")
|
||||
(begin (consume! "⋄") (tok-push! :diamond nil) (scan!)))
|
||||
((= ch "(")
|
||||
(begin (advance!) (tok-push! :lparen nil) (scan!)))
|
||||
((= ch ")")
|
||||
(begin (advance!) (tok-push! :rparen nil) (scan!)))
|
||||
((= ch "[")
|
||||
(begin (advance!) (tok-push! :lbracket nil) (scan!)))
|
||||
((= ch "]")
|
||||
(begin (advance!) (tok-push! :rbracket nil) (scan!)))
|
||||
((= ch "{")
|
||||
(begin (advance!) (tok-push! :lbrace nil) (scan!)))
|
||||
((= ch "}")
|
||||
(begin (advance!) (tok-push! :rbrace nil) (scan!)))
|
||||
((= ch ";")
|
||||
(begin (advance!) (tok-push! :semi nil) (scan!)))
|
||||
((cur-sw? "←")
|
||||
(begin (consume! "←") (tok-push! :assign nil) (scan!)))
|
||||
((= ch ":")
|
||||
(let ((start pos))
|
||||
(begin
|
||||
(advance!)
|
||||
(if (and (< pos src-len) (apl-alpha? (cur-byte)))
|
||||
(begin
|
||||
(read-ident-cont!)
|
||||
(tok-push! :keyword (slice source start pos)))
|
||||
(tok-push! :colon nil))
|
||||
(scan!))))
|
||||
((and (cur-sw? "¯")
|
||||
(< (+ pos (len "¯")) src-len)
|
||||
(apl-digit? (nth source (+ pos (len "¯")))))
|
||||
(begin
|
||||
(consume! "¯")
|
||||
(let ((digits (read-digits! "")))
|
||||
(tok-push! :num (- 0 (parse-int digits 0))))
|
||||
(scan!)))
|
||||
((apl-digit? ch)
|
||||
(begin
|
||||
(let ((digits (read-digits! "")))
|
||||
(tok-push! :num (parse-int digits 0)))
|
||||
(scan!)))
|
||||
((= ch "'")
|
||||
(begin
|
||||
(advance!)
|
||||
(let ((s (read-string! "")))
|
||||
(tok-push! :str s))
|
||||
(scan!)))
|
||||
((or (apl-alpha? ch) (cur-sw? "⎕"))
|
||||
(let ((start pos))
|
||||
(begin
|
||||
(if (cur-sw? "⎕") (consume! "⎕") (advance!))
|
||||
(read-ident-cont!)
|
||||
(tok-push! :name (slice source start pos))
|
||||
(scan!))))
|
||||
(true
|
||||
(let ((g (find-glyph)))
|
||||
(if g
|
||||
(begin (consume! g) (tok-push! :glyph g) (scan!))
|
||||
(begin (advance!) (scan!))))))))))
|
||||
|
||||
(scan!)
|
||||
tokens)))
|
||||
460
lib/apl/transpile.sx
Normal file
460
lib/apl/transpile.sx
Normal file
@@ -0,0 +1,460 @@
|
||||
; APL transpile / AST evaluator
|
||||
;
|
||||
; Walks parsed AST nodes and evaluates against the runtime.
|
||||
; Entry points:
|
||||
; apl-eval-ast : node × env → value
|
||||
; apl-eval-stmts : stmt-list × env → value (handles guards, locals, ⍺← default)
|
||||
; apl-call-dfn : dfn-ast × ⍺ × ⍵ → value (dyadic)
|
||||
; apl-call-dfn-m : dfn-ast × ⍵ → value (monadic)
|
||||
;
|
||||
; Env is a dict; ⍺ stored under "alpha", ⍵ under "omega",
|
||||
; the dfn-ast itself under "nabla" (for ∇ recursion),
|
||||
; user names under their literal name.
|
||||
|
||||
(define
|
||||
apl-monadic-fn
|
||||
(fn
|
||||
(g)
|
||||
(cond
|
||||
((= g "+") apl-plus-m)
|
||||
((= g "-") apl-neg-m)
|
||||
((= g "×") apl-signum)
|
||||
((= g "÷") apl-recip)
|
||||
((= g "⌈") apl-ceil)
|
||||
((= g "⌊") apl-floor)
|
||||
((= g "⍳") apl-iota)
|
||||
((= g "|") apl-abs)
|
||||
((= g "*") apl-exp)
|
||||
((= g "⍟") apl-ln)
|
||||
((= g "!") apl-fact)
|
||||
((= g "○") apl-pi-times)
|
||||
((= g "~") apl-not)
|
||||
((= g "≢") apl-tally)
|
||||
((= g "⍴") apl-shape)
|
||||
((= g "≡") apl-depth)
|
||||
((= g "⊂") apl-enclose)
|
||||
((= g "⊃") apl-disclose)
|
||||
((= g ",") apl-ravel)
|
||||
((= g "⌽") apl-reverse)
|
||||
((= g "⊖") apl-reverse-first)
|
||||
((= g "⍋") apl-grade-up)
|
||||
((= g "⍒") apl-grade-down)
|
||||
((= g "⎕FMT") apl-quad-fmt)
|
||||
(else (error "no monadic fn for glyph")))))
|
||||
|
||||
(define
|
||||
apl-dyadic-fn
|
||||
(fn
|
||||
(g)
|
||||
(cond
|
||||
((= g "+") apl-add)
|
||||
((= g "-") apl-sub)
|
||||
((= g "×") apl-mul)
|
||||
((= g "÷") apl-div)
|
||||
((= g "⌈") apl-max)
|
||||
((= g "⌊") apl-min)
|
||||
((= g "*") apl-pow)
|
||||
((= g "⍟") apl-log)
|
||||
((= g "|") apl-mod)
|
||||
((= g "!") apl-binomial)
|
||||
((= g "○") apl-trig)
|
||||
((= g "<") apl-lt)
|
||||
((= g "≤") apl-le)
|
||||
((= g "=") apl-eq)
|
||||
((= g "≥") apl-ge)
|
||||
((= g ">") apl-gt)
|
||||
((= g "≠") apl-ne)
|
||||
((= g "∧") apl-and)
|
||||
((= g "∨") apl-or)
|
||||
((= g "⍱") apl-nor)
|
||||
((= g "⍲") apl-nand)
|
||||
((= g ",") apl-catenate)
|
||||
((= g "⍪") apl-catenate-first)
|
||||
((= g "⍴") apl-reshape)
|
||||
((= g "↑") apl-take)
|
||||
((= g "↓") apl-drop)
|
||||
((= g "⌷") apl-squad)
|
||||
((= g "⌽") apl-rotate)
|
||||
((= g "⊖") apl-rotate-first)
|
||||
((= g "∊") apl-member)
|
||||
((= g "⍳") apl-index-of)
|
||||
((= g "~") apl-without)
|
||||
(else (error "no dyadic fn for glyph")))))
|
||||
|
||||
(define
|
||||
apl-truthy?
|
||||
(fn
|
||||
(v)
|
||||
(let
|
||||
((rv (get v :ravel)))
|
||||
(if (and (= (len rv) 1) (= (first rv) 0)) false true))))
|
||||
|
||||
(define
|
||||
apl-eval-ast
|
||||
(fn
|
||||
(node env)
|
||||
(let
|
||||
((tag (first node)))
|
||||
(cond
|
||||
((= tag :num) (apl-scalar (nth node 1)))
|
||||
((= tag :vec)
|
||||
(let
|
||||
((items (rest node)))
|
||||
(let
|
||||
((vals (map (fn (n) (apl-eval-ast n env)) items)))
|
||||
(make-array
|
||||
(list (len vals))
|
||||
(map (fn (v) (first (get v :ravel))) vals)))))
|
||||
((= tag :name)
|
||||
(let
|
||||
((nm (nth node 1)))
|
||||
(cond
|
||||
((= nm "⍺") (get env "alpha"))
|
||||
((= nm "⍵") (get env "omega"))
|
||||
((= nm "⎕IO") (apl-quad-io))
|
||||
((= nm "⎕ML") (apl-quad-ml))
|
||||
((= nm "⎕FR") (apl-quad-fr))
|
||||
((= nm "⎕TS") (apl-quad-ts))
|
||||
(else (get env nm)))))
|
||||
((= tag :monad)
|
||||
(let
|
||||
((fn-node (nth node 1)) (arg (nth node 2)))
|
||||
(if
|
||||
(and (= (first fn-node) :fn-glyph) (= (nth fn-node 1) "∇"))
|
||||
(apl-call-dfn-m (get env "nabla") (apl-eval-ast arg env))
|
||||
((apl-resolve-monadic fn-node env) (apl-eval-ast arg env)))))
|
||||
((= tag :dyad)
|
||||
(let
|
||||
((fn-node (nth node 1))
|
||||
(lhs (nth node 2))
|
||||
(rhs (nth node 3)))
|
||||
(if
|
||||
(and (= (first fn-node) :fn-glyph) (= (nth fn-node 1) "∇"))
|
||||
(apl-call-dfn
|
||||
(get env "nabla")
|
||||
(apl-eval-ast lhs env)
|
||||
(apl-eval-ast rhs env))
|
||||
((apl-resolve-dyadic fn-node env)
|
||||
(apl-eval-ast lhs env)
|
||||
(apl-eval-ast rhs env)))))
|
||||
((= tag :program) (apl-eval-stmts (rest node) env))
|
||||
((= tag :dfn) node)
|
||||
(else (error (list "apl-eval-ast: unknown node tag" tag node)))))))
|
||||
|
||||
(define
|
||||
apl-eval-stmts
|
||||
(fn
|
||||
(stmts env)
|
||||
(if
|
||||
(= (len stmts) 0)
|
||||
nil
|
||||
(let
|
||||
((stmt (first stmts)) (more (rest stmts)))
|
||||
(let
|
||||
((tag (first stmt)))
|
||||
(cond
|
||||
((= tag :guard)
|
||||
(let
|
||||
((cond-val (apl-eval-ast (nth stmt 1) env)))
|
||||
(if
|
||||
(apl-truthy? cond-val)
|
||||
(apl-eval-ast (nth stmt 2) env)
|
||||
(apl-eval-stmts more env))))
|
||||
((and (= tag :assign) (= (nth stmt 1) "⍺"))
|
||||
(if
|
||||
(get env "alpha")
|
||||
(apl-eval-stmts more env)
|
||||
(let
|
||||
((v (apl-eval-ast (nth stmt 2) env)))
|
||||
(apl-eval-stmts more (assoc env "alpha" v)))))
|
||||
((= tag :assign)
|
||||
(let
|
||||
((v (apl-eval-ast (nth stmt 2) env)))
|
||||
(apl-eval-stmts more (assoc env (nth stmt 1) v))))
|
||||
((= (len more) 0) (apl-eval-ast stmt env))
|
||||
(else (begin (apl-eval-ast stmt env) (apl-eval-stmts more env)))))))))
|
||||
|
||||
(define
|
||||
apl-call-dfn
|
||||
(fn
|
||||
(dfn-ast alpha omega)
|
||||
(let
|
||||
((stmts (rest dfn-ast)) (env {:omega omega :nabla dfn-ast :alpha alpha}))
|
||||
(apl-eval-stmts stmts env))))
|
||||
|
||||
(define
|
||||
apl-call-dfn-m
|
||||
(fn
|
||||
(dfn-ast omega)
|
||||
(let
|
||||
((stmts (rest dfn-ast)) (env {:omega omega :nabla dfn-ast :alpha nil}))
|
||||
(apl-eval-stmts stmts env))))
|
||||
|
||||
(define
|
||||
apl-tradfn-eval-block
|
||||
(fn
|
||||
(stmts env)
|
||||
(if
|
||||
(= (len stmts) 0)
|
||||
env
|
||||
(let
|
||||
((stmt (first stmts)))
|
||||
(apl-tradfn-eval-block (rest stmts) (apl-tradfn-eval-stmt stmt env))))))
|
||||
|
||||
(define
|
||||
apl-tradfn-eval-while
|
||||
(fn
|
||||
(cond-expr body env)
|
||||
(let
|
||||
((cond-val (apl-eval-ast cond-expr env)))
|
||||
(if
|
||||
(apl-truthy? cond-val)
|
||||
(apl-tradfn-eval-while
|
||||
cond-expr
|
||||
body
|
||||
(apl-tradfn-eval-block body env))
|
||||
env))))
|
||||
|
||||
(define
|
||||
apl-tradfn-eval-for
|
||||
(fn
|
||||
(var-name items body env)
|
||||
(if
|
||||
(= (len items) 0)
|
||||
env
|
||||
(let
|
||||
((env-with-var (assoc env var-name (apl-scalar (first items)))))
|
||||
(apl-tradfn-eval-for
|
||||
var-name
|
||||
(rest items)
|
||||
body
|
||||
(apl-tradfn-eval-block body env-with-var))))))
|
||||
|
||||
(define
|
||||
apl-tradfn-eval-select
|
||||
(fn
|
||||
(val cases default-block env)
|
||||
(if
|
||||
(= (len cases) 0)
|
||||
(apl-tradfn-eval-block default-block env)
|
||||
(let
|
||||
((c (first cases)))
|
||||
(let
|
||||
((case-val (apl-eval-ast (first c) env)))
|
||||
(if
|
||||
(= (first (get val :ravel)) (first (get case-val :ravel)))
|
||||
(apl-tradfn-eval-block (rest c) env)
|
||||
(apl-tradfn-eval-select val (rest cases) default-block env)))))))
|
||||
|
||||
(define
|
||||
apl-tradfn-eval-stmt
|
||||
(fn
|
||||
(stmt env)
|
||||
(let
|
||||
((tag (first stmt)))
|
||||
(cond
|
||||
((= tag :assign)
|
||||
(assoc env (nth stmt 1) (apl-eval-ast (nth stmt 2) env)))
|
||||
((= tag :if)
|
||||
(let
|
||||
((cond-val (apl-eval-ast (nth stmt 1) env)))
|
||||
(if
|
||||
(apl-truthy? cond-val)
|
||||
(apl-tradfn-eval-block (nth stmt 2) env)
|
||||
(apl-tradfn-eval-block (nth stmt 3) env))))
|
||||
((= tag :while)
|
||||
(apl-tradfn-eval-while (nth stmt 1) (nth stmt 2) env))
|
||||
((= tag :for)
|
||||
(let
|
||||
((iter-val (apl-eval-ast (nth stmt 2) env)))
|
||||
(apl-tradfn-eval-for
|
||||
(nth stmt 1)
|
||||
(get iter-val :ravel)
|
||||
(nth stmt 3)
|
||||
env)))
|
||||
((= tag :select)
|
||||
(let
|
||||
((val (apl-eval-ast (nth stmt 1) env)))
|
||||
(apl-tradfn-eval-select val (nth stmt 2) (nth stmt 3) env)))
|
||||
((= tag :trap)
|
||||
(let
|
||||
((codes (nth stmt 1))
|
||||
(try-block (nth stmt 2))
|
||||
(catch-block (nth stmt 3)))
|
||||
(guard
|
||||
(e
|
||||
((apl-trap-matches? codes e)
|
||||
(apl-tradfn-eval-block catch-block env)))
|
||||
(apl-tradfn-eval-block try-block env))))
|
||||
((= tag :throw) (apl-throw (nth stmt 1) (nth stmt 2)))
|
||||
(else (begin (apl-eval-ast stmt env) env))))))
|
||||
|
||||
(define
|
||||
apl-tradfn-loop
|
||||
(fn
|
||||
(stmts line env result-name)
|
||||
(cond
|
||||
((= line 0) (get env result-name))
|
||||
((> line (len stmts)) (get env result-name))
|
||||
(else
|
||||
(let
|
||||
((stmt (nth stmts (- line 1))))
|
||||
(let
|
||||
((tag (first stmt)))
|
||||
(cond
|
||||
((= tag :branch)
|
||||
(let
|
||||
((target (apl-eval-ast (nth stmt 1) env)))
|
||||
(let
|
||||
((target-num (first (get target :ravel))))
|
||||
(apl-tradfn-loop stmts target-num env result-name))))
|
||||
(else
|
||||
(apl-tradfn-loop
|
||||
stmts
|
||||
(+ line 1)
|
||||
(apl-tradfn-eval-stmt stmt env)
|
||||
result-name)))))))))
|
||||
|
||||
(define
|
||||
apl-call-tradfn
|
||||
(fn
|
||||
(tradfn alpha omega)
|
||||
(let
|
||||
((stmts (get tradfn :stmts))
|
||||
(result-name (get tradfn :result))
|
||||
(alpha-name (get tradfn :alpha))
|
||||
(omega-name (get tradfn :omega)))
|
||||
(let
|
||||
((env-a (if alpha-name (assoc {} alpha-name alpha) {})))
|
||||
(let
|
||||
((env-ao (if omega-name (assoc env-a omega-name omega) env-a)))
|
||||
(apl-tradfn-loop stmts 1 env-ao result-name))))))
|
||||
|
||||
(define
|
||||
apl-ast-mentions-list?
|
||||
(fn
|
||||
(lst target)
|
||||
(if
|
||||
(= (len lst) 0)
|
||||
false
|
||||
(if
|
||||
(apl-ast-mentions? (first lst) target)
|
||||
true
|
||||
(apl-ast-mentions-list? (rest lst) target)))))
|
||||
|
||||
(define
|
||||
apl-ast-mentions?
|
||||
(fn
|
||||
(node target)
|
||||
(cond
|
||||
((not (list? node)) false)
|
||||
((= (len node) 0) false)
|
||||
((and (= (first node) :name) (= (nth node 1) target)) true)
|
||||
(else (apl-ast-mentions-list? (rest node) target)))))
|
||||
|
||||
(define
|
||||
apl-dfn-valence
|
||||
(fn
|
||||
(dfn-ast)
|
||||
(let
|
||||
((body (rest dfn-ast)))
|
||||
(cond
|
||||
((apl-ast-mentions-list? body "⍺") :dyadic)
|
||||
((apl-ast-mentions-list? body "⍵") :monadic)
|
||||
(else :niladic)))))
|
||||
|
||||
(define
|
||||
apl-tradfn-valence
|
||||
(fn
|
||||
(tradfn)
|
||||
(cond
|
||||
((get tradfn :alpha) :dyadic)
|
||||
((get tradfn :omega) :monadic)
|
||||
(else :niladic))))
|
||||
|
||||
(define
|
||||
apl-call
|
||||
(fn
|
||||
(f alpha omega)
|
||||
(cond
|
||||
((and (list? f) (> (len f) 0) (= (first f) :dfn))
|
||||
(if alpha (apl-call-dfn f alpha omega) (apl-call-dfn-m f omega)))
|
||||
((dict? f) (apl-call-tradfn f alpha omega))
|
||||
(else (error "apl-call: not a function")))))
|
||||
|
||||
(define
|
||||
apl-resolve-monadic
|
||||
(fn
|
||||
(fn-node env)
|
||||
(let
|
||||
((tag (first fn-node)))
|
||||
(cond
|
||||
((= tag :fn-glyph) (apl-monadic-fn (nth fn-node 1)))
|
||||
((= tag :derived-fn)
|
||||
(let
|
||||
((op (nth fn-node 1)) (inner (nth fn-node 2)))
|
||||
(cond
|
||||
((= op "/")
|
||||
(let
|
||||
((f (apl-resolve-dyadic inner env)))
|
||||
(fn (arr) (apl-reduce f arr))))
|
||||
((= op "⌿")
|
||||
(let
|
||||
((f (apl-resolve-dyadic inner env)))
|
||||
(fn (arr) (apl-reduce-first f arr))))
|
||||
((= op "\\")
|
||||
(let
|
||||
((f (apl-resolve-dyadic inner env)))
|
||||
(fn (arr) (apl-scan f arr))))
|
||||
((= op "⍀")
|
||||
(let
|
||||
((f (apl-resolve-dyadic inner env)))
|
||||
(fn (arr) (apl-scan-first f arr))))
|
||||
((= op "¨")
|
||||
(let
|
||||
((f (apl-resolve-monadic inner env)))
|
||||
(fn (arr) (apl-each f arr))))
|
||||
((= op "⍨")
|
||||
(let
|
||||
((f (apl-resolve-dyadic inner env)))
|
||||
(fn (arr) (apl-commute f arr))))
|
||||
(else (error "apl-resolve-monadic: unsupported op")))))
|
||||
(else (error "apl-resolve-monadic: unknown fn-node tag"))))))
|
||||
|
||||
(define
|
||||
apl-resolve-dyadic
|
||||
(fn
|
||||
(fn-node env)
|
||||
(let
|
||||
((tag (first fn-node)))
|
||||
(cond
|
||||
((= tag :fn-glyph) (apl-dyadic-fn (nth fn-node 1)))
|
||||
((= tag :derived-fn)
|
||||
(let
|
||||
((op (nth fn-node 1)) (inner (nth fn-node 2)))
|
||||
(cond
|
||||
((= op "¨")
|
||||
(let
|
||||
((f (apl-resolve-dyadic inner env)))
|
||||
(fn (a b) (apl-each-dyadic f a b))))
|
||||
((= op "⍨")
|
||||
(let
|
||||
((f (apl-resolve-dyadic inner env)))
|
||||
(fn (a b) (apl-commute-dyadic f a b))))
|
||||
(else (error "apl-resolve-dyadic: unsupported op")))))
|
||||
((= tag :outer)
|
||||
(let
|
||||
((inner (nth fn-node 2)))
|
||||
(let
|
||||
((f (apl-resolve-dyadic inner env)))
|
||||
(fn (a b) (apl-outer f a b)))))
|
||||
((= tag :derived-fn2)
|
||||
(let
|
||||
((f-node (nth fn-node 2)) (g-node (nth fn-node 3)))
|
||||
(let
|
||||
((f (apl-resolve-dyadic f-node env))
|
||||
(g (apl-resolve-dyadic g-node env)))
|
||||
(fn (a b) (apl-inner f g a b)))))
|
||||
(else (error "apl-resolve-dyadic: unknown fn-node tag"))))))
|
||||
|
||||
(define apl-run (fn (src) (apl-eval-ast (parse-apl src) {})))
|
||||
92
lib/guest/ast.sx
Normal file
92
lib/guest/ast.sx
Normal file
@@ -0,0 +1,92 @@
|
||||
;; lib/guest/ast.sx — canonical AST node shapes.
|
||||
;;
|
||||
;; A guest's parser may emit its own AST in whatever shape is convenient
|
||||
;; for that language's evaluator/transpiler. This file gives a SHARED
|
||||
;; canonical shape that cross-language tools (formatters, highlighters,
|
||||
;; debuggers) can target without per-language adapters.
|
||||
;;
|
||||
;; Each canonical node is a tagged list: (KIND ...payload).
|
||||
;;
|
||||
;; Constructors (return a canonical node):
|
||||
;;
|
||||
;; (ast-literal VALUE) — number / string / bool / nil
|
||||
;; (ast-var NAME) — identifier reference
|
||||
;; (ast-app FN ARGS) — function application
|
||||
;; (ast-lambda PARAMS BODY) — anonymous function
|
||||
;; (ast-let BINDINGS BODY) — local bindings
|
||||
;; (ast-letrec BINDINGS BODY) — recursive local bindings
|
||||
;; (ast-if TEST THEN ELSE) — conditional
|
||||
;; (ast-match-clause PATTERN BODY) — one match arm
|
||||
;; (ast-module NAME BODY) — module declaration
|
||||
;; (ast-import NAME) — import directive
|
||||
;;
|
||||
;; Predicates: (ast-literal? X), (ast-var? X), …
|
||||
;; Generic: (ast? X) — any canonical node
|
||||
;; (ast-kind X) — :literal / :var / :app / …
|
||||
;;
|
||||
;; Accessors (one per payload field):
|
||||
;; (ast-literal-value N)
|
||||
;; (ast-var-name N)
|
||||
;; (ast-app-fn N) (ast-app-args N)
|
||||
;; (ast-lambda-params N) (ast-lambda-body N)
|
||||
;; (ast-let-bindings N) (ast-let-body N)
|
||||
;; (ast-letrec-bindings N) (ast-letrec-body N)
|
||||
;; (ast-if-test N) (ast-if-then N) (ast-if-else N)
|
||||
;; (ast-match-clause-pattern N)
|
||||
;; (ast-match-clause-body N)
|
||||
;; (ast-module-name N) (ast-module-body N)
|
||||
;; (ast-import-name N)
|
||||
|
||||
(define ast-literal (fn (v) (list :literal v)))
|
||||
(define ast-var (fn (n) (list :var n)))
|
||||
(define ast-app (fn (f args) (list :app f args)))
|
||||
(define ast-lambda (fn (ps body) (list :lambda ps body)))
|
||||
(define ast-let (fn (bs body) (list :let bs body)))
|
||||
(define ast-letrec (fn (bs body) (list :letrec bs body)))
|
||||
(define ast-if (fn (t th el) (list :if t th el)))
|
||||
(define ast-match-clause (fn (p body) (list :match-clause p body)))
|
||||
(define ast-module (fn (n body) (list :module n body)))
|
||||
(define ast-import (fn (n) (list :import n)))
|
||||
|
||||
(define ast-kind (fn (x) (if (and (list? x) (not (empty? x))) (first x) nil)))
|
||||
|
||||
(define
|
||||
ast?
|
||||
(fn (x)
|
||||
(and (list? x)
|
||||
(not (empty? x))
|
||||
(let ((k (first x)))
|
||||
(or (= k :literal) (= k :var) (= k :app)
|
||||
(= k :lambda) (= k :let) (= k :letrec)
|
||||
(= k :if) (= k :match-clause)
|
||||
(= k :module) (= k :import))))))
|
||||
|
||||
(define ast-literal? (fn (x) (and (ast? x) (= (first x) :literal))))
|
||||
(define ast-var? (fn (x) (and (ast? x) (= (first x) :var))))
|
||||
(define ast-app? (fn (x) (and (ast? x) (= (first x) :app))))
|
||||
(define ast-lambda? (fn (x) (and (ast? x) (= (first x) :lambda))))
|
||||
(define ast-let? (fn (x) (and (ast? x) (= (first x) :let))))
|
||||
(define ast-letrec? (fn (x) (and (ast? x) (= (first x) :letrec))))
|
||||
(define ast-if? (fn (x) (and (ast? x) (= (first x) :if))))
|
||||
(define ast-match-clause? (fn (x) (and (ast? x) (= (first x) :match-clause))))
|
||||
(define ast-module? (fn (x) (and (ast? x) (= (first x) :module))))
|
||||
(define ast-import? (fn (x) (and (ast? x) (= (first x) :import))))
|
||||
|
||||
(define ast-literal-value (fn (n) (nth n 1)))
|
||||
(define ast-var-name (fn (n) (nth n 1)))
|
||||
(define ast-app-fn (fn (n) (nth n 1)))
|
||||
(define ast-app-args (fn (n) (nth n 2)))
|
||||
(define ast-lambda-params (fn (n) (nth n 1)))
|
||||
(define ast-lambda-body (fn (n) (nth n 2)))
|
||||
(define ast-let-bindings (fn (n) (nth n 1)))
|
||||
(define ast-let-body (fn (n) (nth n 2)))
|
||||
(define ast-letrec-bindings (fn (n) (nth n 1)))
|
||||
(define ast-letrec-body (fn (n) (nth n 2)))
|
||||
(define ast-if-test (fn (n) (nth n 1)))
|
||||
(define ast-if-then (fn (n) (nth n 2)))
|
||||
(define ast-if-else (fn (n) (nth n 3)))
|
||||
(define ast-match-clause-pattern (fn (n) (nth n 1)))
|
||||
(define ast-match-clause-body (fn (n) (nth n 2)))
|
||||
(define ast-module-name (fn (n) (nth n 1)))
|
||||
(define ast-module-body (fn (n) (nth n 2)))
|
||||
(define ast-import-name (fn (n) (nth n 1)))
|
||||
185
lib/guest/match.sx
Normal file
185
lib/guest/match.sx
Normal file
@@ -0,0 +1,185 @@
|
||||
;; lib/guest/match.sx — pure pattern-match + unification kit.
|
||||
;;
|
||||
;; Shipped for miniKanren / Datalog / future logic-flavoured guests that
|
||||
;; want immutable unification without writing it from scratch. The two
|
||||
;; existing prolog/haskell engines stay as-is — porting them in place
|
||||
;; risks the 746 tests they currently pass; consumers can migrate
|
||||
;; gradually via the converters in lib/guest/ast.sx.
|
||||
;;
|
||||
;; Term shapes (canonical wire format)
|
||||
;; -----------------------------------
|
||||
;; var (:var NAME) NAME a string
|
||||
;; constructor (:ctor HEAD ARGS) HEAD a string, ARGS a list of terms
|
||||
;; literal number / string / boolean / nil
|
||||
;;
|
||||
;; Guests with their own shape pass adapter callbacks via the cfg arg —
|
||||
;; see (unify-with cfg ...) and (match-pat-with cfg ...) below. The
|
||||
;; default canonical entry points (unify / match-pat) use the wire shape.
|
||||
;;
|
||||
;; Substitution / env
|
||||
;; ------------------
|
||||
;; A substitution is a SX dict mapping VAR-NAME → term. There are no
|
||||
;; trails, no mutation: each step either returns an extended dict or nil.
|
||||
;;
|
||||
;; (empty-subst) → {}
|
||||
;; (walk term s) → term with top-level vars resolved
|
||||
;; (walk* term s) → term with all vars resolved (recursive)
|
||||
;; (extend name term s) → s with NAME → term added
|
||||
;; (occurs? name term s) → bool
|
||||
;;
|
||||
;; Unify (symmetric, miniKanren-flavour)
|
||||
;; -------------------------------------
|
||||
;; (unify u v s) → extended subst or nil
|
||||
;; (unify-with cfg u v s) → ditto, with adapter callbacks:
|
||||
;; :var? :var-name :ctor? :ctor-head
|
||||
;; :ctor-args :occurs-check?
|
||||
;;
|
||||
;; Match (asymmetric, haskell-flavour: pattern → value, vars only in pat)
|
||||
;; ---------------------------------------------------------------------
|
||||
;; (match-pat pat val env) → extended env or nil
|
||||
;; (match-pat-with cfg pat val env)
|
||||
|
||||
(define mk-var (fn (name) (list :var name)))
|
||||
(define mk-ctor (fn (head args) (list :ctor head args)))
|
||||
|
||||
(define is-var? (fn (t) (and (list? t) (not (empty? t)) (= (first t) :var))))
|
||||
(define is-ctor? (fn (t) (and (list? t) (not (empty? t)) (= (first t) :ctor))))
|
||||
(define var-name (fn (t) (nth t 1)))
|
||||
(define ctor-head (fn (t) (nth t 1)))
|
||||
(define ctor-args (fn (t) (nth t 2)))
|
||||
|
||||
(define empty-subst (fn () {}))
|
||||
|
||||
(define
|
||||
walk
|
||||
(fn (t s)
|
||||
(if (and (is-var? t) (has-key? s (var-name t)))
|
||||
(walk (get s (var-name t)) s)
|
||||
t)))
|
||||
|
||||
(define
|
||||
walk*
|
||||
(fn (t s)
|
||||
(let ((w (walk t s)))
|
||||
(cond
|
||||
((is-ctor? w)
|
||||
(mk-ctor (ctor-head w) (map (fn (a) (walk* a s)) (ctor-args w))))
|
||||
(:else w)))))
|
||||
|
||||
(define
|
||||
extend
|
||||
(fn (name term s)
|
||||
(assoc s name term)))
|
||||
|
||||
(define
|
||||
occurs?
|
||||
(fn (name term s)
|
||||
(let ((w (walk term s)))
|
||||
(cond
|
||||
((is-var? w) (= (var-name w) name))
|
||||
((is-ctor? w) (some (fn (a) (occurs? name a s)) (ctor-args w)))
|
||||
(:else false)))))
|
||||
|
||||
(define
|
||||
unify-with
|
||||
(fn (cfg u v s)
|
||||
(let ((var?-fn (get cfg :var?))
|
||||
(var-name-fn (get cfg :var-name))
|
||||
(ctor?-fn (get cfg :ctor?))
|
||||
(ctor-head-fn (get cfg :ctor-head))
|
||||
(ctor-args-fn (get cfg :ctor-args))
|
||||
(occurs?-on (get cfg :occurs-check?)))
|
||||
(let ((wu (walk-with cfg u s))
|
||||
(wv (walk-with cfg v s)))
|
||||
(cond
|
||||
((and (var?-fn wu) (var?-fn wv) (= (var-name-fn wu) (var-name-fn wv))) s)
|
||||
((var?-fn wu)
|
||||
(if (and occurs?-on (occurs-with cfg (var-name-fn wu) wv s))
|
||||
nil
|
||||
(extend (var-name-fn wu) wv s)))
|
||||
((var?-fn wv)
|
||||
(if (and occurs?-on (occurs-with cfg (var-name-fn wv) wu s))
|
||||
nil
|
||||
(extend (var-name-fn wv) wu s)))
|
||||
((and (ctor?-fn wu) (ctor?-fn wv))
|
||||
(if (= (ctor-head-fn wu) (ctor-head-fn wv))
|
||||
(unify-list-with
|
||||
cfg
|
||||
(ctor-args-fn wu)
|
||||
(ctor-args-fn wv)
|
||||
s)
|
||||
nil))
|
||||
(:else (if (= wu wv) s nil)))))))
|
||||
|
||||
(define
|
||||
walk-with
|
||||
(fn (cfg t s)
|
||||
(if (and ((get cfg :var?) t) (has-key? s ((get cfg :var-name) t)))
|
||||
(walk-with cfg (get s ((get cfg :var-name) t)) s)
|
||||
t)))
|
||||
|
||||
(define
|
||||
occurs-with
|
||||
(fn (cfg name term s)
|
||||
(let ((w (walk-with cfg term s)))
|
||||
(cond
|
||||
(((get cfg :var?) w) (= ((get cfg :var-name) w) name))
|
||||
(((get cfg :ctor?) w)
|
||||
(some (fn (a) (occurs-with cfg name a s)) ((get cfg :ctor-args) w)))
|
||||
(:else false)))))
|
||||
|
||||
(define
|
||||
unify-list-with
|
||||
(fn (cfg xs ys s)
|
||||
(cond
|
||||
((and (empty? xs) (empty? ys)) s)
|
||||
((or (empty? xs) (empty? ys)) nil)
|
||||
(:else
|
||||
(let ((s2 (unify-with cfg (first xs) (first ys) s)))
|
||||
(if (= s2 nil)
|
||||
nil
|
||||
(unify-list-with cfg (rest xs) (rest ys) s2)))))))
|
||||
|
||||
(define canonical-cfg
|
||||
{:var? is-var? :var-name var-name
|
||||
:ctor? is-ctor? :ctor-head ctor-head :ctor-args ctor-args
|
||||
:occurs-check? true})
|
||||
|
||||
(define unify (fn (u v s) (unify-with canonical-cfg u v s)))
|
||||
|
||||
;; Asymmetric pattern match (haskell-style): only patterns may contain vars;
|
||||
;; values are concrete. On a var pattern, bind name to value.
|
||||
(define
|
||||
match-pat-with
|
||||
(fn (cfg pat val env)
|
||||
(let ((var?-fn (get cfg :var?))
|
||||
(var-name-fn (get cfg :var-name))
|
||||
(ctor?-fn (get cfg :ctor?))
|
||||
(ctor-head-fn (get cfg :ctor-head))
|
||||
(ctor-args-fn (get cfg :ctor-args)))
|
||||
(cond
|
||||
((var?-fn pat) (extend (var-name-fn pat) val env))
|
||||
((and (ctor?-fn pat) (ctor?-fn val))
|
||||
(if (= (ctor-head-fn pat) (ctor-head-fn val))
|
||||
(match-list-pat-with
|
||||
cfg
|
||||
(ctor-args-fn pat)
|
||||
(ctor-args-fn val)
|
||||
env)
|
||||
nil))
|
||||
((ctor?-fn pat) nil)
|
||||
(:else (if (= pat val) env nil))))))
|
||||
|
||||
(define
|
||||
match-list-pat-with
|
||||
(fn (cfg pats vals env)
|
||||
(cond
|
||||
((and (empty? pats) (empty? vals)) env)
|
||||
((or (empty? pats) (empty? vals)) nil)
|
||||
(:else
|
||||
(let ((env2 (match-pat-with cfg (first pats) (first vals) env)))
|
||||
(if (= env2 nil)
|
||||
nil
|
||||
(match-list-pat-with cfg (rest pats) (rest vals) env2)))))))
|
||||
|
||||
(define match-pat (fn (pat val env) (match-pat-with canonical-cfg pat val env)))
|
||||
28
lib/guest/pratt.sx
Normal file
28
lib/guest/pratt.sx
Normal file
@@ -0,0 +1,28 @@
|
||||
;; lib/guest/pratt.sx — operator-table format + lookup for Pratt-style
|
||||
;; precedence climbing.
|
||||
;;
|
||||
;; The climbing loop stays per-language because the two canaries use
|
||||
;; opposite conventions (Lua: higher prec = tighter; Prolog: lower prec =
|
||||
;; tighter, with xfx/xfy/yfx assoc tags). Forcing a single loop adds
|
||||
;; callback indirection that obscures more than it shares.
|
||||
;;
|
||||
;; What IS shared and gets extracted: the operator-table format and lookup.
|
||||
;; "Grammar is a dict, not hardcoded cond."
|
||||
;;
|
||||
;; Entry shape: (NAME PREC ASSOC).
|
||||
;; NAME — string, the operator's source token.
|
||||
;; PREC — integer, in the host's own convention.
|
||||
;; ASSOC — :left | :right | :none for languages with traditional
|
||||
;; associativity, or "xfx" / "xfy" / "yfx" for Prolog-style.
|
||||
|
||||
(define
|
||||
pratt-op-lookup
|
||||
(fn (table name)
|
||||
(cond
|
||||
((empty? table) nil)
|
||||
((= (first (first table)) name) (first table))
|
||||
(:else (pratt-op-lookup (rest table) name)))))
|
||||
|
||||
(define pratt-op-name (fn (entry) (first entry)))
|
||||
(define pratt-op-prec (fn (entry) (nth entry 1)))
|
||||
(define pratt-op-assoc (fn (entry) (nth entry 2)))
|
||||
63
lib/guest/tests/ast.sx
Normal file
63
lib/guest/tests/ast.sx
Normal file
@@ -0,0 +1,63 @@
|
||||
;; lib/guest/tests/ast.sx — exercises every constructor / predicate /
|
||||
;; accessor in lib/guest/ast.sx so future ports have a stable contract
|
||||
;; to point at.
|
||||
|
||||
(define gast-test-pass 0)
|
||||
(define gast-test-fail 0)
|
||||
(define gast-test-fails (list))
|
||||
|
||||
(define
|
||||
gast-test
|
||||
(fn (name actual expected)
|
||||
(if (= actual expected)
|
||||
(set! gast-test-pass (+ gast-test-pass 1))
|
||||
(begin
|
||||
(set! gast-test-fail (+ gast-test-fail 1))
|
||||
(append! gast-test-fails {:name name :expected expected :actual actual})))))
|
||||
|
||||
;; Constructors round-trip.
|
||||
(gast-test "literal-int" (ast-literal-value (ast-literal 42)) 42)
|
||||
(gast-test "literal-str" (ast-literal-value (ast-literal "hi")) "hi")
|
||||
(gast-test "literal-bool" (ast-literal-value (ast-literal true)) true)
|
||||
(gast-test "var-name" (ast-var-name (ast-var "x")) "x")
|
||||
(gast-test "app-fn" (ast-app-fn (ast-app (ast-var "f") (list (ast-literal 1)))) (ast-var "f"))
|
||||
(gast-test "app-args-len" (len (ast-app-args (ast-app (ast-var "f") (list (ast-literal 1))))) 1)
|
||||
(gast-test "lambda-params" (ast-lambda-params (ast-lambda (list "x" "y") (ast-var "x"))) (list "x" "y"))
|
||||
(gast-test "lambda-body" (ast-lambda-body (ast-lambda (list "x") (ast-var "x"))) (ast-var "x"))
|
||||
(gast-test "let-bindings" (len (ast-let-bindings (ast-let (list {:name "x" :value (ast-literal 1)}) (ast-var "x")))) 1)
|
||||
(gast-test "letrec-body" (ast-letrec-body (ast-letrec (list) (ast-literal 0))) (ast-literal 0))
|
||||
(gast-test "if-test" (ast-if-test (ast-if (ast-literal true) (ast-literal 1) (ast-literal 0))) (ast-literal true))
|
||||
(gast-test "if-then" (ast-if-then (ast-if (ast-literal true) (ast-literal 1) (ast-literal 0))) (ast-literal 1))
|
||||
(gast-test "if-else" (ast-if-else (ast-if (ast-literal true) (ast-literal 1) (ast-literal 0))) (ast-literal 0))
|
||||
(gast-test "match-pattern" (ast-match-clause-pattern (ast-match-clause "P" (ast-literal 1))) "P")
|
||||
(gast-test "match-body" (ast-match-clause-body (ast-match-clause "P" (ast-literal 1))) (ast-literal 1))
|
||||
(gast-test "module-name" (ast-module-name (ast-module "m" (list))) "m")
|
||||
(gast-test "import-name" (ast-import-name (ast-import "lib/foo")) "lib/foo")
|
||||
|
||||
;; Predicates fire only on matching kinds.
|
||||
(gast-test "is-literal" (ast-literal? (ast-literal 1)) true)
|
||||
(gast-test "not-literal" (ast-literal? (ast-var "x")) false)
|
||||
(gast-test "is-var" (ast-var? (ast-var "x")) true)
|
||||
(gast-test "is-app" (ast-app? (ast-app (ast-var "f") (list))) true)
|
||||
(gast-test "is-lambda" (ast-lambda? (ast-lambda (list) (ast-literal 0))) true)
|
||||
(gast-test "is-let" (ast-let? (ast-let (list) (ast-literal 0))) true)
|
||||
(gast-test "is-letrec" (ast-letrec? (ast-letrec (list) (ast-literal 0))) true)
|
||||
(gast-test "is-if" (ast-if? (ast-if (ast-literal true) (ast-literal 1) (ast-literal 0))) true)
|
||||
(gast-test "is-match" (ast-match-clause? (ast-match-clause "P" (ast-literal 1))) true)
|
||||
(gast-test "is-module" (ast-module? (ast-module "m" (list))) true)
|
||||
(gast-test "is-import" (ast-import? (ast-import "x")) true)
|
||||
|
||||
;; ast? recognises any canonical node.
|
||||
(gast-test "ast?-literal" (ast? (ast-literal 0)) true)
|
||||
(gast-test "ast?-foreign" (ast? (list "lua-num" 0)) false)
|
||||
(gast-test "ast?-non-list" (ast? 42) false)
|
||||
|
||||
;; ast-kind dispatch.
|
||||
(gast-test "kind-literal" (ast-kind (ast-literal 0)) :literal)
|
||||
(gast-test "kind-import" (ast-kind (ast-import "x")) :import)
|
||||
|
||||
(define gast-tests-run!
|
||||
(fn ()
|
||||
{:passed gast-test-pass
|
||||
:failed gast-test-fail
|
||||
:total (+ gast-test-pass gast-test-fail)}))
|
||||
108
lib/guest/tests/match.sx
Normal file
108
lib/guest/tests/match.sx
Normal file
@@ -0,0 +1,108 @@
|
||||
;; lib/guest/tests/match.sx — exercises lib/guest/match.sx.
|
||||
|
||||
(define gmatch-test-pass 0)
|
||||
(define gmatch-test-fail 0)
|
||||
(define gmatch-test-fails (list))
|
||||
|
||||
(define
|
||||
gmatch-test
|
||||
(fn (name actual expected)
|
||||
(if (= actual expected)
|
||||
(set! gmatch-test-pass (+ gmatch-test-pass 1))
|
||||
(begin
|
||||
(set! gmatch-test-fail (+ gmatch-test-fail 1))
|
||||
(append! gmatch-test-fails {:name name :expected expected :actual actual})))))
|
||||
|
||||
;; ── walk / extend / occurs ────────────────────────────────────────
|
||||
(gmatch-test "walk-direct"
|
||||
(walk (mk-var "x") (extend "x" 5 (empty-subst))) 5)
|
||||
|
||||
(gmatch-test "walk-chain"
|
||||
(walk (mk-var "a") (extend "a" (mk-var "b") (extend "b" 7 (empty-subst)))) 7)
|
||||
|
||||
(gmatch-test "walk-no-binding"
|
||||
(let ((v (mk-var "u"))) (= (walk v (empty-subst)) v)) true)
|
||||
|
||||
(gmatch-test "walk*-recursive"
|
||||
(walk* (mk-ctor "Just" (list (mk-var "x"))) (extend "x" 9 (empty-subst)))
|
||||
(mk-ctor "Just" (list 9)))
|
||||
|
||||
(gmatch-test "occurs-direct"
|
||||
(occurs? "x" (mk-var "x") (empty-subst)) true)
|
||||
|
||||
(gmatch-test "occurs-nested"
|
||||
(occurs? "x" (mk-ctor "f" (list (mk-var "x"))) (empty-subst)) true)
|
||||
|
||||
(gmatch-test "occurs-not"
|
||||
(occurs? "x" (mk-var "y") (empty-subst)) false)
|
||||
|
||||
;; ── unify (symmetric) ─────────────────────────────────────────────
|
||||
(gmatch-test "unify-equal-literals"
|
||||
(len (unify 5 5 (empty-subst))) 0)
|
||||
|
||||
(gmatch-test "unify-different-literals"
|
||||
(unify 5 6 (empty-subst)) nil)
|
||||
|
||||
(gmatch-test "unify-var-literal"
|
||||
(get (unify (mk-var "x") 5 (empty-subst)) "x") 5)
|
||||
|
||||
(gmatch-test "unify-literal-var"
|
||||
(get (unify 5 (mk-var "x") (empty-subst)) "x") 5)
|
||||
|
||||
(gmatch-test "unify-same-var"
|
||||
(len (unify (mk-var "x") (mk-var "x") (empty-subst))) 0)
|
||||
|
||||
(gmatch-test "unify-two-vars"
|
||||
(let ((s (unify (mk-var "x") (mk-var "y") (empty-subst))))
|
||||
(or (= (get s "x") (mk-var "y")) (= (get s "y") (mk-var "x")))) true)
|
||||
|
||||
(gmatch-test "unify-ctor-equal"
|
||||
(len (unify (mk-ctor "f" (list 1 2)) (mk-ctor "f" (list 1 2)) (empty-subst))) 0)
|
||||
|
||||
(gmatch-test "unify-ctor-with-var"
|
||||
(get (unify (mk-ctor "Just" (list (mk-var "x"))) (mk-ctor "Just" (list 7)) (empty-subst)) "x") 7)
|
||||
|
||||
(gmatch-test "unify-ctor-head-mismatch"
|
||||
(unify (mk-ctor "Just" (list 1)) (mk-ctor "Nothing" (list)) (empty-subst)) nil)
|
||||
|
||||
(gmatch-test "unify-ctor-arity-mismatch"
|
||||
(unify (mk-ctor "f" (list 1 2)) (mk-ctor "f" (list 1)) (empty-subst)) nil)
|
||||
|
||||
(gmatch-test "unify-occurs-check"
|
||||
(unify (mk-var "x") (mk-ctor "f" (list (mk-var "x"))) (empty-subst)) nil)
|
||||
|
||||
(gmatch-test "unify-transitive-vars"
|
||||
(let ((s (unify (mk-var "x") (mk-var "y") (empty-subst))))
|
||||
(let ((s2 (unify (mk-var "y") 42 s)))
|
||||
(walk (mk-var "x") s2))) 42)
|
||||
|
||||
;; ── match-pat (asymmetric) ────────────────────────────────────────
|
||||
(gmatch-test "match-var-binds"
|
||||
(get (match-pat (mk-var "x") 99 (empty-subst)) "x") 99)
|
||||
|
||||
(gmatch-test "match-literal-equal"
|
||||
(len (match-pat 5 5 (empty-subst))) 0)
|
||||
|
||||
(gmatch-test "match-literal-mismatch"
|
||||
(match-pat 5 6 (empty-subst)) nil)
|
||||
|
||||
(gmatch-test "match-ctor-binds"
|
||||
(get (match-pat (mk-ctor "Just" (list (mk-var "y")))
|
||||
(mk-ctor "Just" (list 11))
|
||||
(empty-subst)) "y") 11)
|
||||
|
||||
(gmatch-test "match-ctor-head-mismatch"
|
||||
(match-pat (mk-ctor "Just" (list (mk-var "y")))
|
||||
(mk-ctor "Nothing" (list))
|
||||
(empty-subst)) nil)
|
||||
|
||||
(gmatch-test "match-ctor-arity-mismatch"
|
||||
(match-pat (mk-ctor "f" (list (mk-var "x") (mk-var "y")))
|
||||
(mk-ctor "f" (list 1))
|
||||
(empty-subst)) nil)
|
||||
|
||||
(define gmatch-tests-run!
|
||||
(fn ()
|
||||
{:passed gmatch-test-pass
|
||||
:failed gmatch-test-fail
|
||||
:total (+ gmatch-test-pass gmatch-test-fail)}))
|
||||
@@ -3,28 +3,33 @@
|
||||
(define lua-tok-value (fn (t) (if (= t nil) nil (get t :value))))
|
||||
|
||||
(define
|
||||
lua-binop-prec
|
||||
(fn
|
||||
(op)
|
||||
(cond
|
||||
((= op "or") 1)
|
||||
((= op "and") 2)
|
||||
((= op "<") 3)
|
||||
((= op ">") 3)
|
||||
((= op "<=") 3)
|
||||
((= op ">=") 3)
|
||||
((= op "==") 3)
|
||||
((= op "~=") 3)
|
||||
((= op "..") 5)
|
||||
((= op "+") 6)
|
||||
((= op "-") 6)
|
||||
((= op "*") 7)
|
||||
((= op "/") 7)
|
||||
((= op "%") 7)
|
||||
((= op "^") 10)
|
||||
(else 0))))
|
||||
lua-op-table
|
||||
(list
|
||||
(list "or" 1 :left)
|
||||
(list "and" 2 :left)
|
||||
(list "<" 3 :left)
|
||||
(list ">" 3 :left)
|
||||
(list "<=" 3 :left)
|
||||
(list ">=" 3 :left)
|
||||
(list "==" 3 :left)
|
||||
(list "~=" 3 :left)
|
||||
(list ".." 5 :right)
|
||||
(list "+" 6 :left)
|
||||
(list "-" 6 :left)
|
||||
(list "*" 7 :left)
|
||||
(list "/" 7 :left)
|
||||
(list "%" 7 :left)
|
||||
(list "^" 10 :right)))
|
||||
|
||||
(define lua-binop-right? (fn (op) (or (= op "..") (= op "^"))))
|
||||
(define lua-binop-prec
|
||||
(fn (op)
|
||||
(let ((entry (pratt-op-lookup lua-op-table op)))
|
||||
(if (= entry nil) 0 (pratt-op-prec entry)))))
|
||||
|
||||
(define lua-binop-right?
|
||||
(fn (op)
|
||||
(let ((entry (pratt-op-lookup lua-op-table op)))
|
||||
(and (not (= entry nil)) (= (pratt-op-assoc entry) :right)))))
|
||||
|
||||
(define
|
||||
lua-parse
|
||||
|
||||
@@ -30,6 +30,7 @@ cat > "$TMPFILE" << 'EPOCHS'
|
||||
(epoch 1)
|
||||
(load "lib/guest/lex.sx")
|
||||
(load "lib/guest/prefix.sx")
|
||||
(load "lib/guest/pratt.sx")
|
||||
(load "lib/lua/tokenizer.sx")
|
||||
(epoch 2)
|
||||
(load "lib/lua/parser.sx")
|
||||
|
||||
858
lib/minikanren/clpfd.sx
Normal file
858
lib/minikanren/clpfd.sx
Normal file
@@ -0,0 +1,858 @@
|
||||
;; lib/minikanren/clpfd.sx — Phase 6: native CLP(FD) on miniKanren.
|
||||
;;
|
||||
;; The substitution dict carries an extra reserved key "_fd" that holds a
|
||||
;; constraint-store record:
|
||||
;;
|
||||
;; {:domains {var-name -> sorted-int-list}
|
||||
;; :constraints (... pending constraint closures ...)}
|
||||
;;
|
||||
;; Domains are sorted SX lists of ints (no duplicates).
|
||||
;; Constraints are functions s -> s-or-nil that propagate / re-check.
|
||||
;; They are re-fired after every label binding via fd-fire-store.
|
||||
|
||||
(define fd-key "_fd")
|
||||
|
||||
;; --- domain primitives ---
|
||||
|
||||
(define
|
||||
fd-dom-rev
|
||||
(fn
|
||||
(xs acc)
|
||||
(cond
|
||||
((empty? xs) acc)
|
||||
(:else (fd-dom-rev (rest xs) (cons (first xs) acc))))))
|
||||
|
||||
(define
|
||||
fd-dom-insert
|
||||
(fn
|
||||
(x desc)
|
||||
(cond
|
||||
((empty? desc) (list x))
|
||||
((= x (first desc)) desc)
|
||||
((> x (first desc)) (cons x desc))
|
||||
(:else (cons (first desc) (fd-dom-insert x (rest desc)))))))
|
||||
|
||||
(define
|
||||
fd-dom-sort-dedupe
|
||||
(fn
|
||||
(xs acc)
|
||||
(cond
|
||||
((empty? xs) (fd-dom-rev acc (list)))
|
||||
(:else (fd-dom-sort-dedupe (rest xs) (fd-dom-insert (first xs) acc))))))
|
||||
|
||||
(define fd-dom-from-list (fn (xs) (fd-dom-sort-dedupe xs (list))))
|
||||
|
||||
(define fd-dom-empty? (fn (d) (empty? d)))
|
||||
(define
|
||||
fd-dom-singleton?
|
||||
(fn (d) (and (not (empty? d)) (empty? (rest d)))))
|
||||
(define fd-dom-min (fn (d) (first d)))
|
||||
|
||||
(define
|
||||
fd-dom-last
|
||||
(fn
|
||||
(d)
|
||||
(cond ((empty? (rest d)) (first d)) (:else (fd-dom-last (rest d))))))
|
||||
|
||||
(define fd-dom-max (fn (d) (fd-dom-last d)))
|
||||
(define fd-dom-member? (fn (x d) (some (fn (y) (= x y)) d)))
|
||||
|
||||
(define
|
||||
fd-dom-intersect
|
||||
(fn
|
||||
(a b)
|
||||
(cond
|
||||
((empty? a) (list))
|
||||
((empty? b) (list))
|
||||
((= (first a) (first b))
|
||||
(cons (first a) (fd-dom-intersect (rest a) (rest b))))
|
||||
((< (first a) (first b)) (fd-dom-intersect (rest a) b))
|
||||
(:else (fd-dom-intersect a (rest b))))))
|
||||
|
||||
(define
|
||||
fd-dom-without
|
||||
(fn
|
||||
(x d)
|
||||
(cond
|
||||
((empty? d) (list))
|
||||
((= (first d) x) (rest d))
|
||||
((> (first d) x) d)
|
||||
(:else (cons (first d) (fd-dom-without x (rest d)))))))
|
||||
|
||||
(define
|
||||
fd-dom-range
|
||||
(fn
|
||||
(lo hi)
|
||||
(cond
|
||||
((> lo hi) (list))
|
||||
(:else (cons lo (fd-dom-range (+ lo 1) hi))))))
|
||||
|
||||
;; --- constraint store accessors ---
|
||||
|
||||
(define fd-store-empty (fn () {:domains {} :constraints (list)}))
|
||||
|
||||
(define
|
||||
fd-store-of
|
||||
(fn
|
||||
(s)
|
||||
(cond ((has-key? s fd-key) (get s fd-key)) (:else (fd-store-empty)))))
|
||||
|
||||
(define fd-domains-of (fn (s) (get (fd-store-of s) :domains)))
|
||||
(define fd-with-store (fn (s store) (assoc s fd-key store)))
|
||||
|
||||
(define
|
||||
fd-domain-of
|
||||
(fn
|
||||
(s var-name)
|
||||
(let
|
||||
((doms (fd-domains-of s)))
|
||||
(cond ((has-key? doms var-name) (get doms var-name)) (:else nil)))))
|
||||
|
||||
(define
|
||||
fd-set-domain
|
||||
(fn
|
||||
(s var-name d)
|
||||
(cond
|
||||
((fd-dom-empty? d) nil)
|
||||
(:else
|
||||
(let
|
||||
((store (fd-store-of s)))
|
||||
(let
|
||||
((doms-prime (assoc (get store :domains) var-name d)))
|
||||
(let
|
||||
((store-prime (assoc store :domains doms-prime)))
|
||||
(fd-with-store s store-prime))))))))
|
||||
|
||||
(define
|
||||
fd-add-constraint
|
||||
(fn
|
||||
(s c)
|
||||
(let
|
||||
((store (fd-store-of s)))
|
||||
(let
|
||||
((cs-prime (cons c (get store :constraints))))
|
||||
(let
|
||||
((store-prime (assoc store :constraints cs-prime)))
|
||||
(fd-with-store s store-prime))))))
|
||||
|
||||
(define
|
||||
fd-fire-list
|
||||
(fn
|
||||
(cs s)
|
||||
(cond
|
||||
((empty? cs) s)
|
||||
(:else
|
||||
(let
|
||||
((s2 ((first cs) s)))
|
||||
(cond ((= s2 nil) nil) (:else (fd-fire-list (rest cs) s2))))))))
|
||||
|
||||
(define
|
||||
fd-store-signature
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((doms (fd-domains-of s)))
|
||||
(let
|
||||
((dom-sizes (reduce (fn (acc k) (+ acc (len (get doms k)))) 0 (keys doms))))
|
||||
(+ dom-sizes (len (keys s)))))))
|
||||
|
||||
(define
|
||||
fd-fire-store
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((s2 (fd-fire-list (get (fd-store-of s) :constraints) s)))
|
||||
(cond
|
||||
((= s2 nil) nil)
|
||||
((= (fd-store-signature s) (fd-store-signature s2)) s2)
|
||||
(:else (fd-fire-store s2))))))
|
||||
|
||||
;; --- user-facing goals ---
|
||||
|
||||
(define
|
||||
fd-in
|
||||
(fn
|
||||
(x dom-list)
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((new-dom (fd-dom-from-list dom-list)))
|
||||
(let
|
||||
((wx (mk-walk x s)))
|
||||
(cond
|
||||
((number? wx)
|
||||
(cond ((fd-dom-member? wx new-dom) (unit s)) (:else mzero)))
|
||||
((is-var? wx)
|
||||
(let
|
||||
((existing (fd-domain-of s (var-name wx))))
|
||||
(let
|
||||
((narrowed (cond ((= existing nil) new-dom) (:else (fd-dom-intersect existing new-dom)))))
|
||||
(let
|
||||
((s2 (fd-set-domain s (var-name wx) narrowed)))
|
||||
(cond ((= s2 nil) mzero) (:else (unit s2)))))))
|
||||
(:else mzero)))))))
|
||||
|
||||
;; --- fd-neq ---
|
||||
|
||||
(define
|
||||
fd-neq-prop
|
||||
(fn
|
||||
(x y s)
|
||||
(let
|
||||
((wx (mk-walk x s)) (wy (mk-walk y s)))
|
||||
(cond
|
||||
((and (number? wx) (number? wy))
|
||||
(cond ((= wx wy) nil) (:else s)))
|
||||
((and (number? wx) (is-var? wy))
|
||||
(let
|
||||
((y-dom (fd-domain-of s (var-name wy))))
|
||||
(cond
|
||||
((= y-dom nil) s)
|
||||
(:else
|
||||
(fd-set-domain s (var-name wy) (fd-dom-without wx y-dom))))))
|
||||
((and (number? wy) (is-var? wx))
|
||||
(let
|
||||
((x-dom (fd-domain-of s (var-name wx))))
|
||||
(cond
|
||||
((= x-dom nil) s)
|
||||
(:else
|
||||
(fd-set-domain s (var-name wx) (fd-dom-without wy x-dom))))))
|
||||
(:else s)))))
|
||||
|
||||
(define
|
||||
fd-neq
|
||||
(fn
|
||||
(x y)
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((c (fn (s-prime) (fd-neq-prop x y s-prime))))
|
||||
(let
|
||||
((s2 (fd-add-constraint s c)))
|
||||
(let
|
||||
((s2-or-nil (c s2)))
|
||||
(let
|
||||
((s3 (cond ((= s2-or-nil nil) nil) (:else (fd-fire-store s2-or-nil)))))
|
||||
(cond ((= s3 nil) mzero) (:else (unit s3))))))))))
|
||||
|
||||
;; --- fd-lt ---
|
||||
|
||||
(define
|
||||
fd-lt-prop
|
||||
(fn
|
||||
(x y s)
|
||||
(let
|
||||
((wx (mk-walk x s)) (wy (mk-walk y s)))
|
||||
(cond
|
||||
((and (number? wx) (number? wy))
|
||||
(cond ((< wx wy) s) (:else nil)))
|
||||
((and (number? wx) (is-var? wy))
|
||||
(let
|
||||
((yd (fd-domain-of s (var-name wy))))
|
||||
(cond
|
||||
((= yd nil) s)
|
||||
(:else
|
||||
(fd-set-domain
|
||||
s
|
||||
(var-name wy)
|
||||
(filter (fn (v) (> v wx)) yd))))))
|
||||
((and (is-var? wx) (number? wy))
|
||||
(let
|
||||
((xd (fd-domain-of s (var-name wx))))
|
||||
(cond
|
||||
((= xd nil) s)
|
||||
(:else
|
||||
(fd-set-domain
|
||||
s
|
||||
(var-name wx)
|
||||
(filter (fn (v) (< v wy)) xd))))))
|
||||
((and (is-var? wx) (is-var? wy))
|
||||
(let
|
||||
((xd (fd-domain-of s (var-name wx)))
|
||||
(yd (fd-domain-of s (var-name wy))))
|
||||
(cond
|
||||
((or (= xd nil) (= yd nil)) s)
|
||||
(:else
|
||||
(let
|
||||
((xd-prime (filter (fn (v) (< v (fd-dom-max yd))) xd)))
|
||||
(let
|
||||
((s2 (fd-set-domain s (var-name wx) xd-prime)))
|
||||
(cond
|
||||
((= s2 nil) nil)
|
||||
(:else
|
||||
(let
|
||||
((yd-prime (filter (fn (v) (> v (fd-dom-min xd-prime))) yd)))
|
||||
(fd-set-domain s2 (var-name wy) yd-prime))))))))))
|
||||
(:else s)))))
|
||||
|
||||
(define
|
||||
fd-lt
|
||||
(fn
|
||||
(x y)
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((c (fn (sp) (fd-lt-prop x y sp))))
|
||||
(let
|
||||
((s2 (fd-add-constraint s c)))
|
||||
(let
|
||||
((s2-or-nil (c s2)))
|
||||
(let
|
||||
((s3 (cond ((= s2-or-nil nil) nil) (:else (fd-fire-store s2-or-nil)))))
|
||||
(cond ((= s3 nil) mzero) (:else (unit s3))))))))))
|
||||
|
||||
;; --- fd-lte ---
|
||||
|
||||
(define
|
||||
fd-lte-prop
|
||||
(fn
|
||||
(x y s)
|
||||
(let
|
||||
((wx (mk-walk x s)) (wy (mk-walk y s)))
|
||||
(cond
|
||||
((and (number? wx) (number? wy))
|
||||
(cond ((<= wx wy) s) (:else nil)))
|
||||
((and (number? wx) (is-var? wy))
|
||||
(let
|
||||
((yd (fd-domain-of s (var-name wy))))
|
||||
(cond
|
||||
((= yd nil) s)
|
||||
(:else
|
||||
(fd-set-domain
|
||||
s
|
||||
(var-name wy)
|
||||
(filter (fn (v) (>= v wx)) yd))))))
|
||||
((and (is-var? wx) (number? wy))
|
||||
(let
|
||||
((xd (fd-domain-of s (var-name wx))))
|
||||
(cond
|
||||
((= xd nil) s)
|
||||
(:else
|
||||
(fd-set-domain
|
||||
s
|
||||
(var-name wx)
|
||||
(filter (fn (v) (<= v wy)) xd))))))
|
||||
((and (is-var? wx) (is-var? wy))
|
||||
(let
|
||||
((xd (fd-domain-of s (var-name wx)))
|
||||
(yd (fd-domain-of s (var-name wy))))
|
||||
(cond
|
||||
((or (= xd nil) (= yd nil)) s)
|
||||
(:else
|
||||
(let
|
||||
((xd-prime (filter (fn (v) (<= v (fd-dom-max yd))) xd)))
|
||||
(let
|
||||
((s2 (fd-set-domain s (var-name wx) xd-prime)))
|
||||
(cond
|
||||
((= s2 nil) nil)
|
||||
(:else
|
||||
(let
|
||||
((yd-prime (filter (fn (v) (>= v (fd-dom-min xd-prime))) yd)))
|
||||
(fd-set-domain s2 (var-name wy) yd-prime))))))))))
|
||||
(:else s)))))
|
||||
|
||||
(define
|
||||
fd-lte
|
||||
(fn
|
||||
(x y)
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((c (fn (sp) (fd-lte-prop x y sp))))
|
||||
(let
|
||||
((s2 (fd-add-constraint s c)))
|
||||
(let
|
||||
((s2-or-nil (c s2)))
|
||||
(let
|
||||
((s3 (cond ((= s2-or-nil nil) nil) (:else (fd-fire-store s2-or-nil)))))
|
||||
(cond ((= s3 nil) mzero) (:else (unit s3))))))))))
|
||||
|
||||
;; --- fd-eq ---
|
||||
|
||||
(define
|
||||
fd-eq-prop
|
||||
(fn
|
||||
(x y s)
|
||||
(let
|
||||
((wx (mk-walk x s)) (wy (mk-walk y s)))
|
||||
(cond
|
||||
((and (number? wx) (number? wy))
|
||||
(cond ((= wx wy) s) (:else nil)))
|
||||
((and (number? wx) (is-var? wy))
|
||||
(let
|
||||
((yd (fd-domain-of s (var-name wy))))
|
||||
(cond
|
||||
((and (not (= yd nil)) (not (fd-dom-member? wx yd))) nil)
|
||||
(:else
|
||||
(let
|
||||
((s2 (mk-unify wy wx s)))
|
||||
(cond ((= s2 nil) nil) (:else s2)))))))
|
||||
((and (is-var? wx) (number? wy))
|
||||
(let
|
||||
((xd (fd-domain-of s (var-name wx))))
|
||||
(cond
|
||||
((and (not (= xd nil)) (not (fd-dom-member? wy xd))) nil)
|
||||
(:else
|
||||
(let
|
||||
((s2 (mk-unify wx wy s)))
|
||||
(cond ((= s2 nil) nil) (:else s2)))))))
|
||||
((and (is-var? wx) (is-var? wy))
|
||||
(let
|
||||
((xd (fd-domain-of s (var-name wx)))
|
||||
(yd (fd-domain-of s (var-name wy))))
|
||||
(cond
|
||||
((and (= xd nil) (= yd nil))
|
||||
(let
|
||||
((s2 (mk-unify wx wy s)))
|
||||
(cond ((= s2 nil) nil) (:else s2))))
|
||||
(:else
|
||||
(let
|
||||
((shared (cond ((= xd nil) yd) ((= yd nil) xd) (:else (fd-dom-intersect xd yd)))))
|
||||
(cond
|
||||
((fd-dom-empty? shared) nil)
|
||||
(:else
|
||||
(let
|
||||
((s2 (fd-set-domain s (var-name wx) shared)))
|
||||
(cond
|
||||
((= s2 nil) nil)
|
||||
(:else
|
||||
(let
|
||||
((s3 (fd-set-domain s2 (var-name wy) shared)))
|
||||
(cond
|
||||
((= s3 nil) nil)
|
||||
(:else (mk-unify wx wy s3))))))))))))))
|
||||
(:else s)))))
|
||||
|
||||
(define
|
||||
fd-eq
|
||||
(fn
|
||||
(x y)
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((c (fn (sp) (fd-eq-prop x y sp))))
|
||||
(let
|
||||
((s2 (fd-add-constraint s c)))
|
||||
(let
|
||||
((s2-or-nil (c s2)))
|
||||
(let
|
||||
((s3 (cond ((= s2-or-nil nil) nil) (:else (fd-fire-store s2-or-nil)))))
|
||||
(cond ((= s3 nil) mzero) (:else (unit s3))))))))))
|
||||
|
||||
;; --- labelling ---
|
||||
|
||||
(define
|
||||
fd-try-each-value
|
||||
(fn
|
||||
(x dom s)
|
||||
(cond
|
||||
((empty? dom) mzero)
|
||||
(:else
|
||||
(let
|
||||
((s2 (mk-unify x (first dom) s)))
|
||||
(let
|
||||
((s3 (cond ((= s2 nil) nil) (:else (fd-fire-store s2)))))
|
||||
(let
|
||||
((this-stream (cond ((= s3 nil) mzero) (:else (unit s3))))
|
||||
(rest-stream (fd-try-each-value x (rest dom) s)))
|
||||
(mk-mplus this-stream rest-stream))))))))
|
||||
|
||||
(define
|
||||
fd-label-one
|
||||
(fn
|
||||
(x)
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((wx (mk-walk x s)))
|
||||
(cond
|
||||
((number? wx) (unit s))
|
||||
((is-var? wx)
|
||||
(let
|
||||
((dom (fd-domain-of s (var-name wx))))
|
||||
(cond
|
||||
((= dom nil) mzero)
|
||||
(:else (fd-try-each-value wx dom s)))))
|
||||
(:else mzero))))))
|
||||
|
||||
(define
|
||||
fd-label
|
||||
(fn
|
||||
(vars)
|
||||
(cond
|
||||
((empty? vars) succeed)
|
||||
(:else (mk-conj (fd-label-one (first vars)) (fd-label (rest vars)))))))
|
||||
|
||||
;; --- fd-distinct (pairwise distinct via fd-neq) ---
|
||||
|
||||
(define
|
||||
fd-distinct-from-head
|
||||
(fn
|
||||
(x others)
|
||||
(cond
|
||||
((empty? others) succeed)
|
||||
(:else
|
||||
(mk-conj
|
||||
(fd-neq x (first others))
|
||||
(fd-distinct-from-head x (rest others)))))))
|
||||
|
||||
(define
|
||||
fd-distinct
|
||||
(fn
|
||||
(vars)
|
||||
(cond
|
||||
((empty? vars) succeed)
|
||||
((empty? (rest vars)) succeed)
|
||||
(:else
|
||||
(mk-conj
|
||||
(fd-distinct-from-head (first vars) (rest vars))
|
||||
(fd-distinct (rest vars)))))))
|
||||
|
||||
;; --- fd-plus (x + y = z, ground-cases propagator) ---
|
||||
|
||||
(define
|
||||
fd-bind-or-narrow
|
||||
(fn
|
||||
(w target s)
|
||||
(cond
|
||||
((number? w) (cond ((= w target) s) (:else nil)))
|
||||
((is-var? w)
|
||||
(let
|
||||
((wd (fd-domain-of s (var-name w))))
|
||||
(cond
|
||||
((and (not (= wd nil)) (not (fd-dom-member? target wd))) nil)
|
||||
(:else
|
||||
(let
|
||||
((s2 (mk-unify w target s)))
|
||||
(cond ((= s2 nil) nil) (:else s2)))))))
|
||||
(:else nil))))
|
||||
|
||||
(define
|
||||
fd-narrow-or-skip
|
||||
(fn
|
||||
(s var-key d lo hi)
|
||||
(cond
|
||||
((= d nil) s)
|
||||
(:else
|
||||
(fd-set-domain
|
||||
s
|
||||
var-key
|
||||
(filter (fn (v) (and (>= v lo) (<= v hi))) d))))))
|
||||
|
||||
(define
|
||||
fd-plus-prop-vvn
|
||||
(fn
|
||||
(wx wy wz s)
|
||||
(let
|
||||
((xd (fd-domain-of s (var-name wx)))
|
||||
(yd (fd-domain-of s (var-name wy))))
|
||||
(cond
|
||||
((or (= xd nil) (= yd nil)) s)
|
||||
(:else
|
||||
(let
|
||||
((s1 (fd-narrow-or-skip s (var-name wx) xd (- wz (fd-dom-max yd)) (- wz (fd-dom-min yd)))))
|
||||
(cond
|
||||
((= s1 nil) nil)
|
||||
(:else
|
||||
(let
|
||||
((xd2 (fd-domain-of s1 (var-name wx))))
|
||||
(fd-narrow-or-skip
|
||||
s1
|
||||
(var-name wy)
|
||||
yd
|
||||
(- wz (fd-dom-max xd2))
|
||||
(- wz (fd-dom-min xd2))))))))))))
|
||||
|
||||
(define
|
||||
fd-plus-prop-nvv
|
||||
(fn
|
||||
(wx wy wz s)
|
||||
(let
|
||||
((yd (fd-domain-of s (var-name wy)))
|
||||
(zd (fd-domain-of s (var-name wz))))
|
||||
(cond
|
||||
((or (= yd nil) (= zd nil)) s)
|
||||
(:else
|
||||
(let
|
||||
((s1 (fd-narrow-or-skip s (var-name wy) yd (- (fd-dom-min zd) wx) (- (fd-dom-max zd) wx))))
|
||||
(cond
|
||||
((= s1 nil) nil)
|
||||
(:else
|
||||
(let
|
||||
((yd2 (fd-domain-of s1 (var-name wy))))
|
||||
(fd-narrow-or-skip
|
||||
s1
|
||||
(var-name wz)
|
||||
zd
|
||||
(+ wx (fd-dom-min yd2))
|
||||
(+ wx (fd-dom-max yd2))))))))))))
|
||||
|
||||
(define
|
||||
fd-plus-prop-vnv
|
||||
(fn
|
||||
(wx wy wz s)
|
||||
(let
|
||||
((xd (fd-domain-of s (var-name wx)))
|
||||
(zd (fd-domain-of s (var-name wz))))
|
||||
(cond
|
||||
((or (= xd nil) (= zd nil)) s)
|
||||
(:else
|
||||
(let
|
||||
((s1 (fd-narrow-or-skip s (var-name wx) xd (- (fd-dom-min zd) wy) (- (fd-dom-max zd) wy))))
|
||||
(cond
|
||||
((= s1 nil) nil)
|
||||
(:else
|
||||
(let
|
||||
((xd2 (fd-domain-of s1 (var-name wx))))
|
||||
(fd-narrow-or-skip
|
||||
s1
|
||||
(var-name wz)
|
||||
zd
|
||||
(+ (fd-dom-min xd2) wy)
|
||||
(+ (fd-dom-max xd2) wy)))))))))))
|
||||
|
||||
(define
|
||||
fd-plus-prop-vvv
|
||||
(fn
|
||||
(wx wy wz s)
|
||||
(let
|
||||
((xd (fd-domain-of s (var-name wx)))
|
||||
(yd (fd-domain-of s (var-name wy)))
|
||||
(zd (fd-domain-of s (var-name wz))))
|
||||
(cond
|
||||
((or (= xd nil) (or (= yd nil) (= zd nil))) s)
|
||||
(:else
|
||||
(let
|
||||
((s1 (fd-narrow-or-skip s (var-name wx) xd (- (fd-dom-min zd) (fd-dom-max yd)) (- (fd-dom-max zd) (fd-dom-min yd)))))
|
||||
(cond
|
||||
((= s1 nil) nil)
|
||||
(:else
|
||||
(let
|
||||
((s2 (fd-narrow-or-skip s1 (var-name wy) yd (- (fd-dom-min zd) (fd-dom-max xd)) (- (fd-dom-max zd) (fd-dom-min xd)))))
|
||||
(cond
|
||||
((= s2 nil) nil)
|
||||
(:else
|
||||
(fd-narrow-or-skip
|
||||
s2
|
||||
(var-name wz)
|
||||
zd
|
||||
(+ (fd-dom-min xd) (fd-dom-min yd))
|
||||
(+ (fd-dom-max xd) (fd-dom-max yd))))))))))))))
|
||||
|
||||
(define
|
||||
fd-plus-prop
|
||||
(fn
|
||||
(x y z s)
|
||||
(let
|
||||
((wx (mk-walk x s)) (wy (mk-walk y s)) (wz (mk-walk z s)))
|
||||
(cond
|
||||
((and (number? wx) (number? wy) (number? wz))
|
||||
(cond ((= (+ wx wy) wz) s) (:else nil)))
|
||||
((and (number? wx) (number? wy))
|
||||
(fd-bind-or-narrow wz (+ wx wy) s))
|
||||
((and (number? wx) (number? wz))
|
||||
(fd-bind-or-narrow wy (- wz wx) s))
|
||||
((and (number? wy) (number? wz))
|
||||
(fd-bind-or-narrow wx (- wz wy) s))
|
||||
((and (is-var? wx) (is-var? wy) (number? wz))
|
||||
(fd-plus-prop-vvn wx wy wz s))
|
||||
((and (number? wx) (is-var? wy) (is-var? wz))
|
||||
(fd-plus-prop-nvv wx wy wz s))
|
||||
((and (is-var? wx) (number? wy) (is-var? wz))
|
||||
(fd-plus-prop-vnv wx wy wz s))
|
||||
((and (is-var? wx) (is-var? wy) (is-var? wz))
|
||||
(fd-plus-prop-vvv wx wy wz s))
|
||||
(:else s)))))
|
||||
|
||||
(define
|
||||
fd-plus
|
||||
(fn
|
||||
(x y z)
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((c (fn (sp) (fd-plus-prop x y z sp))))
|
||||
(let
|
||||
((s2 (fd-add-constraint s c)))
|
||||
(let
|
||||
((s2-or-nil (c s2)))
|
||||
(let
|
||||
((s3 (cond ((= s2-or-nil nil) nil) (:else (fd-fire-store s2-or-nil)))))
|
||||
(cond ((= s3 nil) mzero) (:else (unit s3))))))))))
|
||||
|
||||
;; --- fd-times (x * y = z, ground-cases propagator) ---
|
||||
|
||||
(define
|
||||
fd-int-ceil-div
|
||||
(fn
|
||||
(a b)
|
||||
(cond
|
||||
((= (mod a b) 0) (/ a b))
|
||||
(:else (+ (fd-int-floor-div a b) 1)))))
|
||||
|
||||
(define fd-int-floor-div (fn (a b) (/ (- a (mod a b)) b)))
|
||||
|
||||
(define
|
||||
fd-dom-positive?
|
||||
(fn
|
||||
(d)
|
||||
(cond ((empty? d) false) (:else (>= (fd-dom-min d) 1)))))
|
||||
|
||||
(define
|
||||
fd-times-prop-vvv
|
||||
(fn
|
||||
(wx wy wz s)
|
||||
(let
|
||||
((xd (fd-domain-of s (var-name wx)))
|
||||
(yd (fd-domain-of s (var-name wy)))
|
||||
(zd (fd-domain-of s (var-name wz))))
|
||||
(cond
|
||||
((or (= xd nil) (or (= yd nil) (= zd nil))) s)
|
||||
((not (and (fd-dom-positive? xd) (and (fd-dom-positive? yd) (fd-dom-positive? zd))))
|
||||
s)
|
||||
(:else
|
||||
(let
|
||||
((s1 (fd-narrow-or-skip s (var-name wx) xd (fd-int-ceil-div (fd-dom-min zd) (fd-dom-max yd)) (fd-int-floor-div (fd-dom-max zd) (fd-dom-min yd)))))
|
||||
(cond
|
||||
((= s1 nil) nil)
|
||||
(:else
|
||||
(let
|
||||
((s2 (fd-narrow-or-skip s1 (var-name wy) yd (fd-int-ceil-div (fd-dom-min zd) (fd-dom-max xd)) (fd-int-floor-div (fd-dom-max zd) (fd-dom-min xd)))))
|
||||
(cond
|
||||
((= s2 nil) nil)
|
||||
(:else
|
||||
(fd-narrow-or-skip
|
||||
s2
|
||||
(var-name wz)
|
||||
zd
|
||||
(* (fd-dom-min xd) (fd-dom-min yd))
|
||||
(* (fd-dom-max xd) (fd-dom-max yd))))))))))))))
|
||||
|
||||
(define
|
||||
fd-times-prop-vvn
|
||||
(fn
|
||||
(wx wy wz s)
|
||||
(let
|
||||
((xd (fd-domain-of s (var-name wx)))
|
||||
(yd (fd-domain-of s (var-name wy))))
|
||||
(cond
|
||||
((or (= xd nil) (= yd nil)) s)
|
||||
((not (and (fd-dom-positive? xd) (fd-dom-positive? yd))) s)
|
||||
((<= wz 0) s)
|
||||
(:else
|
||||
(let
|
||||
((s1 (fd-narrow-or-skip s (var-name wx) xd (fd-int-ceil-div wz (fd-dom-max yd)) (fd-int-floor-div wz (fd-dom-min yd)))))
|
||||
(cond
|
||||
((= s1 nil) nil)
|
||||
(:else
|
||||
(let
|
||||
((xd2 (fd-domain-of s1 (var-name wx))))
|
||||
(fd-narrow-or-skip
|
||||
s1
|
||||
(var-name wy)
|
||||
yd
|
||||
(fd-int-ceil-div wz (fd-dom-max xd2))
|
||||
(fd-int-floor-div wz (fd-dom-min xd2))))))))))))
|
||||
|
||||
(define
|
||||
fd-times-prop-nvv
|
||||
(fn
|
||||
(wx wy wz s)
|
||||
(cond
|
||||
((<= wx 0) s)
|
||||
(:else
|
||||
(let
|
||||
((yd (fd-domain-of s (var-name wy)))
|
||||
(zd (fd-domain-of s (var-name wz))))
|
||||
(cond
|
||||
((or (= yd nil) (= zd nil)) s)
|
||||
((not (and (fd-dom-positive? yd) (fd-dom-positive? zd))) s)
|
||||
(:else
|
||||
(let
|
||||
((s1 (fd-narrow-or-skip s (var-name wy) yd (fd-int-ceil-div (fd-dom-min zd) wx) (fd-int-floor-div (fd-dom-max zd) wx))))
|
||||
(cond
|
||||
((= s1 nil) nil)
|
||||
(:else
|
||||
(let
|
||||
((yd2 (fd-domain-of s1 (var-name wy))))
|
||||
(fd-narrow-or-skip
|
||||
s1
|
||||
(var-name wz)
|
||||
zd
|
||||
(* wx (fd-dom-min yd2))
|
||||
(* wx (fd-dom-max yd2))))))))))))))
|
||||
|
||||
(define
|
||||
fd-times-prop-vnv
|
||||
(fn
|
||||
(wx wy wz s)
|
||||
(cond
|
||||
((<= wy 0) s)
|
||||
(:else
|
||||
(let
|
||||
((xd (fd-domain-of s (var-name wx)))
|
||||
(zd (fd-domain-of s (var-name wz))))
|
||||
(cond
|
||||
((or (= xd nil) (= zd nil)) s)
|
||||
((not (and (fd-dom-positive? xd) (fd-dom-positive? zd))) s)
|
||||
(:else
|
||||
(let
|
||||
((s1 (fd-narrow-or-skip s (var-name wx) xd (fd-int-ceil-div (fd-dom-min zd) wy) (fd-int-floor-div (fd-dom-max zd) wy))))
|
||||
(cond
|
||||
((= s1 nil) nil)
|
||||
(:else
|
||||
(let
|
||||
((xd2 (fd-domain-of s1 (var-name wx))))
|
||||
(fd-narrow-or-skip
|
||||
s1
|
||||
(var-name wz)
|
||||
zd
|
||||
(* (fd-dom-min xd2) wy)
|
||||
(* (fd-dom-max xd2) wy)))))))))))))
|
||||
|
||||
(define
|
||||
fd-times-prop
|
||||
(fn
|
||||
(x y z s)
|
||||
(let
|
||||
((wx (mk-walk x s)) (wy (mk-walk y s)) (wz (mk-walk z s)))
|
||||
(cond
|
||||
((and (number? wx) (number? wy) (number? wz))
|
||||
(cond ((= (* wx wy) wz) s) (:else nil)))
|
||||
((and (number? wx) (number? wy))
|
||||
(fd-bind-or-narrow wz (* wx wy) s))
|
||||
((and (number? wx) (number? wz))
|
||||
(cond
|
||||
((= wx 0) (cond ((= wz 0) s) (:else nil)))
|
||||
((not (= (mod wz wx) 0)) nil)
|
||||
(:else (fd-bind-or-narrow wy (/ wz wx) s))))
|
||||
((and (number? wy) (number? wz))
|
||||
(cond
|
||||
((= wy 0) (cond ((= wz 0) s) (:else nil)))
|
||||
((not (= (mod wz wy) 0)) nil)
|
||||
(:else (fd-bind-or-narrow wx (/ wz wy) s))))
|
||||
((and (is-var? wx) (is-var? wy) (number? wz))
|
||||
(fd-times-prop-vvn wx wy wz s))
|
||||
((and (number? wx) (is-var? wy) (is-var? wz))
|
||||
(fd-times-prop-nvv wx wy wz s))
|
||||
((and (is-var? wx) (number? wy) (is-var? wz))
|
||||
(fd-times-prop-vnv wx wy wz s))
|
||||
((and (is-var? wx) (is-var? wy) (is-var? wz))
|
||||
(fd-times-prop-vvv wx wy wz s))
|
||||
(:else s)))))
|
||||
|
||||
(define
|
||||
fd-times
|
||||
(fn
|
||||
(x y z)
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((c (fn (sp) (fd-times-prop x y z sp))))
|
||||
(let
|
||||
((s2 (fd-add-constraint s c)))
|
||||
(let
|
||||
((s2-or-nil (c s2)))
|
||||
(let
|
||||
((s3 (cond ((= s2-or-nil nil) nil) (:else (fd-fire-store s2-or-nil)))))
|
||||
(cond ((= s3 nil) mzero) (:else (unit s3))))))))))
|
||||
42
lib/minikanren/conda.sx
Normal file
42
lib/minikanren/conda.sx
Normal file
@@ -0,0 +1,42 @@
|
||||
;; lib/minikanren/conda.sx — Phase 5 piece A: `conda`, the soft-cut.
|
||||
;;
|
||||
;; (conda (g0 g ...) (h0 h ...) ...)
|
||||
;; — first clause whose head g0 produces ANY answer wins; ALL of g0's
|
||||
;; answers are then conj'd with the rest of that clause; later
|
||||
;; clauses are NOT tried.
|
||||
;; — differs from condu only in not wrapping g0 in onceo: condu
|
||||
;; commits to the SINGLE first answer, conda lets the head's full
|
||||
;; answer-set flow into the rest of the clause.
|
||||
;; (Reasoned Schemer chapter 10; Byrd 5.3.)
|
||||
|
||||
(define
|
||||
conda-try
|
||||
(fn
|
||||
(clauses s)
|
||||
(cond
|
||||
((empty? clauses) mzero)
|
||||
(:else
|
||||
(let
|
||||
((cl (first clauses)))
|
||||
(let
|
||||
((head-goal (first cl)) (rest-goals (rest cl)))
|
||||
(let
|
||||
((peek (stream-take 1 (head-goal s))))
|
||||
(if
|
||||
(empty? peek)
|
||||
(conda-try (rest clauses) s)
|
||||
(mk-bind (head-goal s) (mk-conj-list rest-goals))))))))))
|
||||
|
||||
(defmacro
|
||||
conda
|
||||
(&rest clauses)
|
||||
(quasiquote
|
||||
(fn
|
||||
(s)
|
||||
(conda-try
|
||||
(list
|
||||
(splice-unquote
|
||||
(map
|
||||
(fn (cl) (quasiquote (list (splice-unquote cl))))
|
||||
clauses)))
|
||||
s))))
|
||||
39
lib/minikanren/conde.sx
Normal file
39
lib/minikanren/conde.sx
Normal file
@@ -0,0 +1,39 @@
|
||||
;; lib/minikanren/conde.sx — Phase 2 piece C: `conde`, the canonical
|
||||
;; miniKanren and-or form, with implicit Zzz inverse-eta delay so recursive
|
||||
;; relations like appendo terminate.
|
||||
;;
|
||||
;; (conde (g1a g1b ...) (g2a g2b ...) ...)
|
||||
;; ≡ (mk-disj (Zzz (mk-conj g1a g1b ...))
|
||||
;; (Zzz (mk-conj g2a g2b ...)) ...)
|
||||
;;
|
||||
;; `Zzz g` wraps a goal expression in (fn (S) (fn () (g S))) so that
|
||||
;; `g`'s body isn't constructed until the surrounding fn is applied to a
|
||||
;; substitution AND the returned thunk is forced. This is what gives
|
||||
;; miniKanren its laziness — recursive goal definitions can be `(conde
|
||||
;; ... (... (recur ...)))` without infinite descent at construction time.
|
||||
;;
|
||||
;; Hygiene: the substitution parameter is gensym'd so that user goal
|
||||
;; expressions which themselves bind `s` (e.g. `(appendo l s ls)`) keep
|
||||
;; their lexical `s` and don't accidentally reference the wrapper's
|
||||
;; substitution. Without gensym, miniKanren relations that follow the
|
||||
;; common (l s ls) parameter convention are silently miscompiled.
|
||||
|
||||
(defmacro
|
||||
Zzz
|
||||
(g)
|
||||
(let
|
||||
((s-sym (gensym "zzz-s-")))
|
||||
(quasiquote
|
||||
(fn ((unquote s-sym)) (fn () ((unquote g) (unquote s-sym)))))))
|
||||
|
||||
(defmacro
|
||||
conde
|
||||
(&rest clauses)
|
||||
(quasiquote
|
||||
(mk-disj
|
||||
(splice-unquote
|
||||
(map
|
||||
(fn
|
||||
(clause)
|
||||
(quasiquote (Zzz (mk-conj (splice-unquote clause)))))
|
||||
clauses)))))
|
||||
58
lib/minikanren/condu.sx
Normal file
58
lib/minikanren/condu.sx
Normal file
@@ -0,0 +1,58 @@
|
||||
;; lib/minikanren/condu.sx — Phase 2 piece D: `condu` and `onceo`.
|
||||
;;
|
||||
;; Both are commitment forms (no backtracking into discarded options):
|
||||
;;
|
||||
;; (onceo g) — succeeds at most once: takes the first answer
|
||||
;; stream-take produces from (g s).
|
||||
;;
|
||||
;; (condu (g0 g ...) (h0 h ...) ...)
|
||||
;; — first clause whose head goal succeeds wins; only
|
||||
;; the first answer of the head is propagated to the
|
||||
;; rest of that clause; later clauses are not tried.
|
||||
;; (Reasoned Schemer chapter 10; Byrd 5.4.)
|
||||
|
||||
(define
|
||||
onceo
|
||||
(fn
|
||||
(g)
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((peek (stream-take 1 (g s))))
|
||||
(if (empty? peek) mzero (unit (first peek)))))))
|
||||
|
||||
;; condu-try — runtime walker over a list of clauses (each clause a list of
|
||||
;; goals). Forces the head with stream-take 1; if head fails, recurse to
|
||||
;; the next clause; if head succeeds, commits its single answer through
|
||||
;; the rest of the clause.
|
||||
(define
|
||||
condu-try
|
||||
(fn
|
||||
(clauses s)
|
||||
(cond
|
||||
((empty? clauses) mzero)
|
||||
(:else
|
||||
(let
|
||||
((cl (first clauses)))
|
||||
(let
|
||||
((head-goal (first cl)) (rest-goals (rest cl)))
|
||||
(let
|
||||
((peek (stream-take 1 (head-goal s))))
|
||||
(if
|
||||
(empty? peek)
|
||||
(condu-try (rest clauses) s)
|
||||
((mk-conj-list rest-goals) (first peek))))))))))
|
||||
|
||||
(defmacro
|
||||
condu
|
||||
(&rest clauses)
|
||||
(quasiquote
|
||||
(fn
|
||||
(s)
|
||||
(condu-try
|
||||
(list
|
||||
(splice-unquote
|
||||
(map
|
||||
(fn (cl) (quasiquote (list (splice-unquote cl))))
|
||||
clauses)))
|
||||
s))))
|
||||
25
lib/minikanren/defrel.sx
Normal file
25
lib/minikanren/defrel.sx
Normal file
@@ -0,0 +1,25 @@
|
||||
;; lib/minikanren/defrel.sx — Prolog-style defrel macro.
|
||||
;;
|
||||
;; (defrel (NAME ARG1 ARG2 ...)
|
||||
;; (CLAUSE1 ...)
|
||||
;; (CLAUSE2 ...)
|
||||
;; ...)
|
||||
;;
|
||||
;; expands to
|
||||
;;
|
||||
;; (define NAME (fn (ARG1 ARG2 ...) (conde (CLAUSE1 ...) (CLAUSE2 ...))))
|
||||
;;
|
||||
;; This puts each clause's goals immediately after the head, mirroring
|
||||
;; Prolog's `name(Args) :- goals.` shape. Clauses are conde-conjoined
|
||||
;; goals — `Zzz`-wrapping is automatic via `conde`, so recursive
|
||||
;; relations terminate on partial answers.
|
||||
|
||||
(defmacro
|
||||
defrel
|
||||
(head &rest clauses)
|
||||
(let
|
||||
((name (first head)) (args (rest head)))
|
||||
(list
|
||||
(quote define)
|
||||
name
|
||||
(list (quote fn) args (cons (quote conde) clauses)))))
|
||||
71
lib/minikanren/diseq.sx
Normal file
71
lib/minikanren/diseq.sx
Normal file
@@ -0,0 +1,71 @@
|
||||
;; lib/minikanren/diseq.sx — Phase 5 polish: =/= disequality with a
|
||||
;; constraint store, generalising nafc / fd-neq to logic terms.
|
||||
;;
|
||||
;; The constraint store lives under the same `_fd` reserved key as the
|
||||
;; CLP(FD) propagators (a disequality is just another constraint
|
||||
;; closure that the existing fd-fire-store machinery re-runs).
|
||||
;;
|
||||
;; =/= semantics:
|
||||
;; - If u and v walk to ground non-unifiable terms, succeed (drop).
|
||||
;; - If they walk to terms that COULD become equal under a future
|
||||
;; binding, store the constraint; re-check after each binding.
|
||||
;; - If they're already equal (unify with no new bindings), fail.
|
||||
;;
|
||||
;; Implementation: each =/= test attempts (mk-unify wu wv s).
|
||||
;; nil — distinct, keep s, drop the constraint (return s).
|
||||
;; subst eq — equal, fail (return nil).
|
||||
;; subst > — partially unifiable; keep the constraint, return s.
|
||||
;;
|
||||
;; "Substitution equal to s" is detected via key-count: mk-unify only
|
||||
;; ever extends a substitution, never removes from it, so equal
|
||||
;; key-count means no new bindings were needed.
|
||||
|
||||
(define
|
||||
=/=-prop
|
||||
(fn
|
||||
(u v s)
|
||||
(let
|
||||
((s-after (mk-unify u v s)))
|
||||
(cond
|
||||
((= s-after nil) s)
|
||||
((= (len (keys s)) (len (keys s-after))) nil)
|
||||
(:else s)))))
|
||||
|
||||
(define
|
||||
=/=
|
||||
(fn
|
||||
(u v)
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((c (fn (sp) (=/=-prop u v sp))))
|
||||
(let
|
||||
((s2 (fd-add-constraint s c)))
|
||||
(let
|
||||
((s2-or-nil (c s2)))
|
||||
(let
|
||||
((s3 (cond ((= s2-or-nil nil) nil) (:else (fd-fire-store s2-or-nil)))))
|
||||
(cond ((= s3 nil) mzero) (:else (unit s3))))))))))
|
||||
|
||||
;; --- constraint-aware == ---
|
||||
;;
|
||||
;; Plain `==` doesn't fire the constraint store, so a binding that
|
||||
;; should violate a pending =/= goes undetected. `==-cs` is the
|
||||
;; drop-in replacement that fires fd-fire-store after each binding.
|
||||
;; Use ==-cs in any program that mixes =/= (or fd-* goals that should
|
||||
;; re-check after non-FD bindings) with regular unification.
|
||||
|
||||
(define
|
||||
==-cs
|
||||
(fn
|
||||
(u v)
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((s2 (mk-unify u v s)))
|
||||
(cond
|
||||
((= s2 nil) mzero)
|
||||
(:else
|
||||
(let
|
||||
((s3 (fd-fire-store s2)))
|
||||
(cond ((= s3 nil) mzero) (:else (unit s3))))))))))
|
||||
25
lib/minikanren/fd.sx
Normal file
25
lib/minikanren/fd.sx
Normal file
@@ -0,0 +1,25 @@
|
||||
;; lib/minikanren/fd.sx — Phase 6 piece A: minimal finite-domain helpers.
|
||||
;;
|
||||
;; A full CLP(FD) engine (arc consistency, native integer domains, fd-plus
|
||||
;; etc.) is Phase 6 proper. For now we expose two small relations layered
|
||||
;; on the existing list machinery — they're sufficient for permutation
|
||||
;; puzzles, the N-queens-style core of constraint solving:
|
||||
;;
|
||||
;; (ino x dom) — x is a member of dom (alias for membero with the
|
||||
;; constraint-store-friendly argument order).
|
||||
;; (all-distincto l) — all elements of l are pairwise distinct.
|
||||
;;
|
||||
;; all-distincto uses nafc + membero on the tail — it requires the head
|
||||
;; element of each recursive step to be ground enough for membero to be
|
||||
;; finitary, so order matters: prefer (in x dom) goals BEFORE
|
||||
;; (all-distincto (list x ...)) so values get committed first.
|
||||
|
||||
(define ino (fn (x dom) (membero x dom)))
|
||||
|
||||
(define
|
||||
all-distincto
|
||||
(fn
|
||||
(l)
|
||||
(conde
|
||||
((nullo l))
|
||||
((fresh (a d) (conso a d l) (nafc (membero a d)) (all-distincto d))))))
|
||||
23
lib/minikanren/fresh.sx
Normal file
23
lib/minikanren/fresh.sx
Normal file
@@ -0,0 +1,23 @@
|
||||
;; lib/minikanren/fresh.sx — Phase 2 piece B: `fresh` for introducing
|
||||
;; logic variables inside a goal body.
|
||||
;;
|
||||
;; (fresh (x y z) goal1 goal2 ...)
|
||||
;; ≡ (let ((x (make-var)) (y (make-var)) (z (make-var)))
|
||||
;; (mk-conj goal1 goal2 ...))
|
||||
;;
|
||||
;; A macro rather than a function so user-named vars are real lexical
|
||||
;; bindings — which is also what miniKanren convention expects.
|
||||
;; The empty-vars form (fresh () goal ...) is just a goal grouping.
|
||||
|
||||
(defmacro
|
||||
fresh
|
||||
(vars &rest goals)
|
||||
(quasiquote
|
||||
(let
|
||||
(unquote (map (fn (v) (list v (list (quote make-var)))) vars))
|
||||
(mk-conj (splice-unquote goals)))))
|
||||
|
||||
;; call-fresh — functional alternative for code that builds goals
|
||||
;; programmatically:
|
||||
;; ((call-fresh (fn (x) (== x 7))) empty-s) → ({:_.N 7})
|
||||
(define call-fresh (fn (f) (fn (s) ((f (make-var)) s))))
|
||||
58
lib/minikanren/goals.sx
Normal file
58
lib/minikanren/goals.sx
Normal file
@@ -0,0 +1,58 @@
|
||||
;; lib/minikanren/goals.sx — Phase 2 piece B: core goals.
|
||||
;;
|
||||
;; A goal is a function (fn (s) → stream-of-substitutions).
|
||||
;; Goals built here:
|
||||
;; succeed — always returns (unit s)
|
||||
;; fail — always returns mzero
|
||||
;; == — unifies two terms; succeeds with a singleton, else fails
|
||||
;; ==-check — opt-in occurs-checked equality
|
||||
;; conj2 / mk-conj — sequential conjunction of goals
|
||||
;; disj2 / mk-disj — interleaved disjunction of goals (raw — `conde` adds
|
||||
;; the implicit-conj-per-clause sugar in a later commit)
|
||||
|
||||
(define succeed (fn (s) (unit s)))
|
||||
|
||||
(define fail (fn (s) mzero))
|
||||
|
||||
(define
|
||||
==
|
||||
(fn
|
||||
(u v)
|
||||
(fn
|
||||
(s)
|
||||
(let ((s2 (mk-unify u v s))) (if (= s2 nil) mzero (unit s2))))))
|
||||
|
||||
(define
|
||||
==-check
|
||||
(fn
|
||||
(u v)
|
||||
(fn
|
||||
(s)
|
||||
(let ((s2 (mk-unify-check u v s))) (if (= s2 nil) mzero (unit s2))))))
|
||||
|
||||
(define conj2 (fn (g1 g2) (fn (s) (mk-bind (g1 s) g2))))
|
||||
|
||||
(define disj2 (fn (g1 g2) (fn (s) (mk-mplus (g1 s) (g2 s)))))
|
||||
|
||||
;; Fold goals in a list. (mk-conj-list ()) ≡ succeed; (mk-disj-list ()) ≡ fail.
|
||||
(define
|
||||
mk-conj-list
|
||||
(fn
|
||||
(gs)
|
||||
(cond
|
||||
((empty? gs) succeed)
|
||||
((empty? (rest gs)) (first gs))
|
||||
(:else (conj2 (first gs) (mk-conj-list (rest gs)))))))
|
||||
|
||||
(define
|
||||
mk-disj-list
|
||||
(fn
|
||||
(gs)
|
||||
(cond
|
||||
((empty? gs) fail)
|
||||
((empty? (rest gs)) (first gs))
|
||||
(:else (disj2 (first gs) (mk-disj-list (rest gs)))))))
|
||||
|
||||
(define mk-conj (fn (&rest gs) (mk-conj-list gs)))
|
||||
|
||||
(define mk-disj (fn (&rest gs) (mk-disj-list gs)))
|
||||
151
lib/minikanren/intarith.sx
Normal file
151
lib/minikanren/intarith.sx
Normal file
@@ -0,0 +1,151 @@
|
||||
;; lib/minikanren/intarith.sx — fast integer arithmetic via project.
|
||||
;;
|
||||
;; These are ground-only escapes into host arithmetic. They run at native
|
||||
;; speed (host ints) but require their arguments to walk to actual numbers
|
||||
;; — they are not relational the way `pluso` (Peano) is. Use them when
|
||||
;; the puzzle size makes Peano impractical.
|
||||
;;
|
||||
;; Naming: `-i` suffix marks "integer-only" goals.
|
||||
|
||||
(define
|
||||
pluso-i
|
||||
(fn
|
||||
(a b c)
|
||||
(project
|
||||
(a b)
|
||||
(if (and (number? a) (number? b)) (== c (+ a b)) fail))))
|
||||
|
||||
(define
|
||||
minuso-i
|
||||
(fn
|
||||
(a b c)
|
||||
(project
|
||||
(a b)
|
||||
(if (and (number? a) (number? b)) (== c (- a b)) fail))))
|
||||
|
||||
(define
|
||||
*o-i
|
||||
(fn
|
||||
(a b c)
|
||||
(project
|
||||
(a b)
|
||||
(if (and (number? a) (number? b)) (== c (* a b)) fail))))
|
||||
|
||||
(define
|
||||
lto-i
|
||||
(fn
|
||||
(a b)
|
||||
(project
|
||||
(a b)
|
||||
(if (and (number? a) (and (number? b) (< a b))) succeed fail))))
|
||||
|
||||
(define
|
||||
lteo-i
|
||||
(fn
|
||||
(a b)
|
||||
(project
|
||||
(a b)
|
||||
(if (and (number? a) (and (number? b) (<= a b))) succeed fail))))
|
||||
|
||||
(define
|
||||
neqo-i
|
||||
(fn
|
||||
(a b)
|
||||
(project
|
||||
(a b)
|
||||
(if (and (number? a) (and (number? b) (not (= a b)))) succeed fail))))
|
||||
|
||||
(define numbero (fn (x) (project (x) (if (number? x) succeed fail))))
|
||||
|
||||
(define stringo (fn (x) (project (x) (if (string? x) succeed fail))))
|
||||
|
||||
(define symbolo (fn (x) (project (x) (if (symbol? x) succeed fail))))
|
||||
|
||||
(define
|
||||
even-i
|
||||
(fn (n) (project (n) (if (and (number? n) (even? n)) succeed fail))))
|
||||
|
||||
(define
|
||||
odd-i
|
||||
(fn (n) (project (n) (if (and (number? n) (odd? n)) succeed fail))))
|
||||
|
||||
(define
|
||||
sortedo
|
||||
(fn
|
||||
(l)
|
||||
(conde
|
||||
((nullo l))
|
||||
((fresh (a) (== l (list a))))
|
||||
((fresh (a b rest mid) (conso a mid l) (conso b rest mid) (lteo-i a b) (sortedo mid))))))
|
||||
|
||||
(define
|
||||
mino
|
||||
(fn
|
||||
(l m)
|
||||
(conde
|
||||
((fresh (a) (== l (list a)) (== m a)))
|
||||
((fresh (a d rest-min) (conso a d l) (mino d rest-min) (conde ((lteo-i a rest-min) (== m a)) ((lto-i rest-min a) (== m rest-min))))))))
|
||||
|
||||
(define
|
||||
maxo
|
||||
(fn
|
||||
(l m)
|
||||
(conde
|
||||
((fresh (a) (== l (list a)) (== m a)))
|
||||
((fresh (a d rest-max) (conso a d l) (maxo d rest-max) (conde ((lteo-i rest-max a) (== m a)) ((lto-i a rest-max) (== m rest-max))))))))
|
||||
|
||||
(define
|
||||
sumo
|
||||
(fn
|
||||
(l total)
|
||||
(conde
|
||||
((nullo l) (== total 0))
|
||||
((fresh (a d rest-sum) (conso a d l) (sumo d rest-sum) (pluso-i a rest-sum total))))))
|
||||
|
||||
(define
|
||||
producto
|
||||
(fn
|
||||
(l total)
|
||||
(conde
|
||||
((nullo l) (== total 1))
|
||||
((fresh (a d rest-prod) (conso a d l) (producto d rest-prod) (*o-i a rest-prod total))))))
|
||||
|
||||
(define
|
||||
lengtho-i
|
||||
(fn
|
||||
(l n)
|
||||
(conde
|
||||
((nullo l) (== n 0))
|
||||
((fresh (a d n-1) (conso a d l) (lengtho-i d n-1) (pluso-i 1 n-1 n))))))
|
||||
|
||||
(define
|
||||
enumerate-from-i
|
||||
(fn
|
||||
(start l result)
|
||||
(conde
|
||||
((nullo l) (nullo result))
|
||||
((fresh (a d r-rest start-prime) (conso a d l) (conso (list start a) r-rest result) (pluso-i 1 start start-prime) (enumerate-from-i start-prime d r-rest))))))
|
||||
|
||||
(define enumerate-i (fn (l result) (enumerate-from-i 0 l result)))
|
||||
|
||||
(define
|
||||
counto
|
||||
(fn
|
||||
(x l n)
|
||||
(conde
|
||||
((nullo l) (== n 0))
|
||||
((fresh (a d n-rest) (conso a d l) (conde ((== a x) (counto x d n-rest) (pluso-i 1 n-rest n)) ((nafc (== a x)) (counto x d n))))))))
|
||||
|
||||
(define
|
||||
mk-arith-prog
|
||||
(fn
|
||||
(start step len)
|
||||
(cond
|
||||
((= len 0) (list))
|
||||
(:else (cons start (mk-arith-prog (+ start step) step (- len 1)))))))
|
||||
|
||||
(define
|
||||
arith-progo
|
||||
(fn
|
||||
(start step len result)
|
||||
(project (start step len) (== result (mk-arith-prog start step len)))))
|
||||
76
lib/minikanren/matche.sx
Normal file
76
lib/minikanren/matche.sx
Normal file
@@ -0,0 +1,76 @@
|
||||
;; lib/minikanren/matche.sx — Phase 5 piece D: pattern matching over terms.
|
||||
;;
|
||||
;; (matche TARGET
|
||||
;; (PATTERN1 g1 g2 ...)
|
||||
;; (PATTERN2 g1 ...)
|
||||
;; ...)
|
||||
;;
|
||||
;; Pattern grammar:
|
||||
;; _ wildcard — fresh anonymous var
|
||||
;; x plain symbol — fresh var, bind by name
|
||||
;; ATOM literal (number, string, boolean) — must equal
|
||||
;; :keyword keyword literal — emitted bare (keywords self-evaluate
|
||||
;; to their string name in SX, so quoting them changes
|
||||
;; their type from string to keyword)
|
||||
;; () empty list — must equal
|
||||
;; (p1 p2 ... pn) list pattern — recurse on each element
|
||||
;;
|
||||
;; The macro expands to a `conde` whose clauses are
|
||||
;; `((fresh (vars-in-pat) (== target pat-expr) body...))`.
|
||||
;;
|
||||
;; Repeated symbol names within a pattern produce the same fresh var, so
|
||||
;; they unify by `==`. Fixed-length list patterns only — head/tail
|
||||
;; destructuring uses `(fresh (a d) (conso a d target) body)` directly.
|
||||
;;
|
||||
;; Note: the macro builds the expansion via `cons` / `list` rather than a
|
||||
;; quasiquote — quasiquote does not recurse into nested lambda bodies in
|
||||
;; SX, so `\`(matche-clause (quote ,target) cl)` left literal
|
||||
;; `(unquote target)` in the output.
|
||||
|
||||
(define matche-symbol-var? (fn (s) (symbol? s)))
|
||||
|
||||
(define
|
||||
matche-collect-vars-acc
|
||||
(fn
|
||||
(pat acc)
|
||||
(cond
|
||||
((matche-symbol-var? pat)
|
||||
(if (some (fn (s) (= s pat)) acc) acc (append acc (list pat))))
|
||||
((and (list? pat) (not (empty? pat)))
|
||||
(reduce (fn (a p) (matche-collect-vars-acc p a)) acc pat))
|
||||
(:else acc))))
|
||||
|
||||
(define
|
||||
matche-collect-vars
|
||||
(fn (pat) (matche-collect-vars-acc pat (list))))
|
||||
|
||||
(define
|
||||
matche-pattern->expr
|
||||
(fn
|
||||
(pat)
|
||||
(cond
|
||||
((matche-symbol-var? pat) pat)
|
||||
((and (list? pat) (empty? pat)) (list (quote list)))
|
||||
((list? pat) (cons (quote list) (map matche-pattern->expr pat)))
|
||||
((keyword? pat) pat)
|
||||
(:else (list (quote quote) pat)))))
|
||||
|
||||
(define
|
||||
matche-clause
|
||||
(fn
|
||||
(target cl)
|
||||
(let
|
||||
((pat (first cl)) (body (rest cl)))
|
||||
(let
|
||||
((vars (matche-collect-vars pat)))
|
||||
(let
|
||||
((pat-expr (matche-pattern->expr pat)))
|
||||
(list
|
||||
(cons
|
||||
(quote fresh)
|
||||
(cons vars (cons (list (quote ==) target pat-expr) body)))))))))
|
||||
|
||||
(defmacro
|
||||
matche
|
||||
(target &rest clauses)
|
||||
(cons (quote conde) (map (fn (cl) (matche-clause target cl)) clauses)))
|
||||
24
lib/minikanren/nafc.sx
Normal file
24
lib/minikanren/nafc.sx
Normal file
@@ -0,0 +1,24 @@
|
||||
;; lib/minikanren/nafc.sx — Phase 5 piece C: negation as finite failure.
|
||||
;;
|
||||
;; (nafc g)
|
||||
;; succeeds (yields the input substitution) if g has zero answers
|
||||
;; against that substitution; fails (mzero) if g has at least one.
|
||||
;;
|
||||
;; Caveat: `nafc` is unsound under the open-world assumption. It only
|
||||
;; makes sense for goals over fully-ground terms, or with the explicit
|
||||
;; understanding that adding more facts could flip the answer. Use
|
||||
;; `(project (...) ...)` to ensure the relevant vars are ground first.
|
||||
;;
|
||||
;; Caveat 2: stream-take forces g for at least one answer; if g is
|
||||
;; infinitely-ground (say, a divergent search over an unbound list),
|
||||
;; nafc itself will diverge. Standard miniKanren limitation.
|
||||
|
||||
(define
|
||||
nafc
|
||||
(fn
|
||||
(g)
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((peek (stream-take 1 (g s))))
|
||||
(if (empty? peek) (unit s) mzero)))))
|
||||
51
lib/minikanren/peano.sx
Normal file
51
lib/minikanren/peano.sx
Normal file
@@ -0,0 +1,51 @@
|
||||
;; lib/minikanren/peano.sx — Peano-encoded natural-number relations.
|
||||
;;
|
||||
;; Same encoding as `lengtho`: zero is the keyword `:z`; successors are
|
||||
;; `(:s n)`. So 3 = `(:s (:s (:s :z)))`. `(:z)` and `(:s ...)` are normal
|
||||
;; SX values that unify positionally — no special primitives needed.
|
||||
;;
|
||||
;; Peano arithmetic is the canonical miniKanren way to test addition /
|
||||
;; multiplication / less-than relationally without an FD constraint store.
|
||||
;; (CLP(FD) integers come in Phase 6.)
|
||||
|
||||
(define zeroo (fn (n) (== n :z)))
|
||||
|
||||
(define succ-of (fn (n m) (== m (list :s n))))
|
||||
|
||||
(define
|
||||
pluso
|
||||
(fn
|
||||
(a b c)
|
||||
(conde
|
||||
((== a :z) (== b c))
|
||||
((fresh (a-1 c-1) (== a (list :s a-1)) (== c (list :s c-1)) (pluso a-1 b c-1))))))
|
||||
|
||||
(define minuso (fn (a b c) (pluso b c a)))
|
||||
|
||||
(define lteo (fn (a b) (fresh (k) (pluso a k b))))
|
||||
|
||||
(define lto (fn (a b) (fresh (sa) (succ-of a sa) (lteo sa b))))
|
||||
|
||||
(define
|
||||
eveno
|
||||
(fn
|
||||
(n)
|
||||
(conde
|
||||
((== n :z))
|
||||
((fresh (m) (== n (list :s (list :s m))) (eveno m))))))
|
||||
|
||||
(define
|
||||
oddo
|
||||
(fn
|
||||
(n)
|
||||
(conde
|
||||
((== n (list :s :z)))
|
||||
((fresh (m) (== n (list :s (list :s m))) (oddo m))))))
|
||||
|
||||
(define
|
||||
*o
|
||||
(fn
|
||||
(a b c)
|
||||
(conde
|
||||
((== a :z) (== c :z))
|
||||
((fresh (a-1 ab-1) (== a (list :s a-1)) (*o a-1 b ab-1) (pluso b ab-1 c))))))
|
||||
25
lib/minikanren/project.sx
Normal file
25
lib/minikanren/project.sx
Normal file
@@ -0,0 +1,25 @@
|
||||
;; lib/minikanren/project.sx — Phase 5 piece B: `project`.
|
||||
;;
|
||||
;; (project (x y) g1 g2 ...)
|
||||
;; — rebinds each named var to (mk-walk* var s) within the body's
|
||||
;; lexical scope, then runs the conjunction of the body goals on
|
||||
;; the same substitution. Use to escape into regular SX (arithmetic,
|
||||
;; string ops, host predicates) when you need a ground value.
|
||||
;;
|
||||
;; If any of the projected vars is still unbound at this point, the body
|
||||
;; sees the raw `(:var NAME)` term — that is intentional and lets you
|
||||
;; mix project with `(== ground? var)` patterns or with conda guards.
|
||||
;;
|
||||
;; Hygiene: substitution parameter is gensym'd so it doesn't capture user
|
||||
;; vars (`s` is a popular relation parameter name).
|
||||
|
||||
(defmacro
|
||||
project
|
||||
(vars &rest goals)
|
||||
(let
|
||||
((s-sym (gensym "proj-s-")))
|
||||
(quasiquote
|
||||
(fn
|
||||
((unquote s-sym))
|
||||
((let (unquote (map (fn (v) (list v (list (quote mk-walk*) v s-sym))) vars)) (mk-conj (splice-unquote goals)))
|
||||
(unquote s-sym))))))
|
||||
67
lib/minikanren/queens.sx
Normal file
67
lib/minikanren/queens.sx
Normal file
@@ -0,0 +1,67 @@
|
||||
;; lib/minikanren/queens.sx — N-queens via ino + all-distincto + project.
|
||||
;;
|
||||
;; Encoding: q = (c1 c2 ... cn) where ci is the column of the queen in
|
||||
;; row i. Each ci ∈ {1..n}; all distinct (no two queens share a column);
|
||||
;; no two queens on the same diagonal (|ci - cj| ≠ |i - j| for i ≠ j).
|
||||
;;
|
||||
;; The diagonal check uses `project` to escape into host arithmetic
|
||||
;; once both column values are ground.
|
||||
|
||||
(define
|
||||
safe-diag
|
||||
(fn
|
||||
(a b dist)
|
||||
(project (a b) (if (= (abs (- a b)) dist) fail succeed))))
|
||||
|
||||
(define
|
||||
safe-cell-vs-rest
|
||||
(fn
|
||||
(c c-row others next-row)
|
||||
(cond
|
||||
((empty? others) succeed)
|
||||
(:else
|
||||
(mk-conj
|
||||
(safe-diag c (first others) (- next-row c-row))
|
||||
(safe-cell-vs-rest c c-row (rest others) (+ next-row 1)))))))
|
||||
|
||||
(define
|
||||
all-cells-safe
|
||||
(fn
|
||||
(cols start-row)
|
||||
(cond
|
||||
((empty? cols) succeed)
|
||||
(:else
|
||||
(mk-conj
|
||||
(safe-cell-vs-rest
|
||||
(first cols)
|
||||
start-row
|
||||
(rest cols)
|
||||
(+ start-row 1))
|
||||
(all-cells-safe (rest cols) (+ start-row 1)))))))
|
||||
|
||||
(define
|
||||
range-1-to-n
|
||||
(fn
|
||||
(n)
|
||||
(cond
|
||||
((= n 0) (list))
|
||||
(:else (append (range-1-to-n (- n 1)) (list n))))))
|
||||
|
||||
(define
|
||||
ino-each
|
||||
(fn
|
||||
(cols dom)
|
||||
(cond
|
||||
((empty? cols) succeed)
|
||||
(:else (mk-conj (ino (first cols) dom) (ino-each (rest cols) dom))))))
|
||||
|
||||
(define
|
||||
queens-cols
|
||||
(fn
|
||||
(cols n)
|
||||
(let
|
||||
((dom (range-1-to-n n)))
|
||||
(mk-conj
|
||||
(ino-each cols dom)
|
||||
(all-distincto cols)
|
||||
(all-cells-safe cols 1)))))
|
||||
361
lib/minikanren/relations.sx
Normal file
361
lib/minikanren/relations.sx
Normal file
@@ -0,0 +1,361 @@
|
||||
;; lib/minikanren/relations.sx — Phase 4 standard relations.
|
||||
;;
|
||||
;; Programs use native SX lists as data. Relations decompose lists via the
|
||||
;; tagged cons-cell shape `(:cons h t)` because SX has no improper pairs;
|
||||
;; the unifier treats `(:cons h t)` and the native list `(h . t)` as
|
||||
;; equivalent, and `mk-walk*` flattens cons cells back to flat lists for
|
||||
;; reification.
|
||||
|
||||
;; --- pair / list shape relations ---
|
||||
|
||||
(define nullo (fn (l) (== l (list))))
|
||||
|
||||
(define pairo (fn (p) (fresh (a d) (== p (mk-cons a d)))))
|
||||
|
||||
(define caro (fn (p a) (fresh (d) (== p (mk-cons a d)))))
|
||||
|
||||
(define cdro (fn (p d) (fresh (a) (== p (mk-cons a d)))))
|
||||
|
||||
(define conso (fn (a d p) (== p (mk-cons a d))))
|
||||
|
||||
(define firsto caro)
|
||||
(define resto cdro)
|
||||
|
||||
(define
|
||||
listo
|
||||
(fn (l) (conde ((nullo l)) ((fresh (a d) (conso a d l) (listo d))))))
|
||||
|
||||
;; --- appendo: the canary ---
|
||||
;;
|
||||
;; (appendo l s ls) — `ls` is the concatenation of `l` and `s`.
|
||||
;; Runs forwards (l, s known → ls), backwards (ls known → all (l, s) pairs),
|
||||
;; and bidirectionally (mix of bound + unbound).
|
||||
|
||||
(define
|
||||
appendo
|
||||
(fn
|
||||
(l s ls)
|
||||
(conde
|
||||
((nullo l) (== s ls))
|
||||
((fresh (a d res) (conso a d l) (conso a res ls) (appendo d s res))))))
|
||||
|
||||
;; --- membero ---
|
||||
;; (membero x l) — x appears (at least once) in l.
|
||||
|
||||
(define
|
||||
appendo3
|
||||
(fn
|
||||
(l1 l2 l3 result)
|
||||
(fresh (l12) (appendo l1 l2 l12) (appendo l12 l3 result))))
|
||||
|
||||
(define
|
||||
partitiono
|
||||
(fn
|
||||
(pred l yes no)
|
||||
(conde
|
||||
((nullo l) (nullo yes) (nullo no))
|
||||
((fresh (a d y-rest n-rest) (conso a d l) (conde ((pred a) (conso a y-rest yes) (== no n-rest) (partitiono pred d y-rest n-rest)) ((nafc (pred a)) (== yes y-rest) (conso a n-rest no) (partitiono pred d y-rest n-rest))))))))
|
||||
|
||||
(define
|
||||
foldr-o
|
||||
(fn
|
||||
(rel l acc result)
|
||||
(conde
|
||||
((nullo l) (== result acc))
|
||||
((fresh (a d r-rest) (conso a d l) (foldr-o rel d acc r-rest) (rel a r-rest result))))))
|
||||
|
||||
(define
|
||||
foldl-o
|
||||
(fn
|
||||
(rel l acc result)
|
||||
(conde
|
||||
((nullo l) (== result acc))
|
||||
((fresh (a d new-acc) (conso a d l) (rel acc a new-acc) (foldl-o rel d new-acc result))))))
|
||||
|
||||
(define
|
||||
flat-mapo
|
||||
(fn
|
||||
(rel l result)
|
||||
(conde
|
||||
((nullo l) (nullo result))
|
||||
((fresh (a d a-result rest-result) (conso a d l) (rel a a-result) (flat-mapo rel d rest-result) (appendo a-result rest-result result))))))
|
||||
|
||||
(define
|
||||
nub-o
|
||||
(fn
|
||||
(l result)
|
||||
(conde
|
||||
((nullo l) (nullo result))
|
||||
((fresh (a d r-rest) (conso a d l) (conde ((membero a d) (nub-o d result)) ((nafc (membero a d)) (conso a r-rest result) (nub-o d r-rest))))))))
|
||||
|
||||
|
||||
(define
|
||||
take-while-o
|
||||
(fn
|
||||
(pred l result)
|
||||
(conde
|
||||
((nullo l) (nullo result))
|
||||
((fresh (a d r-rest) (conso a d l) (conde ((pred a) (conso a r-rest result) (take-while-o pred d r-rest)) ((nafc (pred a)) (== result (list)))))))))
|
||||
|
||||
(define
|
||||
drop-while-o
|
||||
(fn
|
||||
(pred l result)
|
||||
(conde
|
||||
((nullo l) (nullo result))
|
||||
((fresh (a d) (conso a d l) (conde ((pred a) (drop-while-o pred d result)) ((nafc (pred a)) (== result l))))))))
|
||||
|
||||
(define
|
||||
membero
|
||||
(fn
|
||||
(x l)
|
||||
(conde
|
||||
((fresh (d) (conso x d l)))
|
||||
((fresh (a d) (conso a d l) (membero x d))))))
|
||||
|
||||
(define
|
||||
not-membero
|
||||
(fn
|
||||
(x l)
|
||||
(conde
|
||||
((nullo l))
|
||||
((fresh (a d) (conso a d l) (nafc (== a x)) (not-membero x d))))))
|
||||
|
||||
(define
|
||||
subseto
|
||||
(fn
|
||||
(l1 l2)
|
||||
(conde
|
||||
((nullo l1))
|
||||
((fresh (a d) (conso a d l1) (membero a l2) (subseto d l2))))))
|
||||
|
||||
(define
|
||||
reverseo
|
||||
(fn
|
||||
(l r)
|
||||
(conde
|
||||
((nullo l) (nullo r))
|
||||
((fresh (a d res-rev) (conso a d l) (reverseo d res-rev) (appendo res-rev (list a) r))))))
|
||||
|
||||
(define
|
||||
rev-acco
|
||||
(fn
|
||||
(l acc result)
|
||||
(conde
|
||||
((nullo l) (== result acc))
|
||||
((fresh (a d acc-prime) (conso a d l) (conso a acc acc-prime) (rev-acco d acc-prime result))))))
|
||||
|
||||
(define rev-2o (fn (l result) (rev-acco l (list) result)))
|
||||
|
||||
(define palindromeo (fn (l) (fresh (rev) (reverseo l rev) (== l rev))))
|
||||
|
||||
(define prefixo (fn (p l) (fresh (rest) (appendo p rest l))))
|
||||
|
||||
(define suffixo (fn (s l) (fresh (front) (appendo front s l))))
|
||||
|
||||
(define
|
||||
subo
|
||||
(fn
|
||||
(s l)
|
||||
(fresh
|
||||
(front-and-s back front)
|
||||
(appendo front-and-s back l)
|
||||
(appendo front s front-and-s))))
|
||||
|
||||
(define
|
||||
selecto
|
||||
(fn
|
||||
(x rest l)
|
||||
(conde
|
||||
((conso x rest l))
|
||||
((fresh (a d r) (conso a d l) (conso a r rest) (selecto x r d))))))
|
||||
|
||||
(define
|
||||
lengtho
|
||||
(fn
|
||||
(l n)
|
||||
(conde
|
||||
((nullo l) (== n :z))
|
||||
((fresh (a d n-1) (conso a d l) (== n (list :s n-1)) (lengtho d n-1))))))
|
||||
|
||||
(define
|
||||
inserto
|
||||
(fn
|
||||
(a l p)
|
||||
(conde
|
||||
((conso a l p))
|
||||
((fresh (h t pt) (conso h t l) (conso h pt p) (inserto a t pt))))))
|
||||
|
||||
(define
|
||||
permuteo
|
||||
(fn
|
||||
(l p)
|
||||
(conde
|
||||
((nullo l) (nullo p))
|
||||
((fresh (a d perm-d) (conso a d l) (permuteo d perm-d) (inserto a perm-d p))))))
|
||||
|
||||
(define
|
||||
flatteno
|
||||
(fn
|
||||
(tree flat)
|
||||
(conde
|
||||
((nullo tree) (nullo flat))
|
||||
((pairo tree)
|
||||
(fresh
|
||||
(h t hf tf)
|
||||
(conso h t tree)
|
||||
(flatteno h hf)
|
||||
(flatteno t tf)
|
||||
(appendo hf tf flat)))
|
||||
((nafc (nullo tree)) (nafc (pairo tree)) (== flat (list tree))))))
|
||||
|
||||
(define
|
||||
rembero
|
||||
(fn
|
||||
(x l out)
|
||||
(conde
|
||||
((nullo l) (nullo out))
|
||||
((fresh (a d) (conso a d l) (== a x) (== out d)))
|
||||
((fresh (a d res) (conso a d l) (nafc (== a x)) (conso a res out) (rembero x d res))))))
|
||||
|
||||
(define
|
||||
removeo-allo
|
||||
(fn
|
||||
(x l result)
|
||||
(conde
|
||||
((nullo l) (nullo result))
|
||||
((fresh (a d) (conso a d l) (== a x) (removeo-allo x d result)))
|
||||
((fresh (a d r-rest) (conso a d l) (nafc (== a x)) (conso a r-rest result) (removeo-allo x d r-rest))))))
|
||||
|
||||
(define
|
||||
assoco
|
||||
(fn
|
||||
(key pairs val)
|
||||
(fresh
|
||||
(rest)
|
||||
(conde
|
||||
((conso (list key val) rest pairs))
|
||||
((fresh (other) (conso other rest pairs) (assoco key rest val)))))))
|
||||
|
||||
(define
|
||||
nth-o
|
||||
(fn
|
||||
(n l elem)
|
||||
(conde
|
||||
((== n :z) (fresh (d) (conso elem d l)))
|
||||
((fresh (n-1 a d) (== n (list :s n-1)) (conso a d l) (nth-o n-1 d elem))))))
|
||||
|
||||
(define
|
||||
samelengtho
|
||||
(fn
|
||||
(l1 l2)
|
||||
(conde
|
||||
((nullo l1) (nullo l2))
|
||||
((fresh (a d a-prime d-prime) (conso a d l1) (conso a-prime d-prime l2) (samelengtho d d-prime))))))
|
||||
|
||||
(define
|
||||
mapo
|
||||
(fn
|
||||
(rel l1 l2)
|
||||
(conde
|
||||
((nullo l1) (nullo l2))
|
||||
((fresh (a d a-prime d-prime) (conso a d l1) (conso a-prime d-prime l2) (rel a a-prime) (mapo rel d d-prime))))))
|
||||
|
||||
(define
|
||||
iterate-no
|
||||
(fn
|
||||
(rel n x result)
|
||||
(conde
|
||||
((== n :z) (== result x))
|
||||
((fresh (n-1 mid) (== n (list :s n-1)) (rel x mid) (iterate-no rel n-1 mid result))))))
|
||||
|
||||
(define
|
||||
pairlisto
|
||||
(fn
|
||||
(l1 l2 pairs)
|
||||
(conde
|
||||
((nullo l1) (nullo l2) (nullo pairs))
|
||||
((fresh (a1 d1 a2 d2 d-pairs) (conso a1 d1 l1) (conso a2 d2 l2) (conso (list a1 a2) d-pairs pairs) (pairlisto d1 d2 d-pairs))))))
|
||||
|
||||
(define
|
||||
zip-with-o
|
||||
(fn
|
||||
(rel l1 l2 result)
|
||||
(conde
|
||||
((nullo l1) (nullo l2) (nullo result))
|
||||
((fresh (a1 d1 a2 d2 a-result d-result) (conso a1 d1 l1) (conso a2 d2 l2) (rel a1 a2 a-result) (conso a-result d-result result) (zip-with-o rel d1 d2 d-result))))))
|
||||
|
||||
(define
|
||||
swap-firsto
|
||||
(fn
|
||||
(l result)
|
||||
(fresh
|
||||
(a b rest mid-l mid-r)
|
||||
(conso a mid-l l)
|
||||
(conso b rest mid-l)
|
||||
(conso b mid-r result)
|
||||
(conso a rest mid-r))))
|
||||
|
||||
(define
|
||||
everyo
|
||||
(fn
|
||||
(rel l)
|
||||
(conde
|
||||
((nullo l))
|
||||
((fresh (a d) (conso a d l) (rel a) (everyo rel d))))))
|
||||
|
||||
(define
|
||||
someo
|
||||
(fn
|
||||
(rel l)
|
||||
(conde
|
||||
((fresh (a d) (conso a d l) (rel a)))
|
||||
((fresh (a d) (conso a d l) (someo rel d))))))
|
||||
|
||||
(define
|
||||
lasto
|
||||
(fn
|
||||
(l x)
|
||||
(conde
|
||||
((conso x (list) l))
|
||||
((fresh (a d) (conso a d l) (lasto d x))))))
|
||||
|
||||
(define
|
||||
init-o
|
||||
(fn
|
||||
(l init)
|
||||
(conde
|
||||
((fresh (x) (conso x (list) l) (== init (list))))
|
||||
((fresh (a d d-init) (conso a d l) (conso a d-init init) (init-o d d-init))))))
|
||||
|
||||
(define
|
||||
tako
|
||||
(fn
|
||||
(n l prefix)
|
||||
(conde
|
||||
((== n :z) (== prefix (list)))
|
||||
((fresh (n-1 a d p-rest) (== n (list :s n-1)) (conso a d l) (conso a p-rest prefix) (tako n-1 d p-rest))))))
|
||||
|
||||
(define
|
||||
dropo
|
||||
(fn
|
||||
(n l suffix)
|
||||
(conde
|
||||
((== n :z) (== suffix l))
|
||||
((fresh (n-1 a d) (== n (list :s n-1)) (conso a d l) (dropo n-1 d suffix))))))
|
||||
|
||||
(define
|
||||
repeato
|
||||
(fn
|
||||
(x n result)
|
||||
(conde
|
||||
((== n :z) (== result (list)))
|
||||
((fresh (n-1 r-rest) (== n (list :s n-1)) (conso x r-rest result) (repeato x n-1 r-rest))))))
|
||||
|
||||
(define
|
||||
concato
|
||||
(fn
|
||||
(lists result)
|
||||
(conde
|
||||
((nullo lists) (nullo result))
|
||||
((fresh (h t r-rest) (conso h t lists) (appendo h r-rest result) (concato t r-rest))))))
|
||||
56
lib/minikanren/run.sx
Normal file
56
lib/minikanren/run.sx
Normal file
@@ -0,0 +1,56 @@
|
||||
;; lib/minikanren/run.sx — Phase 3: drive a goal + reify the query var.
|
||||
;;
|
||||
;; reify-name N — make the canonical "_.N" reified symbol.
|
||||
;; reify-s term rs — walk term in rs, add a mapping from each fresh
|
||||
;; unbound var to its _.N name (left-to-right order).
|
||||
;; reify q s — walk* q in s, build reify-s, walk* again to
|
||||
;; substitute reified names in.
|
||||
;; run-n n q-name g... — defmacro: bind q-name to a fresh var, conj goals,
|
||||
;; take ≤ n answers from the stream, reify each
|
||||
;; through q-name. n = -1 takes all (used by run*).
|
||||
;; run* — defmacro: (run* q g...) ≡ (run-n -1 q g...)
|
||||
;; run — defmacro: (run n q g...) ≡ (run-n n q g...)
|
||||
;; The two-segment form is the standard TRS API.
|
||||
|
||||
(define reify-name (fn (n) (make-symbol (str "_." n))))
|
||||
|
||||
(define
|
||||
reify-s
|
||||
(fn
|
||||
(term rs)
|
||||
(let
|
||||
((w (mk-walk term rs)))
|
||||
(cond
|
||||
((is-var? w) (extend (var-name w) (reify-name (len rs)) rs))
|
||||
((mk-list-pair? w) (reduce (fn (acc a) (reify-s a acc)) rs w))
|
||||
(:else rs)))))
|
||||
|
||||
(define
|
||||
reify
|
||||
(fn
|
||||
(term s)
|
||||
(let
|
||||
((w (mk-walk* term s)))
|
||||
(let ((rs (reify-s w (empty-subst)))) (mk-walk* w rs)))))
|
||||
|
||||
(defmacro
|
||||
run-n
|
||||
(n q-name &rest goals)
|
||||
(quasiquote
|
||||
(let
|
||||
(((unquote q-name) (make-var)))
|
||||
(map
|
||||
(fn (s) (reify (unquote q-name) s))
|
||||
(stream-take
|
||||
(unquote n)
|
||||
((mk-conj (splice-unquote goals)) empty-s))))))
|
||||
|
||||
(defmacro
|
||||
run*
|
||||
(q-name &rest goals)
|
||||
(quasiquote (run-n -1 (unquote q-name) (splice-unquote goals))))
|
||||
|
||||
(defmacro
|
||||
run
|
||||
(n q-name &rest goals)
|
||||
(quasiquote (run-n (unquote n) (unquote q-name) (splice-unquote goals))))
|
||||
66
lib/minikanren/stream.sx
Normal file
66
lib/minikanren/stream.sx
Normal file
@@ -0,0 +1,66 @@
|
||||
;; lib/minikanren/stream.sx — Phase 2 piece A: lazy streams of substitutions.
|
||||
;;
|
||||
;; SX has no improper pairs (cons requires a list cdr), so we use a
|
||||
;; tagged stream-cell shape for mature stream elements:
|
||||
;;
|
||||
;; stream ::= mzero empty (the SX empty list)
|
||||
;; | (:s HEAD TAIL) mature cell, TAIL is a stream
|
||||
;; | thunk (fn () ...) → stream when forced
|
||||
;;
|
||||
;; HEAD is a substitution dict. TAIL is again a stream (possibly a thunk),
|
||||
;; which is what gives us laziness — mk-mplus can return a mature head with
|
||||
;; a thunk in the tail, deferring the rest of the search.
|
||||
|
||||
(define mzero (list))
|
||||
|
||||
(define s-cons (fn (h t) (list :s h t)))
|
||||
|
||||
(define
|
||||
s-cons?
|
||||
(fn (s) (and (list? s) (not (empty? s)) (= (first s) :s))))
|
||||
|
||||
(define s-car (fn (s) (nth s 1)))
|
||||
(define s-cdr (fn (s) (nth s 2)))
|
||||
|
||||
(define unit (fn (s) (s-cons s mzero)))
|
||||
|
||||
(define stream-pause? (fn (s) (and (not (list? s)) (callable? s))))
|
||||
|
||||
;; mk-mplus — interleave two streams. If s1 is paused we suspend and
|
||||
;; swap (Reasoned Schemer "interleave"); otherwise mature-cons head with
|
||||
;; mk-mplus of the rest.
|
||||
(define
|
||||
mk-mplus
|
||||
(fn
|
||||
(s1 s2)
|
||||
(cond
|
||||
((empty? s1) s2)
|
||||
((stream-pause? s1) (fn () (mk-mplus s2 (s1))))
|
||||
(:else (s-cons (s-car s1) (mk-mplus (s-cdr s1) s2))))))
|
||||
|
||||
;; mk-bind — apply goal g to every substitution in stream s, mk-mplus-ing.
|
||||
(define
|
||||
mk-bind
|
||||
(fn
|
||||
(s g)
|
||||
(cond
|
||||
((empty? s) mzero)
|
||||
((stream-pause? s) (fn () (mk-bind (s) g)))
|
||||
(:else (mk-mplus (g (s-car s)) (mk-bind (s-cdr s) g))))))
|
||||
|
||||
;; stream-take — force up to n results out of a (possibly lazy) stream
|
||||
;; into a flat SX list of substitutions. n = -1 means take all.
|
||||
(define
|
||||
stream-take
|
||||
(fn
|
||||
(n s)
|
||||
(cond
|
||||
((= n 0) (list))
|
||||
((empty? s) (list))
|
||||
((stream-pause? s) (stream-take n (s)))
|
||||
(:else
|
||||
(cons
|
||||
(s-car s)
|
||||
(stream-take
|
||||
(if (= n -1) -1 (- n 1))
|
||||
(s-cdr s)))))))
|
||||
94
lib/minikanren/tabling-slg.sx
Normal file
94
lib/minikanren/tabling-slg.sx
Normal file
@@ -0,0 +1,94 @@
|
||||
;; lib/minikanren/tabling-slg.sx — Phase 7 piece A: SLG-style tabling.
|
||||
;;
|
||||
;; Naive memoization (table-1/2/3 in tabling.sx) drains the body's
|
||||
;; answer stream eagerly, then caches. Recursive tabled calls with the
|
||||
;; SAME ground key see an empty cache (the in-progress entry doesn't
|
||||
;; exist), so they recurse and the host overflows on cyclic relations.
|
||||
;;
|
||||
;; This module ships the in-progress-sentinel piece of SLG resolution:
|
||||
;; before evaluating the body, mark the cache entry as :in-progress;
|
||||
;; any recursive call to the same key sees the sentinel and returns
|
||||
;; mzero (no answers yet). Outer recursion thus terminates on cycles.
|
||||
;; Limitation: a single pass — answers found by cycle-dependent
|
||||
;; recursive calls are NOT discovered. Full SLG with fixed-point
|
||||
;; iteration (re-running until no new answers) is left for follow-up.
|
||||
|
||||
(define
|
||||
table-2-slg-iter
|
||||
(fn
|
||||
(rel-fn input output s key prev-vals)
|
||||
(begin
|
||||
(mk-tab-store! key prev-vals)
|
||||
(let
|
||||
((all-substs (stream-take -1 ((rel-fn input output) s))))
|
||||
(let
|
||||
((vals (map (fn (s2) (mk-walk* output s2)) all-substs)))
|
||||
(cond
|
||||
((= (len vals) (len prev-vals))
|
||||
(begin
|
||||
(mk-tab-store! key vals)
|
||||
(mk-tab-replay-vals vals output s)))
|
||||
(:else (table-2-slg-iter rel-fn input output s key vals))))))))
|
||||
|
||||
(define
|
||||
table-2-slg
|
||||
(fn
|
||||
(name rel-fn)
|
||||
(fn
|
||||
(input output)
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((winput (mk-walk* input s)))
|
||||
(cond
|
||||
((mk-tab-ground-term? winput)
|
||||
(let
|
||||
((key (str name "/slg/" winput)))
|
||||
(let
|
||||
((cached (mk-tab-lookup key)))
|
||||
(cond
|
||||
((not (= cached :miss))
|
||||
(mk-tab-replay-vals cached output s))
|
||||
(:else
|
||||
(table-2-slg-iter rel-fn input output s key (list)))))))
|
||||
(:else ((rel-fn input output) s))))))))
|
||||
|
||||
(define
|
||||
table-3-slg-iter
|
||||
(fn
|
||||
(rel-fn i1 i2 output s key prev-vals)
|
||||
(begin
|
||||
(mk-tab-store! key prev-vals)
|
||||
(let
|
||||
((all-substs (stream-take -1 ((rel-fn i1 i2 output) s))))
|
||||
(let
|
||||
((vals (map (fn (s2) (mk-walk* output s2)) all-substs)))
|
||||
(cond
|
||||
((= (len vals) (len prev-vals))
|
||||
(begin
|
||||
(mk-tab-store! key vals)
|
||||
(mk-tab-replay-vals vals output s)))
|
||||
(:else (table-3-slg-iter rel-fn i1 i2 output s key vals))))))))
|
||||
|
||||
(define
|
||||
table-3-slg
|
||||
(fn
|
||||
(name rel-fn)
|
||||
(fn
|
||||
(i1 i2 output)
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((wi1 (mk-walk* i1 s)) (wi2 (mk-walk* i2 s)))
|
||||
(cond
|
||||
((and (mk-tab-ground-term? wi1) (mk-tab-ground-term? wi2))
|
||||
(let
|
||||
((key (str name "/slg3/" wi1 "/" wi2)))
|
||||
(let
|
||||
((cached (mk-tab-lookup key)))
|
||||
(cond
|
||||
((not (= cached :miss))
|
||||
(mk-tab-replay-vals cached output s))
|
||||
(:else
|
||||
(table-3-slg-iter rel-fn i1 i2 output s key (list)))))))
|
||||
(:else ((rel-fn i1 i2 output) s))))))))
|
||||
157
lib/minikanren/tabling.sx
Normal file
157
lib/minikanren/tabling.sx
Normal file
@@ -0,0 +1,157 @@
|
||||
;; lib/minikanren/tabling.sx — Phase 7 piece A: naive memoization.
|
||||
;;
|
||||
;; A `table-2` wrapper for 2-arg relations (input, output). Caches by
|
||||
;; ground input (walked at call time). On hit, replays the cached output
|
||||
;; values; on miss, runs the relation, collects all output values from
|
||||
;; the answer stream, stores, then replays.
|
||||
;;
|
||||
;; Limitations of naive memoization (vs proper SLG / producer-consumer
|
||||
;; tabling):
|
||||
;; - Each call must terminate before its result enters the cache —
|
||||
;; so cyclic recursive calls with the SAME ground input would still
|
||||
;; diverge (not addressed here).
|
||||
;; - Caching by full ground walk only; partially-ground args fall
|
||||
;; through to the underlying relation.
|
||||
;;
|
||||
;; Despite the limitations, naive memoization is enough for the
|
||||
;; canonical demo: Fibonacci goes from exponential to linear because
|
||||
;; each fib(k) result is computed at most once.
|
||||
;;
|
||||
;; Cache lifetime: a single global mk-tab-cache. Use `(mk-tab-clear!)`
|
||||
;; between independent queries.
|
||||
|
||||
(define mk-tab-cache {})
|
||||
|
||||
(define mk-tab-clear! (fn () (set! mk-tab-cache {})))
|
||||
|
||||
(define
|
||||
mk-tab-lookup
|
||||
(fn
|
||||
(key)
|
||||
(cond
|
||||
((has-key? mk-tab-cache key) (get mk-tab-cache key))
|
||||
(:else :miss))))
|
||||
|
||||
(define
|
||||
mk-tab-store!
|
||||
(fn (key vals) (set! mk-tab-cache (assoc mk-tab-cache key vals))))
|
||||
|
||||
(define
|
||||
mk-tab-ground-term?
|
||||
(fn
|
||||
(t)
|
||||
(cond
|
||||
((is-var? t) false)
|
||||
((mk-cons-cell? t)
|
||||
(and
|
||||
(mk-tab-ground-term? (mk-cons-head t))
|
||||
(mk-tab-ground-term? (mk-cons-tail t))))
|
||||
((mk-list-pair? t) (every? mk-tab-ground-term? t))
|
||||
(:else true))))
|
||||
|
||||
(define
|
||||
mk-tab-replay-vals
|
||||
(fn
|
||||
(vals output s)
|
||||
(cond
|
||||
((empty? vals) mzero)
|
||||
(:else
|
||||
(let
|
||||
((sp (mk-unify output (first vals) s)))
|
||||
(let
|
||||
((this-stream (cond ((= sp nil) mzero) (:else (unit sp)))))
|
||||
(mk-mplus this-stream (mk-tab-replay-vals (rest vals) output s))))))))
|
||||
|
||||
(define
|
||||
table-2
|
||||
(fn
|
||||
(name rel-fn)
|
||||
(fn
|
||||
(input output)
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((winput (mk-walk* input s)))
|
||||
(cond
|
||||
((mk-tab-ground-term? winput)
|
||||
(let
|
||||
((key (str name "@" winput)))
|
||||
(let
|
||||
((cached (mk-tab-lookup key)))
|
||||
(cond
|
||||
((= cached :miss)
|
||||
(let
|
||||
((all-substs (stream-take -1 ((rel-fn input output) s))))
|
||||
(let
|
||||
((vals (map (fn (s2) (mk-walk* output s2)) all-substs)))
|
||||
(begin
|
||||
(mk-tab-store! key vals)
|
||||
(mk-tab-replay-vals vals output s)))))
|
||||
(:else (mk-tab-replay-vals cached output s))))))
|
||||
(:else ((rel-fn input output) s))))))))
|
||||
|
||||
;; --- table-1: 1-arg relation (one input, no output to cache) ---
|
||||
;; The relation is a predicate `(p input)` that succeeds or fails.
|
||||
;; Cache stores either :ok or :no.
|
||||
|
||||
(define
|
||||
table-1
|
||||
(fn
|
||||
(name rel-fn)
|
||||
(fn
|
||||
(input)
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((winput (mk-walk* input s)))
|
||||
(cond
|
||||
((mk-tab-ground-term? winput)
|
||||
(let
|
||||
((key (str name "@1@" winput)))
|
||||
(let
|
||||
((cached (mk-tab-lookup key)))
|
||||
(cond
|
||||
((= cached :miss)
|
||||
(let
|
||||
((stream ((rel-fn input) s)))
|
||||
(let
|
||||
((peek (stream-take 1 stream)))
|
||||
(cond
|
||||
((empty? peek)
|
||||
(begin (mk-tab-store! key :no) mzero))
|
||||
(:else (begin (mk-tab-store! key :ok) stream))))))
|
||||
((= cached :ok) (unit s))
|
||||
((= cached :no) mzero)
|
||||
(:else mzero)))))
|
||||
(:else ((rel-fn input) s))))))))
|
||||
|
||||
;; --- table-3: 3-arg relation (input1 input2 output) ---
|
||||
;; Cache keyed by (input1, input2). Output values cached as a list.
|
||||
|
||||
(define
|
||||
table-3
|
||||
(fn
|
||||
(name rel-fn)
|
||||
(fn
|
||||
(i1 i2 output)
|
||||
(fn
|
||||
(s)
|
||||
(let
|
||||
((wi1 (mk-walk* i1 s)) (wi2 (mk-walk* i2 s)))
|
||||
(cond
|
||||
((and (mk-tab-ground-term? wi1) (mk-tab-ground-term? wi2))
|
||||
(let
|
||||
((key (str name "@3@" wi1 "/" wi2)))
|
||||
(let
|
||||
((cached (mk-tab-lookup key)))
|
||||
(cond
|
||||
((= cached :miss)
|
||||
(let
|
||||
((all-substs (stream-take -1 ((rel-fn i1 i2 output) s))))
|
||||
(let
|
||||
((vals (map (fn (s2) (mk-walk* output s2)) all-substs)))
|
||||
(begin
|
||||
(mk-tab-store! key vals)
|
||||
(mk-tab-replay-vals vals output s)))))
|
||||
(:else (mk-tab-replay-vals cached output s))))))
|
||||
(:else ((rel-fn i1 i2 output) s))))))))
|
||||
49
lib/minikanren/tests/appendo3.sx
Normal file
49
lib/minikanren/tests/appendo3.sx
Normal file
@@ -0,0 +1,49 @@
|
||||
;; lib/minikanren/tests/appendo3.sx — 3-list append.
|
||||
|
||||
(mk-test
|
||||
"appendo3-forward"
|
||||
(run*
|
||||
q
|
||||
(appendo3
|
||||
(list 1 2)
|
||||
(list 3 4)
|
||||
(list 5 6)
|
||||
q))
|
||||
(list
|
||||
(list 1 2 3 4 5 6)))
|
||||
|
||||
(mk-test
|
||||
"appendo3-empty-everything"
|
||||
(run* q (appendo3 (list) (list) (list) q))
|
||||
(list (list)))
|
||||
|
||||
(mk-test
|
||||
"appendo3-recover-middle"
|
||||
(run*
|
||||
q
|
||||
(appendo3
|
||||
(list 1 2)
|
||||
q
|
||||
(list 5 6)
|
||||
(list 1 2 3 4 5 6)))
|
||||
(list (list 3 4)))
|
||||
|
||||
(mk-test
|
||||
"appendo3-empty-middle"
|
||||
(run*
|
||||
q
|
||||
(appendo3
|
||||
(list 1 2)
|
||||
(list)
|
||||
(list 3 4)
|
||||
q))
|
||||
(list (list 1 2 3 4)))
|
||||
|
||||
(mk-test
|
||||
"appendo3-empty-first-and-last"
|
||||
(run*
|
||||
q
|
||||
(appendo3 (list) (list 1 2 3) (list) q))
|
||||
(list (list 1 2 3)))
|
||||
|
||||
(mk-tests-run!)
|
||||
33
lib/minikanren/tests/arith-prog.sx
Normal file
33
lib/minikanren/tests/arith-prog.sx
Normal file
@@ -0,0 +1,33 @@
|
||||
;; lib/minikanren/tests/arith-prog.sx — arithmetic progression generation.
|
||||
|
||||
(mk-test
|
||||
"arith-progo-zero-len"
|
||||
(run* q (arith-progo 5 1 0 q))
|
||||
(list (list)))
|
||||
|
||||
(mk-test
|
||||
"arith-progo-1-to-5"
|
||||
(run* q (arith-progo 1 1 5 q))
|
||||
(list (list 1 2 3 4 5)))
|
||||
|
||||
(mk-test
|
||||
"arith-progo-evens-from-0"
|
||||
(run* q (arith-progo 0 2 5 q))
|
||||
(list (list 0 2 4 6 8)))
|
||||
|
||||
(mk-test
|
||||
"arith-progo-descending"
|
||||
(run* q (arith-progo 10 -1 4 q))
|
||||
(list (list 10 9 8 7)))
|
||||
|
||||
(mk-test
|
||||
"arith-progo-zero-step"
|
||||
(run* q (arith-progo 7 0 3 q))
|
||||
(list (list 7 7 7)))
|
||||
|
||||
(mk-test
|
||||
"arith-progo-negative-start"
|
||||
(run* q (arith-progo -3 2 4 q))
|
||||
(list (list -3 -1 1 3)))
|
||||
|
||||
(mk-tests-run!)
|
||||
54
lib/minikanren/tests/btree-walko.sx
Normal file
54
lib/minikanren/tests/btree-walko.sx
Normal file
@@ -0,0 +1,54 @@
|
||||
;; lib/minikanren/tests/btree-walko.sx — walk a leaves-of-binary-tree relation
|
||||
;; using matche dispatch on (:leaf v) and (:node left right) patterns.
|
||||
|
||||
(define
|
||||
btree-walko
|
||||
(fn
|
||||
(tree v)
|
||||
(matche
|
||||
tree
|
||||
((:leaf x) (== v x))
|
||||
((:node l r) (conde ((btree-walko l v)) ((btree-walko r v)))))))
|
||||
|
||||
;; A small test tree: ((1 2) (3 (4 5))).
|
||||
(define
|
||||
test-btree
|
||||
(list
|
||||
:node (list :node (list :leaf 1) (list :leaf 2))
|
||||
(list
|
||||
:node (list :leaf 3)
|
||||
(list :node (list :leaf 4) (list :leaf 5)))))
|
||||
|
||||
(mk-test
|
||||
"btree-walko-enumerates-all-leaves"
|
||||
(let
|
||||
((leaves (run* q (btree-walko test-btree q))))
|
||||
(and
|
||||
(= (len leaves) 5)
|
||||
(and
|
||||
(some (fn (l) (= l 1)) leaves)
|
||||
(and
|
||||
(some (fn (l) (= l 2)) leaves)
|
||||
(and
|
||||
(some (fn (l) (= l 3)) leaves)
|
||||
(and
|
||||
(some (fn (l) (= l 4)) leaves)
|
||||
(some (fn (l) (= l 5)) leaves)))))))
|
||||
true)
|
||||
|
||||
(mk-test
|
||||
"btree-walko-find-3-membership"
|
||||
(run 1 q (btree-walko test-btree 3))
|
||||
(list (make-symbol "_.0")))
|
||||
|
||||
(mk-test
|
||||
"btree-walko-find-99-not-present"
|
||||
(run* q (btree-walko test-btree 99))
|
||||
(list))
|
||||
|
||||
(mk-test
|
||||
"btree-walko-leaf-only"
|
||||
(run* q (btree-walko (list :leaf 42) q))
|
||||
(list 42))
|
||||
|
||||
(mk-tests-run!)
|
||||
87
lib/minikanren/tests/classics.sx
Normal file
87
lib/minikanren/tests/classics.sx
Normal file
@@ -0,0 +1,87 @@
|
||||
;; lib/minikanren/tests/classics.sx — small classic-style puzzles that
|
||||
;; exercise the full system end to end (relations + conde + matche +
|
||||
;; fresh + run*). Each test is a self-contained miniKanren program.
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Pet puzzle (3 friends, 3 pets, 1-each).
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(mk-test
|
||||
"classics-pet-puzzle"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(a b c)
|
||||
(== q (list a b c))
|
||||
(permuteo (list :dog :cat :fish) (list a b c))
|
||||
(== b :fish)
|
||||
(conde ((== a :cat)) ((== a :fish)))))
|
||||
(list (list :cat :fish :dog)))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Family-relations puzzle (uses membero on a fact list).
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(define
|
||||
parent-facts
|
||||
(list
|
||||
(list "alice" "bob")
|
||||
(list "alice" "carol")
|
||||
(list "bob" "dave")
|
||||
(list "carol" "eve")
|
||||
(list "dave" "frank")))
|
||||
|
||||
(define parento (fn (x y) (membero (list x y) parent-facts)))
|
||||
|
||||
(define grandparento (fn (x z) (fresh (y) (parento x y) (parento y z))))
|
||||
|
||||
(mk-test
|
||||
"classics-grandparents-of-frank"
|
||||
(run* q (grandparento q "frank"))
|
||||
(list "bob"))
|
||||
|
||||
(mk-test
|
||||
"classics-grandchildren-of-alice"
|
||||
(run* q (grandparento "alice" q))
|
||||
(list "dave" "eve"))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Symbolic differentiation, matche-driven.
|
||||
;; Variable :x: d/dx x = 1
|
||||
;; Sum (:+ a b): d/dx (a+b) = (da + db)
|
||||
;; Product (:* a b): d/dx (a*b) = (da*b + a*db)
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(define
|
||||
diffo
|
||||
(fn
|
||||
(expr var d)
|
||||
(matche
|
||||
expr
|
||||
(:x (== d 1))
|
||||
((:+ a b)
|
||||
(fresh
|
||||
(da db)
|
||||
(== d (list :+ da db))
|
||||
(diffo a var da)
|
||||
(diffo b var db)))
|
||||
((:* a b)
|
||||
(fresh
|
||||
(da db)
|
||||
(== d (list :+ (list :* da b) (list :* a db)))
|
||||
(diffo a var da)
|
||||
(diffo b var db))))))
|
||||
|
||||
(mk-test "classics-diff-of-x" (run* q (diffo :x :x q)) (list 1))
|
||||
|
||||
(mk-test
|
||||
"classics-diff-of-x-plus-x"
|
||||
(run* q (diffo (list :+ :x :x) :x q))
|
||||
(list (list :+ 1 1)))
|
||||
|
||||
(mk-test
|
||||
"classics-diff-of-x-times-x"
|
||||
(run* q (diffo (list :* :x :x) :x q))
|
||||
(list (list :+ (list :* 1 :x) (list :* :x 1))))
|
||||
|
||||
(mk-tests-run!)
|
||||
316
lib/minikanren/tests/clpfd-bounds.sx
Normal file
316
lib/minikanren/tests/clpfd-bounds.sx
Normal file
@@ -0,0 +1,316 @@
|
||||
;; lib/minikanren/tests/clpfd-bounds.sx — Phase 6 piece B: bounds-consistency
|
||||
;; for fd-plus and fd-times in the partial- and all-domain cases.
|
||||
;;
|
||||
;; We probe domains directly (peek at the FD store) before any labelling
|
||||
;; happens. This isolates the propagator's narrowing behaviour from the
|
||||
;; search engine.
|
||||
|
||||
(define
|
||||
probe-dom
|
||||
(fn
|
||||
(goal var-key)
|
||||
(let
|
||||
((s (first (stream-take 1 (goal empty-s)))))
|
||||
(cond ((= s nil) :no-subst) (:else (fd-domain-of s var-key))))))
|
||||
|
||||
;; --- fd-plus partial-domain narrowing ---
|
||||
|
||||
(mk-test
|
||||
"fd-plus-vvn-narrows-x"
|
||||
(let
|
||||
((x (mk-var "x")) (y (mk-var "y")))
|
||||
(probe-dom
|
||||
(mk-conj
|
||||
(fd-in
|
||||
x
|
||||
(list
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10))
|
||||
(fd-in y (list 1 2 3))
|
||||
(fd-plus x y 10))
|
||||
"x"))
|
||||
(list 7 8 9))
|
||||
|
||||
(mk-test
|
||||
"fd-plus-vvn-narrows-y"
|
||||
(let
|
||||
((x (mk-var "x")) (y (mk-var "y")))
|
||||
(probe-dom
|
||||
(mk-conj
|
||||
(fd-in
|
||||
x
|
||||
(list
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10))
|
||||
(fd-in y (list 1 2 3))
|
||||
(fd-plus x y 10))
|
||||
"y"))
|
||||
(list 1 2 3))
|
||||
|
||||
(mk-test
|
||||
"fd-plus-nvv-narrows"
|
||||
(let
|
||||
((y (mk-var "y")) (z (mk-var "z")))
|
||||
(probe-dom
|
||||
(mk-conj
|
||||
(fd-in y (list 1 2 3))
|
||||
(fd-in
|
||||
z
|
||||
(list
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
11
|
||||
12
|
||||
13
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
18
|
||||
19
|
||||
20))
|
||||
(fd-plus 5 y z))
|
||||
"z"))
|
||||
(list 6 7 8))
|
||||
|
||||
(mk-test
|
||||
"fd-plus-vvv-narrows-z"
|
||||
(let
|
||||
((x (mk-var "x")) (y (mk-var "y")) (z (mk-var "z")))
|
||||
(probe-dom
|
||||
(mk-conj
|
||||
(fd-in x (list 1 2 3))
|
||||
(fd-in y (list 1 2 3))
|
||||
(fd-in
|
||||
z
|
||||
(list
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
11
|
||||
12
|
||||
13
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
18
|
||||
19
|
||||
20))
|
||||
(fd-plus x y z))
|
||||
"z"))
|
||||
(list 2 3 4 5 6))
|
||||
|
||||
(mk-test
|
||||
"fd-plus-vvv-narrows-x"
|
||||
(let
|
||||
((x (mk-var "x")) (y (mk-var "y")) (z (mk-var "z")))
|
||||
(probe-dom
|
||||
(mk-conj
|
||||
(fd-in
|
||||
x
|
||||
(list
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10))
|
||||
(fd-in y (list 1 2 3))
|
||||
(fd-in z (list 5 6 7))
|
||||
(fd-plus x y z))
|
||||
"x"))
|
||||
(list 2 3 4 5 6))
|
||||
|
||||
;; --- fd-times partial-domain narrowing (positive domains) ---
|
||||
|
||||
(mk-test
|
||||
"fd-times-vvn-narrows"
|
||||
(let
|
||||
((x (mk-var "x")) (y (mk-var "y")))
|
||||
(probe-dom
|
||||
(mk-conj
|
||||
(fd-in
|
||||
x
|
||||
(list
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6))
|
||||
(fd-in
|
||||
y
|
||||
(list
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6))
|
||||
(fd-times x y 12))
|
||||
"x"))
|
||||
(list 2 3 4 5 6))
|
||||
|
||||
(mk-test
|
||||
"fd-times-nvv-narrows"
|
||||
(let
|
||||
((y (mk-var "y")) (z (mk-var "z")))
|
||||
(probe-dom
|
||||
(mk-conj
|
||||
(fd-in y (list 1 2 3 4))
|
||||
(fd-in
|
||||
z
|
||||
(list
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
11
|
||||
12
|
||||
13
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
18
|
||||
19
|
||||
20))
|
||||
(fd-times 3 y z))
|
||||
"z"))
|
||||
(list
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
11
|
||||
12))
|
||||
|
||||
(mk-test
|
||||
"fd-times-vvv-narrows"
|
||||
(let
|
||||
((x (mk-var "x")) (y (mk-var "y")) (z (mk-var "z")))
|
||||
(probe-dom
|
||||
(mk-conj
|
||||
(fd-in x (list 1 2 3))
|
||||
(fd-in y (list 1 2 3))
|
||||
(fd-in
|
||||
z
|
||||
(list
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
11
|
||||
12
|
||||
13
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
18
|
||||
19
|
||||
20))
|
||||
(fd-times x y z))
|
||||
"z"))
|
||||
(list
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9))
|
||||
|
||||
;; --- bounds force impossible branches to fail early ---
|
||||
|
||||
(mk-test
|
||||
"fd-plus-impossible-via-bounds"
|
||||
(let
|
||||
((x (mk-var "x")) (y (mk-var "y")))
|
||||
(probe-dom
|
||||
(mk-conj
|
||||
(fd-in
|
||||
x
|
||||
(list
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10))
|
||||
(fd-in
|
||||
y
|
||||
(list
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10))
|
||||
(fd-plus x y 100))
|
||||
"x"))
|
||||
:no-subst)
|
||||
|
||||
(mk-tests-run!)
|
||||
52
lib/minikanren/tests/clpfd-distinct.sx
Normal file
52
lib/minikanren/tests/clpfd-distinct.sx
Normal file
@@ -0,0 +1,52 @@
|
||||
;; lib/minikanren/tests/clpfd-distinct.sx — fd-distinct (alldifferent).
|
||||
|
||||
(mk-test
|
||||
"fd-distinct-empty"
|
||||
(run* q (fd-distinct (list)))
|
||||
(list (make-symbol "_.0")))
|
||||
|
||||
(mk-test
|
||||
"fd-distinct-singleton"
|
||||
(run* q (fd-distinct (list 5)))
|
||||
(list (make-symbol "_.0")))
|
||||
|
||||
(mk-test
|
||||
"fd-distinct-pair-distinct"
|
||||
(run* q (fd-distinct (list 1 2)))
|
||||
(list (make-symbol "_.0")))
|
||||
|
||||
(mk-test
|
||||
"fd-distinct-pair-equal-fails"
|
||||
(run* q (fd-distinct (list 5 5)))
|
||||
(list))
|
||||
|
||||
(mk-test
|
||||
"fd-distinct-3-perms-of-3"
|
||||
(let
|
||||
((res (run* q (fresh (a b c) (fd-in a (list 1 2 3)) (fd-in b (list 1 2 3)) (fd-in c (list 1 2 3)) (fd-distinct (list a b c)) (fd-label (list a b c)) (== q (list a b c))))))
|
||||
(= (len res) 6))
|
||||
true)
|
||||
|
||||
(mk-test
|
||||
"fd-distinct-4-perms-of-4-count"
|
||||
(let
|
||||
((res (run* q (fresh (a b c d) (fd-in a (list 1 2 3 4)) (fd-in b (list 1 2 3 4)) (fd-in c (list 1 2 3 4)) (fd-in d (list 1 2 3 4)) (fd-distinct (list a b c d)) (fd-label (list a b c d)) (== q (list a b c d))))))
|
||||
(= (len res) 24))
|
||||
true)
|
||||
|
||||
(mk-test
|
||||
"fd-distinct-pigeonhole-fails"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(a b c d)
|
||||
(fd-in a (list 1 2 3))
|
||||
(fd-in b (list 1 2 3))
|
||||
(fd-in c (list 1 2 3))
|
||||
(fd-in d (list 1 2 3))
|
||||
(fd-distinct (list a b c d))
|
||||
(fd-label (list a b c d))
|
||||
(== q (list a b c d))))
|
||||
(list))
|
||||
|
||||
(mk-tests-run!)
|
||||
133
lib/minikanren/tests/clpfd-domains.sx
Normal file
133
lib/minikanren/tests/clpfd-domains.sx
Normal file
@@ -0,0 +1,133 @@
|
||||
;; lib/minikanren/tests/clpfd-domains.sx — Phase 6 piece B: domain primitives.
|
||||
|
||||
;; --- domain construction ---
|
||||
|
||||
(mk-test
|
||||
"fd-dom-from-list-sorts"
|
||||
(fd-dom-from-list
|
||||
(list 3 1 2 1 5))
|
||||
(list 1 2 3 5))
|
||||
|
||||
(mk-test "fd-dom-from-list-empty" (fd-dom-from-list (list)) (list))
|
||||
|
||||
(mk-test
|
||||
"fd-dom-from-list-single"
|
||||
(fd-dom-from-list (list 7))
|
||||
(list 7))
|
||||
|
||||
(mk-test
|
||||
"fd-dom-range-1-5"
|
||||
(fd-dom-range 1 5)
|
||||
(list 1 2 3 4 5))
|
||||
|
||||
(mk-test "fd-dom-range-empty" (fd-dom-range 5 1) (list))
|
||||
|
||||
;; --- predicates ---
|
||||
|
||||
(mk-test "fd-dom-empty-yes" (fd-dom-empty? (list)) true)
|
||||
(mk-test "fd-dom-empty-no" (fd-dom-empty? (list 1)) false)
|
||||
(mk-test "fd-dom-singleton-yes" (fd-dom-singleton? (list 5)) true)
|
||||
(mk-test
|
||||
"fd-dom-singleton-multi"
|
||||
(fd-dom-singleton? (list 1 2))
|
||||
false)
|
||||
(mk-test "fd-dom-singleton-empty" (fd-dom-singleton? (list)) false)
|
||||
|
||||
(mk-test
|
||||
"fd-dom-min"
|
||||
(fd-dom-min (list 3 7 9))
|
||||
3)
|
||||
(mk-test
|
||||
"fd-dom-max"
|
||||
(fd-dom-max (list 3 7 9))
|
||||
9)
|
||||
|
||||
(mk-test
|
||||
"fd-dom-member-yes"
|
||||
(fd-dom-member?
|
||||
3
|
||||
(list 1 2 3 4))
|
||||
true)
|
||||
(mk-test
|
||||
"fd-dom-member-no"
|
||||
(fd-dom-member?
|
||||
9
|
||||
(list 1 2 3 4))
|
||||
false)
|
||||
|
||||
;; --- intersect / without ---
|
||||
|
||||
(mk-test
|
||||
"fd-dom-intersect"
|
||||
(fd-dom-intersect
|
||||
(list 1 2 3 4 5)
|
||||
(list 2 4 6))
|
||||
(list 2 4))
|
||||
|
||||
(mk-test
|
||||
"fd-dom-intersect-disjoint"
|
||||
(fd-dom-intersect
|
||||
(list 1 2 3)
|
||||
(list 4 5 6))
|
||||
(list))
|
||||
|
||||
(mk-test
|
||||
"fd-dom-intersect-empty"
|
||||
(fd-dom-intersect (list) (list 1 2 3))
|
||||
(list))
|
||||
|
||||
(mk-test
|
||||
"fd-dom-intersect-equal"
|
||||
(fd-dom-intersect
|
||||
(list 1 2 3)
|
||||
(list 1 2 3))
|
||||
(list 1 2 3))
|
||||
|
||||
(mk-test
|
||||
"fd-dom-without-mid"
|
||||
(fd-dom-without
|
||||
3
|
||||
(list 1 2 3 4 5))
|
||||
(list 1 2 4 5))
|
||||
|
||||
(mk-test
|
||||
"fd-dom-without-missing"
|
||||
(fd-dom-without 9 (list 1 2 3))
|
||||
(list 1 2 3))
|
||||
|
||||
(mk-test
|
||||
"fd-dom-without-min"
|
||||
(fd-dom-without 1 (list 1 2 3))
|
||||
(list 2 3))
|
||||
|
||||
;; --- store accessors ---
|
||||
|
||||
(mk-test "fd-domain-of-unset" (fd-domain-of {} "x") nil)
|
||||
|
||||
(mk-test
|
||||
"fd-domain-of-set"
|
||||
(let
|
||||
((s (fd-set-domain {} "x" (list 1 2 3))))
|
||||
(fd-domain-of s "x"))
|
||||
(list 1 2 3))
|
||||
|
||||
(mk-test
|
||||
"fd-set-domain-empty-fails"
|
||||
(fd-set-domain {} "x" (list))
|
||||
nil)
|
||||
|
||||
(mk-test
|
||||
"fd-set-domain-overrides"
|
||||
(let
|
||||
((s (fd-set-domain {} "x" (list 1 2 3))))
|
||||
(fd-domain-of (fd-set-domain s "x" (list 5)) "x"))
|
||||
(list 5))
|
||||
|
||||
(mk-test
|
||||
"fd-set-domain-multiple-vars"
|
||||
(let
|
||||
((s (fd-set-domain (fd-set-domain {} "x" (list 1)) "y" (list 2 3))))
|
||||
(list (fd-domain-of s "x") (fd-domain-of s "y")))
|
||||
(list (list 1) (list 2 3)))
|
||||
|
||||
(mk-tests-run!)
|
||||
120
lib/minikanren/tests/clpfd-in-label.sx
Normal file
120
lib/minikanren/tests/clpfd-in-label.sx
Normal file
@@ -0,0 +1,120 @@
|
||||
;; lib/minikanren/tests/clpfd-in-label.sx — fd-in (domain narrowing) + fd-label.
|
||||
|
||||
;; --- fd-in: domain narrowing ---
|
||||
|
||||
(mk-test
|
||||
"fd-in-bare-label"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x)
|
||||
(fd-in x (list 1 2 3 4 5))
|
||||
(fd-label (list x))
|
||||
(== q x)))
|
||||
(list 1 2 3 4 5))
|
||||
|
||||
(mk-test
|
||||
"fd-in-intersection"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x)
|
||||
(fd-in x (list 1 2 3 4 5))
|
||||
(fd-in x (list 3 4 5 6 7))
|
||||
(fd-label (list x))
|
||||
(== q x)))
|
||||
(list 3 4 5))
|
||||
|
||||
(mk-test
|
||||
"fd-in-disjoint-empty"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x)
|
||||
(fd-in x (list 1 2 3))
|
||||
(fd-in x (list 7 8 9))
|
||||
(fd-label (list x))
|
||||
(== q x)))
|
||||
(list))
|
||||
|
||||
(mk-test
|
||||
"fd-in-singleton-domain"
|
||||
(run*
|
||||
q
|
||||
(fresh (x) (fd-in x (list 5)) (fd-label (list x)) (== q x)))
|
||||
(list 5))
|
||||
|
||||
;; --- ground value checks the domain ---
|
||||
|
||||
(mk-test
|
||||
"fd-in-ground-in-domain"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x)
|
||||
(== x 3)
|
||||
(fd-in x (list 1 2 3 4 5))
|
||||
(== q x)))
|
||||
(list 3))
|
||||
|
||||
(mk-test
|
||||
"fd-in-ground-not-in-domain"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x)
|
||||
(== x 9)
|
||||
(fd-in x (list 1 2 3 4 5))
|
||||
(== q x)))
|
||||
(list))
|
||||
|
||||
;; --- fd-label across multiple vars ---
|
||||
|
||||
(mk-test
|
||||
"fd-label-multiple-vars"
|
||||
(let
|
||||
((res (run* q (fresh (a b) (fd-in a (list 1 2 3)) (fd-in b (list 10 20)) (fd-label (list a b)) (== q (list a b))))))
|
||||
(= (len res) 6))
|
||||
true)
|
||||
|
||||
(mk-test
|
||||
"fd-label-empty-vars"
|
||||
(run* q (fd-label (list)))
|
||||
(list (make-symbol "_.0")))
|
||||
|
||||
;; --- composition with regular goals ---
|
||||
|
||||
(mk-test
|
||||
"fd-in-with-membero-style-filtering"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x)
|
||||
(fd-in
|
||||
x
|
||||
(list
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10))
|
||||
(fd-label (list x))
|
||||
(== q x)))
|
||||
(list
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10))
|
||||
|
||||
(mk-tests-run!)
|
||||
82
lib/minikanren/tests/clpfd-neq.sx
Normal file
82
lib/minikanren/tests/clpfd-neq.sx
Normal file
@@ -0,0 +1,82 @@
|
||||
;; lib/minikanren/tests/clpfd-neq.sx — fd-neq with constraint propagation.
|
||||
|
||||
;; --- ground / domain interaction ---
|
||||
|
||||
(mk-test
|
||||
"fd-neq-ground-distinct"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x)
|
||||
(fd-neq x 5)
|
||||
(fd-in x (list 4 5 6))
|
||||
(fd-label (list x))
|
||||
(== q x)))
|
||||
(list 4 6))
|
||||
|
||||
(mk-test
|
||||
"fd-neq-ground-equal-fails"
|
||||
(run* q (fresh (x) (== x 5) (fd-neq x 5) (== q x)))
|
||||
(list))
|
||||
|
||||
(mk-test
|
||||
"fd-neq-symmetric"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x)
|
||||
(fd-neq 7 x)
|
||||
(fd-in x (list 5 6 7 8 9))
|
||||
(fd-label (list x))
|
||||
(== q x)))
|
||||
(list 5 6 8 9))
|
||||
|
||||
;; --- two vars with overlapping domains ---
|
||||
|
||||
(mk-test
|
||||
"fd-neq-pair-from-3"
|
||||
(let
|
||||
((res (run* q (fresh (x y) (fd-in x (list 1 2 3)) (fd-in y (list 1 2 3)) (fd-neq x y) (fd-label (list x y)) (== q (list x y))))))
|
||||
(= (len res) 6))
|
||||
true)
|
||||
|
||||
(mk-test
|
||||
"fd-all-distinct-3-of-3"
|
||||
(let
|
||||
((res (run* q (fresh (a b c) (fd-in a (list 1 2 3)) (fd-in b (list 1 2 3)) (fd-in c (list 1 2 3)) (fd-neq a b) (fd-neq a c) (fd-neq b c) (fd-label (list a b c)) (== q (list a b c))))))
|
||||
(= (len res) 6))
|
||||
true)
|
||||
|
||||
(mk-test
|
||||
"fd-pigeonhole-fails"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(a b c)
|
||||
(fd-in a (list 1 2))
|
||||
(fd-in b (list 1 2))
|
||||
(fd-in c (list 1 2))
|
||||
(fd-neq a b)
|
||||
(fd-neq a c)
|
||||
(fd-neq b c)
|
||||
(fd-label (list a b c))
|
||||
(== q (list a b c))))
|
||||
(list))
|
||||
|
||||
;; --- propagation when one side becomes ground ---
|
||||
|
||||
(mk-test
|
||||
"fd-neq-propagates-after-ground"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x y)
|
||||
(fd-in x (list 1 2 3))
|
||||
(fd-in y (list 1 2 3))
|
||||
(fd-neq x y)
|
||||
(== x 2)
|
||||
(fd-label (list y))
|
||||
(== q y)))
|
||||
(list 1 3))
|
||||
|
||||
(mk-tests-run!)
|
||||
128
lib/minikanren/tests/clpfd-ord.sx
Normal file
128
lib/minikanren/tests/clpfd-ord.sx
Normal file
@@ -0,0 +1,128 @@
|
||||
;; lib/minikanren/tests/clpfd-ord.sx — fd-lt / fd-lte / fd-eq.
|
||||
|
||||
;; --- fd-lt ---
|
||||
|
||||
(mk-test
|
||||
"fd-lt-narrows-x-against-num"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x)
|
||||
(fd-in x (list 1 2 3 4 5))
|
||||
(fd-lt x 3)
|
||||
(fd-label (list x))
|
||||
(== q x)))
|
||||
(list 1 2))
|
||||
|
||||
(mk-test
|
||||
"fd-lt-narrows-x-against-num-symmetric"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x)
|
||||
(fd-in x (list 1 2 3 4 5))
|
||||
(fd-lt 3 x)
|
||||
(fd-label (list x))
|
||||
(== q x)))
|
||||
(list 4 5))
|
||||
|
||||
(mk-test
|
||||
"fd-lt-pair-ordered"
|
||||
(let
|
||||
((res (run* q (fresh (x y) (fd-in x (list 1 2 3 4)) (fd-in y (list 1 2 3 4)) (fd-lt x y) (fd-label (list x y)) (== q (list x y))))))
|
||||
(= (len res) 6))
|
||||
true)
|
||||
|
||||
(mk-test
|
||||
"fd-lt-impossible-fails"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x)
|
||||
(fd-in x (list 5 6 7))
|
||||
(fd-lt x 3)
|
||||
(fd-label (list x))
|
||||
(== q x)))
|
||||
(list))
|
||||
|
||||
;; --- fd-lte ---
|
||||
|
||||
(mk-test
|
||||
"fd-lte-includes-equal"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x)
|
||||
(fd-in x (list 1 2 3 4 5))
|
||||
(fd-lte x 3)
|
||||
(fd-label (list x))
|
||||
(== q x)))
|
||||
(list 1 2 3))
|
||||
|
||||
(mk-test
|
||||
"fd-lte-equal-bound"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x)
|
||||
(fd-in x (list 1 2 3 4 5))
|
||||
(fd-lte 3 x)
|
||||
(fd-label (list x))
|
||||
(== q x)))
|
||||
(list 3 4 5))
|
||||
|
||||
;; --- fd-eq ---
|
||||
|
||||
(mk-test
|
||||
"fd-eq-bind"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x)
|
||||
(fd-in x (list 1 2 3 4 5))
|
||||
(fd-eq x 3)
|
||||
(== q x)))
|
||||
(list 3))
|
||||
|
||||
(mk-test
|
||||
"fd-eq-out-of-domain-fails"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x)
|
||||
(fd-in x (list 1 2 3))
|
||||
(fd-eq x 5)
|
||||
(== q x)))
|
||||
(list))
|
||||
|
||||
(mk-test
|
||||
"fd-eq-two-vars-share-domain"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x y)
|
||||
(fd-in x (list 1 2 3))
|
||||
(fd-in y (list 2 3 4))
|
||||
(fd-eq x y)
|
||||
(fd-label (list x y))
|
||||
(== q (list x y))))
|
||||
(list (list 2 2) (list 3 3)))
|
||||
|
||||
;; --- combine fd-lt + fd-neq for "between" puzzle ---
|
||||
|
||||
(mk-test
|
||||
"fd-lt-neq-combined"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x y z)
|
||||
(fd-in x (list 1 2 3))
|
||||
(fd-in y (list 1 2 3))
|
||||
(fd-in z (list 1 2 3))
|
||||
(fd-lt x y)
|
||||
(fd-lt y z)
|
||||
(fd-label (list x y z))
|
||||
(== q (list x y z))))
|
||||
(list (list 1 2 3)))
|
||||
|
||||
(mk-tests-run!)
|
||||
62
lib/minikanren/tests/clpfd-plus.sx
Normal file
62
lib/minikanren/tests/clpfd-plus.sx
Normal file
@@ -0,0 +1,62 @@
|
||||
;; lib/minikanren/tests/clpfd-plus.sx — fd-plus (x + y = z).
|
||||
|
||||
(mk-test
|
||||
"fd-plus-all-ground"
|
||||
(run* q (fresh (z) (fd-plus 2 3 z) (== q z)))
|
||||
(list 5))
|
||||
|
||||
(mk-test
|
||||
"fd-plus-recover-x"
|
||||
(run* q (fresh (x) (fd-plus x 3 5) (== q x)))
|
||||
(list 2))
|
||||
|
||||
(mk-test
|
||||
"fd-plus-recover-y"
|
||||
(run* q (fresh (y) (fd-plus 2 y 5) (== q y)))
|
||||
(list 3))
|
||||
|
||||
(mk-test
|
||||
"fd-plus-impossible-fails"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(z)
|
||||
(fd-plus 2 3 z)
|
||||
(== z 99)
|
||||
(== q z)))
|
||||
(list))
|
||||
|
||||
(mk-test
|
||||
"fd-plus-domain-check"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x)
|
||||
(fd-in x (list 3 4 5))
|
||||
(fd-plus x 3 5)
|
||||
(== q x)))
|
||||
(list))
|
||||
|
||||
(mk-test
|
||||
"fd-plus-pairs-summing-to-5"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x y)
|
||||
(fd-in x (list 1 2 3 4))
|
||||
(fd-in y (list 1 2 3 4))
|
||||
(fd-plus x y 5)
|
||||
(fd-label (list x y))
|
||||
(== q (list x y))))
|
||||
(list
|
||||
(list 1 4)
|
||||
(list 2 3)
|
||||
(list 3 2)
|
||||
(list 4 1)))
|
||||
|
||||
(mk-test
|
||||
"fd-plus-z-derived"
|
||||
(run* q (fresh (z) (fd-plus 7 8 z) (== q z)))
|
||||
(list 15))
|
||||
|
||||
(mk-tests-run!)
|
||||
85
lib/minikanren/tests/clpfd-times.sx
Normal file
85
lib/minikanren/tests/clpfd-times.sx
Normal file
@@ -0,0 +1,85 @@
|
||||
;; lib/minikanren/tests/clpfd-times.sx — fd-times (x * y = z).
|
||||
|
||||
(mk-test
|
||||
"fd-times-3-4"
|
||||
(run* q (fresh (z) (fd-times 3 4 z) (== q z)))
|
||||
(list 12))
|
||||
|
||||
(mk-test
|
||||
"fd-times-recover-divisor"
|
||||
(run* q (fresh (x) (fd-times x 5 30) (== q x)))
|
||||
(list 6))
|
||||
|
||||
(mk-test
|
||||
"fd-times-non-divisible-fails"
|
||||
(run* q (fresh (x) (fd-times x 5 31) (== q x)))
|
||||
(list))
|
||||
|
||||
(mk-test
|
||||
"fd-times-by-zero"
|
||||
(run* q (fresh (z) (fd-times 0 99 z) (== q z)))
|
||||
(list 0))
|
||||
|
||||
(mk-test
|
||||
"fd-times-zero-by-anything-zero"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x)
|
||||
(fd-in x (list 1 2 3))
|
||||
(fd-times x 0 0)
|
||||
(fd-label (list x))
|
||||
(== q x)))
|
||||
(list 1 2 3))
|
||||
|
||||
(mk-test
|
||||
"fd-times-12-divisor-pairs"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x y)
|
||||
(fd-in
|
||||
x
|
||||
(list
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6))
|
||||
(fd-in
|
||||
y
|
||||
(list
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6))
|
||||
(fd-times x y 12)
|
||||
(fd-label (list x y))
|
||||
(== q (list x y))))
|
||||
(list
|
||||
(list 2 6)
|
||||
(list 3 4)
|
||||
(list 4 3)
|
||||
(list 6 2)))
|
||||
|
||||
(mk-test
|
||||
"fd-times-square-of-each"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x z)
|
||||
(fd-in x (list 1 2 3 4 5))
|
||||
(fd-times x x z)
|
||||
(fd-label (list x))
|
||||
(== q (list x z))))
|
||||
(list
|
||||
(list 1 1)
|
||||
(list 2 4)
|
||||
(list 3 9)
|
||||
(list 4 16)
|
||||
(list 5 25)))
|
||||
|
||||
(mk-tests-run!)
|
||||
75
lib/minikanren/tests/conda.sx
Normal file
75
lib/minikanren/tests/conda.sx
Normal file
@@ -0,0 +1,75 @@
|
||||
;; lib/minikanren/tests/conda.sx — Phase 5 piece A tests for `conda`.
|
||||
|
||||
;; --- conda commits to first non-failing head, keeps ALL its answers ---
|
||||
|
||||
(mk-test
|
||||
"conda-first-clause-keeps-all"
|
||||
(run*
|
||||
q
|
||||
(conda
|
||||
((mk-disj (== q 1) (== q 2)))
|
||||
((== q 100))))
|
||||
(list 1 2))
|
||||
|
||||
(mk-test
|
||||
"conda-skips-failing-head"
|
||||
(run*
|
||||
q
|
||||
(conda
|
||||
((== 1 2))
|
||||
((mk-disj (== q 10) (== q 20)))))
|
||||
(list 10 20))
|
||||
|
||||
(mk-test
|
||||
"conda-all-fail"
|
||||
(run*
|
||||
q
|
||||
(conda ((== 1 2)) ((== 3 4))))
|
||||
(list))
|
||||
|
||||
(mk-test "conda-no-clauses" (run* q (conda)) (list))
|
||||
|
||||
;; --- conda DIFFERS from condu: conda keeps all head answers ---
|
||||
|
||||
(mk-test
|
||||
"conda-vs-condu-divergence"
|
||||
(list
|
||||
(run*
|
||||
q
|
||||
(conda
|
||||
((mk-disj (== q 1) (== q 2)))
|
||||
((== q 100))))
|
||||
(run*
|
||||
q
|
||||
(condu
|
||||
((mk-disj (== q 1) (== q 2)))
|
||||
((== q 100)))))
|
||||
(list (list 1 2) (list 1)))
|
||||
|
||||
;; --- conda head's rest-goals run on every head answer ---
|
||||
|
||||
(mk-test
|
||||
"conda-rest-goals-run-on-all-answers"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x r)
|
||||
(conda
|
||||
((mk-disj (== x 1) (== x 2))
|
||||
(== r (list :tag x))))
|
||||
(== q r)))
|
||||
(list (list :tag 1) (list :tag 2)))
|
||||
|
||||
;; --- if rest-goals fail on a head answer, that head answer is filtered;
|
||||
;; the clause does not fall through to next clauses (per soft-cut). ---
|
||||
|
||||
(mk-test
|
||||
"conda-rest-fails-no-fallthrough"
|
||||
(run*
|
||||
q
|
||||
(conda
|
||||
((mk-disj (== q 1) (== q 2)) (== q 99))
|
||||
((== q 200))))
|
||||
(list))
|
||||
|
||||
(mk-tests-run!)
|
||||
89
lib/minikanren/tests/conde.sx
Normal file
89
lib/minikanren/tests/conde.sx
Normal file
@@ -0,0 +1,89 @@
|
||||
;; lib/minikanren/tests/conde.sx — Phase 2 piece C tests for `conde`.
|
||||
;;
|
||||
;; Note on ordering: conde clauses are wrapped in Zzz (inverse-eta delay),
|
||||
;; so applying the conde goal to a substitution returns thunks. mk-mplus
|
||||
;; suspends-and-swaps when its left operand is paused, giving fair
|
||||
;; interleaving — this is exactly what makes recursive relations work,
|
||||
;; but it does mean conde answers can interleave rather than appear in
|
||||
;; strict left-to-right clause order.
|
||||
|
||||
;; --- single-clause conde ≡ conj of clause body ---
|
||||
|
||||
(mk-test
|
||||
"conde-one-clause"
|
||||
(let ((q (mk-var "q"))) (run* q (conde ((== q 7)))))
|
||||
(list 7))
|
||||
|
||||
(mk-test
|
||||
"conde-one-clause-multi-goals"
|
||||
(let
|
||||
((q (mk-var "q")))
|
||||
(run* q (conde ((fresh (x) (== x 5) (== q (list x x)))))))
|
||||
(list (list 5 5)))
|
||||
|
||||
;; --- multi-clause: produces one row per clause (interleaved) ---
|
||||
|
||||
(mk-test
|
||||
"conde-three-clauses-as-set"
|
||||
(let
|
||||
((qs (run* q (conde ((== q 1)) ((== q 2)) ((== q 3))))))
|
||||
(and
|
||||
(= (len qs) 3)
|
||||
(and
|
||||
(some (fn (x) (= x 1)) qs)
|
||||
(and
|
||||
(some (fn (x) (= x 2)) qs)
|
||||
(some (fn (x) (= x 3)) qs)))))
|
||||
true)
|
||||
|
||||
(mk-test
|
||||
"conde-mixed-success-failure-as-set"
|
||||
(let
|
||||
((qs (run* q (conde ((== q "a")) ((== 1 2)) ((== q "b"))))))
|
||||
(and
|
||||
(= (len qs) 2)
|
||||
(and (some (fn (x) (= x "a")) qs) (some (fn (x) (= x "b")) qs))))
|
||||
true)
|
||||
|
||||
;; --- conde with conjuncts inside clauses ---
|
||||
|
||||
(mk-test
|
||||
"conde-clause-conj-as-set"
|
||||
(let
|
||||
((rows (run* q (fresh (x y) (conde ((== x 1) (== y 10)) ((== x 2) (== y 20))) (== q (list x y))))))
|
||||
(and
|
||||
(= (len rows) 2)
|
||||
(and
|
||||
(some (fn (r) (= r (list 1 10))) rows)
|
||||
(some (fn (r) (= r (list 2 20))) rows))))
|
||||
true)
|
||||
|
||||
;; --- nested conde ---
|
||||
|
||||
(mk-test
|
||||
"conde-nested-yields-three"
|
||||
(let
|
||||
((qs (run* q (conde ((conde ((== q 1)) ((== q 2)))) ((== q 3))))))
|
||||
(and
|
||||
(= (len qs) 3)
|
||||
(and
|
||||
(some (fn (x) (= x 1)) qs)
|
||||
(and
|
||||
(some (fn (x) (= x 2)) qs)
|
||||
(some (fn (x) (= x 3)) qs)))))
|
||||
true)
|
||||
|
||||
;; --- conde all clauses fail → empty stream ---
|
||||
|
||||
(mk-test
|
||||
"conde-all-fail"
|
||||
(run*
|
||||
q
|
||||
(conde ((== 1 2)) ((== 3 4))))
|
||||
(list))
|
||||
|
||||
;; --- empty conde: no clauses ⇒ fail ---
|
||||
|
||||
(mk-test "conde-no-clauses" (run* q (conde)) (list))
|
||||
|
||||
(mk-tests-run!)
|
||||
86
lib/minikanren/tests/condu.sx
Normal file
86
lib/minikanren/tests/condu.sx
Normal file
@@ -0,0 +1,86 @@
|
||||
;; lib/minikanren/tests/condu.sx — Phase 2 piece D tests for `onceo` and `condu`.
|
||||
|
||||
;; --- onceo: at most one answer ---
|
||||
|
||||
(mk-test
|
||||
"onceo-single-success-passes-through"
|
||||
(let
|
||||
((q (mk-var "q")))
|
||||
(let
|
||||
((res (stream-take 5 ((onceo (== q 7)) empty-s))))
|
||||
(map (fn (s) (mk-walk q s)) res)))
|
||||
(list 7))
|
||||
|
||||
(mk-test
|
||||
"onceo-multi-success-trimmed-to-one"
|
||||
(let
|
||||
((q (mk-var "q")))
|
||||
(let
|
||||
((res (stream-take 5 ((onceo (mk-disj (== q 1) (== q 2) (== q 3))) empty-s))))
|
||||
(map (fn (s) (mk-walk q s)) res)))
|
||||
(list 1))
|
||||
|
||||
(mk-test
|
||||
"onceo-failure-stays-failure"
|
||||
((onceo (== 1 2)) empty-s)
|
||||
(list))
|
||||
|
||||
(mk-test
|
||||
"onceo-conde-trimmed"
|
||||
(let
|
||||
((q (mk-var "q")))
|
||||
(let
|
||||
((res (stream-take 5 ((onceo (conde ((== q "a")) ((== q "b")))) empty-s))))
|
||||
(map (fn (s) (mk-walk q s)) res)))
|
||||
(list "a"))
|
||||
|
||||
;; --- condu: first clause with successful head wins ---
|
||||
|
||||
(mk-test
|
||||
"condu-first-clause-wins"
|
||||
(let
|
||||
((q (mk-var "q")))
|
||||
(let
|
||||
((res (stream-take 10 ((condu ((== q 1)) ((== q 2))) empty-s))))
|
||||
(map (fn (s) (mk-walk q s)) res)))
|
||||
(list 1))
|
||||
|
||||
(mk-test
|
||||
"condu-skips-failing-head"
|
||||
(let
|
||||
((q (mk-var "q")))
|
||||
(let
|
||||
((res (stream-take 10 ((condu ((== 1 2)) ((== q 100)) ((== q 200))) empty-s))))
|
||||
(map (fn (s) (mk-walk q s)) res)))
|
||||
(list 100))
|
||||
|
||||
(mk-test
|
||||
"condu-all-fail-empty"
|
||||
((condu ((== 1 2)) ((== 3 4)))
|
||||
empty-s)
|
||||
(list))
|
||||
|
||||
(mk-test "condu-empty-clauses-fail" ((condu) empty-s) (list))
|
||||
|
||||
;; --- condu commits head's first answer; rest-goals can still backtrack
|
||||
;; within that committed substitution but cannot revisit other heads. ---
|
||||
|
||||
(mk-test
|
||||
"condu-head-onceo-rest-runs"
|
||||
(let
|
||||
((q (mk-var "q")) (r (mk-var "r")))
|
||||
(let
|
||||
((res (stream-take 10 ((condu ((mk-disj (== q 1) (== q 2)) (== r 99))) empty-s))))
|
||||
(map (fn (s) (list (mk-walk q s) (mk-walk r s))) res)))
|
||||
(list (list 1 99)))
|
||||
|
||||
(mk-test
|
||||
"condu-rest-goals-can-fail-the-clause"
|
||||
(let
|
||||
((q (mk-var "q")))
|
||||
(let
|
||||
((res (stream-take 10 ((condu ((== q 1) (== 2 3)) ((== q 99))) empty-s))))
|
||||
(map (fn (s) (mk-walk q s)) res)))
|
||||
(list))
|
||||
|
||||
(mk-tests-run!)
|
||||
35
lib/minikanren/tests/counto.sx
Normal file
35
lib/minikanren/tests/counto.sx
Normal file
@@ -0,0 +1,35 @@
|
||||
;; lib/minikanren/tests/counto.sx — count occurrences of x in l (intarith).
|
||||
|
||||
(mk-test
|
||||
"counto-empty"
|
||||
(run* q (counto 1 (list) q))
|
||||
(list 0))
|
||||
(mk-test
|
||||
"counto-not-found"
|
||||
(run* q (counto 99 (list 1 2 3) q))
|
||||
(list 0))
|
||||
(mk-test
|
||||
"counto-once"
|
||||
(run* q (counto 2 (list 1 2 3) q))
|
||||
(list 1))
|
||||
(mk-test
|
||||
"counto-thrice"
|
||||
(run*
|
||||
q
|
||||
(counto
|
||||
1
|
||||
(list 1 2 1 3 1)
|
||||
q))
|
||||
(list 3))
|
||||
(mk-test
|
||||
"counto-all-same"
|
||||
(run*
|
||||
q
|
||||
(counto 7 (list 7 7 7 7) q))
|
||||
(list 4))
|
||||
(mk-test
|
||||
"counto-string"
|
||||
(run* q (counto "x" (list "x" "y" "x") q))
|
||||
(list 2))
|
||||
|
||||
(mk-tests-run!)
|
||||
48
lib/minikanren/tests/cyclic-graph.sx
Normal file
48
lib/minikanren/tests/cyclic-graph.sx
Normal file
@@ -0,0 +1,48 @@
|
||||
;; lib/minikanren/tests/cyclic-graph.sx — demonstrates the naive-patho
|
||||
;; behaviour on a cyclic graph. Without Phase-7 tabling/SLG, the search
|
||||
;; produces ever-longer paths revisiting the cycle. `run n` truncates;
|
||||
;; `run*` would diverge.
|
||||
|
||||
(define cyclic-edges (list (list :a :b) (list :b :a) (list :b :c)))
|
||||
|
||||
(define cyclic-edgeo (fn (x y) (membero (list x y) cyclic-edges)))
|
||||
|
||||
(define
|
||||
cyclic-patho
|
||||
(fn
|
||||
(x y path)
|
||||
(conde
|
||||
((cyclic-edgeo x y) (== path (list x y)))
|
||||
((fresh (z mid) (cyclic-edgeo x z) (cyclic-patho z y mid) (conso x mid path))))))
|
||||
|
||||
;; --- direct edge ---
|
||||
|
||||
(mk-test
|
||||
"cyclic-direct"
|
||||
(run 1 q (cyclic-patho :a :b q))
|
||||
(list (list :a :b)))
|
||||
|
||||
;; --- runs first 5 paths from a to b: bare edge, then increasing
|
||||
;; numbers of cycle traversals (a->b->a->b, etc.) ---
|
||||
|
||||
(mk-test
|
||||
"cyclic-enumerates-prefix-via-run-n"
|
||||
(let
|
||||
((paths (run 5 q (cyclic-patho :a :b q))))
|
||||
(and
|
||||
(= (len paths) 5)
|
||||
(and
|
||||
(every? (fn (p) (= (first p) :a)) paths)
|
||||
(every? (fn (p) (= (last p) :b)) paths))))
|
||||
true)
|
||||
|
||||
(mk-test
|
||||
"cyclic-finds-c-via-cycle-or-direct"
|
||||
(let
|
||||
((paths (run 3 q (cyclic-patho :a :c q))))
|
||||
(and
|
||||
(>= (len paths) 1)
|
||||
(some (fn (p) (= p (list :a :b :c))) paths)))
|
||||
true)
|
||||
|
||||
(mk-tests-run!)
|
||||
40
lib/minikanren/tests/defrel.sx
Normal file
40
lib/minikanren/tests/defrel.sx
Normal file
@@ -0,0 +1,40 @@
|
||||
;; lib/minikanren/tests/defrel.sx — Prolog-style relation definition macro.
|
||||
|
||||
(defrel
|
||||
(my-membero x l)
|
||||
((fresh (d) (conso x d l)))
|
||||
((fresh (a d) (conso a d l) (my-membero x d))))
|
||||
|
||||
(mk-test
|
||||
"defrel-defines-membero"
|
||||
(run* q (my-membero q (list 1 2 3)))
|
||||
(list 1 2 3))
|
||||
|
||||
(defrel
|
||||
(my-listo l)
|
||||
((nullo l))
|
||||
((fresh (a d) (conso a d l) (my-listo d))))
|
||||
|
||||
(mk-test
|
||||
"defrel-listo-bounded"
|
||||
(run 3 q (my-listo q))
|
||||
(list
|
||||
(list)
|
||||
(list (make-symbol "_.0"))
|
||||
(list (make-symbol "_.0") (make-symbol "_.1"))))
|
||||
|
||||
;; Multi-arg relation with arithmetic.
|
||||
|
||||
(defrel
|
||||
(my-pluso a b c)
|
||||
((== a :z) (== b c))
|
||||
((fresh (a-1 c-1) (== a (list :s a-1)) (== c (list :s c-1)) (my-pluso a-1 b c-1))))
|
||||
|
||||
(mk-test
|
||||
"defrel-pluso-2-3"
|
||||
(run*
|
||||
q
|
||||
(my-pluso (list :s (list :s :z)) (list :s (list :s (list :s :z))) q))
|
||||
(list (list :s (list :s (list :s (list :s (list :s :z)))))))
|
||||
|
||||
(mk-tests-run!)
|
||||
83
lib/minikanren/tests/diseq.sx
Normal file
83
lib/minikanren/tests/diseq.sx
Normal file
@@ -0,0 +1,83 @@
|
||||
;; lib/minikanren/tests/diseq.sx — Phase 5 polish: =/= disequality.
|
||||
|
||||
;; --- ground cases ---
|
||||
|
||||
(mk-test
|
||||
"=/=-ground-distinct"
|
||||
(run* q (=/= 1 2))
|
||||
(list (make-symbol "_.0")))
|
||||
(mk-test "=/=-ground-equal" (run* q (=/= 1 1)) (list))
|
||||
(mk-test
|
||||
"=/=-ground-strings"
|
||||
(run* q (=/= "a" "b"))
|
||||
(list (make-symbol "_.0")))
|
||||
(mk-test "=/=-ground-strings-eq" (run* q (=/= "a" "a")) (list))
|
||||
|
||||
;; --- structural ---
|
||||
|
||||
(mk-test
|
||||
"=/=-pair-distinct"
|
||||
(run* q (=/= (list 1 2) (list 1 3)))
|
||||
(list (make-symbol "_.0")))
|
||||
(mk-test
|
||||
"=/=-pair-equal"
|
||||
(run* q (=/= (list 1 2) (list 1 2)))
|
||||
(list))
|
||||
(mk-test
|
||||
"=/=-pair-vs-atom"
|
||||
(run* q (=/= (list 1) 1))
|
||||
(list (make-symbol "_.0")))
|
||||
|
||||
;; --- partial / late binding ---
|
||||
;;
|
||||
;; ==-cs is required to wake up the constraint store after a binding;
|
||||
;; plain == doesn't fire constraints.
|
||||
|
||||
(mk-test
|
||||
"=/=-late-bind-violates"
|
||||
(run* q (fresh (x) (=/= x 5) (==-cs x 5) (== q x)))
|
||||
(list))
|
||||
|
||||
(mk-test
|
||||
"=/=-late-bind-ok"
|
||||
(run* q (fresh (x) (=/= x 5) (==-cs x 7) (== q x)))
|
||||
(list 7))
|
||||
|
||||
(mk-test
|
||||
"=/=-two-vars-equal-late-fails"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x y)
|
||||
(=/= x y)
|
||||
(==-cs x 1)
|
||||
(==-cs y 1)
|
||||
(== q (list x y))))
|
||||
(list))
|
||||
|
||||
(mk-test
|
||||
"=/=-two-vars-distinct-late"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x y)
|
||||
(=/= x y)
|
||||
(==-cs x 1)
|
||||
(==-cs y 2)
|
||||
(== q (list x y))))
|
||||
(list (list 1 2)))
|
||||
|
||||
;; --- compose with conde / fresh ---
|
||||
|
||||
(mk-test
|
||||
"=/=-with-membero-filter"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x)
|
||||
(membero x (list 1 2 3))
|
||||
(=/= x 2)
|
||||
(== q x)))
|
||||
(list 1 3))
|
||||
|
||||
(mk-tests-run!)
|
||||
31
lib/minikanren/tests/enumerate.sx
Normal file
31
lib/minikanren/tests/enumerate.sx
Normal file
@@ -0,0 +1,31 @@
|
||||
;; lib/minikanren/tests/enumerate.sx — index-each-element relation.
|
||||
|
||||
(mk-test
|
||||
"enumerate-i-empty"
|
||||
(run* q (enumerate-i (list) q))
|
||||
(list (list)))
|
||||
|
||||
(mk-test
|
||||
"enumerate-i-three"
|
||||
(run* q (enumerate-i (list :a :b :c) q))
|
||||
(list
|
||||
(list (list 0 :a) (list 1 :b) (list 2 :c))))
|
||||
|
||||
(mk-test
|
||||
"enumerate-i-strings"
|
||||
(run* q (enumerate-i (list "x" "y" "z") q))
|
||||
(list
|
||||
(list (list 0 "x") (list 1 "y") (list 2 "z"))))
|
||||
|
||||
(mk-test
|
||||
"enumerate-from-i-100"
|
||||
(run* q (enumerate-from-i 100 (list :x :y :z) q))
|
||||
(list
|
||||
(list (list 100 :x) (list 101 :y) (list 102 :z))))
|
||||
|
||||
(mk-test
|
||||
"enumerate-from-i-singleton"
|
||||
(run* q (enumerate-from-i 0 (list :only) q))
|
||||
(list (list (list 0 :only))))
|
||||
|
||||
(mk-tests-run!)
|
||||
75
lib/minikanren/tests/fd.sx
Normal file
75
lib/minikanren/tests/fd.sx
Normal file
@@ -0,0 +1,75 @@
|
||||
;; lib/minikanren/tests/fd.sx — Phase 6 piece A: ino + all-distincto.
|
||||
|
||||
;; --- ino ---
|
||||
|
||||
(mk-test
|
||||
"ino-element-in-domain"
|
||||
(run* q (ino q (list 1 2 3)))
|
||||
(list 1 2 3))
|
||||
|
||||
(mk-test "ino-empty-domain" (run* q (ino q (list))) (list))
|
||||
|
||||
(mk-test
|
||||
"ino-singleton-domain"
|
||||
(run* q (ino q (list 42)))
|
||||
(list 42))
|
||||
|
||||
;; --- all-distincto ---
|
||||
|
||||
(mk-test
|
||||
"all-distincto-empty"
|
||||
(run* q (all-distincto (list)))
|
||||
(list (make-symbol "_.0")))
|
||||
|
||||
(mk-test
|
||||
"all-distincto-singleton"
|
||||
(run* q (all-distincto (list 1)))
|
||||
(list (make-symbol "_.0")))
|
||||
|
||||
(mk-test
|
||||
"all-distincto-distinct-three"
|
||||
(run* q (all-distincto (list 1 2 3)))
|
||||
(list (make-symbol "_.0")))
|
||||
|
||||
(mk-test
|
||||
"all-distincto-duplicate-fails"
|
||||
(run* q (all-distincto (list 1 2 1)))
|
||||
(list))
|
||||
|
||||
(mk-test
|
||||
"all-distincto-adjacent-duplicate-fails"
|
||||
(run* q (all-distincto (list 1 1 2)))
|
||||
(list))
|
||||
|
||||
;; --- ino + all-distincto: classic enumerate-all-permutations ---
|
||||
|
||||
(mk-test
|
||||
"fd-puzzle-three-distinct-from-domain"
|
||||
(let
|
||||
((perms (run* q (fresh (a b c) (== q (list a b c)) (ino a (list 1 2 3)) (ino b (list 1 2 3)) (ino c (list 1 2 3)) (all-distincto (list a b c))))))
|
||||
(and
|
||||
(= (len perms) 6)
|
||||
(and
|
||||
(some (fn (p) (= p (list 1 2 3))) perms)
|
||||
(and
|
||||
(some
|
||||
(fn (p) (= p (list 1 3 2)))
|
||||
perms)
|
||||
(and
|
||||
(some
|
||||
(fn (p) (= p (list 2 1 3)))
|
||||
perms)
|
||||
(and
|
||||
(some
|
||||
(fn (p) (= p (list 2 3 1)))
|
||||
perms)
|
||||
(and
|
||||
(some
|
||||
(fn (p) (= p (list 3 1 2)))
|
||||
perms)
|
||||
(some
|
||||
(fn (p) (= p (list 3 2 1)))
|
||||
perms))))))))
|
||||
true)
|
||||
|
||||
(mk-tests-run!)
|
||||
39
lib/minikanren/tests/flat-mapo.sx
Normal file
39
lib/minikanren/tests/flat-mapo.sx
Normal file
@@ -0,0 +1,39 @@
|
||||
;; lib/minikanren/tests/flat-mapo.sx — concatMap-style relation.
|
||||
|
||||
(mk-test
|
||||
"flat-mapo-empty"
|
||||
(run* q (flat-mapo (fn (x r) (== r (list x x))) (list) q))
|
||||
(list (list)))
|
||||
|
||||
(mk-test
|
||||
"flat-mapo-duplicate-each"
|
||||
(run*
|
||||
q
|
||||
(flat-mapo
|
||||
(fn (x r) (== r (list x x)))
|
||||
(list 1 2 3)
|
||||
q))
|
||||
(list
|
||||
(list 1 1 2 2 3 3)))
|
||||
|
||||
(mk-test
|
||||
"flat-mapo-empty-from-each"
|
||||
(run* q (flat-mapo (fn (x r) (== r (list))) (list :a :b :c) q))
|
||||
(list (list)))
|
||||
|
||||
(mk-test
|
||||
"flat-mapo-singleton-from-each-is-identity"
|
||||
(run* q (flat-mapo (fn (x r) (== r (list x))) (list :a :b :c) q))
|
||||
(list (list :a :b :c)))
|
||||
|
||||
(mk-test
|
||||
"flat-mapo-tag-each"
|
||||
(run*
|
||||
q
|
||||
(flat-mapo
|
||||
(fn (x r) (== r (list :tag x)))
|
||||
(list 1 2)
|
||||
q))
|
||||
(list (list :tag 1 :tag 2)))
|
||||
|
||||
(mk-tests-run!)
|
||||
42
lib/minikanren/tests/flatteno.sx
Normal file
42
lib/minikanren/tests/flatteno.sx
Normal file
@@ -0,0 +1,42 @@
|
||||
(mk-test "flatteno-empty" (run* q (flatteno (list) q)) (list (list)))
|
||||
|
||||
(mk-test
|
||||
"flatteno-atom"
|
||||
(run* q (flatteno 5 q))
|
||||
(list (list 5)))
|
||||
|
||||
(mk-test
|
||||
"flatteno-flat-list"
|
||||
(run* q (flatteno (list 1 2 3) q))
|
||||
(list (list 1 2 3)))
|
||||
|
||||
(mk-test
|
||||
"flatteno-singleton"
|
||||
(run* q (flatteno (list 1) q))
|
||||
(list (list 1)))
|
||||
|
||||
(mk-test
|
||||
"flatteno-nested-once"
|
||||
(run*
|
||||
q
|
||||
(flatteno (list 1 (list 2 3) 4) q))
|
||||
(list (list 1 2 3 4)))
|
||||
|
||||
(mk-test
|
||||
"flatteno-nested-twice"
|
||||
(run*
|
||||
q
|
||||
(flatteno
|
||||
(list
|
||||
1
|
||||
(list 2 (list 3 4))
|
||||
5)
|
||||
q))
|
||||
(list (list 1 2 3 4 5)))
|
||||
|
||||
(mk-test
|
||||
"flatteno-keywords"
|
||||
(run* q (flatteno (list :a (list :b :c) :d) q))
|
||||
(list (list :a :b :c :d)))
|
||||
|
||||
(mk-tests-run!)
|
||||
48
lib/minikanren/tests/foldl-o.sx
Normal file
48
lib/minikanren/tests/foldl-o.sx
Normal file
@@ -0,0 +1,48 @@
|
||||
;; lib/minikanren/tests/foldl-o.sx — relational left fold.
|
||||
|
||||
(mk-test
|
||||
"foldl-o-empty"
|
||||
(run* q (foldl-o pluso-i (list) 42 q))
|
||||
(list 42))
|
||||
|
||||
(mk-test
|
||||
"foldl-o-sum"
|
||||
(run*
|
||||
q
|
||||
(foldl-o
|
||||
pluso-i
|
||||
(list 1 2 3 4 5)
|
||||
0
|
||||
q))
|
||||
(list 15))
|
||||
|
||||
(mk-test
|
||||
"foldl-o-product"
|
||||
(run*
|
||||
q
|
||||
(foldl-o
|
||||
*o-i
|
||||
(list 1 2 3 4)
|
||||
1
|
||||
q))
|
||||
(list 24))
|
||||
|
||||
(mk-test
|
||||
"foldl-o-reverse-via-flip-conso"
|
||||
(run*
|
||||
q
|
||||
(foldl-o
|
||||
(fn (acc x r) (conso x acc r))
|
||||
(list 1 2 3 4)
|
||||
(list)
|
||||
q))
|
||||
(list (list 4 3 2 1)))
|
||||
|
||||
(mk-test
|
||||
"foldl-o-with-init"
|
||||
(run*
|
||||
q
|
||||
(foldl-o pluso-i (list 1 2 3) 100 q))
|
||||
(list 106))
|
||||
|
||||
(mk-tests-run!)
|
||||
38
lib/minikanren/tests/foldr-o.sx
Normal file
38
lib/minikanren/tests/foldr-o.sx
Normal file
@@ -0,0 +1,38 @@
|
||||
;; lib/minikanren/tests/foldr-o.sx — relational right fold.
|
||||
|
||||
(mk-test
|
||||
"foldr-o-empty"
|
||||
(run* q (foldr-o conso (list) (list 99) q))
|
||||
(list (list 99)))
|
||||
|
||||
(mk-test
|
||||
"foldr-o-conso-rebuilds-list"
|
||||
(run* q (foldr-o conso (list 1 2 3) (list) q))
|
||||
(list (list 1 2 3)))
|
||||
|
||||
(mk-test
|
||||
"foldr-o-appendo-flattens"
|
||||
(run*
|
||||
q
|
||||
(foldr-o
|
||||
appendo
|
||||
(list
|
||||
(list 1 2)
|
||||
(list 3)
|
||||
(list 4 5))
|
||||
(list)
|
||||
q))
|
||||
(list (list 1 2 3 4 5)))
|
||||
|
||||
(mk-test
|
||||
"foldr-o-with-acc-init"
|
||||
(run*
|
||||
q
|
||||
(foldr-o
|
||||
conso
|
||||
(list 1 2)
|
||||
(list 9 9)
|
||||
q))
|
||||
(list (list 1 2 9 9)))
|
||||
|
||||
(mk-tests-run!)
|
||||
101
lib/minikanren/tests/fresh.sx
Normal file
101
lib/minikanren/tests/fresh.sx
Normal file
@@ -0,0 +1,101 @@
|
||||
;; lib/minikanren/tests/fresh.sx — Phase 2 piece B tests for `fresh`.
|
||||
|
||||
;; --- empty fresh: pure goal grouping ---
|
||||
|
||||
(mk-test
|
||||
"fresh-empty-vars-equiv-conj"
|
||||
(stream-take 5 ((fresh () (== 1 1)) empty-s))
|
||||
(list empty-s))
|
||||
|
||||
(mk-test
|
||||
"fresh-empty-vars-no-goals-is-succeed"
|
||||
(stream-take 5 ((fresh ()) empty-s))
|
||||
(list empty-s))
|
||||
|
||||
;; --- single var ---
|
||||
|
||||
(mk-test
|
||||
"fresh-one-var-bound"
|
||||
(let
|
||||
((s (first (stream-take 5 ((fresh (x) (== x 7)) empty-s)))))
|
||||
(first (vals s)))
|
||||
7)
|
||||
|
||||
;; --- multiple vars + multiple goals ---
|
||||
|
||||
(mk-test
|
||||
"fresh-two-vars-three-goals"
|
||||
(let
|
||||
((q (mk-var "q"))
|
||||
(g
|
||||
(fresh
|
||||
(x y)
|
||||
(== x 10)
|
||||
(== y 20)
|
||||
(== q (list x y)))))
|
||||
(mk-walk* q (first (stream-take 5 (g empty-s)))))
|
||||
(list 10 20))
|
||||
|
||||
(mk-test
|
||||
"fresh-three-vars"
|
||||
(let
|
||||
((q (mk-var "q"))
|
||||
(g
|
||||
(fresh
|
||||
(a b c)
|
||||
(== a 1)
|
||||
(== b 2)
|
||||
(== c 3)
|
||||
(== q (list a b c)))))
|
||||
(mk-walk* q (first (stream-take 5 (g empty-s)))))
|
||||
(list 1 2 3))
|
||||
|
||||
;; --- fresh interacts with disj ---
|
||||
|
||||
(mk-test
|
||||
"fresh-with-disj"
|
||||
(let
|
||||
((q (mk-var "q")))
|
||||
(let
|
||||
((g (fresh (x) (mk-disj (== x 1) (== x 2)) (== q x))))
|
||||
(let
|
||||
((res (stream-take 5 (g empty-s))))
|
||||
(map (fn (s) (mk-walk q s)) res))))
|
||||
(list 1 2))
|
||||
|
||||
;; --- nested fresh ---
|
||||
|
||||
(mk-test
|
||||
"fresh-nested"
|
||||
(let
|
||||
((q (mk-var "q"))
|
||||
(g
|
||||
(fresh
|
||||
(x)
|
||||
(fresh
|
||||
(y)
|
||||
(== x 1)
|
||||
(== y 2)
|
||||
(== q (list x y))))))
|
||||
(mk-walk* q (first (stream-take 5 (g empty-s)))))
|
||||
(list 1 2))
|
||||
|
||||
;; --- call-fresh (functional alternative) ---
|
||||
|
||||
(mk-test
|
||||
"call-fresh-binds-and-walks"
|
||||
(let
|
||||
((s (first (stream-take 5 ((call-fresh (fn (x) (== x 99))) empty-s)))))
|
||||
(first (vals s)))
|
||||
99)
|
||||
|
||||
(mk-test
|
||||
"call-fresh-distinct-from-outer-vars"
|
||||
(let
|
||||
((q (mk-var "q")))
|
||||
(let
|
||||
((g (call-fresh (fn (x) (mk-conj (== x 5) (== q (list x x)))))))
|
||||
(mk-walk* q (first (stream-take 5 (g empty-s))))))
|
||||
(list 5 5))
|
||||
|
||||
(mk-tests-run!)
|
||||
260
lib/minikanren/tests/goals.sx
Normal file
260
lib/minikanren/tests/goals.sx
Normal file
@@ -0,0 +1,260 @@
|
||||
;; lib/minikanren/tests/goals.sx — Phase 2 tests for stream.sx + goals.sx.
|
||||
;;
|
||||
;; Streams use a tagged shape internally (`(:s head tail)`) so that mature
|
||||
;; cells can have thunk tails — SX has no improper pairs. Test assertions
|
||||
;; therefore stream-take into a plain SX list, or check goal effects via
|
||||
;; mk-walk on the resulting subst, instead of inspecting raw streams.
|
||||
|
||||
;; --- stream-take base cases (input streams use s-cons / mzero) ---
|
||||
|
||||
(mk-test
|
||||
"stream-take-zero-from-mature"
|
||||
(stream-take 0 (s-cons (empty-subst) mzero))
|
||||
(list))
|
||||
|
||||
(mk-test "stream-take-from-mzero" (stream-take 5 mzero) (list))
|
||||
|
||||
(mk-test
|
||||
"stream-take-mature-pair"
|
||||
(stream-take 5 (s-cons :a (s-cons :b mzero)))
|
||||
(list :a :b))
|
||||
|
||||
(mk-test
|
||||
"stream-take-fewer-than-available"
|
||||
(stream-take 1 (s-cons :a (s-cons :b mzero)))
|
||||
(list :a))
|
||||
|
||||
(mk-test
|
||||
"stream-take-all-with-neg-1"
|
||||
(stream-take -1 (s-cons :a (s-cons :b (s-cons :c mzero))))
|
||||
(list :a :b :c))
|
||||
|
||||
;; --- stream-take forces immature thunks ---
|
||||
|
||||
(mk-test
|
||||
"stream-take-forces-thunk"
|
||||
(stream-take 5 (fn () (s-cons :x mzero)))
|
||||
(list :x))
|
||||
|
||||
(mk-test
|
||||
"stream-take-forces-nested-thunks"
|
||||
(stream-take 5 (fn () (fn () (s-cons :y mzero))))
|
||||
(list :y))
|
||||
|
||||
;; --- mk-mplus interleaves ---
|
||||
|
||||
(mk-test
|
||||
"mplus-empty-left"
|
||||
(stream-take 5 (mk-mplus mzero (s-cons :r mzero)))
|
||||
(list :r))
|
||||
|
||||
(mk-test
|
||||
"mplus-empty-right"
|
||||
(stream-take 5 (mk-mplus (s-cons :l mzero) mzero))
|
||||
(list :l))
|
||||
|
||||
(mk-test
|
||||
"mplus-mature-mature"
|
||||
(stream-take
|
||||
5
|
||||
(mk-mplus (s-cons :a (s-cons :b mzero)) (s-cons :c (s-cons :d mzero))))
|
||||
(list :a :b :c :d))
|
||||
|
||||
(mk-test
|
||||
"mplus-with-paused-left-swaps"
|
||||
(stream-take
|
||||
5
|
||||
(mk-mplus
|
||||
(fn () (s-cons :a (s-cons :b mzero)))
|
||||
(s-cons :c (s-cons :d mzero))))
|
||||
(list :c :d :a :b))
|
||||
|
||||
;; --- mk-bind ---
|
||||
|
||||
(mk-test
|
||||
"bind-empty-stream"
|
||||
(stream-take 5 (mk-bind mzero (fn (s) (unit s))))
|
||||
(list))
|
||||
|
||||
(mk-test
|
||||
"bind-singleton-identity"
|
||||
(stream-take
|
||||
5
|
||||
(mk-bind (s-cons 5 mzero) (fn (x) (unit x))))
|
||||
(list 5))
|
||||
|
||||
(mk-test
|
||||
"bind-flat-multi"
|
||||
(stream-take
|
||||
10
|
||||
(mk-bind
|
||||
(s-cons 1 (s-cons 2 mzero))
|
||||
(fn (x) (s-cons x (s-cons (* x 10) mzero)))))
|
||||
(list 1 10 2 20))
|
||||
|
||||
(mk-test
|
||||
"bind-fail-prunes-some"
|
||||
(stream-take
|
||||
10
|
||||
(mk-bind
|
||||
(s-cons 1 (s-cons 2 (s-cons 3 mzero)))
|
||||
(fn (x) (if (= x 2) mzero (unit x)))))
|
||||
(list 1 3))
|
||||
|
||||
;; --- core goals: succeed / fail ---
|
||||
|
||||
(mk-test
|
||||
"succeed-yields-singleton"
|
||||
(stream-take 5 (succeed empty-s))
|
||||
(list empty-s))
|
||||
|
||||
(mk-test "fail-yields-mzero" (stream-take 5 (fail empty-s)) (list))
|
||||
|
||||
;; --- == ---
|
||||
|
||||
(mk-test
|
||||
"eq-ground-success"
|
||||
(stream-take 5 ((== 1 1) empty-s))
|
||||
(list empty-s))
|
||||
|
||||
(mk-test
|
||||
"eq-ground-failure"
|
||||
(stream-take 5 ((== 1 2) empty-s))
|
||||
(list))
|
||||
|
||||
(mk-test
|
||||
"eq-binds-var"
|
||||
(let
|
||||
((x (mk-var "x")))
|
||||
(mk-walk
|
||||
x
|
||||
(first (stream-take 5 ((== x 7) empty-s)))))
|
||||
7)
|
||||
|
||||
(mk-test
|
||||
"eq-list-success"
|
||||
(let
|
||||
((x (mk-var "x")))
|
||||
(mk-walk
|
||||
x
|
||||
(first
|
||||
(stream-take
|
||||
5
|
||||
((== x (list 1 2)) empty-s)))))
|
||||
(list 1 2))
|
||||
|
||||
(mk-test
|
||||
"eq-list-mismatch-fails"
|
||||
(stream-take
|
||||
5
|
||||
((== (list 1 2) (list 1 3)) empty-s))
|
||||
(list))
|
||||
|
||||
;; --- conj2 / mk-conj ---
|
||||
|
||||
(mk-test
|
||||
"conj2-both-bind"
|
||||
(let
|
||||
((x (mk-var "x")) (y (mk-var "y")))
|
||||
(let
|
||||
((s (first (stream-take 5 ((conj2 (== x 1) (== y 2)) empty-s)))))
|
||||
(list (mk-walk x s) (mk-walk y s))))
|
||||
(list 1 2))
|
||||
|
||||
(mk-test
|
||||
"conj2-conflict-empty"
|
||||
(let
|
||||
((x (mk-var "x")))
|
||||
(stream-take
|
||||
5
|
||||
((conj2 (== x 1) (== x 2)) empty-s)))
|
||||
(list))
|
||||
|
||||
(mk-test
|
||||
"conj-empty-is-succeed"
|
||||
(stream-take 5 ((mk-conj) empty-s))
|
||||
(list empty-s))
|
||||
|
||||
(mk-test
|
||||
"conj-single-is-goal"
|
||||
(let
|
||||
((x (mk-var "x")))
|
||||
(mk-walk
|
||||
x
|
||||
(first
|
||||
(stream-take 5 ((mk-conj (== x 99)) empty-s)))))
|
||||
99)
|
||||
|
||||
(mk-test
|
||||
"conj-three-bindings"
|
||||
(let
|
||||
((x (mk-var "x")) (y (mk-var "y")) (z (mk-var "z")))
|
||||
(let
|
||||
((s (first (stream-take 5 ((mk-conj (== x 1) (== y 2) (== z 3)) empty-s)))))
|
||||
(list (mk-walk x s) (mk-walk y s) (mk-walk z s))))
|
||||
(list 1 2 3))
|
||||
|
||||
;; --- disj2 / mk-disj ---
|
||||
|
||||
(mk-test
|
||||
"disj2-both-succeed"
|
||||
(let
|
||||
((q (mk-var "q")))
|
||||
(let
|
||||
((res (stream-take 5 ((disj2 (== q 1) (== q 2)) empty-s))))
|
||||
(map (fn (s) (mk-walk q s)) res)))
|
||||
(list 1 2))
|
||||
|
||||
(mk-test
|
||||
"disj2-fail-or-succeed"
|
||||
(let
|
||||
((q (mk-var "q")))
|
||||
(let
|
||||
((res (stream-take 5 ((disj2 fail (== q 5)) empty-s))))
|
||||
(map (fn (s) (mk-walk q s)) res)))
|
||||
(list 5))
|
||||
|
||||
(mk-test
|
||||
"disj-empty-is-fail"
|
||||
(stream-take 5 ((mk-disj) empty-s))
|
||||
(list))
|
||||
|
||||
(mk-test
|
||||
"disj-three-clauses"
|
||||
(let
|
||||
((q (mk-var "q")))
|
||||
(let
|
||||
((res (stream-take 5 ((mk-disj (== q "a") (== q "b") (== q "c")) empty-s))))
|
||||
(map (fn (s) (mk-walk q s)) res)))
|
||||
(list "a" "b" "c"))
|
||||
|
||||
;; --- conj/disj nesting ---
|
||||
|
||||
(mk-test
|
||||
"disj-of-conj"
|
||||
(let
|
||||
((x (mk-var "x")) (y (mk-var "y")))
|
||||
(let
|
||||
((res (stream-take 5 ((mk-disj (mk-conj (== x 1) (== y 2)) (mk-conj (== x 3) (== y 4))) empty-s))))
|
||||
(map (fn (s) (list (mk-walk x s) (mk-walk y s))) res)))
|
||||
(list (list 1 2) (list 3 4)))
|
||||
|
||||
;; --- ==-check ---
|
||||
|
||||
(mk-test
|
||||
"eq-check-no-occurs-fails"
|
||||
(let
|
||||
((x (mk-var "x")))
|
||||
(stream-take 5 ((==-check x (list 1 x)) empty-s)))
|
||||
(list))
|
||||
|
||||
(mk-test
|
||||
"eq-check-no-occurs-non-occurring-succeeds"
|
||||
(let
|
||||
((x (mk-var "x")))
|
||||
(mk-walk
|
||||
x
|
||||
(first (stream-take 5 ((==-check x 5) empty-s)))))
|
||||
5)
|
||||
|
||||
(mk-tests-run!)
|
||||
70
lib/minikanren/tests/graph.sx
Normal file
70
lib/minikanren/tests/graph.sx
Normal file
@@ -0,0 +1,70 @@
|
||||
;; lib/minikanren/tests/graph.sx — directed-graph reachability via patho.
|
||||
|
||||
(define
|
||||
test-edges
|
||||
(list (list :a :b) (list :b :c) (list :c :d) (list :a :c) (list :d :e)))
|
||||
|
||||
(define edgeo (fn (from to) (membero (list from to) test-edges)))
|
||||
|
||||
(define
|
||||
patho
|
||||
(fn
|
||||
(x y path)
|
||||
(conde
|
||||
((edgeo x y) (== path (list x y)))
|
||||
((fresh (z mid-path) (edgeo x z) (patho z y mid-path) (conso x mid-path path))))))
|
||||
|
||||
;; --- direct edges ---
|
||||
|
||||
(mk-test "patho-direct" (run* q (patho :a :b q)) (list (list :a :b)))
|
||||
|
||||
(mk-test "patho-no-direct-edge" (run* q (patho :e :a q)) (list))
|
||||
|
||||
;; --- indirect ---
|
||||
|
||||
(mk-test
|
||||
"patho-multi-hop"
|
||||
(let
|
||||
((paths (run* q (patho :a :d q))))
|
||||
(and
|
||||
(= (len paths) 2)
|
||||
(and
|
||||
(some (fn (p) (= p (list :a :b :c :d))) paths)
|
||||
(some (fn (p) (= p (list :a :c :d))) paths))))
|
||||
true)
|
||||
|
||||
(mk-test
|
||||
"patho-to-leaf"
|
||||
(let
|
||||
((paths (run* q (patho :a :e q))))
|
||||
(and
|
||||
(= (len paths) 2)
|
||||
(and
|
||||
(some (fn (p) (= p (list :a :b :c :d :e))) paths)
|
||||
(some (fn (p) (= p (list :a :c :d :e))) paths))))
|
||||
true)
|
||||
|
||||
;; --- enumeration with multiplicity ---
|
||||
;; Each path contributes one tuple, so reachable nodes can repeat. Here
|
||||
;; targets are: b (1 path), c (2 paths), d (2 paths), e (2 paths) = 7.
|
||||
|
||||
(mk-test
|
||||
"patho-enumerate-from-a-with-multiplicity"
|
||||
(let
|
||||
((targets (run* q (fresh (path) (patho :a q path)))))
|
||||
(and
|
||||
(= (len targets) 7)
|
||||
(and
|
||||
(some (fn (t) (= t :b)) targets)
|
||||
(and
|
||||
(some (fn (t) (= t :c)) targets)
|
||||
(and
|
||||
(some (fn (t) (= t :d)) targets)
|
||||
(some (fn (t) (= t :e)) targets))))))
|
||||
true)
|
||||
|
||||
;; --- unreachable target ---
|
||||
|
||||
(mk-test "patho-unreachable" (run* q (patho :a :z q)) (list))
|
||||
|
||||
(mk-tests-run!)
|
||||
103
lib/minikanren/tests/intarith.sx
Normal file
103
lib/minikanren/tests/intarith.sx
Normal file
@@ -0,0 +1,103 @@
|
||||
;; lib/minikanren/tests/intarith.sx — ground-only integer arithmetic
|
||||
;; goals that escape into host operations via project.
|
||||
|
||||
;; --- pluso-i ---
|
||||
|
||||
(mk-test
|
||||
"pluso-i-forward"
|
||||
(run* q (pluso-i 7 8 q))
|
||||
(list 15))
|
||||
(mk-test
|
||||
"pluso-i-zero"
|
||||
(run* q (pluso-i 0 0 q))
|
||||
(list 0))
|
||||
(mk-test
|
||||
"pluso-i-negatives"
|
||||
(run* q (pluso-i -5 3 q))
|
||||
(list -2))
|
||||
(mk-test
|
||||
"pluso-i-non-ground-fails"
|
||||
(run* q (fresh (a) (pluso-i a 3 5)))
|
||||
(list))
|
||||
|
||||
;; --- minuso-i ---
|
||||
|
||||
(mk-test
|
||||
"minuso-i-forward"
|
||||
(run* q (minuso-i 10 4 q))
|
||||
(list 6))
|
||||
(mk-test
|
||||
"minuso-i-zero"
|
||||
(run* q (minuso-i 5 5 q))
|
||||
(list 0))
|
||||
|
||||
;; --- *o-i ---
|
||||
|
||||
(mk-test
|
||||
"times-i-forward"
|
||||
(run* q (*o-i 6 7 q))
|
||||
(list 42))
|
||||
(mk-test
|
||||
"times-i-by-zero"
|
||||
(run* q (*o-i 0 99 q))
|
||||
(list 0))
|
||||
(mk-test
|
||||
"times-i-by-one"
|
||||
(run* q (*o-i 1 17 q))
|
||||
(list 17))
|
||||
|
||||
;; --- comparisons ---
|
||||
|
||||
(mk-test
|
||||
"lto-i-true"
|
||||
(run 1 q (lto-i 2 5))
|
||||
(list (make-symbol "_.0")))
|
||||
(mk-test "lto-i-false" (run* q (lto-i 5 2)) (list))
|
||||
(mk-test "lto-i-equal-false" (run* q (lto-i 3 3)) (list))
|
||||
|
||||
(mk-test
|
||||
"lteo-i-equal"
|
||||
(run 1 q (lteo-i 4 4))
|
||||
(list (make-symbol "_.0")))
|
||||
(mk-test
|
||||
"lteo-i-less"
|
||||
(run 1 q (lteo-i 1 4))
|
||||
(list (make-symbol "_.0")))
|
||||
(mk-test "lteo-i-more" (run* q (lteo-i 9 4)) (list))
|
||||
|
||||
(mk-test
|
||||
"neqo-i-different"
|
||||
(run 1 q (neqo-i 3 5))
|
||||
(list (make-symbol "_.0")))
|
||||
(mk-test "neqo-i-same" (run* q (neqo-i 3 3)) (list))
|
||||
|
||||
;; --- composition with relational vars ---
|
||||
|
||||
(mk-test
|
||||
"intarith-with-membero"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x)
|
||||
(membero
|
||||
x
|
||||
(list 1 2 3 4 5))
|
||||
(lto-i x 3)
|
||||
(== q x)))
|
||||
(list 1 2))
|
||||
|
||||
(mk-test "even-i-pos" (run* q (even-i 4)) (list (make-symbol "_.0")))
|
||||
|
||||
(mk-test "even-i-neg" (run* q (even-i 5)) (list))
|
||||
|
||||
(mk-test "odd-i-pos" (run* q (odd-i 7)) (list (make-symbol "_.0")))
|
||||
|
||||
(mk-test "odd-i-neg" (run* q (odd-i 4)) (list))
|
||||
|
||||
(mk-test
|
||||
"even-i-filter"
|
||||
(run* q (fresh (x) (membero x (list 1 2 3 4 5 6)) (even-i x) (== q x)))
|
||||
(list 2 4 6))
|
||||
|
||||
(mk-tests-run!)
|
||||
|
||||
38
lib/minikanren/tests/iterate-no.sx
Normal file
38
lib/minikanren/tests/iterate-no.sx
Normal file
@@ -0,0 +1,38 @@
|
||||
;; lib/minikanren/tests/iterate-no.sx — iterated relation application.
|
||||
|
||||
(define
|
||||
mk-nat
|
||||
(fn (n) (if (= n 0) :z (list :s (mk-nat (- n 1))))))
|
||||
|
||||
(mk-test
|
||||
"iterate-no-zero"
|
||||
(run*
|
||||
q
|
||||
(iterate-no
|
||||
(fn (a b) (== b (list :wrap a)))
|
||||
(mk-nat 0)
|
||||
:seed q))
|
||||
(list :seed))
|
||||
|
||||
(mk-test
|
||||
"iterate-no-three-wraps"
|
||||
(run*
|
||||
q
|
||||
(iterate-no (fn (a b) (== b (list :wrap a))) (mk-nat 3) :x q))
|
||||
(list (list :wrap (list :wrap (list :wrap :x)))))
|
||||
|
||||
(mk-test
|
||||
"iterate-no-succ-three-times"
|
||||
(run*
|
||||
q
|
||||
(iterate-no (fn (a b) (== b (list :s a))) (mk-nat 3) :z q))
|
||||
(list (mk-nat 3)))
|
||||
|
||||
(mk-test
|
||||
"iterate-no-with-list-cons"
|
||||
(run*
|
||||
q
|
||||
(iterate-no (fn (a b) (conso :a a b)) (mk-nat 4) (list) q))
|
||||
(list (list :a :a :a :a)))
|
||||
|
||||
(mk-tests-run!)
|
||||
38
lib/minikanren/tests/lasto.sx
Normal file
38
lib/minikanren/tests/lasto.sx
Normal file
@@ -0,0 +1,38 @@
|
||||
;; lib/minikanren/tests/lasto.sx — last-element + init-without-last.
|
||||
|
||||
(mk-test
|
||||
"lasto-singleton"
|
||||
(run* q (lasto (list 5) q))
|
||||
(list 5))
|
||||
(mk-test
|
||||
"lasto-multi"
|
||||
(run* q (lasto (list 1 2 3 4) q))
|
||||
(list 4))
|
||||
(mk-test "lasto-empty" (run* q (lasto (list) q)) (list))
|
||||
|
||||
(mk-test "lasto-strings" (run* q (lasto (list "a" "b" "c") q)) (list "c"))
|
||||
|
||||
(mk-test
|
||||
"init-o-multi"
|
||||
(run* q (init-o (list 1 2 3 4) q))
|
||||
(list (list 1 2 3)))
|
||||
|
||||
(mk-test
|
||||
"init-o-singleton"
|
||||
(run* q (init-o (list 7) q))
|
||||
(list (list)))
|
||||
|
||||
(mk-test "init-o-empty" (run* q (init-o (list) q)) (list))
|
||||
|
||||
(mk-test
|
||||
"lasto-init-o-roundtrip"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(init last)
|
||||
(lasto (list 1 2 3 4) last)
|
||||
(init-o (list 1 2 3 4) init)
|
||||
(appendo init (list last) q)))
|
||||
(list (list 1 2 3 4)))
|
||||
|
||||
(mk-tests-run!)
|
||||
61
lib/minikanren/tests/latin.sx
Normal file
61
lib/minikanren/tests/latin.sx
Normal file
@@ -0,0 +1,61 @@
|
||||
;; lib/minikanren/tests/latin.sx — 2x2 Latin square via ino + all-distincto.
|
||||
;;
|
||||
;; A 2x2 Latin square has 2 distinct fillings:
|
||||
;; ((1 2) (2 1)) and ((2 1) (1 2)).
|
||||
;; The 3x3 version has 12 fillings but takes minutes under naive search;
|
||||
;; full CLP(FD) (Phase 6 proper) would handle it in milliseconds.
|
||||
|
||||
(define
|
||||
latin-2x2
|
||||
(fn
|
||||
(cells)
|
||||
(let
|
||||
((c11 (nth cells 0))
|
||||
(c12 (nth cells 1))
|
||||
(c21 (nth cells 2))
|
||||
(c22 (nth cells 3))
|
||||
(dom (list 1 2)))
|
||||
(mk-conj
|
||||
(ino c11 dom)
|
||||
(ino c12 dom)
|
||||
(ino c21 dom)
|
||||
(ino c22 dom)
|
||||
(all-distincto (list c11 c12))
|
||||
(all-distincto (list c21 c22))
|
||||
(all-distincto (list c11 c21))
|
||||
(all-distincto (list c12 c22)))))) ;; col 2
|
||||
|
||||
(mk-test
|
||||
"latin-2x2-count"
|
||||
(let
|
||||
((squares (run* q (fresh (a b c d) (== q (list a b c d)) (latin-2x2 (list a b c d))))))
|
||||
(len squares))
|
||||
2)
|
||||
|
||||
(mk-test
|
||||
"latin-2x2-as-set"
|
||||
(let
|
||||
((squares (run* q (fresh (a b c d) (== q (list a b c d)) (latin-2x2 (list a b c d))))))
|
||||
(and
|
||||
(= (len squares) 2)
|
||||
(and
|
||||
(some
|
||||
(fn (s) (= s (list 1 2 2 1)))
|
||||
squares)
|
||||
(some
|
||||
(fn (s) (= s (list 2 1 1 2)))
|
||||
squares))))
|
||||
true)
|
||||
|
||||
(mk-test
|
||||
"latin-2x2-with-clue"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(a b c d)
|
||||
(== a 1)
|
||||
(== q (list a b c d))
|
||||
(latin-2x2 (list a b c d))))
|
||||
(list (list 1 2 2 1)))
|
||||
|
||||
(mk-tests-run!)
|
||||
77
lib/minikanren/tests/laziness.sx
Normal file
77
lib/minikanren/tests/laziness.sx
Normal file
@@ -0,0 +1,77 @@
|
||||
;; lib/minikanren/tests/laziness.sx — verify Zzz wrapping (in conde)
|
||||
;; lets infinitely-recursive relations produce finite prefixes via run-n.
|
||||
|
||||
;; --- a relation that has no base case but conde-protects via Zzz ---
|
||||
|
||||
(define
|
||||
listo-aux
|
||||
(fn
|
||||
(l)
|
||||
(conde ((nullo l)) ((fresh (a d) (conso a d l) (listo-aux d))))))
|
||||
|
||||
(mk-test
|
||||
"infinite-relation-truncates-via-run-n"
|
||||
(run 4 q (listo-aux q))
|
||||
(list
|
||||
(list)
|
||||
(list (make-symbol "_.0"))
|
||||
(list (make-symbol "_.0") (make-symbol "_.1"))
|
||||
(list (make-symbol "_.0") (make-symbol "_.1") (make-symbol "_.2"))))
|
||||
|
||||
;; --- two infinite generators interleaved via mk-disj must both produce
|
||||
;; answers (no starvation) — the fairness test ---
|
||||
|
||||
(define
|
||||
ones-gen
|
||||
(fn
|
||||
(l)
|
||||
(conde
|
||||
((== l (list)))
|
||||
((fresh (d) (conso 1 d l) (ones-gen d))))))
|
||||
|
||||
(define
|
||||
twos-gen
|
||||
(fn
|
||||
(l)
|
||||
(conde
|
||||
((== l (list)))
|
||||
((fresh (d) (conso 2 d l) (twos-gen d))))))
|
||||
|
||||
(mk-test
|
||||
"interleaving-keeps-both-streams-alive"
|
||||
(let
|
||||
((res (run 4 q (mk-disj (ones-gen q) (twos-gen q)))))
|
||||
(and
|
||||
(= (len res) 4)
|
||||
(and
|
||||
(some
|
||||
(fn
|
||||
(x)
|
||||
(and
|
||||
(list? x)
|
||||
(and (not (empty? x)) (= (first x) 1))))
|
||||
res)
|
||||
(some
|
||||
(fn
|
||||
(x)
|
||||
(and
|
||||
(list? x)
|
||||
(and (not (empty? x)) (= (first x) 2))))
|
||||
res))))
|
||||
true)
|
||||
|
||||
;; --- run* terminates on a relation whose conde has finite base case
|
||||
;; reached from any starting point ---
|
||||
|
||||
(mk-test
|
||||
"run-star-terminates-on-bounded-relation"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(l)
|
||||
(== l (list 1 2 3))
|
||||
(listo l)
|
||||
(== q :ok)))
|
||||
(list :ok))
|
||||
|
||||
(mk-tests-run!)
|
||||
28
lib/minikanren/tests/lengtho-i.sx
Normal file
28
lib/minikanren/tests/lengtho-i.sx
Normal file
@@ -0,0 +1,28 @@
|
||||
;; lib/minikanren/tests/lengtho-i.sx — integer-indexed length (fast).
|
||||
|
||||
(mk-test "lengtho-i-empty" (run* q (lengtho-i (list) q)) (list 0))
|
||||
(mk-test
|
||||
"lengtho-i-singleton"
|
||||
(run* q (lengtho-i (list :a) q))
|
||||
(list 1))
|
||||
(mk-test
|
||||
"lengtho-i-three"
|
||||
(run* q (lengtho-i (list 1 2 3) q))
|
||||
(list 3))
|
||||
(mk-test
|
||||
"lengtho-i-five"
|
||||
(run*
|
||||
q
|
||||
(lengtho-i
|
||||
(list 1 2 3 4 5)
|
||||
q))
|
||||
(list 5))
|
||||
|
||||
(mk-test
|
||||
"lengtho-i-mixed-types"
|
||||
(run*
|
||||
q
|
||||
(lengtho-i (list 1 "two" :three (list 4 5)) q))
|
||||
(list 4))
|
||||
|
||||
(mk-tests-run!)
|
||||
126
lib/minikanren/tests/list-relations.sx
Normal file
126
lib/minikanren/tests/list-relations.sx
Normal file
@@ -0,0 +1,126 @@
|
||||
;; lib/minikanren/tests/list-relations.sx — rembero, assoco, nth-o, samelengtho.
|
||||
|
||||
;; --- rembero (remove first occurrence) ---
|
||||
|
||||
(mk-test
|
||||
"rembero-element-present"
|
||||
(run*
|
||||
q
|
||||
(rembero 2 (list 1 2 3 2) q))
|
||||
(list (list 1 3 2)))
|
||||
|
||||
(mk-test
|
||||
"rembero-element-not-present"
|
||||
(run* q (rembero 99 (list 1 2 3) q))
|
||||
(list (list 1 2 3)))
|
||||
|
||||
(mk-test
|
||||
"rembero-empty"
|
||||
(run* q (rembero 1 (list) q))
|
||||
(list (list)))
|
||||
|
||||
(mk-test
|
||||
"rembero-only-element"
|
||||
(run* q (rembero 5 (list 5) q))
|
||||
(list (list)))
|
||||
|
||||
(mk-test
|
||||
"rembero-first-of-many"
|
||||
(run*
|
||||
q
|
||||
(rembero 1 (list 1 2 3 4) q))
|
||||
(list (list 2 3 4)))
|
||||
|
||||
;; --- assoco (alist lookup) ---
|
||||
|
||||
(define
|
||||
test-pairs
|
||||
(list
|
||||
(list "alice" 30)
|
||||
(list "bob" 25)
|
||||
(list "carol" 35)))
|
||||
|
||||
(mk-test
|
||||
"assoco-found"
|
||||
(run* q (assoco "bob" test-pairs q))
|
||||
(list 25))
|
||||
|
||||
(mk-test
|
||||
"assoco-first"
|
||||
(run* q (assoco "alice" test-pairs q))
|
||||
(list 30))
|
||||
|
||||
(mk-test "assoco-missing" (run* q (assoco "dave" test-pairs q)) (list))
|
||||
|
||||
(mk-test
|
||||
"assoco-find-keys-with-value"
|
||||
(run* q (assoco q test-pairs 25))
|
||||
(list "bob"))
|
||||
|
||||
;; --- nth-o (Peano-indexed access) ---
|
||||
|
||||
(mk-test
|
||||
"nth-o-zero"
|
||||
(run* q (nth-o :z (list 10 20 30) q))
|
||||
(list 10))
|
||||
|
||||
(mk-test
|
||||
"nth-o-one"
|
||||
(run* q (nth-o (list :s :z) (list 10 20 30) q))
|
||||
(list 20))
|
||||
|
||||
(mk-test
|
||||
"nth-o-two"
|
||||
(run*
|
||||
q
|
||||
(nth-o (list :s (list :s :z)) (list 10 20 30) q))
|
||||
(list 30))
|
||||
|
||||
(mk-test
|
||||
"nth-o-out-of-range"
|
||||
(run*
|
||||
q
|
||||
(nth-o
|
||||
(list :s (list :s (list :s :z)))
|
||||
(list 10 20 30)
|
||||
q))
|
||||
(list))
|
||||
|
||||
;; --- samelengtho ---
|
||||
|
||||
(mk-test
|
||||
"samelengtho-equal"
|
||||
(run*
|
||||
q
|
||||
(samelengtho (list 1 2 3) (list :a :b :c)))
|
||||
(list (make-symbol "_.0")))
|
||||
|
||||
(mk-test
|
||||
"samelengtho-different-fails"
|
||||
(run* q (samelengtho (list 1 2) (list :a :b :c)))
|
||||
(list))
|
||||
|
||||
(mk-test
|
||||
"samelengtho-empty-equal"
|
||||
(run* q (samelengtho (list) (list)))
|
||||
(list (make-symbol "_.0")))
|
||||
|
||||
(mk-test
|
||||
"samelengtho-builds-vars"
|
||||
(run 1 q (samelengtho (list 1 2 3) q))
|
||||
(list (list (make-symbol "_.0") (make-symbol "_.1") (make-symbol "_.2"))))
|
||||
|
||||
(mk-test
|
||||
"samelengtho-enumerates-pairs"
|
||||
(run
|
||||
3
|
||||
q
|
||||
(fresh (l1 l2) (samelengtho l1 l2) (== q (list l1 l2))))
|
||||
(list
|
||||
(list (list) (list))
|
||||
(list (list (make-symbol "_.0")) (list (make-symbol "_.1")))
|
||||
(list
|
||||
(list (make-symbol "_.0") (make-symbol "_.1"))
|
||||
(list (make-symbol "_.2") (make-symbol "_.3")))))
|
||||
|
||||
(mk-tests-run!)
|
||||
62
lib/minikanren/tests/mapo.sx
Normal file
62
lib/minikanren/tests/mapo.sx
Normal file
@@ -0,0 +1,62 @@
|
||||
;; lib/minikanren/tests/mapo.sx — relational map.
|
||||
|
||||
(mk-test
|
||||
"mapo-identity"
|
||||
(run*
|
||||
q
|
||||
(mapo (fn (a b) (== a b)) (list 1 2 3) q))
|
||||
(list (list 1 2 3)))
|
||||
|
||||
(mk-test
|
||||
"mapo-tag-each"
|
||||
(run*
|
||||
q
|
||||
(mapo
|
||||
(fn (a b) (== b (list :tag a)))
|
||||
(list 1 2 3)
|
||||
q))
|
||||
(list
|
||||
(list
|
||||
(list :tag 1)
|
||||
(list :tag 2)
|
||||
(list :tag 3))))
|
||||
|
||||
(mk-test
|
||||
"mapo-backward"
|
||||
(run*
|
||||
q
|
||||
(mapo (fn (a b) (== a b)) q (list 1 2 3)))
|
||||
(list (list 1 2 3)))
|
||||
|
||||
(mk-test
|
||||
"mapo-empty"
|
||||
(run* q (mapo (fn (a b) (== a b)) (list) q))
|
||||
(list (list)))
|
||||
|
||||
(mk-test
|
||||
"mapo-duplicate"
|
||||
(run* q (mapo (fn (a b) (== b (list a a))) (list :x :y) q))
|
||||
(list (list (list :x :x) (list :y :y))))
|
||||
|
||||
(mk-test
|
||||
"mapo-different-length-fails"
|
||||
(run*
|
||||
q
|
||||
(mapo
|
||||
(fn (a b) (== a b))
|
||||
(list 1 2)
|
||||
(list 1 2 3)))
|
||||
(list))
|
||||
|
||||
;; mapo + arithmetic via intarith
|
||||
(mk-test
|
||||
"mapo-square-each"
|
||||
(run*
|
||||
q
|
||||
(mapo
|
||||
(fn (a b) (*o-i a a b))
|
||||
(list 1 2 3 4)
|
||||
q))
|
||||
(list (list 1 4 9 16)))
|
||||
|
||||
(mk-tests-run!)
|
||||
138
lib/minikanren/tests/matche.sx
Normal file
138
lib/minikanren/tests/matche.sx
Normal file
@@ -0,0 +1,138 @@
|
||||
;; lib/minikanren/tests/matche.sx — Phase 5 piece D tests for `matche`.
|
||||
|
||||
;; --- literal patterns ---
|
||||
|
||||
(mk-test
|
||||
"matche-literal-number"
|
||||
(run* q (matche q (1 (== q 1))))
|
||||
(list 1))
|
||||
|
||||
(mk-test
|
||||
"matche-literal-string"
|
||||
(run* q (matche q ("hello" (== q "hello"))))
|
||||
(list "hello"))
|
||||
|
||||
(mk-test
|
||||
"matche-literal-no-clause-matches"
|
||||
(run*
|
||||
q
|
||||
(matche 7 (1 (== q :a)) (2 (== q :b))))
|
||||
(list))
|
||||
|
||||
;; --- variable patterns ---
|
||||
|
||||
(mk-test
|
||||
"matche-symbol-pattern"
|
||||
(run* q (fresh (x) (== x 99) (matche x (a (== q a)))))
|
||||
(list 99))
|
||||
|
||||
(mk-test
|
||||
"matche-wildcard"
|
||||
(run* q (fresh (x) (== x 7) (matche x (_ (== q :any)))))
|
||||
(list :any))
|
||||
|
||||
;; --- list patterns ---
|
||||
|
||||
(mk-test
|
||||
"matche-empty-list"
|
||||
(run* q (matche (list) (() (== q :ok))))
|
||||
(list :ok))
|
||||
|
||||
(mk-test
|
||||
"matche-pair-binds"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x)
|
||||
(== x (list 1 2))
|
||||
(matche x ((a b) (== q (list b a))))))
|
||||
(list (list 2 1)))
|
||||
|
||||
(mk-test
|
||||
"matche-triple-binds"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x)
|
||||
(== x (list 1 2 3))
|
||||
(matche x ((a b c) (== q (list :sum a b c))))))
|
||||
(list (list :sum 1 2 3)))
|
||||
|
||||
(mk-test
|
||||
"matche-mixed-literal-and-var"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x)
|
||||
(== x (list 1 99 3))
|
||||
(matche x ((1 m 3) (== q m)))))
|
||||
(list 99))
|
||||
|
||||
;; --- multi-clause dispatch ---
|
||||
|
||||
(mk-test
|
||||
"matche-multi-clause-shape"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x)
|
||||
(== x (list 5 6))
|
||||
(matche
|
||||
x
|
||||
(() (== q :empty))
|
||||
((a) (== q (list :one a)))
|
||||
((a b) (== q (list :two a b))))))
|
||||
(list (list :two 5 6)))
|
||||
|
||||
(mk-test
|
||||
"matche-three-shapes-via-fresh"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x)
|
||||
(matche
|
||||
x
|
||||
(() (== q :empty))
|
||||
((a) (== q (list :one a)))
|
||||
((a b) (== q (list :two a b))))))
|
||||
(list
|
||||
:empty (list :one (make-symbol "_.0"))
|
||||
(list :two (make-symbol "_.0") (make-symbol "_.1"))))
|
||||
|
||||
;; --- nested patterns ---
|
||||
|
||||
(mk-test
|
||||
"matche-nested"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x)
|
||||
(==
|
||||
x
|
||||
(list (list 1 2) (list 3 4)))
|
||||
(matche x (((a b) (c d)) (== q (list a b c d))))))
|
||||
(list (list 1 2 3 4)))
|
||||
|
||||
;; --- repeated var names create the same fresh var → must unify ---
|
||||
|
||||
(mk-test
|
||||
"matche-repeated-var-implies-equality"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x)
|
||||
(== x (list 7 7))
|
||||
(matche x ((a a) (== q a)))))
|
||||
(list 7))
|
||||
|
||||
(mk-test
|
||||
"matche-repeated-var-mismatch-fails"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x)
|
||||
(== x (list 7 8))
|
||||
(matche x ((a a) (== q a)))))
|
||||
(list))
|
||||
|
||||
(mk-tests-run!)
|
||||
49
lib/minikanren/tests/minmax.sx
Normal file
49
lib/minikanren/tests/minmax.sx
Normal file
@@ -0,0 +1,49 @@
|
||||
;; lib/minikanren/tests/minmax.sx — mino + maxo via intarith.
|
||||
|
||||
(mk-test
|
||||
"mino-singleton"
|
||||
(run* q (mino (list 7) q))
|
||||
(list 7))
|
||||
(mk-test
|
||||
"mino-of-3"
|
||||
(run* q (mino (list 5 1 3) q))
|
||||
(list 1))
|
||||
(mk-test
|
||||
"mino-of-5"
|
||||
(run*
|
||||
q
|
||||
(mino (list 5 1 3 2 4) q))
|
||||
(list 1))
|
||||
(mk-test
|
||||
"mino-with-dups"
|
||||
(run* q (mino (list 3 3 3) q))
|
||||
(list 3))
|
||||
(mk-test "mino-empty-fails" (run* q (mino (list) q)) (list))
|
||||
|
||||
(mk-test
|
||||
"maxo-singleton"
|
||||
(run* q (maxo (list 7) q))
|
||||
(list 7))
|
||||
(mk-test
|
||||
"maxo-of-5"
|
||||
(run*
|
||||
q
|
||||
(maxo (list 5 1 3 2 4) q))
|
||||
(list 5))
|
||||
(mk-test
|
||||
"maxo-of-negs"
|
||||
(run* q (maxo (list -5 -1 -3) q))
|
||||
(list -1))
|
||||
|
||||
(mk-test
|
||||
"min-and-max-of-list"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(mn mx)
|
||||
(mino (list 5 1 3 2 4) mn)
|
||||
(maxo (list 5 1 3 2 4) mx)
|
||||
(== q (list mn mx))))
|
||||
(list (list 1 5)))
|
||||
|
||||
(mk-tests-run!)
|
||||
50
lib/minikanren/tests/nafc.sx
Normal file
50
lib/minikanren/tests/nafc.sx
Normal file
@@ -0,0 +1,50 @@
|
||||
;; lib/minikanren/tests/nafc.sx — Phase 5 piece C tests for `nafc`.
|
||||
|
||||
(mk-test
|
||||
"nafc-failed-goal-succeeds"
|
||||
(run* q (nafc (== 1 2)))
|
||||
(list (make-symbol "_.0")))
|
||||
|
||||
(mk-test
|
||||
"nafc-successful-goal-fails"
|
||||
(run* q (nafc (== 1 1)))
|
||||
(list))
|
||||
|
||||
(mk-test
|
||||
"nafc-double-negation"
|
||||
(run* q (nafc (nafc (== 1 1))))
|
||||
(list (make-symbol "_.0")))
|
||||
|
||||
(mk-test
|
||||
"nafc-with-conde-no-clauses-succeed"
|
||||
(run*
|
||||
q
|
||||
(nafc
|
||||
(conde ((== 1 2)) ((== 3 4)))))
|
||||
(list (make-symbol "_.0")))
|
||||
|
||||
(mk-test
|
||||
"nafc-with-conde-some-clause-succeeds-fails"
|
||||
(run*
|
||||
q
|
||||
(nafc
|
||||
(conde ((== 1 1)) ((== 3 4)))))
|
||||
(list))
|
||||
|
||||
;; --- composing nafc with == as a guard ---
|
||||
|
||||
(mk-test
|
||||
"nafc-as-guard"
|
||||
(run*
|
||||
q
|
||||
(fresh (x) (== x 5) (nafc (== x 99)) (== q x)))
|
||||
(list 5))
|
||||
|
||||
(mk-test
|
||||
"nafc-guard-blocking"
|
||||
(run*
|
||||
q
|
||||
(fresh (x) (== x 5) (nafc (== x 5)) (== q x)))
|
||||
(list))
|
||||
|
||||
(mk-tests-run!)
|
||||
29
lib/minikanren/tests/not-membero.sx
Normal file
29
lib/minikanren/tests/not-membero.sx
Normal file
@@ -0,0 +1,29 @@
|
||||
;; lib/minikanren/tests/not-membero.sx — relational "not in list".
|
||||
|
||||
(mk-test
|
||||
"not-membero-absent"
|
||||
(run* q (not-membero 99 (list 1 2 3)))
|
||||
(list (make-symbol "_.0")))
|
||||
(mk-test
|
||||
"not-membero-present"
|
||||
(run* q (not-membero 2 (list 1 2 3)))
|
||||
(list))
|
||||
(mk-test
|
||||
"not-membero-empty"
|
||||
(run* q (not-membero 1 (list)))
|
||||
(list (make-symbol "_.0")))
|
||||
|
||||
(mk-test
|
||||
"not-membero-as-filter"
|
||||
(run*
|
||||
q
|
||||
(fresh
|
||||
(x)
|
||||
(membero
|
||||
x
|
||||
(list 1 2 3 4 5))
|
||||
(not-membero x (list 2 4))
|
||||
(== q x)))
|
||||
(list 1 3 5))
|
||||
|
||||
(mk-tests-run!)
|
||||
31
lib/minikanren/tests/nub-o.sx
Normal file
31
lib/minikanren/tests/nub-o.sx
Normal file
@@ -0,0 +1,31 @@
|
||||
;; lib/minikanren/tests/nub-o.sx — relational dedupe (keep last occurrence).
|
||||
|
||||
(mk-test "nub-o-empty" (run* q (nub-o (list) q)) (list (list)))
|
||||
|
||||
(mk-test
|
||||
"nub-o-no-duplicates"
|
||||
(run* q (nub-o (list 1 2 3) q))
|
||||
(list (list 1 2 3)))
|
||||
|
||||
(mk-test
|
||||
"nub-o-with-duplicates"
|
||||
(run*
|
||||
q
|
||||
(nub-o
|
||||
(list 1 2 1 3 2 4)
|
||||
q))
|
||||
(list (list 1 3 2 4)))
|
||||
|
||||
(mk-test
|
||||
"nub-o-all-same"
|
||||
(let
|
||||
((res (run* q (nub-o (list 1 1 1) q))))
|
||||
(every? (fn (r) (= r (list 1))) res))
|
||||
true)
|
||||
|
||||
(mk-test
|
||||
"nub-o-keeps-last"
|
||||
(run* q (nub-o (list 1 2 1) q))
|
||||
(list (list 2 1)))
|
||||
|
||||
(mk-tests-run!)
|
||||
41
lib/minikanren/tests/pairlisto.sx
Normal file
41
lib/minikanren/tests/pairlisto.sx
Normal file
@@ -0,0 +1,41 @@
|
||||
;; lib/minikanren/tests/pairlisto.sx — zip two lists into pair list.
|
||||
|
||||
(mk-test
|
||||
"pairlisto-empty"
|
||||
(run* q (pairlisto (list) (list) q))
|
||||
(list (list)))
|
||||
|
||||
(mk-test
|
||||
"pairlisto-equal-lengths"
|
||||
(run*
|
||||
q
|
||||
(pairlisto (list 1 2 3) (list :a :b :c) q))
|
||||
(list
|
||||
(list (list 1 :a) (list 2 :b) (list 3 :c))))
|
||||
|
||||
(mk-test
|
||||
"pairlisto-recover-l1"
|
||||
(run*
|
||||
q
|
||||
(pairlisto
|
||||
q
|
||||
(list :a :b :c)
|
||||
(list (list 10 :a) (list 20 :b) (list 30 :c))))
|
||||
(list (list 10 20 30)))
|
||||
|
||||
(mk-test
|
||||
"pairlisto-recover-l2"
|
||||
(run*
|
||||
q
|
||||
(pairlisto
|
||||
(list 1 2 3)
|
||||
q
|
||||
(list (list 1 :x) (list 2 :y) (list 3 :z))))
|
||||
(list (list :x :y :z)))
|
||||
|
||||
(mk-test
|
||||
"pairlisto-different-lengths-fails"
|
||||
(run* q (pairlisto (list 1 2) (list :a :b :c) q))
|
||||
(list))
|
||||
|
||||
(mk-tests-run!)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user