Compare commits
152 Commits
bugs/resum
...
loops/hask
| Author | SHA1 | Date | |
|---|---|---|---|
| 4510e7e475 | |||
| aa620b767f | |||
| 23afc9dde3 | |||
| badb428100 | |||
| e83c01cdcc | |||
| 544e79f533 | |||
| 1eb9d0f8d2 | |||
| f182d04e6a | |||
| ab2c40c14c | |||
| d3c34b46b9 | |||
| 80dac0051d | |||
| b661318a45 | |||
| 47d9d07f2e | |||
| d75c61d408 | |||
| 3dae27737c | |||
| f1fea0f2f1 | |||
| f962560652 | |||
| 863e9d93a4 | |||
| a677585639 | |||
| 2defa5e739 | |||
| 64157e9e81 | |||
| e0d447e2ce | |||
| 63ad4563cb | |||
| c04f38a1ba | |||
| 6915730029 | |||
| a774cd26c1 | |||
| b13819c50c | |||
| f26f25f146 | |||
| d9cf00f287 | |||
| 69a0886214 | |||
| 0c0ed0605a | |||
| 63c1e17c75 | |||
| a4fd57cff1 | |||
| 5f27125f01 | |||
| da27958d67 | |||
| d27622d45e | |||
| b6cf20dac7 | |||
| c8b232d40e | |||
| 76d141737a | |||
| 251e6e1bab | |||
| 9307437679 | |||
| b89e321007 | |||
| ca9e12fc57 | |||
| 0dd2fa3058 | |||
| 2adbc101fa | |||
| 67ff2a3ae8 | |||
| 4205989aee | |||
| 49252eaa5c | |||
| aaabe370d6 | |||
| 637ba4102f | |||
| ebbf0fc10c | |||
| 7cf8b74d1d | |||
| 8dfb3f6387 | |||
| d473f39b04 | |||
| d5e66474fe | |||
| 64d36fa66e | |||
| dec1cf3fbe | |||
| 5a8c25bec7 | |||
| c821e21f94 | |||
| 52df09655d | |||
| 5605fe1cc2 | |||
| 379bb93f14 | |||
| 7ce0c797f3 | |||
| 34513908df | |||
| 208953667b | |||
| e6d6273265 | |||
| e95ca4624b | |||
| 5a28cf5dd3 | |||
| f480eb943c | |||
| edc7e865b4 | |||
| e1a020dc90 | |||
| b0974b58c0 | |||
| 6620c0ac06 | |||
| 95cf653ba9 | |||
| ca151d7ed5 | |||
| 12de24e3a0 | |||
| 322eb1d034 | |||
| be820d0337 | |||
| 180b9009bf | |||
| a29bb6feca | |||
| d2638170db | |||
| a5c41d2573 | |||
| 882815e612 | |||
| e27daee4a8 | |||
| ef33e9a43a | |||
| 1b7bd86b43 | |||
| d755caeb9a | |||
| e5fe9ad2d4 | |||
| 3e77dd4ded | |||
| 2d373da06b | |||
| 0f13052900 | |||
| 25cf832998 | |||
| e37167a58e | |||
| 29542ba9d2 | |||
| c2de220cce | |||
| 49eb22243a | |||
| d523df30c2 | |||
| 20a61de693 | |||
| 1b844f6a19 | |||
| ed0853f4a0 | |||
| ec26b61cbe | |||
| 5f758d27c1 | |||
| bee4e0846c | |||
| 51f57aa2fa | |||
| f591ee17c3 | |||
| 31308602ca | |||
| 1900726fc9 | |||
| 16167c5d9b | |||
| 788e8682f5 | |||
| bb134b88e3 | |||
| 84d210b6b3 | |||
| d8dec07df3 | |||
| 3628a504db | |||
| 4c71c5a75e | |||
| 39c7baa44c | |||
| 9eecbde61e | |||
| 4dbd3a0b34 | |||
| ee74a396c5 | |||
| 3d2bdc52b5 | |||
| a8997ab452 | |||
| d570da1dea | |||
| 80d6507e57 | |||
| d67e04a9ad | |||
| 685fcd11d5 | |||
| 4332b4032f | |||
| 3489c9f131 | |||
| f6efba410a | |||
| c56f400403 | |||
| 4a35998469 | |||
| 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 ]
|
||||
674
lib/apl/parser.sx
Normal file
674
lib/apl/parser.sx
Normal file
@@ -0,0 +1,674 @@
|
||||
; 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-known-fn-names (list))
|
||||
|
||||
; ============================================================
|
||||
; Token accessors
|
||||
; ============================================================
|
||||
|
||||
(define
|
||||
apl-collect-fn-bindings
|
||||
(fn
|
||||
(stmt-groups)
|
||||
(set! apl-known-fn-names (list))
|
||||
(for-each
|
||||
(fn
|
||||
(toks)
|
||||
(when
|
||||
(and
|
||||
(>= (len toks) 3)
|
||||
(= (tok-type (nth toks 0)) :name)
|
||||
(= (tok-type (nth toks 1)) :assign)
|
||||
(= (tok-type (nth toks 2)) :lbrace))
|
||||
(set!
|
||||
apl-known-fn-names
|
||||
(cons (tok-val (nth toks 0)) apl-known-fn-names))))
|
||||
stmt-groups)))
|
||||
|
||||
(define
|
||||
apl-parse-op-glyph?
|
||||
(fn (v) (some (fn (g) (= g v)) apl-parse-op-glyphs)))
|
||||
|
||||
(define
|
||||
apl-parse-fn-glyph?
|
||||
(fn (v) (some (fn (g) (= g v)) apl-parse-fn-glyphs)))
|
||||
|
||||
(define tok-type (fn (tok) (get tok :type)))
|
||||
|
||||
; ============================================================
|
||||
; Collect trailing operators starting at index i
|
||||
; Returns {:ops (op ...) :end new-i}
|
||||
; ============================================================
|
||||
|
||||
(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)))))
|
||||
|
||||
; ============================================================
|
||||
; Build a derived-fn node by chaining operators left-to-right
|
||||
; (+/¨ → (:derived-fn "¨" (:derived-fn "/" (:fn-glyph "+"))))
|
||||
; ============================================================
|
||||
|
||||
(define
|
||||
is-fn-tok?
|
||||
(fn
|
||||
(tok)
|
||||
(or
|
||||
(and (= (tok-type tok) :glyph) (apl-parse-fn-glyph? (tok-val tok)))
|
||||
(and
|
||||
(= (tok-type tok) :name)
|
||||
(or
|
||||
(some (fn (q) (= q (tok-val tok))) apl-quad-fn-names)
|
||||
(some (fn (q) (= q (tok-val tok))) apl-known-fn-names))))))
|
||||
|
||||
; ============================================================
|
||||
; Find matching close bracket/paren/brace
|
||||
; Returns the index of the matching close token
|
||||
; ============================================================
|
||||
|
||||
(define collect-ops (fn (tokens i) (collect-ops-loop tokens i (list))))
|
||||
|
||||
(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})))))
|
||||
|
||||
; ============================================================
|
||||
; 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
|
||||
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)))
|
||||
|
||||
; ============================================================
|
||||
; 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
|
||||
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 an array node from 0..n value segments
|
||||
; If n=1 → return that segment's node
|
||||
; If n>1 → return (:vec node1 node2 ...)
|
||||
(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)
|
||||
(cond
|
||||
((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}))))))
|
||||
((some (fn (q) (= q tv)) apl-known-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-name tv) ops)))
|
||||
(collect-segments-loop
|
||||
tokens
|
||||
ni
|
||||
(append acc {:kind "fn" :node fn-node}))))))
|
||||
(else
|
||||
(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
|
||||
((inner-segs (collect-segments inner-tokens)))
|
||||
(if
|
||||
(and
|
||||
(>= (len inner-segs) 2)
|
||||
(every? (fn (s) (= (get s :kind) "fn")) inner-segs))
|
||||
(let
|
||||
((train-node (cons :train (map (fn (s) (get s :node)) inner-segs))))
|
||||
(collect-segments-loop
|
||||
tokens
|
||||
after
|
||||
(append acc {:kind "fn" :node train-node})))
|
||||
(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)))
|
||||
|
||||
|
||||
; ============================================================
|
||||
; Split token list on statement separators (diamond / newline)
|
||||
; Only splits at depth 0 (ignores separators inside { } or ( ) )
|
||||
; ============================================================
|
||||
|
||||
(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)))))
|
||||
|
||||
; ============================================================
|
||||
; Parse a dfn body (tokens between { and })
|
||||
; Handles guard expressions: cond : expr
|
||||
; ============================================================
|
||||
|
||||
(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)))
|
||||
|
||||
(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)))))
|
||||
|
||||
; ============================================================
|
||||
; Parse a single statement (assignment or expression)
|
||||
; ============================================================
|
||||
|
||||
(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)))))
|
||||
|
||||
; ============================================================
|
||||
; Parse an expression from a flat token list
|
||||
; ============================================================
|
||||
|
||||
(define
|
||||
find-top-level-colon
|
||||
(fn (tokens i) (find-top-level-colon-loop tokens i 0)))
|
||||
|
||||
; ============================================================
|
||||
; Main entry point
|
||||
; parse-apl: string → AST
|
||||
; ============================================================
|
||||
|
||||
(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)))))))
|
||||
|
||||
(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))))
|
||||
|
||||
(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)))
|
||||
(begin
|
||||
(apl-collect-fn-bindings stmt-groups)
|
||||
(if
|
||||
(= (len stmt-groups) 0)
|
||||
nil
|
||||
(if
|
||||
(= (len stmt-groups) 1)
|
||||
(parse-stmt (first stmt-groups))
|
||||
(cons :program (map parse-stmt stmt-groups)))))))))
|
||||
|
||||
(define
|
||||
split-bracket-loop
|
||||
(fn
|
||||
(tokens current acc depth)
|
||||
(if
|
||||
(= (len tokens) 0)
|
||||
(append acc (list current))
|
||||
(let
|
||||
((tok (first tokens)) (more (rest tokens)))
|
||||
(let
|
||||
((tt (tok-type tok)))
|
||||
(cond
|
||||
((or (= tt :lparen) (= tt :lbrace) (= tt :lbracket))
|
||||
(split-bracket-loop
|
||||
more
|
||||
(append current (list tok))
|
||||
acc
|
||||
(+ depth 1)))
|
||||
((or (= tt :rparen) (= tt :rbrace) (= tt :rbracket))
|
||||
(split-bracket-loop
|
||||
more
|
||||
(append current (list tok))
|
||||
acc
|
||||
(- depth 1)))
|
||||
((and (= tt :semi) (= depth 0))
|
||||
(split-bracket-loop
|
||||
more
|
||||
(list)
|
||||
(append acc (list current))
|
||||
depth))
|
||||
(else
|
||||
(split-bracket-loop more (append current (list tok)) acc depth))))))))
|
||||
|
||||
(define
|
||||
split-bracket-content
|
||||
(fn (tokens) (split-bracket-loop tokens (list) (list) 0)))
|
||||
|
||||
(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
|
||||
((sections (split-bracket-content inner-tokens)))
|
||||
(if
|
||||
(= (len sections) 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)))
|
||||
(let
|
||||
((axis-exprs (map (fn (toks) (if (= (len toks) 0) :all (parse-apl-expr toks))) sections)))
|
||||
(let
|
||||
((indexed (cons :bracket (cons val-node axis-exprs))))
|
||||
(maybe-bracket indexed tokens next-after)))))))
|
||||
(list val-node after))))
|
||||
1568
lib/apl/runtime.sx
1568
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,38 @@ 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")
|
||||
(load "lib/apl/tests/programs-e2e.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))))
|
||||
314
lib/apl/tests/pipeline.sx
Normal file
314
lib/apl/tests/pipeline.sx
Normal file
@@ -0,0 +1,314 @@
|
||||
; 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))
|
||||
|
||||
(apl-test "decimal: 3.7 → 3.7" (mkrv (apl-run "3.7")) (list 3.7))
|
||||
|
||||
(apl-test "decimal: ¯2.5 → -2.5" (mkrv (apl-run "¯2.5")) (list -2.5))
|
||||
|
||||
(apl-test "decimal: 1.5 + 2.5 → 4" (mkrv (apl-run "1.5 + 2.5")) (list 4))
|
||||
|
||||
(apl-test "decimal: ⌊3.7 → 3" (mkrv (apl-run "⌊ 3.7")) (list 3))
|
||||
|
||||
(apl-test "decimal: ⌈3.7 → 4" (mkrv (apl-run "⌈ 3.7")) (list 4))
|
||||
|
||||
(apl-test
|
||||
"⎕← scalar passthrough"
|
||||
(mkrv (apl-run "⎕← 42"))
|
||||
(list 42))
|
||||
|
||||
(apl-test
|
||||
"⎕← vector passthrough"
|
||||
(mkrv (apl-run "⎕← 1 2 3"))
|
||||
(list 1 2 3))
|
||||
|
||||
(apl-test
|
||||
"string: 'abc' → 3-char vector"
|
||||
(mkrv (apl-run "'abc'"))
|
||||
(list "a" "b" "c"))
|
||||
|
||||
(apl-test "string: 'a' is rank-0 scalar" (mksh (apl-run "'a'")) (list))
|
||||
|
||||
(apl-test "string: 'hello' shape (5)" (mksh (apl-run "'hello'")) (list 5))
|
||||
|
||||
(apl-test
|
||||
"named-fn: f ← {⍺+⍵} ⋄ 3 f 4 → 7"
|
||||
(mkrv (apl-run "f ← {⍺+⍵} ⋄ 3 f 4"))
|
||||
(list 7))
|
||||
|
||||
(apl-test
|
||||
"named-fn monadic: sq ← {⍵×⍵} ⋄ sq 7 → 49"
|
||||
(mkrv (apl-run "sq ← {⍵×⍵} ⋄ sq 7"))
|
||||
(list 49))
|
||||
|
||||
(apl-test
|
||||
"named-fn dyadic: hyp ← {((⍺×⍺)+⍵×⍵)} ⋄ 3 hyp 4 → 25"
|
||||
(mkrv (apl-run "hyp ← {((⍺×⍺)+⍵×⍵)} ⋄ 3 hyp 4"))
|
||||
(list 25))
|
||||
|
||||
(apl-test
|
||||
"named-fn: dbl ← {⍵+⍵} ⋄ dbl ⍳5"
|
||||
(mkrv (apl-run "dbl ← {⍵+⍵} ⋄ dbl ⍳5"))
|
||||
(list 2 4 6 8 10))
|
||||
|
||||
(apl-test
|
||||
"named-fn factorial via ∇ recursion"
|
||||
(mkrv (apl-run "fact ← {0=⍵:1 ⋄ ⍵×∇⍵-1} ⋄ fact 5"))
|
||||
(list 120))
|
||||
|
||||
(apl-test
|
||||
"named-fn used twice in expr: dbl ← {⍵+⍵} ⋄ (dbl 3) + dbl 4"
|
||||
(mkrv (apl-run "dbl ← {⍵+⍵} ⋄ (dbl 3) + dbl 4"))
|
||||
(list 14))
|
||||
|
||||
(apl-test
|
||||
"named-fn with vector arg: neg ← {-⍵} ⋄ neg 1 2 3"
|
||||
(mkrv (apl-run "neg ← {-⍵} ⋄ neg 1 2 3"))
|
||||
(list -1 -2 -3))
|
||||
|
||||
(apl-test
|
||||
"multi-axis: M[2;2] → center"
|
||||
(mkrv (apl-run "M ← (3 3) ⍴ ⍳9 ⋄ M[2;2]"))
|
||||
(list 5))
|
||||
|
||||
(apl-test
|
||||
"multi-axis: M[1;] → first row"
|
||||
(mkrv (apl-run "M ← (3 3) ⍴ ⍳9 ⋄ M[1;]"))
|
||||
(list 1 2 3))
|
||||
|
||||
(apl-test
|
||||
"multi-axis: M[;2] → second column"
|
||||
(mkrv (apl-run "M ← (3 3) ⍴ ⍳9 ⋄ M[;2]"))
|
||||
(list 2 5 8))
|
||||
|
||||
(apl-test
|
||||
"multi-axis: M[1 2;1 2] → 2x2 block"
|
||||
(mkrv (apl-run "M ← (2 3) ⍴ ⍳6 ⋄ M[1 2;1 2]"))
|
||||
(list 1 2 4 5))
|
||||
|
||||
(apl-test
|
||||
"multi-axis: M[1 2;1 2] shape (2 2)"
|
||||
(mksh (apl-run "M ← (2 3) ⍴ ⍳6 ⋄ M[1 2;1 2]"))
|
||||
(list 2 2))
|
||||
|
||||
(apl-test
|
||||
"multi-axis: M[;] full matrix"
|
||||
(mkrv (apl-run "M ← (2 2) ⍴ 10 20 30 40 ⋄ M[;]"))
|
||||
(list 10 20 30 40))
|
||||
|
||||
(apl-test
|
||||
"multi-axis: M[1;] shape collapsed"
|
||||
(mksh (apl-run "M ← (3 3) ⍴ ⍳9 ⋄ M[1;]"))
|
||||
(list 3))
|
||||
|
||||
(apl-test
|
||||
"multi-axis: select all rows of column 3"
|
||||
(mkrv (apl-run "M ← (4 3) ⍴ 1 2 3 4 5 6 7 8 9 10 11 12 ⋄ M[;3]"))
|
||||
(list 3 6 9 12))
|
||||
|
||||
(apl-test
|
||||
"train: mean = (+/÷≢) on 1..5"
|
||||
(mkrv (apl-run "(+/÷≢) 1 2 3 4 5"))
|
||||
(list 3))
|
||||
|
||||
(apl-test
|
||||
"train: mean of 2 4 6 8 10"
|
||||
(mkrv (apl-run "(+/÷≢) 2 4 6 8 10"))
|
||||
(list 6))
|
||||
|
||||
(apl-test
|
||||
"train 2-atop: (- ⌊) 5 → -5"
|
||||
(mkrv (apl-run "(- ⌊) 5"))
|
||||
(list -5))
|
||||
|
||||
(apl-test
|
||||
"train 3-fork dyadic: 2(+×-)5 → -21"
|
||||
(mkrv (apl-run "2 (+ × -) 5"))
|
||||
(list -21))
|
||||
|
||||
(apl-test
|
||||
"train: range = (⌈/-⌊/) on vector"
|
||||
(mkrv (apl-run "(⌈/-⌊/) 3 1 4 1 5 9 2 6"))
|
||||
(list 8))
|
||||
|
||||
(apl-test
|
||||
"train: mean of ⍳10 has shape ()"
|
||||
(mksh (apl-run "(+/÷≢) ⍳10"))
|
||||
(list))
|
||||
96
lib/apl/tests/programs-e2e.sx
Normal file
96
lib/apl/tests/programs-e2e.sx
Normal file
@@ -0,0 +1,96 @@
|
||||
; End-to-end tests of the classic-program archetypes — running APL
|
||||
; source through the full pipeline (tokenize → parse → eval-ast → runtime).
|
||||
;
|
||||
; These mirror the algorithms documented in lib/apl/tests/programs/*.apl
|
||||
; but use forms our pipeline supports today (named functions instead of
|
||||
; the inline ⍵← rebinding idiom; multi-stmt over single one-liners).
|
||||
|
||||
(define mkrv (fn (arr) (get arr :ravel)))
|
||||
(define mksh (fn (arr) (get arr :shape)))
|
||||
|
||||
; ---------- factorial via ∇ recursion (cf. n-queens style) ----------
|
||||
|
||||
(apl-test
|
||||
"e2e: factorial 5! = 120"
|
||||
(mkrv (apl-run "fact ← {0=⍵:1 ⋄ ⍵×∇⍵-1} ⋄ fact 5"))
|
||||
(list 120))
|
||||
|
||||
(apl-test
|
||||
"e2e: factorial 7! = 5040"
|
||||
(mkrv (apl-run "fact ← {0=⍵:1 ⋄ ⍵×∇⍵-1} ⋄ fact 7"))
|
||||
(list 5040))
|
||||
|
||||
(apl-test
|
||||
"e2e: factorial via ×/⍳N (no recursion)"
|
||||
(mkrv (apl-run "fact ← {×/⍳⍵} ⋄ fact 6"))
|
||||
(list 720))
|
||||
|
||||
; ---------- sum / triangular numbers (sum-1..N) ----------
|
||||
|
||||
(apl-test
|
||||
"e2e: triangular(10) = 55"
|
||||
(mkrv (apl-run "tri ← {+/⍳⍵} ⋄ tri 10"))
|
||||
(list 55))
|
||||
|
||||
(apl-test
|
||||
"e2e: triangular(100) = 5050"
|
||||
(mkrv (apl-run "tri ← {+/⍳⍵} ⋄ tri 100"))
|
||||
(list 5050))
|
||||
|
||||
; ---------- sum of squares ----------
|
||||
|
||||
(apl-test
|
||||
"e2e: sum-of-squares 1..5 = 55"
|
||||
(mkrv (apl-run "ss ← {+/⍵×⍵} ⋄ ss ⍳5"))
|
||||
(list 55))
|
||||
|
||||
(apl-test
|
||||
"e2e: sum-of-squares 1..10 = 385"
|
||||
(mkrv (apl-run "ss ← {+/⍵×⍵} ⋄ ss ⍳10"))
|
||||
(list 385))
|
||||
|
||||
; ---------- divisor-counting (prime-sieve building blocks) ----------
|
||||
|
||||
(apl-test
|
||||
"e2e: divisor counts 1..5 via outer mod"
|
||||
(mkrv (apl-run "P ← ⍳ 5 ⋄ +⌿ 0 = P ∘.| P"))
|
||||
(list 1 2 2 3 2))
|
||||
|
||||
(apl-test
|
||||
"e2e: divisor counts 1..10"
|
||||
(mkrv (apl-run "P ← ⍳ 10 ⋄ +⌿ 0 = P ∘.| P"))
|
||||
(list 1 2 2 3 2 4 2 4 3 4))
|
||||
|
||||
(apl-test
|
||||
"e2e: prime-mask 1..10 (count==2)"
|
||||
(mkrv (apl-run "P ← ⍳ 10 ⋄ 2 = +⌿ 0 = P ∘.| P"))
|
||||
(list 0 1 1 0 1 0 1 0 0 0))
|
||||
|
||||
; ---------- monadic primitives chained ----------
|
||||
|
||||
(apl-test
|
||||
"e2e: sum of |abs| = 15"
|
||||
(mkrv (apl-run "+/|¯1 ¯2 ¯3 ¯4 ¯5"))
|
||||
(list 15))
|
||||
|
||||
(apl-test
|
||||
"e2e: max of squares 1..6"
|
||||
(mkrv (apl-run "⌈/(⍳6)×⍳6"))
|
||||
(list 36))
|
||||
|
||||
; ---------- nested named functions ----------
|
||||
|
||||
(apl-test
|
||||
"e2e: compose dbl and sq via two named fns"
|
||||
(mkrv (apl-run "dbl ← {⍵+⍵} ⋄ sq ← {⍵×⍵} ⋄ sq dbl 3"))
|
||||
(list 36))
|
||||
|
||||
(apl-test
|
||||
"e2e: max-of-two as named dyadic fn"
|
||||
(mkrv (apl-run "mx ← {⍺⌈⍵} ⋄ 5 mx 3"))
|
||||
(list 5))
|
||||
|
||||
(apl-test
|
||||
"e2e: sqrt-via-newton 1 step from 1 → 2.5"
|
||||
(mkrv (apl-run "step ← {(⍵+⍺÷⍵)÷2} ⋄ 4 step 1"))
|
||||
(list 2.5))
|
||||
306
lib/apl/tests/programs.sx
Normal file
306
lib/apl/tests/programs.sx
Normal file
@@ -0,0 +1,306 @@
|
||||
; 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 "queens 8 → 92 solutions" (mkrv (apl-queens 8)) (list 92))
|
||||
|
||||
(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)
|
||||
180
lib/apl/tokenizer.sx
Normal file
180
lib/apl/tokenizer.sx
Normal file
@@ -0,0 +1,180 @@
|
||||
(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! "")))
|
||||
(if (and (< pos src-len) (= (cur-byte) ".")
|
||||
(< (+ pos 1) src-len) (apl-digit? (nth source (+ pos 1))))
|
||||
(begin (advance!)
|
||||
(let ((frac (read-digits! "")))
|
||||
(tok-push! :num (- 0 (string->number (str digits "." frac))))))
|
||||
(tok-push! :num (- 0 (parse-int digits 0)))))
|
||||
(scan!)))
|
||||
((apl-digit? ch)
|
||||
(begin
|
||||
(let ((digits (read-digits! "")))
|
||||
(if (and (< pos src-len) (= (cur-byte) ".")
|
||||
(< (+ pos 1) src-len) (apl-digit? (nth source (+ pos 1))))
|
||||
(begin (advance!)
|
||||
(let ((frac (read-digits! "")))
|
||||
(tok-push! :num (string->number (str digits "." frac)))))
|
||||
(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!))
|
||||
(if (and (< pos src-len) (cur-sw? "←"))
|
||||
(consume! "←")
|
||||
(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)))
|
||||
540
lib/apl/transpile.sx
Normal file
540
lib/apl/transpile.sx
Normal file
@@ -0,0 +1,540 @@
|
||||
; 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)
|
||||
((= g "⎕←") apl-quad-print)
|
||||
(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 :str)
|
||||
(let
|
||||
((s (nth node 1)))
|
||||
(if
|
||||
(= (len s) 1)
|
||||
(apl-scalar s)
|
||||
(make-array
|
||||
(list (len s))
|
||||
(map (fn (i) (slice s i (+ i 1))) (range 0 (len s)))))))
|
||||
((= 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)
|
||||
((= tag :bracket)
|
||||
(let
|
||||
((arr-expr (nth node 1)) (axis-exprs (rest (rest node))))
|
||||
(let
|
||||
((arr (apl-eval-ast arr-expr env))
|
||||
(axes
|
||||
(map
|
||||
(fn (a) (if (= a :all) nil (apl-eval-ast a env)))
|
||||
axis-exprs)))
|
||||
(apl-bracket-multi axes arr))))
|
||||
(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")))))
|
||||
((= tag :fn-name)
|
||||
(let
|
||||
((nm (nth fn-node 1)))
|
||||
(let
|
||||
((bound (get env nm)))
|
||||
(if
|
||||
(and
|
||||
(list? bound)
|
||||
(> (len bound) 0)
|
||||
(= (first bound) :dfn))
|
||||
(fn (arg) (apl-call-dfn-m bound arg))
|
||||
(error "apl-resolve-monadic: name not bound to dfn")))))
|
||||
((= tag :train)
|
||||
(let
|
||||
((fns (rest fn-node)))
|
||||
(let
|
||||
((n (len fns)))
|
||||
(cond
|
||||
((= n 2)
|
||||
(let
|
||||
((g (apl-resolve-monadic (nth fns 0) env))
|
||||
(h (apl-resolve-monadic (nth fns 1) env)))
|
||||
(fn (arg) (g (h arg)))))
|
||||
((= n 3)
|
||||
(let
|
||||
((f (apl-resolve-monadic (nth fns 0) env))
|
||||
(g (apl-resolve-dyadic (nth fns 1) env))
|
||||
(h (apl-resolve-monadic (nth fns 2) env)))
|
||||
(fn (arg) (g (f arg) (h arg)))))
|
||||
(else (error "monadic train arity not 2 or 3"))))))
|
||||
(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 :fn-name)
|
||||
(let
|
||||
((nm (nth fn-node 1)))
|
||||
(let
|
||||
((bound (get env nm)))
|
||||
(if
|
||||
(and
|
||||
(list? bound)
|
||||
(> (len bound) 0)
|
||||
(= (first bound) :dfn))
|
||||
(fn (a b) (apl-call-dfn bound a b))
|
||||
(error "apl-resolve-dyadic: name not bound to dfn")))))
|
||||
((= 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)))))
|
||||
((= tag :train)
|
||||
(let
|
||||
((fns (rest fn-node)))
|
||||
(let
|
||||
((n (len fns)))
|
||||
(cond
|
||||
((= n 2)
|
||||
(let
|
||||
((g (apl-resolve-monadic (nth fns 0) env))
|
||||
(h (apl-resolve-dyadic (nth fns 1) env)))
|
||||
(fn (a b) (g (h a b)))))
|
||||
((= n 3)
|
||||
(let
|
||||
((f (apl-resolve-dyadic (nth fns 0) env))
|
||||
(g (apl-resolve-dyadic (nth fns 1) env))
|
||||
(h (apl-resolve-dyadic (nth fns 2) env)))
|
||||
(fn (a b) (g (f a b) (h a b)))))
|
||||
(else (error "dyadic train arity not 2 or 3"))))))
|
||||
(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)))
|
||||
180
lib/guest/hm.sx
Normal file
180
lib/guest/hm.sx
Normal file
@@ -0,0 +1,180 @@
|
||||
;; lib/guest/hm.sx — Hindley-Milner type-inference foundations.
|
||||
;;
|
||||
;; Builds on lib/guest/match.sx (terms + unify) and ast.sx (canonical
|
||||
;; AST shapes). This file ships the ALGEBRA — types, schemes, free
|
||||
;; type-vars, generalize / instantiate, substitution composition — so a
|
||||
;; full Algorithm W (or J) can be assembled on top either inside this
|
||||
;; file or in a host-specific consumer (haskell/infer.sx,
|
||||
;; lib/ocaml/types.sx, …).
|
||||
;;
|
||||
;; Per the brief the second consumer for this step is OCaml-on-SX
|
||||
;; Phase 5 (paired sequencing). Until that lands, the algebra is the
|
||||
;; deliverable; the host-flavoured assembly (lambda / app / let
|
||||
;; inference rules with substitution threading) lives in the host.
|
||||
;;
|
||||
;; Types
|
||||
;; -----
|
||||
;; A type is a canonical match.sx term — type variables use mk-var,
|
||||
;; type constructors use mk-ctor:
|
||||
;; (hm-tv NAME) type variable
|
||||
;; (hm-arrow A B) A -> B
|
||||
;; (hm-con NAME ARGS) named n-ary constructor
|
||||
;; (hm-int) / (hm-bool) / (hm-string) primitive constructors
|
||||
;;
|
||||
;; Schemes
|
||||
;; -------
|
||||
;; (hm-scheme VARS TYPE) ∀ VARS . TYPE
|
||||
;; (hm-monotype TYPE) empty quantifier
|
||||
;; (hm-scheme? S) (hm-scheme-vars S) (hm-scheme-type S)
|
||||
;;
|
||||
;; Free type variables
|
||||
;; -------------------
|
||||
;; (hm-ftv TYPE) names occurring in TYPE
|
||||
;; (hm-ftv-scheme S) free names (minus quantifiers)
|
||||
;; (hm-ftv-env ENV) free across an env (name -> scheme)
|
||||
;;
|
||||
;; Substitution
|
||||
;; ------------
|
||||
;; (hm-apply SUBST TYPE) substitute through a type
|
||||
;; (hm-apply-scheme SUBST S) leaves bound vars alone
|
||||
;; (hm-apply-env SUBST ENV)
|
||||
;; (hm-compose S2 S1) apply S1 then S2
|
||||
;;
|
||||
;; Generalize / Instantiate
|
||||
;; ------------------------
|
||||
;; (hm-generalize TYPE ENV) → scheme over ftv(t) - ftv(env)
|
||||
;; (hm-instantiate SCHEME COUNTER) → fresh-var instance
|
||||
;; (hm-fresh-tv COUNTER) → (:var "tN"), bumps COUNTER
|
||||
;;
|
||||
;; Inference (literal only — the rest of Algorithm W lives in the host)
|
||||
;; --------------------------------------------------------------------
|
||||
;; (hm-infer-literal EXPR) → {:subst {} :type T}
|
||||
;;
|
||||
;; A complete Algorithm W consumes this kit by assembling lambda / app
|
||||
;; / let rules in the host language file.
|
||||
|
||||
(define hm-tv (fn (name) (list :var name)))
|
||||
(define hm-con (fn (name args) (list :ctor name args)))
|
||||
(define hm-arrow (fn (a b) (hm-con "->" (list a b))))
|
||||
(define hm-int (fn () (hm-con "Int" (list))))
|
||||
(define hm-bool (fn () (hm-con "Bool" (list))))
|
||||
(define hm-string (fn () (hm-con "String" (list))))
|
||||
|
||||
(define hm-scheme (fn (vars t) (list :scheme vars t)))
|
||||
(define hm-monotype (fn (t) (hm-scheme (list) t)))
|
||||
(define hm-scheme? (fn (s) (and (list? s) (not (empty? s)) (= (first s) :scheme))))
|
||||
(define hm-scheme-vars (fn (s) (nth s 1)))
|
||||
(define hm-scheme-type (fn (s) (nth s 2)))
|
||||
|
||||
(define
|
||||
hm-fresh-tv
|
||||
(fn (counter)
|
||||
(let ((n (first counter)))
|
||||
(begin
|
||||
(set-nth! counter 0 (+ n 1))
|
||||
(hm-tv (str "t" (+ n 1)))))))
|
||||
|
||||
(define
|
||||
hm-ftv-acc
|
||||
(fn (t acc)
|
||||
(cond
|
||||
((is-var? t)
|
||||
(if (some (fn (n) (= n (var-name t))) acc) acc (cons (var-name t) acc)))
|
||||
((is-ctor? t)
|
||||
(let ((a acc))
|
||||
(begin
|
||||
(for-each (fn (x) (set! a (hm-ftv-acc x a))) (ctor-args t))
|
||||
a)))
|
||||
(:else acc))))
|
||||
|
||||
(define hm-ftv (fn (t) (hm-ftv-acc t (list))))
|
||||
|
||||
(define
|
||||
hm-ftv-scheme
|
||||
(fn (s)
|
||||
(let ((qs (hm-scheme-vars s))
|
||||
(all (hm-ftv (hm-scheme-type s))))
|
||||
(filter (fn (n) (not (some (fn (q) (= q n)) qs))) all))))
|
||||
|
||||
(define
|
||||
hm-ftv-env
|
||||
(fn (env)
|
||||
(let ((acc (list)))
|
||||
(begin
|
||||
(for-each
|
||||
(fn (k)
|
||||
(for-each
|
||||
(fn (n)
|
||||
(when (not (some (fn (m) (= m n)) acc))
|
||||
(set! acc (cons n acc))))
|
||||
(hm-ftv-scheme (get env k))))
|
||||
(keys env))
|
||||
acc))))
|
||||
|
||||
(define hm-apply (fn (subst t) (walk* t subst)))
|
||||
|
||||
(define
|
||||
hm-apply-scheme
|
||||
(fn (subst s)
|
||||
(let ((qs (hm-scheme-vars s))
|
||||
(d {}))
|
||||
(begin
|
||||
(for-each
|
||||
(fn (k)
|
||||
(when (not (some (fn (q) (= q k)) qs))
|
||||
(dict-set! d k (get subst k))))
|
||||
(keys subst))
|
||||
(hm-scheme qs (walk* (hm-scheme-type s) d))))))
|
||||
|
||||
(define
|
||||
hm-apply-env
|
||||
(fn (subst env)
|
||||
(let ((d {}))
|
||||
(begin
|
||||
(for-each
|
||||
(fn (k) (dict-set! d k (hm-apply-scheme subst (get env k))))
|
||||
(keys env))
|
||||
d))))
|
||||
|
||||
(define
|
||||
hm-compose
|
||||
(fn (s2 s1)
|
||||
(let ((d {}))
|
||||
(begin
|
||||
(for-each (fn (k) (dict-set! d k (walk* (get s1 k) s2))) (keys s1))
|
||||
(for-each
|
||||
(fn (k) (when (not (has-key? d k)) (dict-set! d k (get s2 k))))
|
||||
(keys s2))
|
||||
d))))
|
||||
|
||||
(define
|
||||
hm-generalize
|
||||
(fn (t env)
|
||||
(let ((tvars (hm-ftv t))
|
||||
(evars (hm-ftv-env env)))
|
||||
(let ((qs (filter (fn (n) (not (some (fn (m) (= m n)) evars))) tvars)))
|
||||
(hm-scheme qs t)))))
|
||||
|
||||
(define
|
||||
hm-instantiate
|
||||
(fn (s counter)
|
||||
(let ((qs (hm-scheme-vars s))
|
||||
(subst {}))
|
||||
(begin
|
||||
(for-each
|
||||
(fn (q) (set! subst (assoc subst q (hm-fresh-tv counter))))
|
||||
qs)
|
||||
(walk* (hm-scheme-type s) subst)))))
|
||||
|
||||
;; Literal inference — the only AST kind whose typing rule is closed
|
||||
;; in the kit. Lambda / app / let live in host code so the host's own
|
||||
;; AST conventions stay untouched.
|
||||
(define
|
||||
hm-infer-literal
|
||||
(fn (expr)
|
||||
(let ((v (ast-literal-value expr)))
|
||||
(cond
|
||||
((number? v) {:subst {} :type (hm-int)})
|
||||
((string? v) {:subst {} :type (hm-string)})
|
||||
((boolean? v) {:subst {} :type (hm-bool)})
|
||||
(:else (error (str "hm-infer-literal: unknown kind: " v)))))))
|
||||
145
lib/guest/layout.sx
Normal file
145
lib/guest/layout.sx
Normal file
@@ -0,0 +1,145 @@
|
||||
;; lib/guest/layout.sx — configurable off-side / layout-sensitive lexer.
|
||||
;;
|
||||
;; Inserts virtual open / close / separator tokens based on indentation.
|
||||
;; Configurable enough to encode either the Haskell 98 layout rule (let /
|
||||
;; where / do / of opens a virtual brace at the next token's column) or
|
||||
;; a Python-ish indent / dedent rule (a colon at the end of a line opens
|
||||
;; a block at the next non-blank line's column).
|
||||
;;
|
||||
;; Token shape (input + output)
|
||||
;; ----------------------------
|
||||
;; Each token is a dict {:type :value :line :col …}. The kit reads
|
||||
;; only :type / :value / :line / :col and passes everything else
|
||||
;; through. The input stream MUST be free of newline filler tokens
|
||||
;; (preprocess them away with your tokenizer) — line breaks are detected
|
||||
;; by comparing :line of consecutive tokens.
|
||||
;;
|
||||
;; Config
|
||||
;; ------
|
||||
;; :open-keywords list of strings; a token whose :value matches
|
||||
;; opens a new layout block at the next token's
|
||||
;; column (Haskell: let/where/do/of).
|
||||
;; :open-trailing-fn (fn (tok) -> bool) — alternative trigger that
|
||||
;; fires AFTER the token is emitted. Use for
|
||||
;; Python-style trailing `:`.
|
||||
;; :open-token / :close-token / :sep-token
|
||||
;; templates {:type :value} merged with :line and
|
||||
;; :col when virtual tokens are emitted.
|
||||
;; :explicit-open? (fn (tok) -> bool) — if the next token after a
|
||||
;; trigger satisfies this, suppress virtual layout
|
||||
;; for that block (Haskell: `{`).
|
||||
;; :module-prelude? if true, wrap whole input in an implicit block
|
||||
;; at the first token's column (Haskell yes,
|
||||
;; Python no).
|
||||
;;
|
||||
;; Public entry
|
||||
;; ------------
|
||||
;; (layout-pass cfg tokens) -> tokens with virtual layout inserted.
|
||||
|
||||
(define
|
||||
layout-mk-virtual
|
||||
(fn (template line col)
|
||||
(assoc (assoc template :line line) :col col)))
|
||||
|
||||
(define
|
||||
layout-is-open-kw?
|
||||
(fn (tok open-kws)
|
||||
(and (= (get tok :type) "reserved")
|
||||
(some (fn (k) (= k (get tok :value))) open-kws))))
|
||||
|
||||
(define
|
||||
layout-pass
|
||||
(fn (cfg tokens)
|
||||
(let ((open-kws (get cfg :open-keywords))
|
||||
(trailing-fn (get cfg :open-trailing-fn))
|
||||
(open-tmpl (get cfg :open-token))
|
||||
(close-tmpl (get cfg :close-token))
|
||||
(sep-tmpl (get cfg :sep-token))
|
||||
(mod-prelude? (get cfg :module-prelude?))
|
||||
(expl?-fn (get cfg :explicit-open?))
|
||||
(out (list))
|
||||
(stack (list))
|
||||
(n (len tokens))
|
||||
(i 0)
|
||||
(prev-line -1)
|
||||
(pending-open false)
|
||||
(just-opened false))
|
||||
(define
|
||||
emit-closes-while-greater
|
||||
(fn (col line)
|
||||
(when (and (not (empty? stack)) (> (first stack) col))
|
||||
(do
|
||||
(append! out (layout-mk-virtual close-tmpl line col))
|
||||
(set! stack (rest stack))
|
||||
(emit-closes-while-greater col line)))))
|
||||
(define
|
||||
emit-pending-open
|
||||
(fn (line col)
|
||||
(do
|
||||
(append! out (layout-mk-virtual open-tmpl line col))
|
||||
(set! stack (cons col stack))
|
||||
(set! pending-open false)
|
||||
(set! just-opened true))))
|
||||
(define
|
||||
layout-step
|
||||
(fn ()
|
||||
(when (< i n)
|
||||
(let ((tok (nth tokens i)))
|
||||
(let ((line (get tok :line)) (col (get tok :col)))
|
||||
(cond
|
||||
(pending-open
|
||||
(cond
|
||||
((and (not (= expl?-fn nil)) (expl?-fn tok))
|
||||
(do
|
||||
(set! pending-open false)
|
||||
(append! out tok)
|
||||
(set! prev-line line)
|
||||
(set! i (+ i 1))
|
||||
(layout-step)))
|
||||
(:else
|
||||
(do
|
||||
(emit-pending-open line col)
|
||||
(layout-step)))))
|
||||
(:else
|
||||
(let ((on-fresh-line? (and (> prev-line 0) (> line prev-line))))
|
||||
(do
|
||||
(when on-fresh-line?
|
||||
(let ((stack-before stack))
|
||||
(begin
|
||||
(emit-closes-while-greater col line)
|
||||
(when (and (not (empty? stack))
|
||||
(= (first stack) col)
|
||||
(not just-opened)
|
||||
;; suppress separator if a dedent fired
|
||||
;; — the dedent is itself the separator
|
||||
(= (len stack) (len stack-before)))
|
||||
(append! out (layout-mk-virtual sep-tmpl line col))))))
|
||||
(set! just-opened false)
|
||||
(append! out tok)
|
||||
(set! prev-line line)
|
||||
(set! i (+ i 1))
|
||||
(cond
|
||||
((layout-is-open-kw? tok open-kws)
|
||||
(set! pending-open true))
|
||||
((and (not (= trailing-fn nil)) (trailing-fn tok))
|
||||
(set! pending-open true)))
|
||||
(layout-step))))))))))
|
||||
(begin
|
||||
;; Module prelude: implicit layout block at the first token's column.
|
||||
(when (and mod-prelude? (> n 0))
|
||||
(let ((tok (nth tokens 0)))
|
||||
(do
|
||||
(append! out (layout-mk-virtual open-tmpl (get tok :line) (get tok :col)))
|
||||
(set! stack (cons (get tok :col) stack))
|
||||
(set! just-opened true))))
|
||||
(layout-step)
|
||||
;; EOF: close every remaining block.
|
||||
(define close-rest
|
||||
(fn ()
|
||||
(when (not (empty? stack))
|
||||
(do
|
||||
(append! out (layout-mk-virtual close-tmpl 0 0))
|
||||
(set! stack (rest stack))
|
||||
(close-rest)))))
|
||||
(close-rest)
|
||||
out))))
|
||||
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)}))
|
||||
89
lib/guest/tests/hm.sx
Normal file
89
lib/guest/tests/hm.sx
Normal file
@@ -0,0 +1,89 @@
|
||||
;; lib/guest/tests/hm.sx — exercises lib/guest/hm.sx algebra.
|
||||
|
||||
(define ghm-test-pass 0)
|
||||
(define ghm-test-fail 0)
|
||||
(define ghm-test-fails (list))
|
||||
|
||||
(define
|
||||
ghm-test
|
||||
(fn (name actual expected)
|
||||
(if (= actual expected)
|
||||
(set! ghm-test-pass (+ ghm-test-pass 1))
|
||||
(begin
|
||||
(set! ghm-test-fail (+ ghm-test-fail 1))
|
||||
(append! ghm-test-fails {:name name :expected expected :actual actual})))))
|
||||
|
||||
;; ── Type constructors ─────────────────────────────────────────────
|
||||
(ghm-test "tv" (hm-tv "a") (list :var "a"))
|
||||
(ghm-test "int" (hm-int) (list :ctor "Int" (list)))
|
||||
(ghm-test "arrow" (ctor-head (hm-arrow (hm-int) (hm-bool))) "->")
|
||||
(ghm-test "arrow-args-len" (len (ctor-args (hm-arrow (hm-int) (hm-bool)))) 2)
|
||||
|
||||
;; ── Schemes ───────────────────────────────────────────────────────
|
||||
(ghm-test "scheme-vars" (hm-scheme-vars (hm-scheme (list "a") (hm-tv "a"))) (list "a"))
|
||||
(ghm-test "monotype-vars" (hm-scheme-vars (hm-monotype (hm-int))) (list))
|
||||
(ghm-test "scheme?-yes" (hm-scheme? (hm-monotype (hm-int))) true)
|
||||
(ghm-test "scheme?-no" (hm-scheme? (hm-int)) false)
|
||||
|
||||
;; ── Fresh tyvars ──────────────────────────────────────────────────
|
||||
(ghm-test "fresh-1"
|
||||
(let ((c (list 0))) (var-name (hm-fresh-tv c))) "t1")
|
||||
(ghm-test "fresh-bumps"
|
||||
(let ((c (list 5))) (begin (hm-fresh-tv c) (first c))) 6)
|
||||
|
||||
;; ── Free type variables ──────────────────────────────────────────
|
||||
(ghm-test "ftv-int" (hm-ftv (hm-int)) (list))
|
||||
(ghm-test "ftv-tv" (hm-ftv (hm-tv "a")) (list "a"))
|
||||
(ghm-test "ftv-arrow"
|
||||
(len (hm-ftv (hm-arrow (hm-tv "a") (hm-arrow (hm-tv "b") (hm-tv "a"))))) 2)
|
||||
(ghm-test "ftv-scheme-quantified"
|
||||
(hm-ftv-scheme (hm-scheme (list "a") (hm-arrow (hm-tv "a") (hm-tv "b")))) (list "b"))
|
||||
(ghm-test "ftv-env"
|
||||
(let ((env (assoc {} "f" (hm-monotype (hm-arrow (hm-tv "x") (hm-tv "y"))))))
|
||||
(len (hm-ftv-env env))) 2)
|
||||
|
||||
;; ── Substitution / apply / compose ───────────────────────────────
|
||||
(ghm-test "apply-tv"
|
||||
(hm-apply (assoc {} "a" (hm-int)) (hm-tv "a")) (hm-int))
|
||||
(ghm-test "apply-arrow"
|
||||
(ctor-head
|
||||
(hm-apply (assoc {} "a" (hm-int))
|
||||
(hm-arrow (hm-tv "a") (hm-tv "b")))) "->")
|
||||
(ghm-test "compose-1-then-2"
|
||||
(var-name
|
||||
(hm-apply
|
||||
(hm-compose (assoc {} "b" (hm-tv "c")) (assoc {} "a" (hm-tv "b")))
|
||||
(hm-tv "a"))) "c")
|
||||
|
||||
;; ── Generalize / Instantiate ─────────────────────────────────────
|
||||
;; forall a. a -> a instantiated twice yields fresh vars each time
|
||||
(ghm-test "generalize-id"
|
||||
(len (hm-scheme-vars (hm-generalize (hm-arrow (hm-tv "a") (hm-tv "a")) {}))) 1)
|
||||
|
||||
(ghm-test "generalize-skips-env"
|
||||
;; ftv(t)={a,b}, ftv(env)={a}, qs={b}
|
||||
(let ((env (assoc {} "x" (hm-monotype (hm-tv "a")))))
|
||||
(len (hm-scheme-vars
|
||||
(hm-generalize (hm-arrow (hm-tv "a") (hm-tv "b")) env)))) 1)
|
||||
|
||||
(ghm-test "instantiate-fresh"
|
||||
(let ((s (hm-scheme (list "a") (hm-arrow (hm-tv "a") (hm-tv "a"))))
|
||||
(c (list 0)))
|
||||
(let ((t1 (hm-instantiate s c)) (t2 (hm-instantiate s c)))
|
||||
(not (= (var-name (first (ctor-args t1)))
|
||||
(var-name (first (ctor-args t2)))))))
|
||||
true)
|
||||
|
||||
;; ── Inference (literal only) ─────────────────────────────────────
|
||||
(ghm-test "infer-int"
|
||||
(ctor-head (get (hm-infer-literal (ast-literal 42)) :type)) "Int")
|
||||
(ghm-test "infer-string"
|
||||
(ctor-head (get (hm-infer-literal (ast-literal "hi")) :type)) "String")
|
||||
(ghm-test "infer-bool"
|
||||
(ctor-head (get (hm-infer-literal (ast-literal true)) :type)) "Bool")
|
||||
|
||||
(define ghm-tests-run!
|
||||
(fn ()
|
||||
{:passed ghm-test-pass
|
||||
:failed ghm-test-fail
|
||||
:total (+ ghm-test-pass ghm-test-fail)}))
|
||||
180
lib/guest/tests/layout.sx
Normal file
180
lib/guest/tests/layout.sx
Normal file
@@ -0,0 +1,180 @@
|
||||
;; lib/guest/tests/layout.sx — synthetic Python-ish off-side fixture.
|
||||
;;
|
||||
;; Exercises lib/guest/layout.sx with a config different from Haskell's
|
||||
;; (no module-prelude, layout opens via trailing `:` not via reserved
|
||||
;; keyword) to prove the kit isn't Haskell-shaped.
|
||||
|
||||
(define glayout-test-pass 0)
|
||||
(define glayout-test-fail 0)
|
||||
(define glayout-test-fails (list))
|
||||
|
||||
(define
|
||||
glayout-test
|
||||
(fn (name actual expected)
|
||||
(if (= actual expected)
|
||||
(set! glayout-test-pass (+ glayout-test-pass 1))
|
||||
(begin
|
||||
(set! glayout-test-fail (+ glayout-test-fail 1))
|
||||
(append! glayout-test-fails {:name name :expected expected :actual actual})))))
|
||||
|
||||
;; Convenience: build a token from {type value line col}.
|
||||
(define
|
||||
glayout-tok
|
||||
(fn (ty val line col)
|
||||
{:type ty :value val :line line :col col}))
|
||||
|
||||
;; Project a token list to ((type value) ...) for compact comparison.
|
||||
(define
|
||||
glayout-shape
|
||||
(fn (toks)
|
||||
(map (fn (t) (list (get t :type) (get t :value))) toks)))
|
||||
|
||||
;; ── Haskell-flavour: keyword opens block ─────────────────────────
|
||||
(define
|
||||
glayout-haskell-cfg
|
||||
{:open-keywords (list "let" "where" "do" "of")
|
||||
:open-trailing-fn nil
|
||||
:open-token {:type "vlbrace" :value "{"}
|
||||
:close-token {:type "vrbrace" :value "}"}
|
||||
:sep-token {:type "vsemi" :value ";"}
|
||||
:module-prelude? false
|
||||
:explicit-open? (fn (tok) (= (get tok :type) "lbrace"))})
|
||||
|
||||
;; do
|
||||
;; a
|
||||
;; b
|
||||
;; c ← outside the do-block
|
||||
(glayout-test "haskell-do-block"
|
||||
(glayout-shape
|
||||
(layout-pass
|
||||
glayout-haskell-cfg
|
||||
(list (glayout-tok "reserved" "do" 1 1)
|
||||
(glayout-tok "ident" "a" 2 3)
|
||||
(glayout-tok "ident" "b" 3 3)
|
||||
(glayout-tok "ident" "c" 4 1))))
|
||||
(list (list "reserved" "do")
|
||||
(list "vlbrace" "{")
|
||||
(list "ident" "a")
|
||||
(list "vsemi" ";")
|
||||
(list "ident" "b")
|
||||
(list "vrbrace" "}")
|
||||
(list "ident" "c")))
|
||||
|
||||
;; Explicit `{` after `do` suppresses virtual layout.
|
||||
(glayout-test "haskell-explicit-brace"
|
||||
(glayout-shape
|
||||
(layout-pass
|
||||
glayout-haskell-cfg
|
||||
(list (glayout-tok "reserved" "do" 1 1)
|
||||
(glayout-tok "lbrace" "{" 1 4)
|
||||
(glayout-tok "ident" "a" 1 6)
|
||||
(glayout-tok "rbrace" "}" 1 8))))
|
||||
(list (list "reserved" "do")
|
||||
(list "lbrace" "{")
|
||||
(list "ident" "a")
|
||||
(list "rbrace" "}")))
|
||||
|
||||
;; Single-statement do-block on the same line.
|
||||
(glayout-test "haskell-do-inline"
|
||||
(glayout-shape
|
||||
(layout-pass
|
||||
glayout-haskell-cfg
|
||||
(list (glayout-tok "reserved" "do" 1 1)
|
||||
(glayout-tok "ident" "a" 1 4))))
|
||||
(list (list "reserved" "do")
|
||||
(list "vlbrace" "{")
|
||||
(list "ident" "a")
|
||||
(list "vrbrace" "}")))
|
||||
|
||||
;; Module-prelude: wrap whole input in implicit layout block at first
|
||||
;; tok's column.
|
||||
(glayout-test "haskell-module-prelude"
|
||||
(glayout-shape
|
||||
(layout-pass
|
||||
(assoc glayout-haskell-cfg :module-prelude? true)
|
||||
(list (glayout-tok "ident" "x" 1 1)
|
||||
(glayout-tok "ident" "y" 2 1)
|
||||
(glayout-tok "ident" "z" 3 1))))
|
||||
(list (list "vlbrace" "{")
|
||||
(list "ident" "x")
|
||||
(list "vsemi" ";")
|
||||
(list "ident" "y")
|
||||
(list "vsemi" ";")
|
||||
(list "ident" "z")
|
||||
(list "vrbrace" "}")))
|
||||
|
||||
;; ── Python-flavour: trailing `:` opens block ─────────────────────
|
||||
(define
|
||||
glayout-python-cfg
|
||||
{:open-keywords (list)
|
||||
:open-trailing-fn (fn (tok) (and (= (get tok :type) "punct")
|
||||
(= (get tok :value) ":")))
|
||||
:open-token {:type "indent" :value "INDENT"}
|
||||
:close-token {:type "dedent" :value "DEDENT"}
|
||||
:sep-token {:type "newline" :value "NEWLINE"}
|
||||
:module-prelude? false
|
||||
:explicit-open? nil})
|
||||
|
||||
;; if x:
|
||||
;; a
|
||||
;; b
|
||||
;; c
|
||||
(glayout-test "python-if-block"
|
||||
(glayout-shape
|
||||
(layout-pass
|
||||
glayout-python-cfg
|
||||
(list (glayout-tok "reserved" "if" 1 1)
|
||||
(glayout-tok "ident" "x" 1 4)
|
||||
(glayout-tok "punct" ":" 1 5)
|
||||
(glayout-tok "ident" "a" 2 5)
|
||||
(glayout-tok "ident" "b" 3 5)
|
||||
(glayout-tok "ident" "c" 4 1))))
|
||||
(list (list "reserved" "if")
|
||||
(list "ident" "x")
|
||||
(list "punct" ":")
|
||||
(list "indent" "INDENT")
|
||||
(list "ident" "a")
|
||||
(list "newline" "NEWLINE")
|
||||
(list "ident" "b")
|
||||
(list "dedent" "DEDENT")
|
||||
(list "ident" "c")))
|
||||
|
||||
;; Nested Python-style blocks.
|
||||
;; def f():
|
||||
;; if x:
|
||||
;; a
|
||||
;; b
|
||||
(glayout-test "python-nested"
|
||||
(glayout-shape
|
||||
(layout-pass
|
||||
glayout-python-cfg
|
||||
(list (glayout-tok "reserved" "def" 1 1)
|
||||
(glayout-tok "ident" "f" 1 5)
|
||||
(glayout-tok "punct" "(" 1 6)
|
||||
(glayout-tok "punct" ")" 1 7)
|
||||
(glayout-tok "punct" ":" 1 8)
|
||||
(glayout-tok "reserved" "if" 2 5)
|
||||
(glayout-tok "ident" "x" 2 8)
|
||||
(glayout-tok "punct" ":" 2 9)
|
||||
(glayout-tok "ident" "a" 3 9)
|
||||
(glayout-tok "ident" "b" 4 5))))
|
||||
(list (list "reserved" "def")
|
||||
(list "ident" "f")
|
||||
(list "punct" "(")
|
||||
(list "punct" ")")
|
||||
(list "punct" ":")
|
||||
(list "indent" "INDENT")
|
||||
(list "reserved" "if")
|
||||
(list "ident" "x")
|
||||
(list "punct" ":")
|
||||
(list "indent" "INDENT")
|
||||
(list "ident" "a")
|
||||
(list "dedent" "DEDENT")
|
||||
(list "ident" "b")
|
||||
(list "dedent" "DEDENT")))
|
||||
|
||||
(define glayout-tests-run!
|
||||
(fn ()
|
||||
{:passed glayout-test-pass
|
||||
:failed glayout-test-fail
|
||||
:total (+ glayout-test-pass glayout-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)}))
|
||||
@@ -14,6 +14,8 @@ PRELOADS=(
|
||||
lib/haskell/runtime.sx
|
||||
lib/haskell/match.sx
|
||||
lib/haskell/eval.sx
|
||||
lib/haskell/map.sx
|
||||
lib/haskell/set.sx
|
||||
lib/haskell/testlib.sx
|
||||
)
|
||||
|
||||
@@ -36,6 +38,24 @@ SUITES=(
|
||||
"matrix:lib/haskell/tests/program-matrix.sx"
|
||||
"wordcount:lib/haskell/tests/program-wordcount.sx"
|
||||
"powers:lib/haskell/tests/program-powers.sx"
|
||||
"caesar:lib/haskell/tests/program-caesar.sx"
|
||||
"runlength-str:lib/haskell/tests/program-runlength-str.sx"
|
||||
"showadt:lib/haskell/tests/program-showadt.sx"
|
||||
"showio:lib/haskell/tests/program-showio.sx"
|
||||
"partial:lib/haskell/tests/program-partial.sx"
|
||||
"statistics:lib/haskell/tests/program-statistics.sx"
|
||||
"newton:lib/haskell/tests/program-newton.sx"
|
||||
"wordfreq:lib/haskell/tests/program-wordfreq.sx"
|
||||
"mapgraph:lib/haskell/tests/program-mapgraph.sx"
|
||||
"uniquewords:lib/haskell/tests/program-uniquewords.sx"
|
||||
"setops:lib/haskell/tests/program-setops.sx"
|
||||
"shapes:lib/haskell/tests/program-shapes.sx"
|
||||
"person:lib/haskell/tests/program-person.sx"
|
||||
"config:lib/haskell/tests/program-config.sx"
|
||||
"counter:lib/haskell/tests/program-counter.sx"
|
||||
"accumulate:lib/haskell/tests/program-accumulate.sx"
|
||||
"safediv:lib/haskell/tests/program-safediv.sx"
|
||||
"trycatch:lib/haskell/tests/program-trycatch.sx"
|
||||
)
|
||||
|
||||
emit_scoreboard_json() {
|
||||
|
||||
@@ -131,119 +131,281 @@
|
||||
(let
|
||||
((tag (first node)))
|
||||
(cond
|
||||
;; Transformations
|
||||
((= tag "where")
|
||||
(list
|
||||
:let
|
||||
(map hk-desugar (nth node 2))
|
||||
:let (map hk-desugar (nth node 2))
|
||||
(hk-desugar (nth node 1))))
|
||||
((= tag "guarded") (hk-guards-to-if (nth node 1)))
|
||||
((= tag "list-comp")
|
||||
(hk-lc-desugar
|
||||
(hk-desugar (nth node 1))
|
||||
(nth node 2)))
|
||||
|
||||
;; Expression nodes
|
||||
(hk-lc-desugar (hk-desugar (nth node 1)) (nth node 2)))
|
||||
((= tag "app")
|
||||
(list
|
||||
:app
|
||||
(hk-desugar (nth node 1))
|
||||
:app (hk-desugar (nth node 1))
|
||||
(hk-desugar (nth node 2))))
|
||||
((= tag "p-rec")
|
||||
(let
|
||||
((cname (nth node 1))
|
||||
(field-pats (nth node 2))
|
||||
(field-order (hk-record-field-names cname)))
|
||||
(cond
|
||||
((nil? field-order)
|
||||
(raise (str "p-rec: no record info for " cname)))
|
||||
(:else
|
||||
(list
|
||||
:p-con
|
||||
cname
|
||||
(map
|
||||
(fn
|
||||
(fname)
|
||||
(let
|
||||
((p (hk-find-rec-pair field-pats fname)))
|
||||
(cond
|
||||
((nil? p) (list :p-wild))
|
||||
(:else (hk-desugar (nth p 1))))))
|
||||
field-order))))))
|
||||
((= tag "rec-update")
|
||||
(list
|
||||
:rec-update
|
||||
(hk-desugar (nth node 1))
|
||||
(map
|
||||
(fn (p) (list (first p) (hk-desugar (nth p 1))))
|
||||
(nth node 2))))
|
||||
((= tag "rec-create")
|
||||
(let
|
||||
((cname (nth node 1))
|
||||
(field-pairs (nth node 2))
|
||||
(field-order (hk-record-field-names cname)))
|
||||
(cond
|
||||
((nil? field-order)
|
||||
(raise (str "rec-create: no record info for " cname)))
|
||||
(:else
|
||||
(let
|
||||
((acc (list :con cname)))
|
||||
(begin
|
||||
(for-each
|
||||
(fn
|
||||
(fname)
|
||||
(let
|
||||
((pair
|
||||
(hk-find-rec-pair field-pairs fname)))
|
||||
(cond
|
||||
((nil? pair)
|
||||
(raise
|
||||
(str
|
||||
"rec-create: missing field "
|
||||
fname
|
||||
" for "
|
||||
cname)))
|
||||
(:else
|
||||
(set!
|
||||
acc
|
||||
(list
|
||||
:app
|
||||
acc
|
||||
(hk-desugar (nth pair 1))))))))
|
||||
field-order)
|
||||
acc))))))
|
||||
((= tag "op")
|
||||
(list
|
||||
:op
|
||||
(nth node 1)
|
||||
:op (nth node 1)
|
||||
(hk-desugar (nth node 2))
|
||||
(hk-desugar (nth node 3))))
|
||||
((= tag "type-ann") (hk-desugar (nth node 1)))
|
||||
((= tag "neg") (list :neg (hk-desugar (nth node 1))))
|
||||
((= tag "if")
|
||||
(list
|
||||
:if
|
||||
(hk-desugar (nth node 1))
|
||||
:if (hk-desugar (nth node 1))
|
||||
(hk-desugar (nth node 2))
|
||||
(hk-desugar (nth node 3))))
|
||||
((= tag "tuple")
|
||||
(list :tuple (map hk-desugar (nth node 1))))
|
||||
((= tag "list")
|
||||
(list :list (map hk-desugar (nth node 1))))
|
||||
((= tag "tuple") (list :tuple (map hk-desugar (nth node 1))))
|
||||
((= tag "list") (list :list (map hk-desugar (nth node 1))))
|
||||
((= tag "range")
|
||||
(list
|
||||
:range
|
||||
(hk-desugar (nth node 1))
|
||||
:range (hk-desugar (nth node 1))
|
||||
(hk-desugar (nth node 2))))
|
||||
((= tag "range-step")
|
||||
(list
|
||||
:range-step
|
||||
(hk-desugar (nth node 1))
|
||||
:range-step (hk-desugar (nth node 1))
|
||||
(hk-desugar (nth node 2))
|
||||
(hk-desugar (nth node 3))))
|
||||
((= tag "lambda")
|
||||
(list
|
||||
:lambda
|
||||
(nth node 1)
|
||||
(hk-desugar (nth node 2))))
|
||||
(list :lambda (nth node 1) (hk-desugar (nth node 2))))
|
||||
((= tag "let")
|
||||
(list
|
||||
:let
|
||||
(map hk-desugar (nth node 1))
|
||||
:let (map hk-desugar (nth node 1))
|
||||
(hk-desugar (nth node 2))))
|
||||
((= tag "case")
|
||||
(list
|
||||
:case
|
||||
(hk-desugar (nth node 1))
|
||||
:case (hk-desugar (nth node 1))
|
||||
(map hk-desugar (nth node 2))))
|
||||
((= tag "alt")
|
||||
(list :alt (nth node 1) (hk-desugar (nth node 2))))
|
||||
(list :alt (hk-desugar (nth node 1)) (hk-desugar (nth node 2))))
|
||||
((= tag "do") (hk-desugar-do (nth node 1)))
|
||||
((= tag "sect-left")
|
||||
(list
|
||||
:sect-left
|
||||
(nth node 1)
|
||||
(hk-desugar (nth node 2))))
|
||||
(list :sect-left (nth node 1) (hk-desugar (nth node 2))))
|
||||
((= tag "sect-right")
|
||||
(list
|
||||
:sect-right
|
||||
(nth node 1)
|
||||
(hk-desugar (nth node 2))))
|
||||
|
||||
;; Top-level
|
||||
(list :sect-right (nth node 1) (hk-desugar (nth node 2))))
|
||||
((= tag "program")
|
||||
(list :program (map hk-desugar (nth node 1))))
|
||||
(list :program (map hk-desugar (hk-expand-records (nth node 1)))))
|
||||
((= tag "module")
|
||||
(list
|
||||
:module
|
||||
(nth node 1)
|
||||
:module (nth node 1)
|
||||
(nth node 2)
|
||||
(nth node 3)
|
||||
(map hk-desugar (nth node 4))))
|
||||
|
||||
;; Decls carrying a body
|
||||
(map hk-desugar (hk-expand-records (nth node 4)))))
|
||||
((= tag "fun-clause")
|
||||
(list
|
||||
:fun-clause
|
||||
(nth node 1)
|
||||
(nth node 2)
|
||||
:fun-clause (nth node 1)
|
||||
(map hk-desugar (nth node 2))
|
||||
(hk-desugar (nth node 3))))
|
||||
((= tag "instance-decl")
|
||||
(list
|
||||
:instance-decl (nth node 1)
|
||||
(nth node 2)
|
||||
(map hk-desugar (nth node 3))))
|
||||
((= tag "pat-bind")
|
||||
(list
|
||||
:pat-bind
|
||||
(nth node 1)
|
||||
(hk-desugar (nth node 2))))
|
||||
(list :pat-bind (nth node 1) (hk-desugar (nth node 2))))
|
||||
((= tag "bind")
|
||||
(list
|
||||
:bind
|
||||
(nth node 1)
|
||||
(hk-desugar (nth node 2))))
|
||||
|
||||
;; Everything else: leaf literals, vars, cons, patterns,
|
||||
;; types, imports, type-sigs, data / newtype / fixity, …
|
||||
(list :bind (nth node 1) (hk-desugar (nth node 2))))
|
||||
(:else node)))))))
|
||||
|
||||
;; Convenience — tokenize + layout + parse + desugar.
|
||||
(define
|
||||
hk-core
|
||||
(fn (src) (hk-desugar (hk-parse-top src))))
|
||||
(define hk-record-fields (dict))
|
||||
|
||||
(define
|
||||
hk-core-expr
|
||||
(fn (src) (hk-desugar (hk-parse src))))
|
||||
hk-register-record-fields!
|
||||
(fn (cname fields) (dict-set! hk-record-fields cname fields)))
|
||||
|
||||
(define
|
||||
hk-record-field-names
|
||||
(fn
|
||||
(cname)
|
||||
(if (has-key? hk-record-fields cname) (get hk-record-fields cname) nil)))
|
||||
|
||||
(define
|
||||
hk-record-field-index
|
||||
(fn
|
||||
(cname fname)
|
||||
(let
|
||||
((fields (hk-record-field-names cname)))
|
||||
(cond
|
||||
((nil? fields) -1)
|
||||
(:else
|
||||
(let
|
||||
((i 0) (idx -1))
|
||||
(begin
|
||||
(for-each
|
||||
(fn
|
||||
(f)
|
||||
(begin (when (= f fname) (set! idx i)) (set! i (+ i 1))))
|
||||
fields)
|
||||
idx)))))))
|
||||
|
||||
(define
|
||||
hk-find-rec-pair
|
||||
(fn
|
||||
(pairs name)
|
||||
(cond
|
||||
((empty? pairs) nil)
|
||||
((= (first (first pairs)) name) (first pairs))
|
||||
(:else (hk-find-rec-pair (rest pairs) name)))))
|
||||
|
||||
(define
|
||||
hk-record-accessors
|
||||
(fn
|
||||
(cname rec-fields)
|
||||
(let
|
||||
((n (len rec-fields)) (i 0) (out (list)))
|
||||
(define
|
||||
hk-ra-loop
|
||||
(fn
|
||||
()
|
||||
(when
|
||||
(< i n)
|
||||
(let
|
||||
((field (nth rec-fields i)))
|
||||
(let
|
||||
((fname (first field)) (j 0) (pats (list)))
|
||||
(define
|
||||
hk-pat-loop
|
||||
(fn
|
||||
()
|
||||
(when
|
||||
(< j n)
|
||||
(begin
|
||||
(append!
|
||||
pats
|
||||
(if
|
||||
(= j i)
|
||||
(list "p-var" "__rec_field")
|
||||
(list "p-wild")))
|
||||
(set! j (+ j 1))
|
||||
(hk-pat-loop)))))
|
||||
(hk-pat-loop)
|
||||
(append!
|
||||
out
|
||||
(list
|
||||
"fun-clause"
|
||||
fname
|
||||
(list (list "p-con" cname pats))
|
||||
(list "var" "__rec_field")))
|
||||
(set! i (+ i 1))
|
||||
(hk-ra-loop))))))
|
||||
(hk-ra-loop)
|
||||
out)))
|
||||
|
||||
(define
|
||||
hk-expand-records
|
||||
(fn
|
||||
(decls)
|
||||
(let
|
||||
((out (list)))
|
||||
(for-each
|
||||
(fn
|
||||
(d)
|
||||
(cond
|
||||
((and (list? d) (= (first d) "data"))
|
||||
(let
|
||||
((dname (nth d 1))
|
||||
(tvars (nth d 2))
|
||||
(cons-list (nth d 3))
|
||||
(deriving (if (> (len d) 4) (nth d 4) (list)))
|
||||
(new-cons (list))
|
||||
(accessors (list)))
|
||||
(begin
|
||||
(for-each
|
||||
(fn
|
||||
(c)
|
||||
(cond
|
||||
((= (first c) "con-rec")
|
||||
(let
|
||||
((cname (nth c 1)) (rec-fields (nth c 2)))
|
||||
(begin
|
||||
(hk-register-record-fields!
|
||||
cname
|
||||
(map (fn (f) (first f)) rec-fields))
|
||||
(append!
|
||||
new-cons
|
||||
(list
|
||||
"con-def"
|
||||
cname
|
||||
(map (fn (f) (nth f 1)) rec-fields)))
|
||||
(for-each
|
||||
(fn (a) (append! accessors a))
|
||||
(hk-record-accessors cname rec-fields)))))
|
||||
(:else (append! new-cons c))))
|
||||
cons-list)
|
||||
(append!
|
||||
out
|
||||
(if
|
||||
(empty? deriving)
|
||||
(list "data" dname tvars new-cons)
|
||||
(list "data" dname tvars new-cons deriving)))
|
||||
(for-each (fn (a) (append! out a)) accessors))))
|
||||
(:else (append! out d))))
|
||||
decls)
|
||||
out)))
|
||||
|
||||
(define hk-core (fn (src) (hk-desugar (hk-parse-top src))))
|
||||
|
||||
(define hk-core-expr (fn (src) (hk-desugar (hk-parse src))))
|
||||
|
||||
1023
lib/haskell/eval.sx
1023
lib/haskell/eval.sx
File diff suppressed because one or more lines are too long
520
lib/haskell/map.sx
Normal file
520
lib/haskell/map.sx
Normal file
@@ -0,0 +1,520 @@
|
||||
;; map.sx — Phase 11 Data.Map: weight-balanced BST in pure SX.
|
||||
;;
|
||||
;; Algorithm: Adams's weight-balanced tree (the same family as Haskell's
|
||||
;; Data.Map). Each node tracks its size; rotations maintain the invariant
|
||||
;;
|
||||
;; size(small-side) * delta >= size(large-side) (delta = 3)
|
||||
;;
|
||||
;; with single or double rotations chosen by the gamma ratio (gamma = 2).
|
||||
;; The size field is an Int and is included so `size`, `lookup`, etc. are
|
||||
;; O(log n) on both extremes of the tree.
|
||||
;;
|
||||
;; Representation:
|
||||
;; Empty → ("Map-Empty")
|
||||
;; Node → ("Map-Node" key val left right size)
|
||||
;;
|
||||
;; All operations are pure SX — no mutation of nodes once constructed.
|
||||
;; The user-facing Haskell layer (Phase 11 next iteration) wraps these
|
||||
;; for `import Data.Map as Map`.
|
||||
|
||||
;; ── Constructors ────────────────────────────────────────────
|
||||
(define hk-map-empty (list "Map-Empty"))
|
||||
|
||||
(define
|
||||
hk-map-node
|
||||
(fn
|
||||
(k v l r)
|
||||
(list "Map-Node" k v l r (+ 1 (+ (hk-map-size l) (hk-map-size r))))))
|
||||
|
||||
;; ── Predicates and accessors ────────────────────────────────
|
||||
(define hk-map-empty? (fn (m) (and (list? m) (= (first m) "Map-Empty"))))
|
||||
|
||||
(define hk-map-node? (fn (m) (and (list? m) (= (first m) "Map-Node"))))
|
||||
|
||||
(define
|
||||
hk-map-size
|
||||
(fn (m) (cond ((hk-map-empty? m) 0) (:else (nth m 5)))))
|
||||
|
||||
(define hk-map-key (fn (m) (nth m 1)))
|
||||
(define hk-map-val (fn (m) (nth m 2)))
|
||||
(define hk-map-left (fn (m) (nth m 3)))
|
||||
(define hk-map-right (fn (m) (nth m 4)))
|
||||
|
||||
;; ── Weight-balanced rotations ───────────────────────────────
|
||||
;; delta and gamma per Adams 1992 / Haskell Data.Map.
|
||||
|
||||
(define hk-map-delta 3)
|
||||
(define hk-map-gamma 2)
|
||||
|
||||
(define
|
||||
hk-map-single-l
|
||||
(fn
|
||||
(k v l r)
|
||||
(let
|
||||
((rk (hk-map-key r))
|
||||
(rv (hk-map-val r))
|
||||
(rl (hk-map-left r))
|
||||
(rr (hk-map-right r)))
|
||||
(hk-map-node rk rv (hk-map-node k v l rl) rr))))
|
||||
|
||||
(define
|
||||
hk-map-single-r
|
||||
(fn
|
||||
(k v l r)
|
||||
(let
|
||||
((lk (hk-map-key l))
|
||||
(lv (hk-map-val l))
|
||||
(ll (hk-map-left l))
|
||||
(lr (hk-map-right l)))
|
||||
(hk-map-node lk lv ll (hk-map-node k v lr r)))))
|
||||
|
||||
(define
|
||||
hk-map-double-l
|
||||
(fn
|
||||
(k v l r)
|
||||
(let
|
||||
((rk (hk-map-key r))
|
||||
(rv (hk-map-val r))
|
||||
(rl (hk-map-left r))
|
||||
(rr (hk-map-right r))
|
||||
(rlk (hk-map-key (hk-map-left r)))
|
||||
(rlv (hk-map-val (hk-map-left r)))
|
||||
(rll (hk-map-left (hk-map-left r)))
|
||||
(rlr (hk-map-right (hk-map-left r))))
|
||||
(hk-map-node
|
||||
rlk
|
||||
rlv
|
||||
(hk-map-node k v l rll)
|
||||
(hk-map-node rk rv rlr rr)))))
|
||||
|
||||
(define
|
||||
hk-map-double-r
|
||||
(fn
|
||||
(k v l r)
|
||||
(let
|
||||
((lk (hk-map-key l))
|
||||
(lv (hk-map-val l))
|
||||
(ll (hk-map-left l))
|
||||
(lr (hk-map-right l))
|
||||
(lrk (hk-map-key (hk-map-right l)))
|
||||
(lrv (hk-map-val (hk-map-right l)))
|
||||
(lrl (hk-map-left (hk-map-right l)))
|
||||
(lrr (hk-map-right (hk-map-right l))))
|
||||
(hk-map-node
|
||||
lrk
|
||||
lrv
|
||||
(hk-map-node lk lv ll lrl)
|
||||
(hk-map-node k v lrr r)))))
|
||||
|
||||
;; ── Balanced node constructor ──────────────────────────────
|
||||
;; Use this in place of hk-map-node when one side may have grown
|
||||
;; or shrunk by one and we need to restore the weight invariant.
|
||||
(define
|
||||
hk-map-balance
|
||||
(fn
|
||||
(k v l r)
|
||||
(let
|
||||
((sl (hk-map-size l)) (sr (hk-map-size r)))
|
||||
(cond
|
||||
((<= (+ sl sr) 1) (hk-map-node k v l r))
|
||||
((> sr (* hk-map-delta sl))
|
||||
(let
|
||||
((rl (hk-map-left r)) (rr (hk-map-right r)))
|
||||
(cond
|
||||
((< (hk-map-size rl) (* hk-map-gamma (hk-map-size rr)))
|
||||
(hk-map-single-l k v l r))
|
||||
(:else (hk-map-double-l k v l r)))))
|
||||
((> sl (* hk-map-delta sr))
|
||||
(let
|
||||
((ll (hk-map-left l)) (lr (hk-map-right l)))
|
||||
(cond
|
||||
((< (hk-map-size lr) (* hk-map-gamma (hk-map-size ll)))
|
||||
(hk-map-single-r k v l r))
|
||||
(:else (hk-map-double-r k v l r)))))
|
||||
(:else (hk-map-node k v l r))))))
|
||||
|
||||
(define
|
||||
hk-map-singleton
|
||||
(fn (k v) (hk-map-node k v hk-map-empty hk-map-empty)))
|
||||
|
||||
(define
|
||||
hk-map-insert
|
||||
(fn
|
||||
(k v m)
|
||||
(cond
|
||||
((hk-map-empty? m) (hk-map-singleton k v))
|
||||
(:else
|
||||
(let
|
||||
((mk (hk-map-key m)))
|
||||
(cond
|
||||
((< k mk)
|
||||
(hk-map-balance
|
||||
mk
|
||||
(hk-map-val m)
|
||||
(hk-map-insert k v (hk-map-left m))
|
||||
(hk-map-right m)))
|
||||
((> k mk)
|
||||
(hk-map-balance
|
||||
mk
|
||||
(hk-map-val m)
|
||||
(hk-map-left m)
|
||||
(hk-map-insert k v (hk-map-right m))))
|
||||
(:else (hk-map-node k v (hk-map-left m) (hk-map-right m)))))))))
|
||||
|
||||
(define
|
||||
hk-map-lookup
|
||||
(fn
|
||||
(k m)
|
||||
(cond
|
||||
((hk-map-empty? m) (list "Nothing"))
|
||||
(:else
|
||||
(let
|
||||
((mk (hk-map-key m)))
|
||||
(cond
|
||||
((< k mk) (hk-map-lookup k (hk-map-left m)))
|
||||
((> k mk) (hk-map-lookup k (hk-map-right m)))
|
||||
(:else (list "Just" (hk-map-val m)))))))))
|
||||
|
||||
(define
|
||||
hk-map-member
|
||||
(fn
|
||||
(k m)
|
||||
(cond
|
||||
((hk-map-empty? m) false)
|
||||
(:else
|
||||
(let
|
||||
((mk (hk-map-key m)))
|
||||
(cond
|
||||
((< k mk) (hk-map-member k (hk-map-left m)))
|
||||
((> k mk) (hk-map-member k (hk-map-right m)))
|
||||
(:else true)))))))
|
||||
|
||||
(define hk-map-null hk-map-empty?)
|
||||
|
||||
(define
|
||||
hk-map-find-min
|
||||
(fn
|
||||
(m)
|
||||
(cond
|
||||
((hk-map-empty? (hk-map-left m))
|
||||
(list (hk-map-key m) (hk-map-val m)))
|
||||
(:else (hk-map-find-min (hk-map-left m))))))
|
||||
|
||||
(define
|
||||
hk-map-delete-min
|
||||
(fn
|
||||
(m)
|
||||
(cond
|
||||
((hk-map-empty? (hk-map-left m)) (hk-map-right m))
|
||||
(:else
|
||||
(hk-map-balance
|
||||
(hk-map-key m)
|
||||
(hk-map-val m)
|
||||
(hk-map-delete-min (hk-map-left m))
|
||||
(hk-map-right m))))))
|
||||
|
||||
(define
|
||||
hk-map-find-max
|
||||
(fn
|
||||
(m)
|
||||
(cond
|
||||
((hk-map-empty? (hk-map-right m))
|
||||
(list (hk-map-key m) (hk-map-val m)))
|
||||
(:else (hk-map-find-max (hk-map-right m))))))
|
||||
|
||||
(define
|
||||
hk-map-delete-max
|
||||
(fn
|
||||
(m)
|
||||
(cond
|
||||
((hk-map-empty? (hk-map-right m)) (hk-map-left m))
|
||||
(:else
|
||||
(hk-map-balance
|
||||
(hk-map-key m)
|
||||
(hk-map-val m)
|
||||
(hk-map-left m)
|
||||
(hk-map-delete-max (hk-map-right m)))))))
|
||||
|
||||
(define
|
||||
hk-map-glue
|
||||
(fn
|
||||
(l r)
|
||||
(cond
|
||||
((hk-map-empty? l) r)
|
||||
((hk-map-empty? r) l)
|
||||
((> (hk-map-size l) (hk-map-size r))
|
||||
(let
|
||||
((mp (hk-map-find-max l)))
|
||||
(hk-map-balance (first mp) (nth mp 1) (hk-map-delete-max l) r)))
|
||||
(:else
|
||||
(let
|
||||
((mp (hk-map-find-min r)))
|
||||
(hk-map-balance (first mp) (nth mp 1) l (hk-map-delete-min r)))))))
|
||||
|
||||
(define
|
||||
hk-map-delete
|
||||
(fn
|
||||
(k m)
|
||||
(cond
|
||||
((hk-map-empty? m) m)
|
||||
(:else
|
||||
(let
|
||||
((mk (hk-map-key m)))
|
||||
(cond
|
||||
((< k mk)
|
||||
(hk-map-balance
|
||||
mk
|
||||
(hk-map-val m)
|
||||
(hk-map-delete k (hk-map-left m))
|
||||
(hk-map-right m)))
|
||||
((> k mk)
|
||||
(hk-map-balance
|
||||
mk
|
||||
(hk-map-val m)
|
||||
(hk-map-left m)
|
||||
(hk-map-delete k (hk-map-right m))))
|
||||
(:else (hk-map-glue (hk-map-left m) (hk-map-right m)))))))))
|
||||
|
||||
(define
|
||||
hk-map-from-list
|
||||
(fn
|
||||
(pairs)
|
||||
(reduce
|
||||
(fn (acc p) (hk-map-insert (first p) (nth p 1) acc))
|
||||
hk-map-empty
|
||||
pairs)))
|
||||
|
||||
(define
|
||||
hk-map-to-asc-list
|
||||
(fn
|
||||
(m)
|
||||
(cond
|
||||
((hk-map-empty? m) (list))
|
||||
(:else
|
||||
(append
|
||||
(hk-map-to-asc-list (hk-map-left m))
|
||||
(cons
|
||||
(list (hk-map-key m) (hk-map-val m))
|
||||
(hk-map-to-asc-list (hk-map-right m))))))))
|
||||
|
||||
(define hk-map-to-list hk-map-to-asc-list)
|
||||
|
||||
(define
|
||||
hk-map-keys
|
||||
(fn
|
||||
(m)
|
||||
(cond
|
||||
((hk-map-empty? m) (list))
|
||||
(:else
|
||||
(append
|
||||
(hk-map-keys (hk-map-left m))
|
||||
(cons (hk-map-key m) (hk-map-keys (hk-map-right m))))))))
|
||||
|
||||
(define
|
||||
hk-map-elems
|
||||
(fn
|
||||
(m)
|
||||
(cond
|
||||
((hk-map-empty? m) (list))
|
||||
(:else
|
||||
(append
|
||||
(hk-map-elems (hk-map-left m))
|
||||
(cons (hk-map-val m) (hk-map-elems (hk-map-right m))))))))
|
||||
|
||||
(define
|
||||
hk-map-union-with
|
||||
(fn
|
||||
(f m1 m2)
|
||||
(reduce
|
||||
(fn
|
||||
(acc p)
|
||||
(let
|
||||
((k (first p)) (v (nth p 1)))
|
||||
(let
|
||||
((look (hk-map-lookup k acc)))
|
||||
(cond
|
||||
((= (first look) "Just")
|
||||
(hk-map-insert k (f (nth look 1) v) acc))
|
||||
(:else (hk-map-insert k v acc))))))
|
||||
m1
|
||||
(hk-map-to-asc-list m2))))
|
||||
|
||||
(define
|
||||
hk-map-intersection-with
|
||||
(fn
|
||||
(f m1 m2)
|
||||
(reduce
|
||||
(fn
|
||||
(acc p)
|
||||
(let
|
||||
((k (first p)) (v1 (nth p 1)))
|
||||
(let
|
||||
((look (hk-map-lookup k m2)))
|
||||
(cond
|
||||
((= (first look) "Just")
|
||||
(hk-map-insert k (f v1 (nth look 1)) acc))
|
||||
(:else acc)))))
|
||||
hk-map-empty
|
||||
(hk-map-to-asc-list m1))))
|
||||
|
||||
(define
|
||||
hk-map-difference
|
||||
(fn
|
||||
(m1 m2)
|
||||
(reduce
|
||||
(fn
|
||||
(acc p)
|
||||
(let
|
||||
((k (first p)) (v (nth p 1)))
|
||||
(cond ((hk-map-member k m2) acc) (:else (hk-map-insert k v acc)))))
|
||||
hk-map-empty
|
||||
(hk-map-to-asc-list m1))))
|
||||
|
||||
(define
|
||||
hk-map-foldl-with-key
|
||||
(fn
|
||||
(f acc m)
|
||||
(cond
|
||||
((hk-map-empty? m) acc)
|
||||
(:else
|
||||
(let
|
||||
((acc1 (hk-map-foldl-with-key f acc (hk-map-left m))))
|
||||
(let
|
||||
((acc2 (f acc1 (hk-map-key m) (hk-map-val m))))
|
||||
(hk-map-foldl-with-key f acc2 (hk-map-right m))))))))
|
||||
|
||||
(define
|
||||
hk-map-foldr-with-key
|
||||
(fn
|
||||
(f acc m)
|
||||
(cond
|
||||
((hk-map-empty? m) acc)
|
||||
(:else
|
||||
(let
|
||||
((acc1 (hk-map-foldr-with-key f acc (hk-map-right m))))
|
||||
(let
|
||||
((acc2 (f (hk-map-key m) (hk-map-val m) acc1)))
|
||||
(hk-map-foldr-with-key f acc2 (hk-map-left m))))))))
|
||||
|
||||
(define
|
||||
hk-map-map-with-key
|
||||
(fn
|
||||
(f m)
|
||||
(cond
|
||||
((hk-map-empty? m) m)
|
||||
(:else
|
||||
(list
|
||||
"Map-Node"
|
||||
(hk-map-key m)
|
||||
(f (hk-map-key m) (hk-map-val m))
|
||||
(hk-map-map-with-key f (hk-map-left m))
|
||||
(hk-map-map-with-key f (hk-map-right m))
|
||||
(hk-map-size m))))))
|
||||
|
||||
(define
|
||||
hk-map-filter-with-key
|
||||
(fn
|
||||
(p m)
|
||||
(hk-map-foldr-with-key
|
||||
(fn (k v acc) (cond ((p k v) (hk-map-insert k v acc)) (:else acc)))
|
||||
hk-map-empty
|
||||
m)))
|
||||
|
||||
(define
|
||||
hk-map-adjust
|
||||
(fn
|
||||
(f k m)
|
||||
(cond
|
||||
((hk-map-empty? m) m)
|
||||
(:else
|
||||
(let
|
||||
((mk (hk-map-key m)))
|
||||
(cond
|
||||
((< k mk)
|
||||
(hk-map-node
|
||||
mk
|
||||
(hk-map-val m)
|
||||
(hk-map-adjust f k (hk-map-left m))
|
||||
(hk-map-right m)))
|
||||
((> k mk)
|
||||
(hk-map-node
|
||||
mk
|
||||
(hk-map-val m)
|
||||
(hk-map-left m)
|
||||
(hk-map-adjust f k (hk-map-right m))))
|
||||
(:else
|
||||
(hk-map-node
|
||||
mk
|
||||
(f (hk-map-val m))
|
||||
(hk-map-left m)
|
||||
(hk-map-right m)))))))))
|
||||
|
||||
(define
|
||||
hk-map-insert-with
|
||||
(fn
|
||||
(f k v m)
|
||||
(cond
|
||||
((hk-map-empty? m) (hk-map-singleton k v))
|
||||
(:else
|
||||
(let
|
||||
((mk (hk-map-key m)))
|
||||
(cond
|
||||
((< k mk)
|
||||
(hk-map-balance
|
||||
mk
|
||||
(hk-map-val m)
|
||||
(hk-map-insert-with f k v (hk-map-left m))
|
||||
(hk-map-right m)))
|
||||
((> k mk)
|
||||
(hk-map-balance
|
||||
mk
|
||||
(hk-map-val m)
|
||||
(hk-map-left m)
|
||||
(hk-map-insert-with f k v (hk-map-right m))))
|
||||
(:else
|
||||
(hk-map-node
|
||||
mk
|
||||
(f v (hk-map-val m))
|
||||
(hk-map-left m)
|
||||
(hk-map-right m)))))))))
|
||||
|
||||
(define
|
||||
hk-map-insert-with-key
|
||||
(fn
|
||||
(f k v m)
|
||||
(cond
|
||||
((hk-map-empty? m) (hk-map-singleton k v))
|
||||
(:else
|
||||
(let
|
||||
((mk (hk-map-key m)))
|
||||
(cond
|
||||
((< k mk)
|
||||
(hk-map-balance
|
||||
mk
|
||||
(hk-map-val m)
|
||||
(hk-map-insert-with-key f k v (hk-map-left m))
|
||||
(hk-map-right m)))
|
||||
((> k mk)
|
||||
(hk-map-balance
|
||||
mk
|
||||
(hk-map-val m)
|
||||
(hk-map-left m)
|
||||
(hk-map-insert-with-key f k v (hk-map-right m))))
|
||||
(:else
|
||||
(hk-map-node
|
||||
mk
|
||||
(f k v (hk-map-val m))
|
||||
(hk-map-left m)
|
||||
(hk-map-right m)))))))))
|
||||
|
||||
(define
|
||||
hk-map-alter
|
||||
(fn
|
||||
(f k m)
|
||||
(let
|
||||
((look (hk-map-lookup k m)))
|
||||
(let
|
||||
((res (f look)))
|
||||
(cond
|
||||
((= (first res) "Nothing") (hk-map-delete k m))
|
||||
(:else (hk-map-insert k (nth res 1) m)))))))
|
||||
@@ -87,45 +87,41 @@
|
||||
((nil? res) nil)
|
||||
(:else (assoc res (nth pat 1) val)))))
|
||||
(:else
|
||||
(let ((fv (hk-force val)))
|
||||
(let
|
||||
((fv (hk-force val)))
|
||||
(cond
|
||||
((= tag "p-int")
|
||||
(if
|
||||
(and (number? fv) (= fv (nth pat 1)))
|
||||
env
|
||||
nil))
|
||||
(if (and (number? fv) (= fv (nth pat 1))) env nil))
|
||||
((= tag "p-float")
|
||||
(if
|
||||
(and (number? fv) (= fv (nth pat 1)))
|
||||
env
|
||||
nil))
|
||||
(if (and (number? fv) (= fv (nth pat 1))) env nil))
|
||||
((= tag "p-string")
|
||||
(if
|
||||
(and (string? fv) (= fv (nth pat 1)))
|
||||
env
|
||||
nil))
|
||||
(if (and (string? fv) (= fv (nth pat 1))) env nil))
|
||||
((= tag "p-char")
|
||||
(if
|
||||
(and (string? fv) (= fv (nth pat 1)))
|
||||
env
|
||||
nil))
|
||||
(if (and (string? fv) (= fv (nth pat 1))) env nil))
|
||||
((= tag "p-con")
|
||||
(let
|
||||
((pat-name (nth pat 1)) (pat-args (nth pat 2)))
|
||||
(cond
|
||||
((and (= pat-name ":") (hk-str? fv) (not (hk-str-null? fv)))
|
||||
(let
|
||||
((str-head (hk-str-head fv))
|
||||
(str-tail (hk-str-tail fv)))
|
||||
(let
|
||||
((head-pat (nth pat-args 0))
|
||||
(tail-pat (nth pat-args 1)))
|
||||
(let
|
||||
((res (hk-match head-pat str-head env)))
|
||||
(cond
|
||||
((nil? res) nil)
|
||||
(:else (hk-match tail-pat str-tail res)))))))
|
||||
((not (hk-is-con-val? fv)) nil)
|
||||
((not (= (hk-val-con-name fv) pat-name)) nil)
|
||||
(:else
|
||||
(let
|
||||
((val-args (hk-val-con-args fv)))
|
||||
(cond
|
||||
((not (= (len pat-args) (len val-args)))
|
||||
nil)
|
||||
(:else
|
||||
(hk-match-all
|
||||
pat-args
|
||||
val-args
|
||||
env))))))))
|
||||
((not (= (len val-args) (len pat-args))) nil)
|
||||
(:else (hk-match-all pat-args val-args env))))))))
|
||||
((= tag "p-tuple")
|
||||
(let
|
||||
((items (nth pat 1)))
|
||||
@@ -134,13 +130,8 @@
|
||||
((not (= (hk-val-con-name fv) "Tuple")) nil)
|
||||
((not (= (len (hk-val-con-args fv)) (len items)))
|
||||
nil)
|
||||
(:else
|
||||
(hk-match-all
|
||||
items
|
||||
(hk-val-con-args fv)
|
||||
env)))))
|
||||
((= tag "p-list")
|
||||
(hk-match-list-pat (nth pat 1) fv env))
|
||||
(:else (hk-match-all items (hk-val-con-args fv) env)))))
|
||||
((= tag "p-list") (hk-match-list-pat (nth pat 1) fv env))
|
||||
(:else nil))))))))))
|
||||
|
||||
(define
|
||||
@@ -161,17 +152,26 @@
|
||||
hk-match-list-pat
|
||||
(fn
|
||||
(items val env)
|
||||
(let ((fv (hk-force val)))
|
||||
(let
|
||||
((fv (hk-force val)))
|
||||
(cond
|
||||
((empty? items)
|
||||
(if
|
||||
(and
|
||||
(hk-is-con-val? fv)
|
||||
(= (hk-val-con-name fv) "[]"))
|
||||
(or
|
||||
(and (hk-is-con-val? fv) (= (hk-val-con-name fv) "[]"))
|
||||
(and (hk-str? fv) (hk-str-null? fv)))
|
||||
env
|
||||
nil))
|
||||
(:else
|
||||
(cond
|
||||
((and (hk-str? fv) (not (hk-str-null? fv)))
|
||||
(let
|
||||
((h (hk-str-head fv)) (t (hk-str-tail fv)))
|
||||
(let
|
||||
((res (hk-match (first items) h env)))
|
||||
(cond
|
||||
((nil? res) nil)
|
||||
(:else (hk-match-list-pat (rest items) t res))))))
|
||||
((not (hk-is-con-val? fv)) nil)
|
||||
((not (= (hk-val-con-name fv) ":")) nil)
|
||||
(:else
|
||||
@@ -183,11 +183,7 @@
|
||||
((res (hk-match (first items) h env)))
|
||||
(cond
|
||||
((nil? res) nil)
|
||||
(:else
|
||||
(hk-match-list-pat
|
||||
(rest items)
|
||||
t
|
||||
res)))))))))))))
|
||||
(:else (hk-match-list-pat (rest items) t res)))))))))))))
|
||||
|
||||
;; ── Convenience: parse a pattern from source for tests ─────
|
||||
;; (Uses the parser's case-alt entry — `case _ of pat -> 0` —
|
||||
|
||||
@@ -208,9 +208,19 @@
|
||||
((= (get t "type") "char")
|
||||
(do (hk-advance!) (list :char (get t "value"))))
|
||||
((= (get t "type") "varid")
|
||||
(do (hk-advance!) (list :var (get t "value"))))
|
||||
(do
|
||||
(hk-advance!)
|
||||
(cond
|
||||
((hk-match? "lbrace" nil)
|
||||
(hk-parse-rec-update (list :var (get t "value"))))
|
||||
(:else (list :var (get t "value"))))))
|
||||
((= (get t "type") "conid")
|
||||
(do (hk-advance!) (list :con (get t "value"))))
|
||||
(do
|
||||
(hk-advance!)
|
||||
(cond
|
||||
((hk-match? "lbrace" nil)
|
||||
(hk-parse-rec-create (get t "value")))
|
||||
(:else (list :con (get t "value"))))))
|
||||
((= (get t "type") "qvarid")
|
||||
(do (hk-advance!) (list :var (get t "value"))))
|
||||
((= (get t "type") "qconid")
|
||||
@@ -265,38 +275,47 @@
|
||||
(list :sect-right op-name expr-e))))))
|
||||
(:else
|
||||
(let
|
||||
((first-e (hk-parse-expr-inner))
|
||||
(items (list))
|
||||
(is-tuple false))
|
||||
(append! items first-e)
|
||||
(define
|
||||
hk-tup-loop
|
||||
(fn
|
||||
()
|
||||
(when
|
||||
(hk-match? "comma" nil)
|
||||
(do
|
||||
(hk-advance!)
|
||||
(set! is-tuple true)
|
||||
(append! items (hk-parse-expr-inner))
|
||||
(hk-tup-loop)))))
|
||||
(hk-tup-loop)
|
||||
((first-e (hk-parse-expr-inner)))
|
||||
(cond
|
||||
((hk-match? "rparen" nil)
|
||||
((hk-match? "reservedop" "::")
|
||||
(do
|
||||
(hk-advance!)
|
||||
(if is-tuple (list :tuple items) first-e)))
|
||||
(let
|
||||
((ann-type (hk-parse-type)))
|
||||
(hk-expect! "rparen" nil)
|
||||
(list :type-ann first-e ann-type))))
|
||||
(:else
|
||||
(let
|
||||
((op-info2 (hk-section-op-info)))
|
||||
((items (list)) (is-tuple false))
|
||||
(append! items first-e)
|
||||
(define
|
||||
hk-tup-loop
|
||||
(fn
|
||||
()
|
||||
(when
|
||||
(hk-match? "comma" nil)
|
||||
(do
|
||||
(hk-advance!)
|
||||
(set! is-tuple true)
|
||||
(append! items (hk-parse-expr-inner))
|
||||
(hk-tup-loop)))))
|
||||
(hk-tup-loop)
|
||||
(cond
|
||||
((and (not (nil? op-info2)) (not is-tuple) (let ((after2 (hk-peek-at (get op-info2 "len")))) (and (not (nil? after2)) (= (get after2 "type") "rparen"))))
|
||||
(let
|
||||
((op-name (get op-info2 "name")))
|
||||
(hk-consume-op!)
|
||||
((hk-match? "rparen" nil)
|
||||
(do
|
||||
(hk-advance!)
|
||||
(list :sect-left op-name first-e)))
|
||||
(:else (hk-err "expected ')' after expression"))))))))))))))
|
||||
(if is-tuple (list :tuple items) first-e)))
|
||||
(:else
|
||||
(let
|
||||
((op-info2 (hk-section-op-info)))
|
||||
(cond
|
||||
((and (not (nil? op-info2)) (not is-tuple) (let ((after2 (hk-peek-at (get op-info2 "len")))) (and (not (nil? after2)) (= (get after2 "type") "rparen"))))
|
||||
(let
|
||||
((op-name (get op-info2 "name")))
|
||||
(hk-consume-op!)
|
||||
(hk-advance!)
|
||||
(list :sect-left op-name first-e)))
|
||||
(:else (hk-err "expected ')' after expression")))))))))))))))))
|
||||
(define
|
||||
hk-comp-qual-is-gen?
|
||||
(fn
|
||||
@@ -456,6 +475,90 @@
|
||||
(do
|
||||
(hk-expect! "rbracket" nil)
|
||||
(list :list (list first-e))))))))))
|
||||
(define
|
||||
hk-parse-rec-create
|
||||
(fn
|
||||
(cname)
|
||||
(begin
|
||||
(hk-expect! "lbrace" nil)
|
||||
(let
|
||||
((fields (list)))
|
||||
(define
|
||||
hk-rc-loop
|
||||
(fn
|
||||
()
|
||||
(when
|
||||
(hk-match? "varid" nil)
|
||||
(let
|
||||
((fname (get (hk-advance!) "value")))
|
||||
(begin
|
||||
(hk-expect! "reservedop" "=")
|
||||
(let
|
||||
((fexpr (hk-parse-expr-inner)))
|
||||
(begin
|
||||
(append! fields (list fname fexpr))
|
||||
(when
|
||||
(hk-match? "comma" nil)
|
||||
(begin (hk-advance!) (hk-rc-loop))))))))))
|
||||
(hk-rc-loop)
|
||||
(hk-expect! "rbrace" nil)
|
||||
(list :rec-create cname fields)))))
|
||||
(define
|
||||
hk-parse-rec-update
|
||||
(fn
|
||||
(rec-expr)
|
||||
(begin
|
||||
(hk-expect! "lbrace" nil)
|
||||
(let
|
||||
((fields (list)))
|
||||
(define
|
||||
hk-ru-loop
|
||||
(fn
|
||||
()
|
||||
(when
|
||||
(hk-match? "varid" nil)
|
||||
(let
|
||||
((fname (get (hk-advance!) "value")))
|
||||
(begin
|
||||
(hk-expect! "reservedop" "=")
|
||||
(let
|
||||
((fexpr (hk-parse-expr-inner)))
|
||||
(begin
|
||||
(append! fields (list fname fexpr))
|
||||
(when
|
||||
(hk-match? "comma" nil)
|
||||
(begin (hk-advance!) (hk-ru-loop))))))))))
|
||||
(hk-ru-loop)
|
||||
(hk-expect! "rbrace" nil)
|
||||
(list :rec-update rec-expr fields)))))
|
||||
(define
|
||||
hk-parse-rec-pat
|
||||
(fn
|
||||
(cname)
|
||||
(begin
|
||||
(hk-expect! "lbrace" nil)
|
||||
(let
|
||||
((field-pats (list)))
|
||||
(define
|
||||
hk-rp-loop
|
||||
(fn
|
||||
()
|
||||
(when
|
||||
(hk-match? "varid" nil)
|
||||
(let
|
||||
((fname (get (hk-advance!) "value")))
|
||||
(begin
|
||||
(hk-expect! "reservedop" "=")
|
||||
(let
|
||||
((fpat (hk-parse-pat)))
|
||||
(begin
|
||||
(append! field-pats (list fname fpat))
|
||||
(when
|
||||
(hk-match? "comma" nil)
|
||||
(begin (hk-advance!) (hk-rp-loop))))))))))
|
||||
(hk-rp-loop)
|
||||
(hk-expect! "rbrace" nil)
|
||||
(list :p-rec cname field-pats)))))
|
||||
(define
|
||||
hk-parse-fexp
|
||||
(fn
|
||||
@@ -696,7 +799,12 @@
|
||||
(:else
|
||||
(do (hk-advance!) (list :p-var (get t "value")))))))
|
||||
((= (get t "type") "conid")
|
||||
(do (hk-advance!) (list :p-con (get t "value") (list))))
|
||||
(do
|
||||
(hk-advance!)
|
||||
(cond
|
||||
((hk-match? "lbrace" nil)
|
||||
(hk-parse-rec-pat (get t "value")))
|
||||
(:else (list :p-con (get t "value") (list))))))
|
||||
((= (get t "type") "qconid")
|
||||
(do (hk-advance!) (list :p-con (get t "value") (list))))
|
||||
((= (get t "type") "lparen") (hk-parse-paren-pat))
|
||||
@@ -762,16 +870,24 @@
|
||||
(cond
|
||||
((and (not (nil? t)) (or (= (get t "type") "conid") (= (get t "type") "qconid")))
|
||||
(let
|
||||
((name (get (hk-advance!) "value")) (args (list)))
|
||||
(define
|
||||
hk-pca-loop
|
||||
(fn
|
||||
()
|
||||
(when
|
||||
(hk-apat-start? (hk-peek))
|
||||
(do (append! args (hk-parse-apat)) (hk-pca-loop)))))
|
||||
(hk-pca-loop)
|
||||
(list :p-con name args)))
|
||||
((name (get (hk-advance!) "value")))
|
||||
(cond
|
||||
((hk-match? "lbrace" nil)
|
||||
(hk-parse-rec-pat name))
|
||||
(:else
|
||||
(let
|
||||
((args (list)))
|
||||
(define
|
||||
hk-pca-loop
|
||||
(fn
|
||||
()
|
||||
(when
|
||||
(hk-apat-start? (hk-peek))
|
||||
(do
|
||||
(append! args (hk-parse-apat))
|
||||
(hk-pca-loop)))))
|
||||
(hk-pca-loop)
|
||||
(list :p-con name args))))))
|
||||
(:else (hk-parse-apat))))))
|
||||
(define
|
||||
hk-parse-pat
|
||||
@@ -1212,16 +1328,47 @@
|
||||
(not (hk-match? "conid" nil))
|
||||
(hk-err "expected constructor name"))
|
||||
(let
|
||||
((name (get (hk-advance!) "value")) (fields (list)))
|
||||
(define
|
||||
hk-cd-loop
|
||||
(fn
|
||||
()
|
||||
(when
|
||||
(hk-atype-start? (hk-peek))
|
||||
(do (append! fields (hk-parse-atype)) (hk-cd-loop)))))
|
||||
(hk-cd-loop)
|
||||
(list :con-def name fields))))
|
||||
((name (get (hk-advance!) "value")))
|
||||
(cond
|
||||
((hk-match? "lbrace" nil)
|
||||
(begin
|
||||
(hk-advance!)
|
||||
(let
|
||||
((rec-fields (list)))
|
||||
(define
|
||||
hk-rec-loop
|
||||
(fn
|
||||
()
|
||||
(when
|
||||
(hk-match? "varid" nil)
|
||||
(let
|
||||
((fname (get (hk-advance!) "value")))
|
||||
(begin
|
||||
(hk-expect! "reservedop" "::")
|
||||
(let
|
||||
((ftype (hk-parse-type)))
|
||||
(begin
|
||||
(append! rec-fields (list fname ftype))
|
||||
(when
|
||||
(hk-match? "comma" nil)
|
||||
(begin (hk-advance!) (hk-rec-loop))))))))))
|
||||
(hk-rec-loop)
|
||||
(hk-expect! "rbrace" nil)
|
||||
(list :con-rec name rec-fields))))
|
||||
(:else
|
||||
(let
|
||||
((fields (list)))
|
||||
(define
|
||||
hk-cd-loop
|
||||
(fn
|
||||
()
|
||||
(when
|
||||
(hk-atype-start? (hk-peek))
|
||||
(begin
|
||||
(append! fields (hk-parse-atype))
|
||||
(hk-cd-loop)))))
|
||||
(hk-cd-loop)
|
||||
(list :con-def name fields)))))))
|
||||
(define
|
||||
hk-parse-tvars
|
||||
(fn
|
||||
@@ -1586,10 +1733,18 @@
|
||||
(= (hk-peek-type) "eof")
|
||||
(hk-match? "vrbrace" nil)
|
||||
(hk-match? "rbrace" nil))))
|
||||
(define
|
||||
hk-body-step
|
||||
(fn
|
||||
()
|
||||
(cond
|
||||
((hk-match? "reserved" "import")
|
||||
(append! imports (hk-parse-import)))
|
||||
(:else (append! decls (hk-parse-decl))))))
|
||||
(when
|
||||
(not (hk-body-at-end?))
|
||||
(do
|
||||
(append! decls (hk-parse-decl))
|
||||
(hk-body-step)
|
||||
(define
|
||||
hk-body-loop
|
||||
(fn
|
||||
@@ -1600,7 +1755,7 @@
|
||||
(hk-advance!)
|
||||
(when
|
||||
(not (hk-body-at-end?))
|
||||
(append! decls (hk-parse-decl)))
|
||||
(hk-body-step))
|
||||
(hk-body-loop)))))
|
||||
(hk-body-loop)))
|
||||
(list imports decls))))
|
||||
|
||||
@@ -12,12 +12,7 @@
|
||||
|
||||
(define
|
||||
hk-register-con!
|
||||
(fn
|
||||
(cname arity type-name)
|
||||
(dict-set!
|
||||
hk-constructors
|
||||
cname
|
||||
{:arity arity :type type-name})))
|
||||
(fn (cname arity type-name) (dict-set! hk-constructors cname {:arity arity :type type-name})))
|
||||
|
||||
(define hk-is-con? (fn (name) (has-key? hk-constructors name)))
|
||||
|
||||
@@ -48,26 +43,15 @@
|
||||
(fn
|
||||
(data-node)
|
||||
(let
|
||||
((type-name (nth data-node 1))
|
||||
(cons-list (nth data-node 3)))
|
||||
((type-name (nth data-node 1)) (cons-list (nth data-node 3)))
|
||||
(for-each
|
||||
(fn
|
||||
(cd)
|
||||
(hk-register-con!
|
||||
(nth cd 1)
|
||||
(len (nth cd 2))
|
||||
type-name))
|
||||
(fn (cd) (hk-register-con! (nth cd 1) (len (nth cd 2)) type-name))
|
||||
cons-list))))
|
||||
|
||||
;; (:newtype NAME TVARS CNAME FIELD)
|
||||
(define
|
||||
hk-register-newtype!
|
||||
(fn
|
||||
(nt-node)
|
||||
(hk-register-con!
|
||||
(nth nt-node 3)
|
||||
1
|
||||
(nth nt-node 1))))
|
||||
(fn (nt-node) (hk-register-con! (nth nt-node 3) 1 (nth nt-node 1))))
|
||||
|
||||
;; Walk a decls list, registering every `data` / `newtype` decl.
|
||||
(define
|
||||
@@ -78,15 +62,9 @@
|
||||
(fn
|
||||
(d)
|
||||
(cond
|
||||
((and
|
||||
(list? d)
|
||||
(not (empty? d))
|
||||
(= (first d) "data"))
|
||||
((and (list? d) (not (empty? d)) (= (first d) "data"))
|
||||
(hk-register-data! d))
|
||||
((and
|
||||
(list? d)
|
||||
(not (empty? d))
|
||||
(= (first d) "newtype"))
|
||||
((and (list? d) (not (empty? d)) (= (first d) "newtype"))
|
||||
(hk-register-newtype! d))
|
||||
(:else nil)))
|
||||
decls)))
|
||||
@@ -99,16 +77,12 @@
|
||||
((nil? ast) nil)
|
||||
((not (list? ast)) nil)
|
||||
((empty? ast) nil)
|
||||
((= (first ast) "program")
|
||||
(hk-register-decls! (nth ast 1)))
|
||||
((= (first ast) "module")
|
||||
(hk-register-decls! (nth ast 4)))
|
||||
((= (first ast) "program") (hk-register-decls! (nth ast 1)))
|
||||
((= (first ast) "module") (hk-register-decls! (nth ast 4)))
|
||||
(:else nil))))
|
||||
|
||||
;; Convenience: source → AST → desugar → register.
|
||||
(define
|
||||
hk-load-source!
|
||||
(fn (src) (hk-register-program! (hk-core src))))
|
||||
(define hk-load-source! (fn (src) (hk-register-program! (hk-core src))))
|
||||
|
||||
;; ── Built-in constructors pre-registered ─────────────────────
|
||||
;; Bool — used implicitly by `if`, comparison operators.
|
||||
@@ -122,9 +96,55 @@
|
||||
;; Standard Prelude types — pre-registered so expression-level
|
||||
;; programs can use them without a `data` decl.
|
||||
(hk-register-con! "Nothing" 0 "Maybe")
|
||||
(hk-register-con! "Just" 1 "Maybe")
|
||||
(hk-register-con! "Left" 1 "Either")
|
||||
(hk-register-con! "Right" 1 "Either")
|
||||
(hk-register-con! "Just" 1 "Maybe")
|
||||
(hk-register-con! "Left" 1 "Either")
|
||||
(hk-register-con! "Right" 1 "Either")
|
||||
(hk-register-con! "LT" 0 "Ordering")
|
||||
(hk-register-con! "EQ" 0 "Ordering")
|
||||
(hk-register-con! "GT" 0 "Ordering")
|
||||
(hk-register-con! "SomeException" 1 "SomeException")
|
||||
|
||||
(define
|
||||
hk-str?
|
||||
(fn (v) (or (string? v) (and (dict? v) (has-key? v "hk-str")))))
|
||||
|
||||
(define
|
||||
hk-str-head
|
||||
(fn
|
||||
(v)
|
||||
(if
|
||||
(string? v)
|
||||
(char-code (char-at v 0))
|
||||
(char-code (char-at (get v "hk-str") (get v "hk-off"))))))
|
||||
|
||||
(define
|
||||
hk-str-tail
|
||||
(fn
|
||||
(v)
|
||||
(let
|
||||
((buf (if (string? v) v (get v "hk-str")))
|
||||
(off (if (string? v) 1 (+ (get v "hk-off") 1))))
|
||||
(if (>= off (string-length buf)) (list "[]") {:hk-off off :hk-str buf}))))
|
||||
|
||||
(define
|
||||
hk-str-null?
|
||||
(fn
|
||||
(v)
|
||||
(if
|
||||
(string? v)
|
||||
(= (string-length v) 0)
|
||||
(>= (get v "hk-off") (string-length (get v "hk-str"))))))
|
||||
|
||||
(define
|
||||
hk-str-to-native
|
||||
(fn
|
||||
(v)
|
||||
(if
|
||||
(string? v)
|
||||
v
|
||||
(let
|
||||
((buf (get v "hk-str")) (off (get v "hk-off")))
|
||||
(reduce
|
||||
(fn (acc i) (str acc (char-at buf i)))
|
||||
""
|
||||
(range off (string-length buf)))))))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"date": "2026-05-06",
|
||||
"total_pass": 156,
|
||||
"date": "2026-05-08",
|
||||
"total_pass": 285,
|
||||
"total_fail": 0,
|
||||
"programs": {
|
||||
"fib": {"pass": 2, "fail": 0},
|
||||
@@ -9,7 +9,7 @@
|
||||
"nqueens": {"pass": 2, "fail": 0},
|
||||
"calculator": {"pass": 5, "fail": 0},
|
||||
"collatz": {"pass": 11, "fail": 0},
|
||||
"palindrome": {"pass": 8, "fail": 0},
|
||||
"palindrome": {"pass": 12, "fail": 0},
|
||||
"maybe": {"pass": 12, "fail": 0},
|
||||
"fizzbuzz": {"pass": 12, "fail": 0},
|
||||
"anagram": {"pass": 9, "fail": 0},
|
||||
@@ -19,7 +19,25 @@
|
||||
"primes": {"pass": 12, "fail": 0},
|
||||
"zipwith": {"pass": 9, "fail": 0},
|
||||
"matrix": {"pass": 8, "fail": 0},
|
||||
"wordcount": {"pass": 7, "fail": 0},
|
||||
"powers": {"pass": 14, "fail": 0}
|
||||
"wordcount": {"pass": 10, "fail": 0},
|
||||
"powers": {"pass": 14, "fail": 0},
|
||||
"caesar": {"pass": 8, "fail": 0},
|
||||
"runlength-str": {"pass": 9, "fail": 0},
|
||||
"showadt": {"pass": 5, "fail": 0},
|
||||
"showio": {"pass": 5, "fail": 0},
|
||||
"partial": {"pass": 7, "fail": 0},
|
||||
"statistics": {"pass": 5, "fail": 0},
|
||||
"newton": {"pass": 5, "fail": 0},
|
||||
"wordfreq": {"pass": 7, "fail": 0},
|
||||
"mapgraph": {"pass": 6, "fail": 0},
|
||||
"uniquewords": {"pass": 4, "fail": 0},
|
||||
"setops": {"pass": 8, "fail": 0},
|
||||
"shapes": {"pass": 5, "fail": 0},
|
||||
"person": {"pass": 7, "fail": 0},
|
||||
"config": {"pass": 10, "fail": 0},
|
||||
"counter": {"pass": 7, "fail": 0},
|
||||
"accumulate": {"pass": 8, "fail": 0},
|
||||
"safediv": {"pass": 8, "fail": 0},
|
||||
"trycatch": {"pass": 8, "fail": 0}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Haskell-on-SX Scoreboard
|
||||
|
||||
Updated 2026-05-06 · Phase 6 (prelude extras + 18 programs)
|
||||
Updated 2026-05-08 · Phase 6 (prelude extras + 18 programs)
|
||||
|
||||
| Program | Tests | Status |
|
||||
|---------|-------|--------|
|
||||
@@ -10,7 +10,7 @@ Updated 2026-05-06 · Phase 6 (prelude extras + 18 programs)
|
||||
| nqueens.hs | 2/2 | ✓ |
|
||||
| calculator.hs | 5/5 | ✓ |
|
||||
| collatz.hs | 11/11 | ✓ |
|
||||
| palindrome.hs | 8/8 | ✓ |
|
||||
| palindrome.hs | 12/12 | ✓ |
|
||||
| maybe.hs | 12/12 | ✓ |
|
||||
| fizzbuzz.hs | 12/12 | ✓ |
|
||||
| anagram.hs | 9/9 | ✓ |
|
||||
@@ -20,6 +20,24 @@ Updated 2026-05-06 · Phase 6 (prelude extras + 18 programs)
|
||||
| primes.hs | 12/12 | ✓ |
|
||||
| zipwith.hs | 9/9 | ✓ |
|
||||
| matrix.hs | 8/8 | ✓ |
|
||||
| wordcount.hs | 7/7 | ✓ |
|
||||
| wordcount.hs | 10/10 | ✓ |
|
||||
| powers.hs | 14/14 | ✓ |
|
||||
| **Total** | **156/156** | **18/18 programs** |
|
||||
| caesar.hs | 8/8 | ✓ |
|
||||
| runlength-str.hs | 9/9 | ✓ |
|
||||
| showadt.hs | 5/5 | ✓ |
|
||||
| showio.hs | 5/5 | ✓ |
|
||||
| partial.hs | 7/7 | ✓ |
|
||||
| statistics.hs | 5/5 | ✓ |
|
||||
| newton.hs | 5/5 | ✓ |
|
||||
| wordfreq.hs | 7/7 | ✓ |
|
||||
| mapgraph.hs | 6/6 | ✓ |
|
||||
| uniquewords.hs | 4/4 | ✓ |
|
||||
| setops.hs | 8/8 | ✓ |
|
||||
| shapes.hs | 5/5 | ✓ |
|
||||
| person.hs | 7/7 | ✓ |
|
||||
| config.hs | 10/10 | ✓ |
|
||||
| counter.hs | 7/7 | ✓ |
|
||||
| accumulate.hs | 8/8 | ✓ |
|
||||
| safediv.hs | 8/8 | ✓ |
|
||||
| trycatch.hs | 8/8 | ✓ |
|
||||
| **Total** | **285/285** | **36/36 programs** |
|
||||
|
||||
62
lib/haskell/set.sx
Normal file
62
lib/haskell/set.sx
Normal file
@@ -0,0 +1,62 @@
|
||||
;; set.sx — Phase 12 Data.Set: wraps Data.Map with unit values.
|
||||
;;
|
||||
;; A Set is a Map from key to (). All set operations delegate to the map
|
||||
;; ops, ignoring the value side. Storage representation matches Data.Map:
|
||||
;;
|
||||
;; Empty → ("Map-Empty")
|
||||
;; Node → ("Map-Node" key () left right size)
|
||||
;;
|
||||
;; Tradeoff: trivial maintenance burden, slight overhead per node from
|
||||
;; the unused value slot. Faster path forward than re-implementing the
|
||||
;; weight-balanced BST.
|
||||
;;
|
||||
;; Functions live in this file; the Haskell-level `import Data.Set` /
|
||||
;; `import qualified Data.Set as Set` wiring (next Phase 12 box) binds
|
||||
;; them under the chosen alias.
|
||||
|
||||
(define hk-set-unit (list "Tuple"))
|
||||
|
||||
(define hk-set-empty hk-map-empty)
|
||||
|
||||
(define hk-set-singleton (fn (k) (hk-map-singleton k hk-set-unit)))
|
||||
|
||||
(define hk-set-insert (fn (k s) (hk-map-insert k hk-set-unit s)))
|
||||
|
||||
(define hk-set-delete hk-map-delete)
|
||||
(define hk-set-member hk-map-member)
|
||||
(define hk-set-size hk-map-size)
|
||||
(define hk-set-null hk-map-null)
|
||||
(define hk-set-to-asc-list hk-map-keys)
|
||||
(define hk-set-to-list hk-map-keys)
|
||||
|
||||
(define
|
||||
hk-set-from-list
|
||||
(fn (xs) (reduce (fn (acc k) (hk-set-insert k acc)) hk-set-empty xs)))
|
||||
|
||||
(define
|
||||
hk-set-union
|
||||
(fn (a b) (hk-map-union-with (fn (x y) hk-set-unit) a b)))
|
||||
|
||||
(define
|
||||
hk-set-intersection
|
||||
(fn (a b) (hk-map-intersection-with (fn (x y) hk-set-unit) a b)))
|
||||
|
||||
(define hk-set-difference hk-map-difference)
|
||||
|
||||
(define
|
||||
hk-set-is-subset-of
|
||||
(fn (a b) (= (hk-map-size (hk-map-difference a b)) 0)))
|
||||
|
||||
(define
|
||||
hk-set-filter
|
||||
(fn (p s) (hk-map-filter-with-key (fn (k v) (p k)) s)))
|
||||
|
||||
(define hk-set-map (fn (f s) (hk-set-from-list (map f (hk-map-keys s)))))
|
||||
|
||||
(define
|
||||
hk-set-foldr
|
||||
(fn (f z s) (hk-map-foldr-with-key (fn (k v acc) (f k acc)) z s)))
|
||||
|
||||
(define
|
||||
hk-set-foldl
|
||||
(fn (f z s) (hk-map-foldl-with-key (fn (acc k v) (f acc k)) z s)))
|
||||
@@ -55,6 +55,8 @@ for FILE in "${FILES[@]}"; do
|
||||
(load "lib/haskell/runtime.sx")
|
||||
(load "lib/haskell/match.sx")
|
||||
(load "lib/haskell/eval.sx")
|
||||
(load "lib/haskell/map.sx")
|
||||
(load "lib/haskell/set.sx")
|
||||
$INFER_LOAD
|
||||
(load "lib/haskell/testlib.sx")
|
||||
(epoch 2)
|
||||
@@ -98,6 +100,8 @@ EPOCHS
|
||||
(load "lib/haskell/runtime.sx")
|
||||
(load "lib/haskell/match.sx")
|
||||
(load "lib/haskell/eval.sx")
|
||||
(load "lib/haskell/map.sx")
|
||||
(load "lib/haskell/set.sx")
|
||||
$INFER_LOAD
|
||||
(load "lib/haskell/testlib.sx")
|
||||
(epoch 2)
|
||||
|
||||
@@ -56,3 +56,21 @@
|
||||
(append!
|
||||
hk-test-fails
|
||||
{:actual actual :expected expected :name name})))))
|
||||
|
||||
(define
|
||||
hk-test-error
|
||||
(fn
|
||||
(name thunk expected-substring)
|
||||
(let
|
||||
((caught (guard (e (true (if (string? e) e (str e)))) (begin (thunk) nil))))
|
||||
(cond
|
||||
((nil? caught)
|
||||
(do
|
||||
(set! hk-test-fail (+ hk-test-fail 1))
|
||||
(append! hk-test-fails {:actual "no error raised" :expected (str "error containing: " expected-substring) :name name})))
|
||||
((>= (index-of caught expected-substring) 0)
|
||||
(set! hk-test-pass (+ hk-test-pass 1)))
|
||||
(:else
|
||||
(do
|
||||
(set! hk-test-fail (+ hk-test-fail 1))
|
||||
(append! hk-test-fails {:actual caught :expected (str "error containing: " expected-substring) :name name})))))))
|
||||
|
||||
86
lib/haskell/tests/class-defaults.sx
Normal file
86
lib/haskell/tests/class-defaults.sx
Normal file
@@ -0,0 +1,86 @@
|
||||
;; class-defaults.sx — Phase 13: class default method implementations.
|
||||
|
||||
;; ── Eq default: myNeq derived from myEq via `not (myEq x y)` ──
|
||||
(define
|
||||
hk-myeq-source
|
||||
"class MyEq a where\n myEq :: a -> a -> Bool\n myNeq :: a -> a -> Bool\n myNeq x y = not (myEq x y)\ninstance MyEq Int where\n myEq x y = x == y\n")
|
||||
|
||||
(hk-test
|
||||
"Eq default: myNeq 3 5 = True (no explicit myNeq in instance)"
|
||||
(hk-deep-force (hk-run (str hk-myeq-source "main = myNeq 3 5\n")))
|
||||
(list "True"))
|
||||
|
||||
(hk-test
|
||||
"Eq default: myNeq 3 3 = False"
|
||||
(hk-deep-force (hk-run (str hk-myeq-source "main = myNeq 3 3\n")))
|
||||
(list "False"))
|
||||
|
||||
(hk-test
|
||||
"Eq default: myEq still works in same instance"
|
||||
(hk-deep-force (hk-run (str hk-myeq-source "main = myEq 7 7\n")))
|
||||
(list "True"))
|
||||
|
||||
;; ── Override path: instance can still provide the method explicitly. ──
|
||||
(hk-test
|
||||
"Default override: instance-provided beats class default"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"class Hi a where\n greet :: a -> String\n greet x = \"default\"\ninstance Hi Bool where\n greet x = \"override\"\nmain = greet True"))
|
||||
"override")
|
||||
|
||||
(hk-test
|
||||
"Default fallback: empty instance picks default"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"class Hi a where\n greet :: a -> String\n greet x = \"default\"\ninstance Hi Bool where\nmain = greet True"))
|
||||
"default")
|
||||
|
||||
(define
|
||||
hk-myord-source
|
||||
"class MyOrd a where\n myCmp :: a -> a -> Bool\n myMax :: a -> a -> a\n myMin :: a -> a -> a\n myMax a b = if myCmp a b then a else b\n myMin a b = if myCmp a b then b else a\ninstance MyOrd Int where\n myCmp x y = x >= y\n")
|
||||
|
||||
(hk-test
|
||||
"Ord default: myMax 3 5 = 5"
|
||||
(hk-deep-force (hk-run (str hk-myord-source "main = myMax 3 5\n")))
|
||||
5)
|
||||
|
||||
(hk-test
|
||||
"Ord default: myMax 8 2 = 8"
|
||||
(hk-deep-force (hk-run (str hk-myord-source "main = myMax 8 2\n")))
|
||||
8)
|
||||
|
||||
(hk-test
|
||||
"Ord default: myMin 3 5 = 3"
|
||||
(hk-deep-force (hk-run (str hk-myord-source "main = myMin 3 5\n")))
|
||||
3)
|
||||
|
||||
(hk-test
|
||||
"Ord default: myMin 8 2 = 2"
|
||||
(hk-deep-force (hk-run (str hk-myord-source "main = myMin 8 2\n")))
|
||||
2)
|
||||
|
||||
(hk-test
|
||||
"Ord default: myMax of equals returns first"
|
||||
(hk-deep-force (hk-run (str hk-myord-source "main = myMax 4 4\n")))
|
||||
4)
|
||||
|
||||
(define
|
||||
hk-mynum-source
|
||||
"class MyNum a where\n mySub :: a -> a -> a\n myLt :: a -> a -> Bool\n myNegate :: a -> a\n myAbs :: a -> a\n myNegate x = mySub (mySub x x) x\n myAbs x = if myLt x (mySub x x) then myNegate x else x\ninstance MyNum Int where\n mySub x y = x - y\n myLt x y = x < y\n")
|
||||
|
||||
(hk-test
|
||||
"Num default: myNegate 5 = -5"
|
||||
(hk-deep-force (hk-run (str hk-mynum-source "main = myNegate 5\n")))
|
||||
-5)
|
||||
|
||||
(hk-test
|
||||
"Num default: myAbs (myNegate 7) = 7"
|
||||
(hk-deep-force (hk-run (str hk-mynum-source "main = myAbs (myNegate 7)\n")))
|
||||
7)
|
||||
|
||||
(hk-test
|
||||
"Num default: myAbs 9 = 9"
|
||||
(hk-deep-force (hk-run (str hk-mynum-source "main = myAbs 9\n")))
|
||||
9)
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
@@ -12,14 +12,14 @@
|
||||
"deriving Show: constructor with arg"
|
||||
(hk-deep-force
|
||||
(hk-run "data Wrapper = Wrap Int deriving (Show)\nmain = show (Wrap 42)"))
|
||||
"(Wrap 42)")
|
||||
"Wrap 42")
|
||||
|
||||
(hk-test
|
||||
"deriving Show: nested constructors"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"data Tree = Leaf | Node Int Tree Tree deriving (Show)\nmain = show (Node 1 Leaf Leaf)"))
|
||||
"(Node 1 Leaf Leaf)")
|
||||
"Node 1 Leaf Leaf")
|
||||
|
||||
(hk-test
|
||||
"deriving Show: second constructor"
|
||||
@@ -30,6 +30,31 @@
|
||||
|
||||
;; ─── Eq ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
(hk-test
|
||||
"deriving Show: nested ADT wraps inner constructor in parens"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"data Tree = Leaf | Node Int Tree Tree deriving (Show)\nmain = show (Node 1 Leaf (Node 2 Leaf Leaf))"))
|
||||
"Node 1 Leaf (Node 2 Leaf Leaf)")
|
||||
|
||||
(hk-test
|
||||
"deriving Show: Maybe Maybe wraps inner Just"
|
||||
(hk-deep-force (hk-run "main = show (Just (Just 3))"))
|
||||
"Just (Just 3)")
|
||||
|
||||
(hk-test
|
||||
"deriving Show: negative argument wrapped in parens"
|
||||
(hk-deep-force (hk-run "main = show (Just (negate 3))"))
|
||||
"Just (-3)")
|
||||
|
||||
(hk-test
|
||||
"deriving Show: list element does not need parens"
|
||||
(hk-deep-force
|
||||
(hk-run "data Box = Box [Int] deriving (Show)\nmain = show (Box [1,2,3])"))
|
||||
"Box [1,2,3]")
|
||||
|
||||
;; ─── combined Eq + Show ───────────────────────────────────────────────────────
|
||||
|
||||
(hk-test
|
||||
"deriving Eq: same constructor"
|
||||
(hk-deep-force
|
||||
@@ -58,14 +83,12 @@
|
||||
"data Color = Red | Green | Blue deriving (Eq)\nmain = show (Red /= Blue)"))
|
||||
"True")
|
||||
|
||||
;; ─── combined Eq + Show ───────────────────────────────────────────────────────
|
||||
|
||||
(hk-test
|
||||
"deriving Eq Show: combined in parens"
|
||||
"deriving Eq Show: combined"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"data Shape = Circle Int | Square Int deriving (Eq, Show)\nmain = show (Circle 5)"))
|
||||
"(Circle 5)")
|
||||
"Circle 5")
|
||||
|
||||
(hk-test
|
||||
"deriving Eq Show: eq on constructor with arg"
|
||||
|
||||
99
lib/haskell/tests/errors.sx
Normal file
99
lib/haskell/tests/errors.sx
Normal file
@@ -0,0 +1,99 @@
|
||||
;; errors.sx — Phase 9 error / undefined / partial-fn coverage via hk-test-error.
|
||||
|
||||
;; ── error builtin ────────────────────────────────────────────
|
||||
(define
|
||||
hk-as-list
|
||||
(fn
|
||||
(xs)
|
||||
(cond
|
||||
((and (list? xs) (= (first xs) "[]")) (list))
|
||||
((and (list? xs) (= (first xs) ":"))
|
||||
(cons (nth xs 1) (hk-as-list (nth xs 2))))
|
||||
(:else xs))))
|
||||
|
||||
(hk-test-error
|
||||
"error: raises with literal message"
|
||||
(fn () (hk-deep-force (hk-run "main = error \"boom\"")))
|
||||
"hk-error: boom")
|
||||
|
||||
(hk-test-error
|
||||
"error: raises with computed message"
|
||||
(fn () (hk-deep-force (hk-run "main = error (\"oops: \" ++ show 42)")))
|
||||
"hk-error: oops: 42")
|
||||
|
||||
;; ── undefined ────────────────────────────────────────────────
|
||||
(hk-test-error
|
||||
"error: nested in if branch (only fires when forced)"
|
||||
(fn
|
||||
()
|
||||
(hk-deep-force (hk-run "main = if 1 == 1 then error \"taken\" else 0")))
|
||||
"taken")
|
||||
|
||||
(hk-test-error
|
||||
"undefined: raises Prelude.undefined"
|
||||
(fn () (hk-deep-force (hk-run "main = undefined")))
|
||||
"Prelude.undefined")
|
||||
|
||||
;; The non-strict path: undefined doesn't fire when not forced.
|
||||
(hk-test-error
|
||||
"undefined: forced via arithmetic"
|
||||
(fn () (hk-deep-force (hk-run "main = undefined + 1")))
|
||||
"Prelude.undefined")
|
||||
|
||||
;; ── partial functions ───────────────────────────────────────
|
||||
(hk-test
|
||||
"undefined: lazy, not forced when discarded"
|
||||
(hk-deep-force (hk-run "main = let _ = undefined in 5"))
|
||||
5)
|
||||
|
||||
(hk-test-error
|
||||
"head []: raises Prelude.head: empty list"
|
||||
(fn () (hk-deep-force (hk-run "main = head []")))
|
||||
"Prelude.head: empty list")
|
||||
|
||||
(hk-test-error
|
||||
"tail []: raises Prelude.tail: empty list"
|
||||
(fn () (hk-deep-force (hk-run "main = tail []")))
|
||||
"Prelude.tail: empty list")
|
||||
|
||||
;; head and tail still work on non-empty lists.
|
||||
(hk-test-error
|
||||
"fromJust Nothing: raises Maybe.fromJust: Nothing"
|
||||
(fn () (hk-deep-force (hk-run "main = fromJust Nothing")))
|
||||
"Maybe.fromJust: Nothing")
|
||||
|
||||
(hk-test
|
||||
"head [42]: still works"
|
||||
(hk-deep-force (hk-run "main = head [42]"))
|
||||
42)
|
||||
|
||||
;; ── error in IO context ─────────────────────────────────────
|
||||
(hk-test
|
||||
"tail [1,2,3]: still works"
|
||||
(hk-as-list (hk-deep-force (hk-run "main = tail [1,2,3]")))
|
||||
(list 2 3))
|
||||
|
||||
(hk-test
|
||||
"hk-run-io: error in main lands in io-lines"
|
||||
(let
|
||||
((lines (hk-run-io "main = error \"caught here\"")))
|
||||
(>= (index-of (str lines) "caught here") 0))
|
||||
true)
|
||||
|
||||
;; ── hk-test-error helper itself ─────────────────────────────
|
||||
(hk-test
|
||||
"hk-run-io: putStrLn before error preserves earlier output"
|
||||
(let
|
||||
((lines (hk-run-io "main = do { putStrLn \"first\"; error \"died\"; putStrLn \"never\" }")))
|
||||
(and
|
||||
(>= (index-of (str lines) "first") 0)
|
||||
(>= (index-of (str lines) "died") 0)))
|
||||
true)
|
||||
|
||||
;; hk-as-list helper for converting a forced Haskell cons into an SX list.
|
||||
(hk-test-error
|
||||
"hk-test-error: matches partial substring inside wrapped exception"
|
||||
(fn () (hk-deep-force (hk-run "main = error \"unique-marker-xyz\"")))
|
||||
"unique-marker-xyz")
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
@@ -231,16 +231,82 @@
|
||||
1)
|
||||
|
||||
;; ── Laziness: app args evaluate only when forced ──
|
||||
(hk-test
|
||||
"error builtin: raises with hk-error prefix"
|
||||
(guard
|
||||
(e (true (>= (index-of e "hk-error: boom") 0)))
|
||||
(begin (hk-deep-force (hk-run "main = error \"boom\"")) false))
|
||||
true)
|
||||
|
||||
(hk-test
|
||||
"error builtin: raises with computed message"
|
||||
(guard
|
||||
(e (true (>= (index-of e "hk-error: oops: 42") 0)))
|
||||
(begin
|
||||
(hk-deep-force (hk-run "main = error (\"oops: \" ++ show 42)"))
|
||||
false))
|
||||
true)
|
||||
|
||||
(hk-test
|
||||
"undefined: raises hk-error with Prelude.undefined message"
|
||||
(guard
|
||||
(e (true (>= (index-of e "hk-error: Prelude.undefined") 0)))
|
||||
(begin (hk-deep-force (hk-run "main = undefined")) false))
|
||||
true)
|
||||
|
||||
(hk-test
|
||||
"undefined: lazy — only fires when forced"
|
||||
(hk-deep-force (hk-run "main = if True then 42 else undefined"))
|
||||
42)
|
||||
|
||||
(hk-test
|
||||
"head []: raises Prelude.head: empty list"
|
||||
(guard
|
||||
(e (true (>= (index-of e "Prelude.head: empty list") 0)))
|
||||
(begin (hk-deep-force (hk-run "main = head []")) false))
|
||||
true)
|
||||
|
||||
(hk-test
|
||||
"tail []: raises Prelude.tail: empty list"
|
||||
(guard
|
||||
(e (true (>= (index-of e "Prelude.tail: empty list") 0)))
|
||||
(begin (hk-deep-force (hk-run "main = tail []")) false))
|
||||
true)
|
||||
|
||||
;; ── not / id built-ins ──
|
||||
(hk-test
|
||||
"fromJust Nothing: raises Maybe.fromJust: Nothing"
|
||||
(guard
|
||||
(e (true (>= (index-of e "Maybe.fromJust: Nothing") 0)))
|
||||
(begin (hk-deep-force (hk-run "main = fromJust Nothing")) false))
|
||||
true)
|
||||
(hk-test
|
||||
"fromJust (Just 5) = 5"
|
||||
(hk-deep-force (hk-run "main = fromJust (Just 5)"))
|
||||
5)
|
||||
(hk-test
|
||||
"head [42] = 42 (still works for non-empty)"
|
||||
(hk-deep-force (hk-run "main = head [42]"))
|
||||
42)
|
||||
|
||||
(hk-test-error
|
||||
"hk-test-error helper: catches matching error"
|
||||
(fn () (hk-deep-force (hk-run "main = error \"boom\"")))
|
||||
"hk-error: boom")
|
||||
|
||||
(hk-test-error
|
||||
"hk-test-error helper: catches head [] error"
|
||||
(fn () (hk-deep-force (hk-run "main = head []")))
|
||||
"Prelude.head: empty list")
|
||||
|
||||
(hk-test
|
||||
"second arg never forced"
|
||||
(hk-eval-expr-source
|
||||
"(\\x y -> x) 1 (error \"never\")")
|
||||
(hk-eval-expr-source "(\\x y -> x) 1 (error \"never\")")
|
||||
1)
|
||||
|
||||
(hk-test
|
||||
"first arg never forced"
|
||||
(hk-eval-expr-source
|
||||
"(\\x y -> y) (error \"never\") 99")
|
||||
(hk-eval-expr-source "(\\x y -> y) (error \"never\") 99")
|
||||
99)
|
||||
|
||||
(hk-test
|
||||
@@ -251,9 +317,7 @@
|
||||
|
||||
(hk-test
|
||||
"lazy: const drops its second argument"
|
||||
(hk-prog-val
|
||||
"const x y = x\nresult = const 5 (error \"boom\")"
|
||||
"result")
|
||||
(hk-prog-val "const x y = x\nresult = const 5 (error \"boom\")" "result")
|
||||
5)
|
||||
|
||||
(hk-test
|
||||
@@ -270,9 +334,10 @@
|
||||
"result")
|
||||
(list "True"))
|
||||
|
||||
;; ── not / id built-ins ──
|
||||
(hk-test "not True" (hk-eval-expr-source "not True") (list "False"))
|
||||
|
||||
(hk-test "not False" (hk-eval-expr-source "not False") (list "True"))
|
||||
|
||||
(hk-test "id" (hk-eval-expr-source "id 42") 42)
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
|
||||
105
lib/haskell/tests/exceptions.sx
Normal file
105
lib/haskell/tests/exceptions.sx
Normal file
@@ -0,0 +1,105 @@
|
||||
;; Phase 16 — Exception handling unit tests.
|
||||
|
||||
(hk-test
|
||||
"catch — success path returns the action result"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"main = catch (return 42) (\\(SomeException m) -> return 0)"))
|
||||
(list "IO" 42))
|
||||
|
||||
(hk-test
|
||||
"catch — error caught, handler receives message"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"main = catch (error \"boom\") (\\(SomeException m) -> return m)"))
|
||||
(list "IO" "boom"))
|
||||
|
||||
(hk-test
|
||||
"try — success returns Right v"
|
||||
(hk-deep-force
|
||||
(hk-run "main = try (return 42)"))
|
||||
(list "IO" (list "Right" 42)))
|
||||
|
||||
(hk-test
|
||||
"try — error returns Left (SomeException msg)"
|
||||
(hk-deep-force
|
||||
(hk-run "main = try (error \"oops\")"))
|
||||
(list "IO" (list "Left" (list "SomeException" "oops"))))
|
||||
|
||||
(hk-test
|
||||
"handle — flip catch — caught error message"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"main = handle (\\(SomeException m) -> return m) (error \"hot\")"))
|
||||
(list "IO" "hot"))
|
||||
|
||||
(hk-test
|
||||
"throwIO + catch — handler sees the SomeException"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"main = catch (throwIO (SomeException \"bang\")) (\\(SomeException m) -> return m)"))
|
||||
(list "IO" "bang"))
|
||||
|
||||
(hk-test
|
||||
"throwIO + try — Left side"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"main = try (throwIO (SomeException \"x\"))"))
|
||||
(list "IO" (list "Left" (list "SomeException" "x"))))
|
||||
|
||||
(hk-test
|
||||
"evaluate — pure value returns IO v"
|
||||
(hk-deep-force
|
||||
(hk-run "main = evaluate (1 + 2 + 3)"))
|
||||
(list "IO" 6))
|
||||
|
||||
(hk-test
|
||||
"evaluate — error surfaces as catchable exception"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"main = catch (evaluate (error \"deep\")) (\\(SomeException m) -> return m)"))
|
||||
(list "IO" "deep"))
|
||||
|
||||
(hk-test
|
||||
"nested catch — inner handler runs first"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"main = catch (catch (error \"inner\") (\\(SomeException m) -> error (m ++ \"-rethrown\"))) (\\(SomeException m) -> return m)"))
|
||||
(list "IO" "inner-rethrown"))
|
||||
|
||||
(hk-test
|
||||
"catch chain — handler can succeed inside IO"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"main = do { x <- catch (error \"e1\") (\\(SomeException m) -> return 100); return (x + 1) }"))
|
||||
(list "IO" 101))
|
||||
|
||||
(hk-test
|
||||
"try then bind on Right"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"branch (Right v) = return (v * 2)
|
||||
branch (Left _) = return 0
|
||||
main = do { r <- try (return 21); branch r }"))
|
||||
(list "IO" 42))
|
||||
|
||||
(hk-test
|
||||
"try then bind on Left"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"branch (Right _) = return \"ok\"
|
||||
branch (Left (SomeException m)) = return m
|
||||
main = do { r <- try (error \"failed\"); branch r }"))
|
||||
(list "IO" "failed"))
|
||||
|
||||
(hk-test
|
||||
"catch — handler can use closed-over IORef"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.IORef as IORef
|
||||
main = do
|
||||
r <- IORef.newIORef 0
|
||||
catch (error \"x\") (\\(SomeException m) -> IORef.writeIORef r 7)
|
||||
v <- IORef.readIORef r
|
||||
return v"))
|
||||
(list "IO" 7))
|
||||
31
lib/haskell/tests/instance-where.sx
Normal file
31
lib/haskell/tests/instance-where.sx
Normal file
@@ -0,0 +1,31 @@
|
||||
;; instance-where.sx — Phase 13: where-clauses inside instance bodies.
|
||||
|
||||
(hk-test
|
||||
"instance method body with where-helper (Bool)"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"class Greet a where\n greet :: a -> String\ninstance Greet Bool where\n greet x = mkMsg x\n where mkMsg True = \"yes\"\n mkMsg False = \"no\"\nmain = greet True"))
|
||||
"yes")
|
||||
|
||||
(hk-test
|
||||
"instance method body with where-helper (False branch)"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"class Greet a where\n greet :: a -> String\ninstance Greet Bool where\n greet x = mkMsg x\n where mkMsg True = \"yes\"\n mkMsg False = \"no\"\nmain = greet False"))
|
||||
"no")
|
||||
|
||||
(hk-test
|
||||
"instance method body with where-binding referenced multiple times"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"class Twice a where\n twice :: a -> Int\ninstance Twice Int where\n twice x = h + h\n where h = x + 1\nmain = twice 5"))
|
||||
12)
|
||||
|
||||
(hk-test
|
||||
"instance method body with multi-binding where"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"class Calc a where\n calc :: a -> Int\ninstance Calc Int where\n calc x = a + b\n where a = x * 2\n b = x + 1\nmain = calc 3"))
|
||||
10)
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
@@ -64,12 +64,11 @@
|
||||
|
||||
(hk-test
|
||||
"readFile error on missing file"
|
||||
(guard
|
||||
(e (true (>= (index-of e "file not found") 0)))
|
||||
(begin
|
||||
(set! hk-vfs (dict))
|
||||
(hk-run-io "main = readFile \"no.txt\" >>= putStrLn")
|
||||
false))
|
||||
(begin
|
||||
(set! hk-vfs (dict))
|
||||
(let
|
||||
((lines (hk-run-io "main = readFile \"no.txt\" >>= putStrLn")))
|
||||
(>= (index-of (str lines) "file not found") 0)))
|
||||
true)
|
||||
|
||||
(hk-test
|
||||
|
||||
94
lib/haskell/tests/ioref.sx
Normal file
94
lib/haskell/tests/ioref.sx
Normal file
@@ -0,0 +1,94 @@
|
||||
;; Phase 15 — IORef unit tests.
|
||||
|
||||
(hk-test
|
||||
"newIORef + readIORef returns initial value"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.IORef as IORef\nmain = do { r <- IORef.newIORef 42; v <- IORef.readIORef r; return v }"))
|
||||
(list "IO" 42))
|
||||
|
||||
(hk-test
|
||||
"writeIORef updates the cell"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.IORef as IORef\nmain = do { r <- IORef.newIORef 0; IORef.writeIORef r 99; v <- IORef.readIORef r; return v }"))
|
||||
(list "IO" 99))
|
||||
|
||||
(hk-test
|
||||
"writeIORef returns IO ()"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.IORef as IORef\nmain = do { r <- IORef.newIORef 0; IORef.writeIORef r 1 }"))
|
||||
(list "IO" (list "Tuple")))
|
||||
|
||||
(hk-test
|
||||
"modifyIORef applies a function"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.IORef as IORef\nmain = do { r <- IORef.newIORef 5; IORef.modifyIORef r (\\x -> x * 2); v <- IORef.readIORef r; return v }"))
|
||||
(list "IO" 10))
|
||||
|
||||
(hk-test
|
||||
"modifyIORef' (strict) applies a function"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.IORef as IORef\nmain = do { r <- IORef.newIORef 7; IORef.modifyIORef' r (\\x -> x + 3); v <- IORef.readIORef r; return v }"))
|
||||
(list "IO" 10))
|
||||
|
||||
(hk-test
|
||||
"two reads return the same value"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.IORef as IORef\nmain = do { r <- IORef.newIORef 11; a <- IORef.readIORef r; b <- IORef.readIORef r; return (a + b) }"))
|
||||
(list "IO" 22))
|
||||
|
||||
(hk-test
|
||||
"shared ref across do-steps: write then read"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.IORef as IORef\nmain = do { r <- IORef.newIORef 1; IORef.writeIORef r 2; IORef.writeIORef r 3; v <- IORef.readIORef r; return v }"))
|
||||
(list "IO" 3))
|
||||
|
||||
(hk-test
|
||||
"two refs are independent"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.IORef as IORef\nmain = do { r1 <- IORef.newIORef 1; r2 <- IORef.newIORef 2; IORef.writeIORef r1 10; a <- IORef.readIORef r1; b <- IORef.readIORef r2; return (a + b) }"))
|
||||
(list "IO" 12))
|
||||
|
||||
(hk-test
|
||||
"string-valued IORef"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.IORef as IORef\nmain = do { r <- IORef.newIORef \"hi\"; IORef.writeIORef r \"bye\"; v <- IORef.readIORef r; return v }"))
|
||||
(list "IO" "bye"))
|
||||
|
||||
(hk-test
|
||||
"list-valued IORef + cons"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.IORef as IORef\nmain = do { r <- IORef.newIORef [1,2,3]; IORef.modifyIORef r (\\xs -> 0 : xs); v <- IORef.readIORef r; return v }"))
|
||||
(list
|
||||
"IO"
|
||||
(list ":" 0 (list ":" 1 (list ":" 2 (list ":" 3 (list "[]")))))))
|
||||
|
||||
(hk-test
|
||||
"counter loop: increment N times"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.IORef as IORef\nloop r 0 = return ()\nloop r n = do { IORef.modifyIORef r (\\x -> x + 1); loop r (n - 1) }\nmain = do { r <- IORef.newIORef 0; loop r 10; v <- IORef.readIORef r; return v }"))
|
||||
(list "IO" 10))
|
||||
|
||||
(hk-test
|
||||
"modifyIORef' inside a loop"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.IORef as IORef\ngo r 0 = return ()\ngo r n = do { IORef.modifyIORef' r (\\x -> x + n); go r (n - 1) }\nmain = do { r <- IORef.newIORef 0; go r 5; v <- IORef.readIORef r; return v }"))
|
||||
(list "IO" 15))
|
||||
|
||||
(hk-test
|
||||
"newIORef inside a function passed via parameter"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.IORef as IORef\nbump r = IORef.modifyIORef r (\\x -> x + 100)\nmain = do { r <- IORef.newIORef 1; bump r; v <- IORef.readIORef r; return v }"))
|
||||
(list "IO" 101))
|
||||
196
lib/haskell/tests/map.sx
Normal file
196
lib/haskell/tests/map.sx
Normal file
@@ -0,0 +1,196 @@
|
||||
;; map.sx — Phase 11 Data.Map unit tests.
|
||||
;;
|
||||
;; Tests both the SX-level `hk-map-*` helpers and the Haskell-level
|
||||
;; `Map.*` aliases bound by the import handler.
|
||||
|
||||
(define
|
||||
hk-as-list
|
||||
(fn
|
||||
(xs)
|
||||
(cond
|
||||
((and (list? xs) (= (first xs) "[]")) (list))
|
||||
((and (list? xs) (= (first xs) ":"))
|
||||
(cons (nth xs 1) (hk-as-list (nth xs 2))))
|
||||
(:else xs))))
|
||||
|
||||
;; ── SX-level (direct hk-map-*) ───────────────────────────────
|
||||
(hk-test
|
||||
"hk-map-empty: size 0, null true"
|
||||
(list (hk-map-size hk-map-empty) (hk-map-null hk-map-empty))
|
||||
(list 0 true))
|
||||
|
||||
(hk-test
|
||||
"hk-map-singleton: lookup hit"
|
||||
(let
|
||||
((m (hk-map-singleton 5 "five")))
|
||||
(list (hk-map-size m) (hk-map-lookup 5 m)))
|
||||
(list 1 (list "Just" "five")))
|
||||
|
||||
(hk-test
|
||||
"hk-map-insert: lookup hit on inserted"
|
||||
(let ((m (hk-map-insert 1 "a" hk-map-empty))) (hk-map-lookup 1 m))
|
||||
(list "Just" "a"))
|
||||
|
||||
(hk-test
|
||||
"hk-map-lookup: miss returns Nothing"
|
||||
(hk-map-lookup 99 (hk-map-singleton 1 "a"))
|
||||
(list "Nothing"))
|
||||
|
||||
(hk-test
|
||||
"hk-map-insert: overwrites existing key"
|
||||
(let
|
||||
((m (hk-map-insert 1 "second" (hk-map-insert 1 "first" hk-map-empty))))
|
||||
(hk-map-lookup 1 m))
|
||||
(list "Just" "second"))
|
||||
|
||||
(hk-test
|
||||
"hk-map-delete: removes key"
|
||||
(let
|
||||
((m (hk-map-insert 2 "b" (hk-map-insert 1 "a" hk-map-empty))))
|
||||
(let
|
||||
((m2 (hk-map-delete 1 m)))
|
||||
(list (hk-map-size m2) (hk-map-lookup 1 m2) (hk-map-lookup 2 m2))))
|
||||
(list 1 (list "Nothing") (list "Just" "b")))
|
||||
|
||||
(hk-test
|
||||
"hk-map-delete: missing key is no-op"
|
||||
(let ((m (hk-map-singleton 1 "a"))) (hk-map-size (hk-map-delete 99 m)))
|
||||
1)
|
||||
|
||||
(hk-test
|
||||
"hk-map-member: true on existing"
|
||||
(hk-map-member 1 (hk-map-singleton 1 "a"))
|
||||
true)
|
||||
|
||||
(hk-test
|
||||
"hk-map-member: false on missing"
|
||||
(hk-map-member 99 (hk-map-singleton 1 "a"))
|
||||
false)
|
||||
|
||||
(hk-test
|
||||
"hk-map-from-list: builds map; keys sorted"
|
||||
(hk-map-keys
|
||||
(hk-map-from-list
|
||||
(list (list 3 "c") (list 1 "a") (list 5 "e") (list 2 "b"))))
|
||||
(list 1 2 3 5))
|
||||
|
||||
(hk-test
|
||||
"hk-map-from-list: duplicates — last wins"
|
||||
(hk-map-lookup
|
||||
1
|
||||
(hk-map-from-list (list (list 1 "first") (list 1 "second"))))
|
||||
(list "Just" "second"))
|
||||
|
||||
(hk-test
|
||||
"hk-map-to-asc-list: ordered traversal"
|
||||
(hk-map-to-asc-list
|
||||
(hk-map-from-list (list (list 3 "c") (list 1 "a") (list 2 "b"))))
|
||||
(list (list 1 "a") (list 2 "b") (list 3 "c")))
|
||||
|
||||
(hk-test
|
||||
"hk-map-elems: in key order"
|
||||
(hk-map-elems
|
||||
(hk-map-from-list (list (list 3 30) (list 1 10) (list 2 20))))
|
||||
(list 10 20 30))
|
||||
|
||||
(hk-test
|
||||
"hk-map-union-with: combines duplicates"
|
||||
(hk-map-to-asc-list
|
||||
(hk-map-union-with
|
||||
(fn (a b) (str a "+" b))
|
||||
(hk-map-from-list (list (list 1 "a") (list 2 "b")))
|
||||
(hk-map-from-list (list (list 2 "B") (list 3 "c")))))
|
||||
(list (list 1 "a") (list 2 "b+B") (list 3 "c")))
|
||||
|
||||
(hk-test
|
||||
"hk-map-intersection-with: keeps shared keys"
|
||||
(hk-map-to-asc-list
|
||||
(hk-map-intersection-with
|
||||
+
|
||||
(hk-map-from-list (list (list 1 10) (list 2 20)))
|
||||
(hk-map-from-list (list (list 2 200) (list 3 30)))))
|
||||
(list (list 2 220)))
|
||||
|
||||
(hk-test
|
||||
"hk-map-difference: drops m2 keys"
|
||||
(hk-map-keys
|
||||
(hk-map-difference
|
||||
(hk-map-from-list (list (list 1 "a") (list 2 "b") (list 3 "c")))
|
||||
(hk-map-from-list (list (list 2 "x")))))
|
||||
(list 1 3))
|
||||
|
||||
(hk-test
|
||||
"hk-map-foldl-with-key: in-order accumulate"
|
||||
(hk-map-foldl-with-key
|
||||
(fn (acc k v) (str acc k v))
|
||||
""
|
||||
(hk-map-from-list (list (list 3 "c") (list 1 "a") (list 2 "b"))))
|
||||
"1a2b3c")
|
||||
|
||||
(hk-test
|
||||
"hk-map-map-with-key: transforms values"
|
||||
(hk-map-to-asc-list
|
||||
(hk-map-map-with-key
|
||||
(fn (k v) (* k v))
|
||||
(hk-map-from-list (list (list 2 10) (list 3 100)))))
|
||||
(list (list 2 20) (list 3 300)))
|
||||
|
||||
(hk-test
|
||||
"hk-map-filter-with-key: keeps matches"
|
||||
(hk-map-keys
|
||||
(hk-map-filter-with-key
|
||||
(fn (k v) (> k 1))
|
||||
(hk-map-from-list (list (list 1 "a") (list 2 "b") (list 3 "c")))))
|
||||
(list 2 3))
|
||||
|
||||
(hk-test
|
||||
"hk-map-adjust: applies f to existing"
|
||||
(hk-map-lookup
|
||||
1
|
||||
(hk-map-adjust (fn (v) (* v 10)) 1 (hk-map-singleton 1 5)))
|
||||
(list "Just" 50))
|
||||
|
||||
(hk-test
|
||||
"hk-map-insert-with: combines on existing"
|
||||
(hk-map-lookup 1 (hk-map-insert-with + 1 5 (hk-map-singleton 1 10)))
|
||||
(list "Just" 15))
|
||||
|
||||
(hk-test
|
||||
"hk-map-alter: Nothing → delete"
|
||||
(hk-map-size
|
||||
(hk-map-alter
|
||||
(fn (mv) (list "Nothing"))
|
||||
1
|
||||
(hk-map-from-list (list (list 1 "a") (list 2 "b")))))
|
||||
1)
|
||||
|
||||
;; ── Haskell-level (Map.*) via import wiring ─────────────────
|
||||
(hk-test
|
||||
"Map.size after Map.insert chain"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.Map as Map\nmain = Map.size (Map.insert 2 \"b\" (Map.insert 1 \"a\" Map.empty))"))
|
||||
2)
|
||||
|
||||
(hk-test
|
||||
"Map.lookup hit"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.Map as Map\nmain = Map.lookup 1 (Map.insert 1 \"a\" Map.empty)"))
|
||||
(list "Just" "a"))
|
||||
|
||||
(hk-test
|
||||
"Map.lookup miss"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.Map as Map\nmain = Map.lookup 99 (Map.insert 1 \"a\" Map.empty)"))
|
||||
(list "Nothing"))
|
||||
|
||||
(hk-test
|
||||
"Map.member true"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.Map as Map\nmain = Map.member 5 (Map.insert 5 \"x\" Map.empty)"))
|
||||
(list "True"))
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
180
lib/haskell/tests/numerics.sx
Normal file
180
lib/haskell/tests/numerics.sx
Normal file
@@ -0,0 +1,180 @@
|
||||
;; numerics.sx — Phase 10 numeric tower verification.
|
||||
;;
|
||||
;; Practical integer-precision limit in Haskell-on-SX:
|
||||
;; • Raw SX `(* a b)` stays exact up to ±2^62 (≈ 4.6e18, OCaml int63).
|
||||
;; • BUT the Haskell tokenizer/parser parses an integer literal as a float
|
||||
;; once it exceeds 2^53 (≈ 9.007e15). Once any operand is a float, the
|
||||
;; binop result is a float (and decimal-precision is lost past 2^53).
|
||||
;; • Therefore: programs that stay below ~9e15 are exact; larger literals
|
||||
;; or accumulated products silently become floats. `factorial 18` is the
|
||||
;; last factorial that stays exact (6.4e15); `factorial 19` already floats.
|
||||
;;
|
||||
;; In Haskell terms, `Int` and `Integer` both currently map to SX number, so
|
||||
;; we don't yet support arbitrary-precision Integer. Documented; unbounded
|
||||
;; Integer is out of scope for Phase 10 — see Phase 11+ if it becomes needed.
|
||||
|
||||
(define
|
||||
hk-as-list
|
||||
(fn
|
||||
(xs)
|
||||
(cond
|
||||
((and (list? xs) (= (first xs) "[]")) (list))
|
||||
((and (list? xs) (= (first xs) ":"))
|
||||
(cons (nth xs 1) (hk-as-list (nth xs 2))))
|
||||
(:else xs))))
|
||||
|
||||
(hk-test
|
||||
"factorial 10 = 3628800 (small, exact)"
|
||||
(hk-deep-force
|
||||
(hk-run "fact 0 = 1\nfact n = n * fact (n - 1)\nmain = fact 10"))
|
||||
3628800)
|
||||
|
||||
(hk-test
|
||||
"factorial 15 = 1307674368000 (mid-range, exact)"
|
||||
(hk-deep-force
|
||||
(hk-run "fact 0 = 1\nfact n = n * fact (n - 1)\nmain = fact 15"))
|
||||
1307674368000)
|
||||
|
||||
(hk-test
|
||||
"factorial 18 = 6402373705728000 (last exact factorial)"
|
||||
(hk-deep-force
|
||||
(hk-run "fact 0 = 1\nfact n = n * fact (n - 1)\nmain = fact 18"))
|
||||
6402373705728000)
|
||||
|
||||
(hk-test
|
||||
"1000000 * 1000000 = 10^12 (exact)"
|
||||
(hk-deep-force (hk-run "main = 1000000 * 1000000"))
|
||||
1000000000000)
|
||||
|
||||
(hk-test
|
||||
"1000000000 * 1000000000 = 10^18 (exact, at boundary)"
|
||||
(hk-deep-force (hk-run "main = 1000000000 * 1000000000"))
|
||||
1e+18)
|
||||
|
||||
(hk-test
|
||||
"2^62 boundary: pow accumulates exactly"
|
||||
(hk-deep-force
|
||||
(hk-run "pow b 0 = 1\npow b n = b * pow b (n - 1)\nmain = pow 2 62"))
|
||||
4.6116860184273879e+18)
|
||||
|
||||
(hk-test
|
||||
"show factorial 12 = 479001600 (whole, fits in 32-bit)"
|
||||
(hk-deep-force
|
||||
(hk-run "fact 0 = 1\nfact n = n * fact (n - 1)\nmain = show (fact 12)"))
|
||||
"479001600")
|
||||
|
||||
(hk-test
|
||||
"negate large positive — preserves magnitude"
|
||||
(hk-deep-force (hk-run "main = negate 1000000000000000000"))
|
||||
-1e+18)
|
||||
|
||||
(hk-test
|
||||
"abs negative large — preserves magnitude"
|
||||
(hk-deep-force (hk-run "main = abs (negate 1000000000000000000)"))
|
||||
1e+18)
|
||||
|
||||
(hk-test
|
||||
"div on large ints"
|
||||
(hk-deep-force (hk-run "main = div 1000000000000000000 1000000000"))
|
||||
1000000000)
|
||||
|
||||
(hk-test
|
||||
"fromIntegral 42 = 42 (identity in our runtime)"
|
||||
(hk-deep-force (hk-run "main = fromIntegral 42"))
|
||||
42)
|
||||
|
||||
(hk-test
|
||||
"fromIntegral preserves negative"
|
||||
(hk-deep-force (hk-run "main = fromIntegral (negate 7)"))
|
||||
-7)
|
||||
|
||||
(hk-test
|
||||
"fromIntegral round-trips through arithmetic"
|
||||
(hk-deep-force (hk-run "main = fromIntegral 5 + fromIntegral 3"))
|
||||
8)
|
||||
|
||||
(hk-test
|
||||
"fromIntegral in a program (mixing with map)"
|
||||
(hk-as-list (hk-deep-force (hk-run "main = map fromIntegral [1,2,3]")))
|
||||
(list 1 2 3))
|
||||
|
||||
(hk-test
|
||||
"toInteger 100 = 100 (identity)"
|
||||
(hk-deep-force (hk-run "main = toInteger 100"))
|
||||
100)
|
||||
|
||||
(hk-test
|
||||
"fromInteger 7 = 7 (identity)"
|
||||
(hk-deep-force (hk-run "main = fromInteger 7"))
|
||||
7)
|
||||
|
||||
(hk-test
|
||||
"toInteger / fromInteger round-trip"
|
||||
(hk-deep-force (hk-run "main = fromInteger (toInteger 42)"))
|
||||
42)
|
||||
|
||||
(hk-test
|
||||
"toInteger preserves negative"
|
||||
(hk-deep-force (hk-run "main = toInteger (negate 13)"))
|
||||
-13)
|
||||
|
||||
(hk-test
|
||||
"show 3.14 = 3.14"
|
||||
(hk-deep-force (hk-run "main = show 3.14"))
|
||||
"3.14")
|
||||
|
||||
(hk-test
|
||||
"show 1.0e10 — whole-valued float renders as decimal (int/float ambiguity)"
|
||||
(hk-deep-force (hk-run "main = show 1.0e10"))
|
||||
"10000000000")
|
||||
|
||||
(hk-test
|
||||
"show 0.001 uses scientific form (sub-0.1)"
|
||||
(hk-deep-force (hk-run "main = show 0.001"))
|
||||
"1.0e-3")
|
||||
|
||||
(hk-test
|
||||
"show negative float"
|
||||
(hk-deep-force (hk-run "main = show (negate 3.14)"))
|
||||
"-3.14")
|
||||
|
||||
(hk-test "sqrt 16 = 4" (hk-deep-force (hk-run "main = sqrt 16")) 4)
|
||||
|
||||
(hk-test "floor 3.7 = 3" (hk-deep-force (hk-run "main = floor 3.7")) 3)
|
||||
|
||||
(hk-test "ceiling 3.2 = 4" (hk-deep-force (hk-run "main = ceiling 3.2")) 4)
|
||||
|
||||
(hk-test
|
||||
"ceiling on whole = self"
|
||||
(hk-deep-force (hk-run "main = ceiling 4"))
|
||||
4)
|
||||
|
||||
(hk-test "round 2.6 = 3" (hk-deep-force (hk-run "main = round 2.6")) 3)
|
||||
|
||||
(hk-test
|
||||
"truncate -3.7 = -3"
|
||||
(hk-deep-force (hk-run "main = truncate (negate 3.7)"))
|
||||
-3)
|
||||
|
||||
(hk-test "recip 4.0 = 0.25" (hk-deep-force (hk-run "main = recip 4.0")) 0.25)
|
||||
|
||||
(hk-test "1.0 / 4.0 = 0.25" (hk-deep-force (hk-run "main = 1.0 / 4.0")) 0.25)
|
||||
|
||||
(hk-test
|
||||
"fromRational 0.5 = 0.5 (identity)"
|
||||
(hk-deep-force (hk-run "main = fromRational 0.5"))
|
||||
0.5)
|
||||
|
||||
(hk-test "pi ≈ 3.14159" (hk-deep-force (hk-run "main = pi")) 3.14159)
|
||||
|
||||
(hk-test "exp 0 = 1" (hk-deep-force (hk-run "main = exp 0")) 1)
|
||||
|
||||
(hk-test "sin 0 = 0" (hk-deep-force (hk-run "main = sin 0")) 0)
|
||||
|
||||
(hk-test "cos 0 = 1" (hk-deep-force (hk-run "main = cos 0")) 1)
|
||||
|
||||
(hk-test "2 ** 10 = 1024" (hk-deep-force (hk-run "main = 2 ** 10")) 1024)
|
||||
|
||||
(hk-test "log (exp 5) ≈ 5" (hk-deep-force (hk-run "main = log (exp 5)")) 5)
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
102
lib/haskell/tests/parse-extras.sx
Normal file
102
lib/haskell/tests/parse-extras.sx
Normal file
@@ -0,0 +1,102 @@
|
||||
;; Phase 17 — parser polish unit tests.
|
||||
|
||||
(hk-test
|
||||
"type-ann: literal int annotated"
|
||||
(hk-deep-force (hk-run "main = (42 :: Int)"))
|
||||
42)
|
||||
|
||||
(hk-test
|
||||
"type-ann: arithmetic annotated"
|
||||
(hk-deep-force (hk-run "main = (1 + 2 :: Int)"))
|
||||
3)
|
||||
|
||||
(hk-test
|
||||
"type-ann: function arg annotated"
|
||||
(hk-deep-force
|
||||
(hk-run "f x = x + 1\nmain = f (1 :: Int)"))
|
||||
2)
|
||||
|
||||
(hk-test
|
||||
"type-ann: string annotated"
|
||||
(hk-deep-force (hk-run "main = (\"hi\" :: String)"))
|
||||
"hi")
|
||||
|
||||
(hk-test
|
||||
"type-ann: bool annotated"
|
||||
(hk-deep-force (hk-run "main = (True :: Bool)"))
|
||||
(list "True"))
|
||||
|
||||
(hk-test
|
||||
"type-ann: tuple annotated"
|
||||
(hk-deep-force (hk-run "main = ((1, 2) :: (Int, Int))"))
|
||||
(list "Tuple" 1 2))
|
||||
|
||||
(hk-test
|
||||
"type-ann: nested annotation in arithmetic"
|
||||
(hk-deep-force (hk-run "main = (1 :: Int) + (2 :: Int)"))
|
||||
3)
|
||||
|
||||
(hk-test
|
||||
"type-ann: function-typed annotation passes through eval"
|
||||
(hk-deep-force
|
||||
(hk-run "main = let f = ((\\x -> x + 1) :: Int -> Int) in f 5"))
|
||||
6)
|
||||
|
||||
(hk-test
|
||||
"no regression: plain parens still work"
|
||||
(hk-deep-force (hk-run "main = (5)"))
|
||||
5)
|
||||
|
||||
(hk-test
|
||||
"no regression: 3-tuple still works"
|
||||
(hk-deep-force (hk-run "main = (1, 2, 3)"))
|
||||
(list "Tuple" 1 2 3))
|
||||
|
||||
(hk-test
|
||||
"no regression: section-left still works"
|
||||
(hk-deep-force (hk-run "main = (3 +) 4"))
|
||||
7)
|
||||
|
||||
(hk-test
|
||||
"no regression: section-right still works"
|
||||
(hk-deep-force (hk-run "main = (+ 3) 4"))
|
||||
7)
|
||||
|
||||
(hk-test
|
||||
"import: still works as the very first decl"
|
||||
(hk-deep-force
|
||||
(hk-run "import qualified Data.IORef as I
|
||||
main = do { r <- I.newIORef 7; I.readIORef r }"))
|
||||
(list "IO" 7))
|
||||
|
||||
(hk-test
|
||||
"import: between decls — after main"
|
||||
(hk-deep-force
|
||||
(hk-run "main = do { r <- I.newIORef 11; I.readIORef r }
|
||||
import qualified Data.IORef as I"))
|
||||
(list "IO" 11))
|
||||
|
||||
(hk-test
|
||||
"import: between two decls — uses helper after import"
|
||||
(hk-deep-force
|
||||
(hk-run "f x = x + 100
|
||||
import qualified Data.IORef as I
|
||||
main = do { r <- I.newIORef 5; I.modifyIORef r f; I.readIORef r }"))
|
||||
(list "IO" 105))
|
||||
|
||||
(hk-test
|
||||
"import: two imports in different positions"
|
||||
(hk-deep-force
|
||||
(hk-run "import qualified Data.IORef as I
|
||||
helper x = x * 2
|
||||
import qualified Data.Map as M
|
||||
main = do { r <- I.newIORef (helper 21); I.readIORef r }"))
|
||||
(list "IO" 42))
|
||||
|
||||
(hk-test
|
||||
"import: unqualified, mid-file"
|
||||
(hk-deep-force
|
||||
(hk-run "go x = x
|
||||
import Data.IORef
|
||||
main = go 9"))
|
||||
9)
|
||||
81
lib/haskell/tests/program-accumulate.sx
Normal file
81
lib/haskell/tests/program-accumulate.sx
Normal file
@@ -0,0 +1,81 @@
|
||||
;; accumulate.hs — accumulate results into an IORef [Int] (Phase 15 conformance).
|
||||
|
||||
(define
|
||||
hk-accumulate-source
|
||||
"import qualified Data.IORef as IORef\n\npush :: IORef [Int] -> Int -> IO ()\npush r x = IORef.modifyIORef r (\\xs -> x : xs)\n\npushAll :: IORef [Int] -> [Int] -> IO ()\npushAll r [] = return ()\npushAll r (x:xs) = do\n push r x\n pushAll r xs\n\nreadReversed :: IORef [Int] -> IO [Int]\nreadReversed r = do\n xs <- IORef.readIORef r\n return (reverse xs)\n\ndoubleEach :: IORef [Int] -> [Int] -> IO ()\ndoubleEach r [] = return ()\ndoubleEach r (x:xs) = do\n push r (x * 2)\n doubleEach r xs\n\nsumIntoRef :: IORef Int -> [Int] -> IO ()\nsumIntoRef r [] = return ()\nsumIntoRef r (x:xs) = do\n IORef.modifyIORef r (\\acc -> acc + x)\n sumIntoRef r xs\n\n")
|
||||
|
||||
(hk-test
|
||||
"accumulate.hs — push three then read length"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-accumulate-source
|
||||
"main = do { r <- IORef.newIORef []; push r 1; push r 2; push r 3; xs <- IORef.readIORef r; return (length xs) }")))
|
||||
(list "IO" 3))
|
||||
|
||||
(hk-test
|
||||
"accumulate.hs — pushAll preserves reverse order"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-accumulate-source
|
||||
"main = do { r <- IORef.newIORef []; pushAll r [1,2,3,4]; xs <- IORef.readIORef r; return xs }")))
|
||||
(list
|
||||
"IO"
|
||||
(list ":" 4 (list ":" 3 (list ":" 2 (list ":" 1 (list "[]")))))))
|
||||
|
||||
(hk-test
|
||||
"accumulate.hs — readReversed gives original order"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-accumulate-source
|
||||
"main = do { r <- IORef.newIORef []; pushAll r [10,20,30]; readReversed r }")))
|
||||
(list "IO" (list ":" 10 (list ":" 20 (list ":" 30 (list "[]"))))))
|
||||
|
||||
(hk-test
|
||||
"accumulate.hs — doubleEach maps then accumulates"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-accumulate-source
|
||||
"main = do { r <- IORef.newIORef []; doubleEach r [1,2,3]; readReversed r }")))
|
||||
(list "IO" (list ":" 2 (list ":" 4 (list ":" 6 (list "[]"))))))
|
||||
|
||||
(hk-test
|
||||
"accumulate.hs — sum into Int IORef"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-accumulate-source
|
||||
"main = do { r <- IORef.newIORef 0; sumIntoRef r [1,2,3,4,5]; v <- IORef.readIORef r; return v }")))
|
||||
(list "IO" 15))
|
||||
|
||||
(hk-test
|
||||
"accumulate.hs — empty list leaves ref untouched"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-accumulate-source
|
||||
"main = do { r <- IORef.newIORef [99]; pushAll r []; xs <- IORef.readIORef r; return xs }")))
|
||||
(list "IO" (list ":" 99 (list "[]"))))
|
||||
|
||||
(hk-test
|
||||
"accumulate.hs — pushAll then sumIntoRef on the same input"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-accumulate-source
|
||||
"main = do { r <- IORef.newIORef 0; sumIntoRef r [10,20,30,40]; v <- IORef.readIORef r; return v }")))
|
||||
(list "IO" 100))
|
||||
|
||||
(hk-test
|
||||
"accumulate.hs — accumulate results from a recursive helper"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-accumulate-source
|
||||
"squaresUpTo r 0 = return ()\nsquaresUpTo r n = do { push r (n * n); squaresUpTo r (n - 1) }\nmain = do { r <- IORef.newIORef []; squaresUpTo r 4; readReversed r }")))
|
||||
(list
|
||||
"IO"
|
||||
(list ":" 16 (list ":" 9 (list ":" 4 (list ":" 1 (list "[]")))))))
|
||||
80
lib/haskell/tests/program-caesar.sx
Normal file
80
lib/haskell/tests/program-caesar.sx
Normal file
@@ -0,0 +1,80 @@
|
||||
;; caesar.hs — Caesar cipher.
|
||||
;; Source: https://rosettacode.org/wiki/Caesar_cipher#Haskell (adapted).
|
||||
;;
|
||||
;; Exercises chr, ord, isUpper, isLower, mod, string pattern matching
|
||||
;; (x:xs) over a String (which is now a [Char] string view), and map
|
||||
;; from the Phase 7 string=[Char] foundation.
|
||||
|
||||
(define
|
||||
hk-prog-val
|
||||
(fn
|
||||
(src name)
|
||||
(hk-deep-force (get (hk-eval-program (hk-core src)) name))))
|
||||
|
||||
(define
|
||||
hk-as-list
|
||||
(fn
|
||||
(xs)
|
||||
(cond
|
||||
((and (list? xs) (= (first xs) "[]")) (list))
|
||||
((and (list? xs) (= (first xs) ":"))
|
||||
(cons (nth xs 1) (hk-as-list (nth xs 2))))
|
||||
(:else xs))))
|
||||
|
||||
(define
|
||||
hk-caesar-source
|
||||
"shift n c = if isUpper c\n then chr (mod ((ord c) - 65 + n) 26 + 65)\n else if isLower c\n then chr (mod ((ord c) - 97 + n) 26 + 97)\n else chr c\n\ncaesarRec n [] = []\ncaesarRec n (x:xs) = shift n x : caesarRec n xs\n\ncaesarMap n s = map (shift n) s\n")
|
||||
|
||||
(hk-test
|
||||
"caesar.hs — caesarRec 3 \"ABC\" = DEF"
|
||||
(hk-as-list
|
||||
(hk-prog-val (str hk-caesar-source "r = caesarRec 3 \"ABC\"\n") "r"))
|
||||
(list "D" "E" "F"))
|
||||
|
||||
(hk-test
|
||||
"caesar.hs — caesarRec 13 \"Hello\" = Uryyb"
|
||||
(hk-as-list
|
||||
(hk-prog-val (str hk-caesar-source "r = caesarRec 13 \"Hello\"\n") "r"))
|
||||
(list "U" "r" "y" "y" "b"))
|
||||
|
||||
(hk-test
|
||||
"caesar.hs — caesarRec 1 \"AZ\" wraps to BA"
|
||||
(hk-as-list
|
||||
(hk-prog-val (str hk-caesar-source "r = caesarRec 1 \"AZ\"\n") "r"))
|
||||
(list "B" "A"))
|
||||
|
||||
(hk-test
|
||||
"caesar.hs — caesarRec 0 \"World\" identity"
|
||||
(hk-as-list
|
||||
(hk-prog-val (str hk-caesar-source "r = caesarRec 0 \"World\"\n") "r"))
|
||||
(list "W" "o" "r" "l" "d"))
|
||||
|
||||
(hk-test
|
||||
"caesar.hs — caesarRec preserves punctuation"
|
||||
(hk-as-list
|
||||
(hk-prog-val (str hk-caesar-source "r = caesarRec 3 \"Hi!\"\n") "r"))
|
||||
(list "K" "l" "!"))
|
||||
|
||||
(hk-test
|
||||
"caesar.hs — caesarMap 3 \"abc\" via map"
|
||||
(hk-as-list
|
||||
(hk-prog-val (str hk-caesar-source "r = caesarMap 3 \"abc\"\n") "r"))
|
||||
(list "d" "e" "f"))
|
||||
|
||||
(hk-test
|
||||
"caesar.hs — caesarMap 13 round-trips with caesarMap 13"
|
||||
(hk-as-list
|
||||
(hk-prog-val
|
||||
(str
|
||||
hk-caesar-source
|
||||
"r = caesarMap 13 (foldr (\\c acc -> c : acc) [] (caesarMap 13 \"Hello\"))\n")
|
||||
"r"))
|
||||
(list "H" "e" "l" "l" "o"))
|
||||
|
||||
(hk-test
|
||||
"caesar.hs — caesarRec 25 \"AB\" = ZA"
|
||||
(hk-as-list
|
||||
(hk-prog-val (str hk-caesar-source "r = caesarRec 25 \"AB\"\n") "r"))
|
||||
(list "Z" "A"))
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
63
lib/haskell/tests/program-config.sx
Normal file
63
lib/haskell/tests/program-config.sx
Normal file
@@ -0,0 +1,63 @@
|
||||
;; config.hs — multi-field config record; partial update; defaultConfig
|
||||
;; constant.
|
||||
;;
|
||||
;; Exercises Phase 14: 4-field record, defaultConfig as a CAF, partial
|
||||
;; updates that change one or two fields, accessors over derived configs.
|
||||
|
||||
(define
|
||||
hk-config-source
|
||||
"data Config = Config { host :: String, port :: Int, retries :: Int, debug :: Bool } deriving (Show)\n\ndefaultConfig = Config { host = \"localhost\", port = 8080, retries = 3, debug = False }\n\ndevConfig = defaultConfig { debug = True }\nremoteConfig = defaultConfig { host = \"api.example.com\", port = 443 }\n")
|
||||
|
||||
(hk-test
|
||||
"config.hs — defaultConfig host"
|
||||
(hk-deep-force (hk-run (str hk-config-source "main = host defaultConfig")))
|
||||
"localhost")
|
||||
|
||||
(hk-test
|
||||
"config.hs — defaultConfig port"
|
||||
(hk-deep-force (hk-run (str hk-config-source "main = port defaultConfig")))
|
||||
8080)
|
||||
|
||||
(hk-test
|
||||
"config.hs — defaultConfig retries"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-config-source "main = retries defaultConfig")))
|
||||
3)
|
||||
|
||||
(hk-test
|
||||
"config.hs — devConfig flips debug"
|
||||
(hk-deep-force (hk-run (str hk-config-source "main = debug devConfig")))
|
||||
(list "True"))
|
||||
|
||||
(hk-test
|
||||
"config.hs — devConfig preserves host"
|
||||
(hk-deep-force (hk-run (str hk-config-source "main = host devConfig")))
|
||||
"localhost")
|
||||
|
||||
(hk-test
|
||||
"config.hs — devConfig preserves port"
|
||||
(hk-deep-force (hk-run (str hk-config-source "main = port devConfig")))
|
||||
8080)
|
||||
|
||||
(hk-test
|
||||
"config.hs — remoteConfig new host"
|
||||
(hk-deep-force (hk-run (str hk-config-source "main = host remoteConfig")))
|
||||
"api.example.com")
|
||||
|
||||
(hk-test
|
||||
"config.hs — remoteConfig new port"
|
||||
(hk-deep-force (hk-run (str hk-config-source "main = port remoteConfig")))
|
||||
443)
|
||||
|
||||
(hk-test
|
||||
"config.hs — remoteConfig preserves retries"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-config-source "main = retries remoteConfig")))
|
||||
3)
|
||||
|
||||
(hk-test
|
||||
"config.hs — remoteConfig preserves debug"
|
||||
(hk-deep-force (hk-run (str hk-config-source "main = debug remoteConfig")))
|
||||
(list "False"))
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
66
lib/haskell/tests/program-counter.sx
Normal file
66
lib/haskell/tests/program-counter.sx
Normal file
@@ -0,0 +1,66 @@
|
||||
;; counter.hs — IORef-backed mutable counter (Phase 15 conformance).
|
||||
|
||||
(define
|
||||
hk-counter-source
|
||||
"import qualified Data.IORef as IORef\n\ncount :: IORef Int -> Int -> IO ()\ncount r 0 = return ()\ncount r n = do\n IORef.modifyIORef r (\\x -> x + 1)\n count r (n - 1)\n\ncountBy :: IORef Int -> Int -> Int -> IO ()\ncountBy r step 0 = return ()\ncountBy r step n = do\n IORef.modifyIORef r (\\x -> x + step)\n countBy r step (n - 1)\n\nnewCounter :: Int -> IO (IORef Int)\nnewCounter v = IORef.newIORef v\n\nbumpAndRead :: IORef Int -> IO Int\nbumpAndRead r = do\n IORef.modifyIORef r (\\x -> x + 1)\n IORef.readIORef r\n\n")
|
||||
|
||||
(hk-test
|
||||
"counter.hs — start at 0, count 5 ⇒ 5"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-counter-source
|
||||
"main = do { r <- newCounter 0; count r 5; v <- IORef.readIORef r; return v }")))
|
||||
(list "IO" 5))
|
||||
|
||||
(hk-test
|
||||
"counter.hs — start at 100, count 10 ⇒ 110"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-counter-source
|
||||
"main = do { r <- newCounter 100; count r 10; v <- IORef.readIORef r; return v }")))
|
||||
(list "IO" 110))
|
||||
|
||||
(hk-test
|
||||
"counter.hs — countBy step 5, n 4 ⇒ 20"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-counter-source
|
||||
"main = do { r <- newCounter 0; countBy r 5 4; v <- IORef.readIORef r; return v }")))
|
||||
(list "IO" 20))
|
||||
|
||||
(hk-test
|
||||
"counter.hs — bumpAndRead returns updated value"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str hk-counter-source "main = do { r <- newCounter 41; bumpAndRead r }")))
|
||||
(list "IO" 42))
|
||||
|
||||
(hk-test
|
||||
"counter.hs — count then countBy compose"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-counter-source
|
||||
"main = do { r <- newCounter 0; count r 3; countBy r 10 2; v <- IORef.readIORef r; return v }")))
|
||||
(list "IO" 23))
|
||||
|
||||
(hk-test
|
||||
"counter.hs — two independent counters"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-counter-source
|
||||
"main = do { a <- newCounter 0; b <- newCounter 0; count a 7; countBy b 100 2; va <- IORef.readIORef a; vb <- IORef.readIORef b; return (va + vb) }")))
|
||||
(list "IO" 207))
|
||||
|
||||
(hk-test
|
||||
"counter.hs — modifyIORef' (strict) variant"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-counter-source
|
||||
"tick r 0 = return ()\ntick r n = do { IORef.modifyIORef' r (\\x -> x + 1); tick r (n - 1) }\nmain = do { r <- newCounter 0; tick r 50; v <- IORef.readIORef r; return v }")))
|
||||
(list "IO" 50))
|
||||
46
lib/haskell/tests/program-mapgraph.sx
Normal file
46
lib/haskell/tests/program-mapgraph.sx
Normal file
@@ -0,0 +1,46 @@
|
||||
;; mapgraph.hs — adjacency-list using Data.Map (BFS-style traversal).
|
||||
;;
|
||||
;; Exercises Phase 11: `import qualified Data.Map as Map`, `Map.empty`,
|
||||
;; `Map.insert`, `Map.lookup`, `Map.findWithDefault`. Adjacency lists are
|
||||
;; stored as `Map Int [Int]`; `neighbors` does a default-empty lookup.
|
||||
|
||||
(define
|
||||
hk-mapgraph-source
|
||||
"import qualified Data.Map as Map\n\nemptyG = Map.empty\n\naddEdge u v g = Map.insertWith add u [v] g\n where add new old = new ++ old\n\nbuild = addEdge 1 2 (addEdge 1 3 (addEdge 2 4 (addEdge 3 4 (addEdge 4 5 emptyG))))\n\nneighbors n g = Map.findWithDefault [] n g\n")
|
||||
|
||||
(hk-test
|
||||
"mapgraph.hs — neighbors of 1"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-mapgraph-source "main = neighbors 1 build\n")))
|
||||
(list ":" 2 (list ":" 3 (list "[]"))))
|
||||
|
||||
(hk-test
|
||||
"mapgraph.hs — neighbors of 4"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-mapgraph-source "main = neighbors 4 build\n")))
|
||||
(list ":" 5 (list "[]")))
|
||||
|
||||
(hk-test
|
||||
"mapgraph.hs — neighbors of 5 (leaf, no entry) defaults to []"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-mapgraph-source "main = neighbors 5 build\n")))
|
||||
(list "[]"))
|
||||
|
||||
(hk-test
|
||||
"mapgraph.hs — neighbors of 99 (absent) defaults to []"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-mapgraph-source "main = neighbors 99 build\n")))
|
||||
(list "[]"))
|
||||
|
||||
(hk-test
|
||||
"mapgraph.hs — Map.member 1"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-mapgraph-source "main = Map.member 1 build\n")))
|
||||
(list "True"))
|
||||
|
||||
(hk-test
|
||||
"mapgraph.hs — Map.size = 4 source nodes"
|
||||
(hk-deep-force (hk-run (str hk-mapgraph-source "main = Map.size build\n")))
|
||||
4)
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
49
lib/haskell/tests/program-newton.sx
Normal file
49
lib/haskell/tests/program-newton.sx
Normal file
@@ -0,0 +1,49 @@
|
||||
;; newton.hs — Newton's method for square root.
|
||||
;; Source: classic numerical analysis exercise.
|
||||
;;
|
||||
;; Exercises Phase 10: `Float`, `abs`, `/`, iteration via `until`.
|
||||
|
||||
(define
|
||||
hk-prog-val
|
||||
(fn
|
||||
(src name)
|
||||
(hk-deep-force (get (hk-eval-program (hk-core src)) name))))
|
||||
|
||||
(define
|
||||
hk-newton-source
|
||||
"improve x guess = (guess + x / guess) / 2\n\ngoodEnough x guess = abs (guess * guess - x) < 0.0001\n\nnewtonSqrt x = newtonHelp x 1.0\n\nnewtonHelp x guess = if goodEnough x guess\n then guess\n else newtonHelp x (improve x guess)\n")
|
||||
|
||||
(hk-test
|
||||
"newton.hs — newtonSqrt 4 ≈ 2"
|
||||
(hk-prog-val
|
||||
(str hk-newton-source "r = abs (newtonSqrt 4.0 - 2.0) < 0.001\n")
|
||||
"r")
|
||||
(list "True"))
|
||||
|
||||
(hk-test
|
||||
"newton.hs — newtonSqrt 9 ≈ 3"
|
||||
(hk-prog-val
|
||||
(str hk-newton-source "r = abs (newtonSqrt 9.0 - 3.0) < 0.001\n")
|
||||
"r")
|
||||
(list "True"))
|
||||
|
||||
(hk-test
|
||||
"newton.hs — newtonSqrt 2 ≈ 1.41421"
|
||||
(hk-prog-val
|
||||
(str hk-newton-source "r = abs (newtonSqrt 2.0 - 1.41421) < 0.001\n")
|
||||
"r")
|
||||
(list "True"))
|
||||
|
||||
(hk-test
|
||||
"newton.hs — improve converges (one step)"
|
||||
(hk-prog-val (str hk-newton-source "r = improve 4.0 1.0\n") "r")
|
||||
2.5)
|
||||
|
||||
(hk-test
|
||||
"newton.hs — newtonSqrt 100 ≈ 10"
|
||||
(hk-prog-val
|
||||
(str hk-newton-source "r = abs (newtonSqrt 100.0 - 10.0) < 0.001\n")
|
||||
"r")
|
||||
(list "True"))
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
58
lib/haskell/tests/program-partial.sx
Normal file
58
lib/haskell/tests/program-partial.sx
Normal file
@@ -0,0 +1,58 @@
|
||||
;; partial.hs — exercises Phase 9 partial functions caught at the top level.
|
||||
;;
|
||||
;; Each program calls a partial function on bad input; hk-run-io catches the
|
||||
;; raise and appends the error message to io-lines so tests can inspect.
|
||||
|
||||
(hk-test
|
||||
"partial.hs — main = print (head [])"
|
||||
(let
|
||||
((lines (hk-run-io "main = print (head [])")))
|
||||
(>= (index-of (str lines) "Prelude.head: empty list") 0))
|
||||
true)
|
||||
|
||||
(hk-test
|
||||
"partial.hs — main = print (tail [])"
|
||||
(let
|
||||
((lines (hk-run-io "main = print (tail [])")))
|
||||
(>= (index-of (str lines) "Prelude.tail: empty list") 0))
|
||||
true)
|
||||
|
||||
(hk-test
|
||||
"partial.hs — main = print (fromJust Nothing)"
|
||||
(let
|
||||
((lines (hk-run-io "main = print (fromJust Nothing)")))
|
||||
(>= (index-of (str lines) "Maybe.fromJust: Nothing") 0))
|
||||
true)
|
||||
|
||||
(hk-test
|
||||
"partial.hs — putStrLn before error preserves prior output"
|
||||
(let
|
||||
((lines (hk-run-io "main = do { putStrLn \"step 1\"; putStrLn (show (head [])); putStrLn \"never\" }")))
|
||||
(and
|
||||
(>= (index-of (str lines) "step 1") 0)
|
||||
(>= (index-of (str lines) "Prelude.head: empty list") 0)
|
||||
(= (index-of (str lines) "never") -1)))
|
||||
true)
|
||||
|
||||
(hk-test
|
||||
"partial.hs — undefined as IO action"
|
||||
(let
|
||||
((lines (hk-run-io "main = print undefined")))
|
||||
(>= (index-of (str lines) "Prelude.undefined") 0))
|
||||
true)
|
||||
|
||||
(hk-test
|
||||
"partial.hs — catches error from a user-thrown error"
|
||||
(let
|
||||
((lines (hk-run-io "main = error \"boom from main\"")))
|
||||
(>= (index-of (str lines) "boom from main") 0))
|
||||
true)
|
||||
|
||||
;; Negative case: when no error is raised, io-lines doesn't contain
|
||||
;; "Prelude" prefixes from our error path.
|
||||
(hk-test
|
||||
"partial.hs — happy path: head [42] succeeds, no error in output"
|
||||
(hk-run-io "main = print (head [42])")
|
||||
(list "42"))
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
51
lib/haskell/tests/program-person.sx
Normal file
51
lib/haskell/tests/program-person.sx
Normal file
@@ -0,0 +1,51 @@
|
||||
;; person.hs — record type with accessors, update, deriving Show.
|
||||
;;
|
||||
;; Exercises Phase 14: data with record syntax, accessor functions,
|
||||
;; record creation, record update, deriving Show on a record.
|
||||
|
||||
(define
|
||||
hk-person-source
|
||||
"data Person = Person { name :: String, age :: Int } deriving (Show)\n\nalice = Person { name = \"alice\", age = 30 }\nbob = Person { name = \"bob\", age = 25 }\n\nbirthday p = p { age = age p + 1 }\n")
|
||||
|
||||
(hk-test
|
||||
"person.hs — alice's name"
|
||||
(hk-deep-force (hk-run (str hk-person-source "main = name alice")))
|
||||
"alice")
|
||||
|
||||
(hk-test
|
||||
"person.hs — alice's age"
|
||||
(hk-deep-force (hk-run (str hk-person-source "main = age alice")))
|
||||
30)
|
||||
|
||||
(hk-test
|
||||
"person.hs — birthday adds one year"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-person-source "main = age (birthday alice)")))
|
||||
31)
|
||||
|
||||
(hk-test
|
||||
"person.hs — birthday preserves name"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-person-source "main = name (birthday alice)")))
|
||||
"alice")
|
||||
|
||||
(hk-test
|
||||
"person.hs — show alice"
|
||||
(hk-deep-force (hk-run (str hk-person-source "main = show alice")))
|
||||
"Person \"alice\" 30")
|
||||
|
||||
(hk-test
|
||||
"person.hs — bob has different name"
|
||||
(hk-deep-force (hk-run (str hk-person-source "main = name bob")))
|
||||
"bob")
|
||||
|
||||
(hk-test
|
||||
"person.hs — pattern match in function"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-person-source
|
||||
"greet (Person { name = n }) = \"Hi, \" ++ n\nmain = greet alice")))
|
||||
"Hi, alice")
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
83
lib/haskell/tests/program-runlength-str.sx
Normal file
83
lib/haskell/tests/program-runlength-str.sx
Normal file
@@ -0,0 +1,83 @@
|
||||
;; runlength-str.hs — run-length encoding on a String.
|
||||
;; Source: https://rosettacode.org/wiki/Run-length_encoding#Haskell (adapted).
|
||||
;;
|
||||
;; Exercises String pattern matching `(x:xs)`, `span` over a string view,
|
||||
;; tuple construction `(Int, Char)`, character equality, and tuple-in-cons
|
||||
;; patterns `((n, c) : rest)` — all enabled by Phase 7 string=[Char].
|
||||
|
||||
(define
|
||||
hk-prog-val
|
||||
(fn
|
||||
(src name)
|
||||
(hk-deep-force (get (hk-eval-program (hk-core src)) name))))
|
||||
|
||||
(define
|
||||
hk-as-list
|
||||
(fn
|
||||
(xs)
|
||||
(cond
|
||||
((and (list? xs) (= (first xs) "[]")) (list))
|
||||
((and (list? xs) (= (first xs) ":"))
|
||||
(cons (nth xs 1) (hk-as-list (nth xs 2))))
|
||||
(:else xs))))
|
||||
|
||||
(define
|
||||
hk-rle-source
|
||||
"encodeRL [] = []\nencodeRL (x:xs) = let (same, rest) = span eqX xs\n eqX y = y == x\n in (1 + length same, x) : encodeRL rest\n\nreplicateRL 0 _ = []\nreplicateRL n c = c : replicateRL (n - 1) c\n\ndecodeRL [] = []\ndecodeRL ((n, c) : rest) = replicateRL n c ++ decodeRL rest\n")
|
||||
|
||||
(hk-test
|
||||
"rle.hs — encodeRL [] = []"
|
||||
(hk-as-list (hk-prog-val (str hk-rle-source "r = encodeRL \"\"\n") "r"))
|
||||
(list))
|
||||
|
||||
(hk-test
|
||||
"rle.hs — length (encodeRL \"aabbbcc\") = 3"
|
||||
(hk-prog-val (str hk-rle-source "r = length (encodeRL \"aabbbcc\")\n") "r")
|
||||
3)
|
||||
|
||||
(hk-test
|
||||
"rle.hs — map fst (encodeRL \"aabbbcc\") = [2,3,2]"
|
||||
(hk-as-list
|
||||
(hk-prog-val (str hk-rle-source "r = map fst (encodeRL \"aabbbcc\")\n") "r"))
|
||||
(list 2 3 2))
|
||||
|
||||
(hk-test
|
||||
"rle.hs — map snd (encodeRL \"aabbbcc\") = [97,98,99]"
|
||||
(hk-as-list
|
||||
(hk-prog-val (str hk-rle-source "r = map snd (encodeRL \"aabbbcc\")\n") "r"))
|
||||
(list 97 98 99))
|
||||
|
||||
(hk-test
|
||||
"rle.hs — counts of encodeRL \"aabbbccddddee\" = [2,3,2,4,2]"
|
||||
(hk-as-list
|
||||
(hk-prog-val
|
||||
(str hk-rle-source "r = map fst (encodeRL \"aabbbccddddee\")\n")
|
||||
"r"))
|
||||
(list 2 3 2 4 2))
|
||||
|
||||
(hk-test
|
||||
"rle.hs — chars of encodeRL \"aabbbccddddee\" = [97,98,99,100,101]"
|
||||
(hk-as-list
|
||||
(hk-prog-val
|
||||
(str hk-rle-source "r = map snd (encodeRL \"aabbbccddddee\")\n")
|
||||
"r"))
|
||||
(list 97 98 99 100 101))
|
||||
|
||||
(hk-test
|
||||
"rle.hs — singleton encodeRL \"x\""
|
||||
(hk-as-list
|
||||
(hk-prog-val (str hk-rle-source "r = map fst (encodeRL \"x\")\n") "r"))
|
||||
(list 1))
|
||||
|
||||
(hk-test
|
||||
"rle.hs — decodeRL round-trip preserves \"aabbbcc\""
|
||||
(hk-as-list
|
||||
(hk-prog-val (str hk-rle-source "r = decodeRL (encodeRL \"aabbbcc\")\n") "r"))
|
||||
(list 97 97 98 98 98 99 99))
|
||||
|
||||
(hk-test
|
||||
"rle.hs — replicateRL 4 65 = [65,65,65,65]"
|
||||
(hk-as-list (hk-prog-val (str hk-rle-source "r = replicateRL 4 65\n") "r"))
|
||||
(list 65 65 65 65))
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
80
lib/haskell/tests/program-safediv.sx
Normal file
80
lib/haskell/tests/program-safediv.sx
Normal file
@@ -0,0 +1,80 @@
|
||||
;; safediv.hs — safe division using catch (Phase 16 conformance).
|
||||
|
||||
(define
|
||||
hk-safediv-source
|
||||
"safeDiv :: Int -> Int -> IO Int
|
||||
safeDiv _ 0 = throwIO (SomeException \"division by zero\")
|
||||
safeDiv x y = return (x `div` y)
|
||||
|
||||
guarded :: Int -> Int -> IO Int
|
||||
guarded x y = catch (safeDiv x y) (\\(SomeException _) -> return 0)
|
||||
|
||||
reason :: Int -> Int -> IO String
|
||||
reason x y = catch (safeDiv x y `seq` return \"ok\")
|
||||
(\\(SomeException m) -> return m)
|
||||
|
||||
bothBranches :: Int -> Int -> IO Int
|
||||
bothBranches x y = do
|
||||
v <- catch (safeDiv x y) (\\(SomeException _) -> return (-1))
|
||||
return (v + 100)
|
||||
|
||||
")
|
||||
|
||||
(hk-test
|
||||
"safediv.hs — divide by non-zero"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str hk-safediv-source "main = guarded 10 2")))
|
||||
(list "IO" 5))
|
||||
|
||||
(hk-test
|
||||
"safediv.hs — divide by zero returns 0"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str hk-safediv-source "main = guarded 10 0")))
|
||||
(list "IO" 0))
|
||||
|
||||
(hk-test
|
||||
"safediv.hs — divide by zero — reason captured"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str hk-safediv-source "main = catch (safeDiv 1 0) (\\(SomeException m) -> return 0) >> reason 1 0")))
|
||||
(list "IO" "division by zero"))
|
||||
|
||||
(hk-test
|
||||
"safediv.hs — bothBranches success path"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str hk-safediv-source "main = bothBranches 8 2")))
|
||||
(list "IO" 104))
|
||||
|
||||
(hk-test
|
||||
"safediv.hs — bothBranches failure path"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str hk-safediv-source "main = bothBranches 8 0")))
|
||||
(list "IO" 99))
|
||||
|
||||
(hk-test
|
||||
"safediv.hs — chained safeDiv with catch"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str hk-safediv-source
|
||||
"main = do { a <- guarded 20 4; b <- guarded 7 0; return (a + b) }")))
|
||||
(list "IO" 5))
|
||||
|
||||
(hk-test
|
||||
"safediv.hs — try then bind through Either"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str hk-safediv-source
|
||||
"main = do { r <- try (safeDiv 1 0); case r of { Right v -> return v; Left (SomeException m) -> return 999 } }")))
|
||||
(list "IO" 999))
|
||||
|
||||
(hk-test
|
||||
"safediv.hs — handle (flip catch)"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str hk-safediv-source
|
||||
"main = handle (\\(SomeException _) -> return 0) (safeDiv 5 0)")))
|
||||
(list "IO" 0))
|
||||
61
lib/haskell/tests/program-setops.sx
Normal file
61
lib/haskell/tests/program-setops.sx
Normal file
@@ -0,0 +1,61 @@
|
||||
;; setops.hs — set union/intersection/difference on integer sets.
|
||||
;;
|
||||
;; Exercises Phase 12: `import qualified Data.Set as Set`, all three
|
||||
;; combining operations + isSubsetOf.
|
||||
|
||||
(define
|
||||
hk-setops-source
|
||||
"import qualified Data.Set as Set\n\ns1 = Set.insert 1 (Set.insert 2 (Set.insert 3 Set.empty))\ns2 = Set.insert 3 (Set.insert 4 (Set.insert 5 Set.empty))\ns3 = Set.insert 1 (Set.insert 2 Set.empty)\n")
|
||||
|
||||
(hk-test
|
||||
"setops.hs — union size = 5"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-setops-source "main = Set.size (Set.union s1 s2)\n")))
|
||||
5)
|
||||
|
||||
(hk-test
|
||||
"setops.hs — intersection size = 1"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str hk-setops-source "main = Set.size (Set.intersection s1 s2)\n")))
|
||||
1)
|
||||
|
||||
(hk-test
|
||||
"setops.hs — intersection contains 3"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str hk-setops-source "main = Set.member 3 (Set.intersection s1 s2)\n")))
|
||||
(list "True"))
|
||||
|
||||
(hk-test
|
||||
"setops.hs — difference s1 s2 size = 2"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-setops-source "main = Set.size (Set.difference s1 s2)\n")))
|
||||
2)
|
||||
|
||||
(hk-test
|
||||
"setops.hs — difference doesn't contain shared key"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str hk-setops-source "main = Set.member 3 (Set.difference s1 s2)\n")))
|
||||
(list "False"))
|
||||
|
||||
(hk-test
|
||||
"setops.hs — s3 is subset of s1"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-setops-source "main = Set.isSubsetOf s3 s1\n")))
|
||||
(list "True"))
|
||||
|
||||
(hk-test
|
||||
"setops.hs — s1 not subset of s3"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-setops-source "main = Set.isSubsetOf s1 s3\n")))
|
||||
(list "False"))
|
||||
|
||||
(hk-test
|
||||
"setops.hs — empty set is subset of anything"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-setops-source "main = Set.isSubsetOf Set.empty s1\n")))
|
||||
(list "True"))
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
40
lib/haskell/tests/program-shapes.sx
Normal file
40
lib/haskell/tests/program-shapes.sx
Normal file
@@ -0,0 +1,40 @@
|
||||
;; shapes.hs — class Area with a default perimeter, two instances
|
||||
;; using where-local helpers.
|
||||
;;
|
||||
;; Exercises Phase 13: class default method (perimeter), instance
|
||||
;; methods that use `where`-bindings.
|
||||
|
||||
(define
|
||||
hk-shapes-source
|
||||
"class Shape a where\n area :: a -> Int\n perimeter :: a -> Int\n perimeter x = quadrilateral x\n where quadrilateral y = 2 * (sideA y + sideB y)\n sideA z = 1\n sideB z = 1\n\ndata Square = Square Int\ndata Rect = Rect Int Int\n\ninstance Shape Square where\n area (Square s) = s * s\n perimeter (Square s) = 4 * s\n\ninstance Shape Rect where\n area (Rect w h) = w * h\n perimeter (Rect w h) = peri\n where peri = 2 * (w + h)\n")
|
||||
|
||||
(hk-test
|
||||
"shapes.hs — area of Square 5 = 25"
|
||||
(hk-deep-force (hk-run (str hk-shapes-source "main = area (Square 5)\n")))
|
||||
25)
|
||||
|
||||
(hk-test
|
||||
"shapes.hs — perimeter of Square 5 = 20"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-shapes-source "main = perimeter (Square 5)\n")))
|
||||
20)
|
||||
|
||||
(hk-test
|
||||
"shapes.hs — area of Rect 3 4 = 12"
|
||||
(hk-deep-force (hk-run (str hk-shapes-source "main = area (Rect 3 4)\n")))
|
||||
12)
|
||||
|
||||
(hk-test
|
||||
"shapes.hs — perimeter of Rect 3 4 = 14 (via where-bound)"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-shapes-source "main = perimeter (Rect 3 4)\n")))
|
||||
14)
|
||||
|
||||
(hk-test
|
||||
"shapes.hs — Square sums area + perimeter"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str hk-shapes-source "main = area (Square 4) + perimeter (Square 4)\n")))
|
||||
32)
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
45
lib/haskell/tests/program-showadt.sx
Normal file
45
lib/haskell/tests/program-showadt.sx
Normal file
@@ -0,0 +1,45 @@
|
||||
;; showadt.hs — `deriving (Show)` on a multi-constructor recursive ADT.
|
||||
;; Source: classic exposition example, e.g. Real World Haskell ch.6.
|
||||
;;
|
||||
;; Exercises Phase 8: `deriving (Show)` on an ADT whose constructors recurse
|
||||
;; into themselves; precedence-based paren wrapping for nested arguments;
|
||||
;; `print` from the prelude (which is `putStrLn (show x)`).
|
||||
|
||||
(define
|
||||
hk-showadt-source
|
||||
"data Expr = Lit Int | Add Expr Expr | Mul Expr Expr deriving (Show)\n\nmain = do\n print (Lit 3)\n print (Add (Lit 1) (Lit 2))\n print (Mul (Lit 3) (Add (Lit 4) (Lit 5)))\n")
|
||||
|
||||
(hk-test
|
||||
"showadt.hs — main prints three lines"
|
||||
(hk-run-io hk-showadt-source)
|
||||
(list "Lit 3" "Add (Lit 1) (Lit 2)" "Mul (Lit 3) (Add (Lit 4) (Lit 5))"))
|
||||
|
||||
(hk-test
|
||||
"showadt.hs — show Lit 3"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"data Expr = Lit Int | Add Expr Expr | Mul Expr Expr deriving (Show)\nmain = show (Lit 3)"))
|
||||
"Lit 3")
|
||||
|
||||
(hk-test
|
||||
"showadt.hs — show Add wraps both args"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"data Expr = Lit Int | Add Expr Expr | Mul Expr Expr deriving (Show)\nmain = show (Add (Lit 1) (Lit 2))"))
|
||||
"Add (Lit 1) (Lit 2)")
|
||||
|
||||
(hk-test
|
||||
"showadt.hs — fully nested Mul of Adds"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"data Expr = Lit Int | Add Expr Expr | Mul Expr Expr deriving (Show)\nmain = show (Mul (Add (Lit 1) (Lit 2)) (Add (Lit 3) (Lit 4)))"))
|
||||
"Mul (Add (Lit 1) (Lit 2)) (Add (Lit 3) (Lit 4))")
|
||||
|
||||
(hk-test
|
||||
"showadt.hs — Lit with negative literal wraps int in parens"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"data Expr = Lit Int | Add Expr Expr | Mul Expr Expr deriving (Show)\nmain = show (Lit (negate 7))"))
|
||||
"Lit (-7)")
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
36
lib/haskell/tests/program-showio.sx
Normal file
36
lib/haskell/tests/program-showio.sx
Normal file
@@ -0,0 +1,36 @@
|
||||
;; showio.hs — `print` on various types inside a `do` block.
|
||||
;;
|
||||
;; Exercises Phase 8 `print x = putStrLn (show x)` and the IO monad's
|
||||
;; statement sequencing. Each `print` produces one io-line.
|
||||
|
||||
(define
|
||||
hk-showio-source
|
||||
"main = do\n print 42\n print True\n print False\n print [1,2,3]\n print (1, 2)\n print (Just 5)\n print Nothing\n print \"hello\"\n")
|
||||
|
||||
(hk-test
|
||||
"showio.hs — main produces 8 lines, all show-formatted"
|
||||
(hk-run-io hk-showio-source)
|
||||
(list "42" "True" "False" "[1,2,3]" "(1,2)" "Just 5" "Nothing" "\"hello\""))
|
||||
|
||||
(hk-test
|
||||
"showio.hs — print Int alone"
|
||||
(hk-run-io "main = print 42")
|
||||
(list "42"))
|
||||
|
||||
(hk-test
|
||||
"showio.hs — print list of Maybe"
|
||||
(hk-run-io "main = print [Just 1, Nothing, Just 3]")
|
||||
(list "[Just 1,Nothing,Just 3]"))
|
||||
|
||||
(hk-test
|
||||
"showio.hs — print nested tuple"
|
||||
(hk-run-io "main = print ((1, 2), (3, 4))")
|
||||
(list "((1,2),(3,4))"))
|
||||
|
||||
(hk-test
|
||||
"showio.hs — print derived ADT inside do"
|
||||
(hk-run-io
|
||||
"data Color = Red | Green | Blue deriving (Show)\nmain = do { print Red; print Green; print Blue }")
|
||||
(list "Red" "Green" "Blue"))
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
45
lib/haskell/tests/program-statistics.sx
Normal file
45
lib/haskell/tests/program-statistics.sx
Normal file
@@ -0,0 +1,45 @@
|
||||
;; statistics.hs — mean, variance, std-dev on a [Double].
|
||||
;; Source: classic textbook example.
|
||||
;;
|
||||
;; Exercises Phase 10: `fromIntegral`, `/`, `sqrt`, list ops on `[Double]`.
|
||||
|
||||
(define
|
||||
hk-prog-val
|
||||
(fn
|
||||
(src name)
|
||||
(hk-deep-force (get (hk-eval-program (hk-core src)) name))))
|
||||
|
||||
(define
|
||||
hk-stats-source
|
||||
"mean xs = sum xs / fromIntegral (length xs)\n\nvariance xs = let m = mean xs\n sqDiff x = (x - m) * (x - m)\n in sum (map sqDiff xs) / fromIntegral (length xs)\n\nstdDev xs = sqrt (variance xs)\n")
|
||||
|
||||
(hk-test
|
||||
"statistics.hs — mean [1,2,3,4,5] = 3"
|
||||
(hk-prog-val (str hk-stats-source "r = mean [1.0,2.0,3.0,4.0,5.0]\n") "r")
|
||||
3)
|
||||
|
||||
(hk-test
|
||||
"statistics.hs — mean [10,20,30] = 20"
|
||||
(hk-prog-val (str hk-stats-source "r = mean [10.0,20.0,30.0]\n") "r")
|
||||
20)
|
||||
|
||||
(hk-test
|
||||
"statistics.hs — variance [2,4,4,4,5,5,7,9] = 4"
|
||||
(hk-prog-val
|
||||
(str hk-stats-source "r = variance [2.0,4.0,4.0,4.0,5.0,5.0,7.0,9.0]\n")
|
||||
"r")
|
||||
4)
|
||||
|
||||
(hk-test
|
||||
"statistics.hs — stdDev [2,4,4,4,5,5,7,9] = 2"
|
||||
(hk-prog-val
|
||||
(str hk-stats-source "r = stdDev [2.0,4.0,4.0,4.0,5.0,5.0,7.0,9.0]\n")
|
||||
"r")
|
||||
2)
|
||||
|
||||
(hk-test
|
||||
"statistics.hs — variance of constant list = 0"
|
||||
(hk-prog-val (str hk-stats-source "r = variance [5.0,5.0,5.0,5.0]\n") "r")
|
||||
0)
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
95
lib/haskell/tests/program-trycatch.sx
Normal file
95
lib/haskell/tests/program-trycatch.sx
Normal file
@@ -0,0 +1,95 @@
|
||||
;; trycatch.hs — try pattern: branch on Left/Right (Phase 16 conformance).
|
||||
|
||||
(define
|
||||
hk-trycatch-source
|
||||
"parseInt :: String -> IO Int
|
||||
parseInt \"zero\" = return 0
|
||||
parseInt \"one\" = return 1
|
||||
parseInt \"two\" = return 2
|
||||
parseInt s = throwIO (SomeException (\"unknown: \" ++ s))
|
||||
|
||||
describe :: Either SomeException Int -> String
|
||||
describe (Right v) = \"got \" ++ show v
|
||||
describe (Left (SomeException m)) = \"err: \" ++ m
|
||||
|
||||
trial :: String -> IO String
|
||||
trial s = do
|
||||
r <- try (parseInt s)
|
||||
return (describe r)
|
||||
|
||||
run3 :: String -> String -> String -> IO [String]
|
||||
run3 a b c = do
|
||||
ra <- trial a
|
||||
rb <- trial b
|
||||
rc <- trial c
|
||||
return [ra, rb, rc]
|
||||
|
||||
")
|
||||
|
||||
(hk-test
|
||||
"trycatch.hs — Right branch"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str hk-trycatch-source "main = trial \"one\"")))
|
||||
(list "IO" "got 1"))
|
||||
|
||||
(hk-test
|
||||
"trycatch.hs — Left branch with message"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str hk-trycatch-source "main = trial \"banana\"")))
|
||||
(list "IO" "err: unknown: banana"))
|
||||
|
||||
(hk-test
|
||||
"trycatch.hs — chain over three inputs, all good"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str hk-trycatch-source "main = run3 \"zero\" \"one\" \"two\"")))
|
||||
(list "IO"
|
||||
(list ":" "got 0"
|
||||
(list ":" "got 1"
|
||||
(list ":" "got 2"
|
||||
(list "[]"))))))
|
||||
|
||||
(hk-test
|
||||
"trycatch.hs — chain over three inputs, mixed"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str hk-trycatch-source "main = run3 \"zero\" \"qux\" \"two\"")))
|
||||
(list "IO"
|
||||
(list ":" "got 0"
|
||||
(list ":" "err: unknown: qux"
|
||||
(list ":" "got 2"
|
||||
(list "[]"))))))
|
||||
|
||||
(hk-test
|
||||
"trycatch.hs — Left from throwIO carries message"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str hk-trycatch-source
|
||||
"main = do { r <- try (throwIO (SomeException \"explicit\")); return (describe r) }")))
|
||||
(list "IO" "err: explicit"))
|
||||
|
||||
(hk-test
|
||||
"trycatch.hs — Right preserves the int"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str hk-trycatch-source
|
||||
"main = do { r <- try (return 42); return (describe r) }")))
|
||||
(list "IO" "got 42"))
|
||||
|
||||
(hk-test
|
||||
"trycatch.hs — pattern-bind on Right inside do"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str hk-trycatch-source
|
||||
"main = do { Right v <- try (parseInt \"two\"); return (v + 100) }")))
|
||||
(list "IO" 102))
|
||||
|
||||
(hk-test
|
||||
"trycatch.hs — handle alias on parseInt failure"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str hk-trycatch-source
|
||||
"main = handle (\\(SomeException m) -> return (\"caught: \" ++ m)) (parseInt \"nope\" >>= (\\v -> return (show v)))")))
|
||||
(list "IO" "caught: unknown: nope"))
|
||||
35
lib/haskell/tests/program-uniquewords.sx
Normal file
35
lib/haskell/tests/program-uniquewords.sx
Normal file
@@ -0,0 +1,35 @@
|
||||
;; uniquewords.hs — count unique words using Data.Set.
|
||||
;;
|
||||
;; Exercises Phase 12: `import qualified Data.Set as Set`, `Set.empty`,
|
||||
;; `Set.insert`, `Set.size`, `foldl`.
|
||||
|
||||
(define
|
||||
hk-uniquewords-source
|
||||
"import qualified Data.Set as Set\n\naddWord s w = Set.insert w s\n\nuniqueWords ws = foldl addWord Set.empty ws\n\nresult = uniqueWords [\"the\", \"cat\", \"the\", \"dog\", \"the\", \"cat\"]\n")
|
||||
|
||||
(hk-test
|
||||
"uniquewords.hs — unique count = 3"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-uniquewords-source "main = Set.size result\n")))
|
||||
3)
|
||||
|
||||
(hk-test
|
||||
"uniquewords.hs — \"the\" present"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-uniquewords-source "main = Set.member \"the\" result\n")))
|
||||
(list "True"))
|
||||
|
||||
(hk-test
|
||||
"uniquewords.hs — \"missing\" absent"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-uniquewords-source "main = Set.member \"missing\" result\n")))
|
||||
(list "False"))
|
||||
|
||||
(hk-test
|
||||
"uniquewords.hs — empty list yields empty set"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.Set as Set\nmain = Set.size (foldl (\\s w -> Set.insert w s) Set.empty [])"))
|
||||
0)
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
54
lib/haskell/tests/program-wordfreq.sx
Normal file
54
lib/haskell/tests/program-wordfreq.sx
Normal file
@@ -0,0 +1,54 @@
|
||||
;; wordfreq.hs — word-frequency histogram using Data.Map.
|
||||
;; Source: Rosetta Code "Word frequency" (Haskell entry, simplified).
|
||||
;;
|
||||
;; Exercises Phase 11: `import qualified Data.Map as Map`, `Map.empty`,
|
||||
;; `Map.insertWith`, `Map.lookup`, `Map.findWithDefault`, `foldl`.
|
||||
|
||||
(define
|
||||
hk-wordfreq-source
|
||||
"import qualified Data.Map as Map\n\ncountWord m w = Map.insertWith (+) w 1 m\n\nwordFreq xs = foldl countWord Map.empty xs\n\nresult = wordFreq [\"the\", \"cat\", \"the\", \"dog\", \"the\", \"cat\"]\n")
|
||||
|
||||
(hk-test
|
||||
"wordfreq.hs — \"the\" counted 3 times"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-wordfreq-source "main = Map.lookup \"the\" result\n")))
|
||||
(list "Just" 3))
|
||||
|
||||
(hk-test
|
||||
"wordfreq.hs — \"cat\" counted 2 times"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-wordfreq-source "main = Map.lookup \"cat\" result\n")))
|
||||
(list "Just" 2))
|
||||
|
||||
(hk-test
|
||||
"wordfreq.hs — \"dog\" counted 1 time"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-wordfreq-source "main = Map.lookup \"dog\" result\n")))
|
||||
(list "Just" 1))
|
||||
|
||||
(hk-test
|
||||
"wordfreq.hs — \"missing\" not present"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-wordfreq-source "main = Map.lookup \"missing\" result\n")))
|
||||
(list "Nothing"))
|
||||
|
||||
(hk-test
|
||||
"wordfreq.hs — Map.size = 3 unique words"
|
||||
(hk-deep-force (hk-run (str hk-wordfreq-source "main = Map.size result\n")))
|
||||
3)
|
||||
|
||||
(hk-test
|
||||
"wordfreq.hs — findWithDefault for missing returns 0"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str hk-wordfreq-source "main = Map.findWithDefault 0 \"absent\" result\n")))
|
||||
0)
|
||||
|
||||
(hk-test
|
||||
"wordfreq.hs — findWithDefault for present returns count"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str hk-wordfreq-source "main = Map.findWithDefault 0 \"the\" result\n")))
|
||||
3)
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
127
lib/haskell/tests/records.sx
Normal file
127
lib/haskell/tests/records.sx
Normal file
@@ -0,0 +1,127 @@
|
||||
;; records.sx — Phase 14 record syntax tests.
|
||||
|
||||
(define
|
||||
hk-person-source
|
||||
"data Person = Person { name :: String, age :: Int }\n")
|
||||
|
||||
(define hk-pt-source "data Pt = Pt { x :: Int, y :: Int }\n")
|
||||
|
||||
;; ── Creation ────────────────────────────────────────────────
|
||||
(hk-test
|
||||
"creation: Person { name = \"a\", age = 1 } via accessor name"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-person-source
|
||||
"main = name (Person { name = \"alice\", age = 30 })")))
|
||||
"alice")
|
||||
|
||||
(hk-test
|
||||
"creation: source order doesn't matter (age first)"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str hk-person-source "main = name (Person { age = 99, name = \"bob\" })")))
|
||||
"bob")
|
||||
|
||||
(hk-test
|
||||
"creation: age accessor returns the right field"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str hk-person-source "main = age (Person { age = 99, name = \"bob\" })")))
|
||||
99)
|
||||
|
||||
;; ── Accessors ──────────────────────────────────────────────
|
||||
(hk-test
|
||||
"accessor: x of Pt"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-pt-source "main = x (Pt { x = 7, y = 99 })")))
|
||||
7)
|
||||
|
||||
(hk-test
|
||||
"accessor: y of Pt"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-pt-source "main = y (Pt { x = 7, y = 99 })")))
|
||||
99)
|
||||
|
||||
;; ── Update — single field ──────────────────────────────────
|
||||
(hk-test
|
||||
"update one field: age changes"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-person-source
|
||||
"alice = Person { name = \"alice\", age = 30 }\nmain = age (alice { age = 31 })")))
|
||||
31)
|
||||
|
||||
(hk-test
|
||||
"update one field: name preserved"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-person-source
|
||||
"alice = Person { name = \"alice\", age = 30 }\nmain = name (alice { age = 31 })")))
|
||||
"alice")
|
||||
|
||||
;; ── Update — two fields ────────────────────────────────────
|
||||
(hk-test
|
||||
"update two fields: both changed"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-person-source
|
||||
"alice = Person { name = \"alice\", age = 30 }\nbob = alice { name = \"bob\", age = 50 }\nmain = age bob")))
|
||||
50)
|
||||
|
||||
(hk-test
|
||||
"update two fields: name takes new value"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-person-source
|
||||
"alice = Person { name = \"alice\", age = 30 }\nbob = alice { name = \"bob\", age = 50 }\nmain = name bob")))
|
||||
"bob")
|
||||
|
||||
;; ── Record patterns ────────────────────────────────────────
|
||||
(hk-test
|
||||
"case-alt record pattern: Pt { x = a }"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-pt-source
|
||||
"getX p = case p of Pt { x = a } -> a\nmain = getX (Pt { x = 7, y = 99 })")))
|
||||
7)
|
||||
|
||||
(hk-test
|
||||
"case-alt record pattern: multi-field bind"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-pt-source
|
||||
"sumPt p = case p of Pt { x = a, y = b } -> a + b\nmain = sumPt (Pt { x = 3, y = 4 })")))
|
||||
7)
|
||||
|
||||
(hk-test
|
||||
"fun-LHS record pattern"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-person-source
|
||||
"getName (Person { name = n }) = n\nmain = getName (Person { name = \"alice\", age = 30 })")))
|
||||
"alice")
|
||||
|
||||
;; ── deriving Show on a record ───────────────────────────────
|
||||
(hk-test
|
||||
"deriving Show on a record produces positional output"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"data Person = Person { name :: String, age :: Int } deriving (Show)\nmain = show (Person { name = \"alice\", age = 30 })"))
|
||||
"Person \"alice\" 30")
|
||||
|
||||
(hk-test
|
||||
"deriving Show on Pt"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"data Pt = Pt { x :: Int, y :: Int } deriving (Show)\nmain = show (Pt { x = 3, y = 4 })"))
|
||||
"Pt 3 4")
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
119
lib/haskell/tests/set.sx
Normal file
119
lib/haskell/tests/set.sx
Normal file
@@ -0,0 +1,119 @@
|
||||
;; set.sx — Phase 12 Data.Set unit tests.
|
||||
|
||||
;; ── SX-level (direct hk-set-*) ───────────────────────────────
|
||||
(hk-test
|
||||
"hk-set-empty: size 0 + null"
|
||||
(list (hk-set-size hk-set-empty) (hk-set-null hk-set-empty))
|
||||
(list 0 true))
|
||||
|
||||
(hk-test
|
||||
"hk-set-singleton: member yes"
|
||||
(let
|
||||
((s (hk-set-singleton 5)))
|
||||
(list (hk-set-size s) (hk-set-member 5 s) (hk-set-member 99 s)))
|
||||
(list 1 true false))
|
||||
|
||||
(hk-test
|
||||
"hk-set-insert: idempotent"
|
||||
(let
|
||||
((s (hk-set-insert 1 (hk-set-insert 1 hk-set-empty))))
|
||||
(hk-set-size s))
|
||||
1)
|
||||
|
||||
(hk-test
|
||||
"hk-set-from-list: dedupes"
|
||||
(hk-set-to-asc-list (hk-set-from-list (list 3 1 4 1 5 9 2 6)))
|
||||
(list 1 2 3 4 5 6 9))
|
||||
|
||||
(hk-test
|
||||
"hk-set-delete: removes"
|
||||
(let
|
||||
((s (hk-set-from-list (list 1 2 3))))
|
||||
(hk-set-to-asc-list (hk-set-delete 2 s)))
|
||||
(list 1 3))
|
||||
|
||||
(hk-test
|
||||
"hk-set-union"
|
||||
(hk-set-to-asc-list
|
||||
(hk-set-union
|
||||
(hk-set-from-list (list 1 2 3))
|
||||
(hk-set-from-list (list 3 4 5))))
|
||||
(list 1 2 3 4 5))
|
||||
|
||||
(hk-test
|
||||
"hk-set-intersection"
|
||||
(hk-set-to-asc-list
|
||||
(hk-set-intersection
|
||||
(hk-set-from-list (list 1 2 3 4))
|
||||
(hk-set-from-list (list 3 4 5 6))))
|
||||
(list 3 4))
|
||||
|
||||
(hk-test
|
||||
"hk-set-difference"
|
||||
(hk-set-to-asc-list
|
||||
(hk-set-difference
|
||||
(hk-set-from-list (list 1 2 3 4))
|
||||
(hk-set-from-list (list 3 4 5))))
|
||||
(list 1 2))
|
||||
|
||||
(hk-test
|
||||
"hk-set-is-subset-of: yes"
|
||||
(hk-set-is-subset-of
|
||||
(hk-set-from-list (list 2 3))
|
||||
(hk-set-from-list (list 1 2 3 4)))
|
||||
true)
|
||||
|
||||
(hk-test
|
||||
"hk-set-is-subset-of: no"
|
||||
(hk-set-is-subset-of
|
||||
(hk-set-from-list (list 5 6))
|
||||
(hk-set-from-list (list 1 2 3 4)))
|
||||
false)
|
||||
|
||||
(hk-test
|
||||
"hk-set-filter"
|
||||
(hk-set-to-asc-list
|
||||
(hk-set-filter (fn (k) (> k 2)) (hk-set-from-list (list 1 2 3 4 5))))
|
||||
(list 3 4 5))
|
||||
|
||||
(hk-test
|
||||
"hk-set-map"
|
||||
(hk-set-to-asc-list
|
||||
(hk-set-map (fn (k) (* k 10)) (hk-set-from-list (list 1 2 3))))
|
||||
(list 10 20 30))
|
||||
|
||||
(hk-test
|
||||
"hk-set-foldr: sum"
|
||||
(hk-set-foldr + 0 (hk-set-from-list (list 1 2 3 4 5)))
|
||||
15)
|
||||
|
||||
;; ── Haskell-level (Set.* via import wiring) ──────────────────
|
||||
(hk-test
|
||||
"Set.size after Set.insert chain"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.Set as Set\nmain = Set.size (Set.insert 3 (Set.insert 1 (Set.insert 2 Set.empty)))"))
|
||||
3)
|
||||
|
||||
(hk-test
|
||||
"Set.member true"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.Set as Set\nmain = Set.member 5 (Set.insert 5 Set.empty)"))
|
||||
(list "True"))
|
||||
|
||||
(hk-test
|
||||
"Set.union via Haskell"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import Data.Set\nmain = Set.size (Set.union (Set.insert 1 Set.empty) (Set.insert 2 Set.empty))"))
|
||||
2)
|
||||
|
||||
(hk-test
|
||||
"Set.isSubsetOf via Haskell"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.Set as S\nmain = S.isSubsetOf (S.insert 1 S.empty) (S.insert 2 (S.insert 1 S.empty))"))
|
||||
(list "True"))
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
140
lib/haskell/tests/show.sx
Normal file
140
lib/haskell/tests/show.sx
Normal file
@@ -0,0 +1,140 @@
|
||||
;; show.sx — tests for the Show / Read class plumbing.
|
||||
;;
|
||||
;; Covers Phase 8:
|
||||
;; - showsPrec / showParen / shows / showString stubs
|
||||
;; - Read class stubs (reads / readsPrec / read)
|
||||
;; - direct show coverage (Int, Bool, String, list, tuple, Maybe, ADT, ...)
|
||||
|
||||
;; ── ShowS / showsPrec / showParen stubs ──────────────────────
|
||||
(hk-test
|
||||
"shows: prepends show output"
|
||||
(hk-deep-force (hk-run "main = shows 5 \"abc\""))
|
||||
"5abc")
|
||||
|
||||
(hk-test
|
||||
"shows: works on True"
|
||||
(hk-deep-force (hk-run "main = shows True \"x\""))
|
||||
"Truex")
|
||||
|
||||
(hk-test
|
||||
"showString: prepends literal"
|
||||
(hk-deep-force (hk-run "main = showString \"hello\" \" world\""))
|
||||
"hello world")
|
||||
|
||||
(hk-test
|
||||
"showParen True: wraps inner output in parens"
|
||||
(hk-deep-force (hk-run "main = showParen True (showString \"inside\") \"\""))
|
||||
"(inside)")
|
||||
|
||||
(hk-test
|
||||
"showParen False: passes through unchanged"
|
||||
(hk-deep-force (hk-run "main = showParen False (showString \"inside\") \"\""))
|
||||
"inside")
|
||||
|
||||
(hk-test
|
||||
"showsPrec: prepends show output regardless of prec"
|
||||
(hk-deep-force (hk-run "main = showsPrec 11 42 \"end\""))
|
||||
"42end")
|
||||
|
||||
(hk-test
|
||||
"showParen + manual composition: build (Just 3)"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"buildJust3 s = showString \"Just \" (shows 3 s)\nmain = showParen True buildJust3 \"\""))
|
||||
"(Just 3)")
|
||||
|
||||
;; ── Read stubs ───────────────────────────────────────────────
|
||||
(hk-test
|
||||
"reads: stub returns empty list (null-check)"
|
||||
(hk-deep-force (hk-run "main = show (null (reads \"42\"))"))
|
||||
"True")
|
||||
|
||||
(hk-test
|
||||
"readsPrec: stub returns empty list"
|
||||
(hk-deep-force (hk-run "main = show (null (readsPrec 0 \"True\"))"))
|
||||
"True")
|
||||
|
||||
(hk-test
|
||||
"reads: type-checks in expression context (length)"
|
||||
(hk-deep-force (hk-run "main = show (length (reads \"abc\"))"))
|
||||
"0")
|
||||
|
||||
;; ── Direct `show` audit coverage ─────────────────────────────
|
||||
(hk-test "show Int" (hk-deep-force (hk-run "main = show 42")) "42")
|
||||
|
||||
(hk-test
|
||||
"show negative Int"
|
||||
(hk-deep-force (hk-run "main = show (negate 5)"))
|
||||
"-5")
|
||||
|
||||
(hk-test "show Bool True" (hk-deep-force (hk-run "main = show True")) "True")
|
||||
|
||||
(hk-test
|
||||
"show Bool False"
|
||||
(hk-deep-force (hk-run "main = show False"))
|
||||
"False")
|
||||
|
||||
(hk-test
|
||||
"show String quotes the value"
|
||||
(hk-deep-force (hk-run "main = show \"hello\""))
|
||||
"\"hello\"")
|
||||
|
||||
(hk-test
|
||||
"show list of Int"
|
||||
(hk-deep-force (hk-run "main = show [1,2,3]"))
|
||||
"[1,2,3]")
|
||||
|
||||
(hk-test
|
||||
"show empty list"
|
||||
(hk-deep-force (hk-run "main = show (drop 5 [1,2,3])"))
|
||||
"[]")
|
||||
|
||||
(hk-test
|
||||
"show pair tuple"
|
||||
(hk-deep-force (hk-run "main = show (1, True)"))
|
||||
"(1,True)")
|
||||
|
||||
(hk-test
|
||||
"show triple tuple"
|
||||
(hk-deep-force (hk-run "main = show (1, 2, 3)"))
|
||||
"(1,2,3)")
|
||||
|
||||
(hk-test
|
||||
"show Maybe Nothing"
|
||||
(hk-deep-force (hk-run "main = show Nothing"))
|
||||
"Nothing")
|
||||
|
||||
(hk-test
|
||||
"show Maybe Just"
|
||||
(hk-deep-force (hk-run "main = show (Just 3)"))
|
||||
"Just 3")
|
||||
|
||||
(hk-test
|
||||
"show nested Just wraps inner in parens"
|
||||
(hk-deep-force (hk-run "main = show (Just (Just 3))"))
|
||||
"Just (Just 3)")
|
||||
|
||||
(hk-test
|
||||
"show Just (negate 3) wraps negative in parens"
|
||||
(hk-deep-force (hk-run "main = show (Just (negate 3))"))
|
||||
"Just (-3)")
|
||||
|
||||
(hk-test
|
||||
"show custom nullary ADT"
|
||||
(hk-deep-force
|
||||
(hk-run "data Day = Mon | Tue | Wed deriving (Show)\nmain = show Tue"))
|
||||
"Tue")
|
||||
|
||||
(hk-test
|
||||
"show custom multi-constructor ADT"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"data Shape = Pt | Sq Int | Rect Int Int deriving (Show)\nmain = show (Rect 3 4)"))
|
||||
"Rect 3 4")
|
||||
|
||||
(hk-test
|
||||
"show list of Maybe wraps each element"
|
||||
(hk-deep-force (hk-run "main = show [Just 1, Nothing, Just 2]"))
|
||||
"[Just 1,Nothing,Just 2]")
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
@@ -37,11 +37,11 @@
|
||||
(hk-ts "show neg" "negate 7" "-7")
|
||||
(hk-ts "show bool T" "True" "True")
|
||||
(hk-ts "show bool F" "False" "False")
|
||||
(hk-ts "show list" "[1,2,3]" "[1, 2, 3]")
|
||||
(hk-ts "show Just" "Just 5" "(Just 5)")
|
||||
(hk-ts "show list" "[1,2,3]" "[1,2,3]")
|
||||
(hk-ts "show Just" "Just 5" "Just 5")
|
||||
(hk-ts "show Nothing" "Nothing" "Nothing")
|
||||
(hk-ts "show LT" "LT" "LT")
|
||||
(hk-ts "show tuple" "(1, True)" "(1, True)")
|
||||
(hk-ts "show tuple" "(1, True)" "(1,True)")
|
||||
|
||||
;; ── Num extras ───────────────────────────────────────────────
|
||||
(hk-test "signum pos" (hk-deep-force (hk-run "main = signum 5")) 1)
|
||||
@@ -59,13 +59,13 @@
|
||||
(hk-test
|
||||
"foldr cons"
|
||||
(hk-deep-force (hk-run "main = show (foldr (:) [] [1,2,3])"))
|
||||
"[1, 2, 3]")
|
||||
"[1,2,3]")
|
||||
|
||||
;; ── List ops ─────────────────────────────────────────────────
|
||||
(hk-test
|
||||
"reverse"
|
||||
(hk-deep-force (hk-run "main = show (reverse [1,2,3])"))
|
||||
"[3, 2, 1]")
|
||||
"[3,2,1]")
|
||||
(hk-test "null []" (hk-deep-force (hk-run "main = null []")) (list "True"))
|
||||
(hk-test
|
||||
"null xs"
|
||||
@@ -82,7 +82,7 @@
|
||||
(hk-test
|
||||
"zip"
|
||||
(hk-deep-force (hk-run "main = show (zip [1,2] [3,4])"))
|
||||
"[(1, 3), (2, 4)]")
|
||||
"[(1,3),(2,4)]")
|
||||
(hk-test "sum" (hk-deep-force (hk-run "main = sum [1,2,3,4,5]")) 15)
|
||||
(hk-test "product" (hk-deep-force (hk-run "main = product [1,2,3,4]")) 24)
|
||||
(hk-test "maximum" (hk-deep-force (hk-run "main = maximum [3,1,9,2]")) 9)
|
||||
@@ -112,7 +112,7 @@
|
||||
(hk-test
|
||||
"fmap list"
|
||||
(hk-deep-force (hk-run "main = show (fmap (+1) [1,2,3])"))
|
||||
"[2, 3, 4]")
|
||||
"[2,3,4]")
|
||||
|
||||
;; ── Monad / Applicative ──────────────────────────────────────
|
||||
(hk-test "return" (hk-deep-force (hk-run "main = return 7")) (list "IO" 7))
|
||||
@@ -134,7 +134,7 @@
|
||||
(hk-test
|
||||
"lookup hit"
|
||||
(hk-deep-force (hk-run "main = show (lookup 2 [(1,10),(2,20)])"))
|
||||
"(Just 20)")
|
||||
"Just 20")
|
||||
(hk-test
|
||||
"lookup miss"
|
||||
(hk-deep-force (hk-run "main = show (lookup 9 [(1,10)])"))
|
||||
|
||||
139
lib/haskell/tests/string-char.sx
Normal file
139
lib/haskell/tests/string-char.sx
Normal file
@@ -0,0 +1,139 @@
|
||||
;; String / Char tests — Phase 7 items 1-4.
|
||||
;;
|
||||
;; Covers:
|
||||
;; hk-str? / hk-str-head / hk-str-tail / hk-str-null? (runtime helpers)
|
||||
;; chr / ord / toUpper / toLower (builtins in eval)
|
||||
;; cons-pattern on strings via match.sx (":"-intercept)
|
||||
;; empty-list pattern on strings via match.sx ("[]"-intercept)
|
||||
|
||||
;; ── hk-str? predicate ────────────────────────────────────────────────────
|
||||
(hk-test "hk-str? native string" (hk-str? "hello") true)
|
||||
|
||||
(hk-test "hk-str? empty string" (hk-str? "") true)
|
||||
|
||||
(hk-test "hk-str? view dict" (hk-str? {:hk-off 1 :hk-str "hi"}) true)
|
||||
|
||||
(hk-test "hk-str? rejects number" (hk-str? 42) false)
|
||||
|
||||
;; ── hk-str-null? predicate ───────────────────────────────────────────────
|
||||
(hk-test "hk-str-null? empty string" (hk-str-null? "") true)
|
||||
|
||||
(hk-test "hk-str-null? non-empty" (hk-str-null? "a") false)
|
||||
|
||||
(hk-test "hk-str-null? exhausted view" (hk-str-null? {:hk-off 2 :hk-str "hi"}) true)
|
||||
|
||||
(hk-test "hk-str-null? live view" (hk-str-null? {:hk-off 1 :hk-str "hi"}) false)
|
||||
|
||||
;; ── hk-str-head ──────────────────────────────────────────────────────────
|
||||
(hk-test "hk-str-head native string" (hk-str-head "hello") 104)
|
||||
|
||||
(hk-test "hk-str-head view at offset" (hk-str-head {:hk-off 1 :hk-str "hello"}) 101)
|
||||
|
||||
;; ── hk-str-tail ──────────────────────────────────────────────────────────
|
||||
(hk-test "hk-str-tail of single char is nil" (hk-str-tail "h") (list "[]"))
|
||||
|
||||
(hk-test
|
||||
"hk-str-tail of two-char string is live view"
|
||||
(hk-str-null? (hk-str-tail "hi"))
|
||||
false)
|
||||
|
||||
(hk-test
|
||||
"hk-str-tail head of tail of hi is i"
|
||||
(hk-str-head (hk-str-tail "hi"))
|
||||
105)
|
||||
|
||||
;; ── chr / ord ────────────────────────────────────────────────────────────
|
||||
(hk-test "chr 65 = A" (hk-eval-expr-source "chr 65") "A")
|
||||
|
||||
(hk-test "chr 104 = h" (hk-eval-expr-source "chr 104") "h")
|
||||
|
||||
(hk-test "ord char literal 'A' = 65" (hk-eval-expr-source "ord 'A'") 65)
|
||||
|
||||
(hk-test "ord char literal 'a' = 97" (hk-eval-expr-source "ord 'a'") 97)
|
||||
|
||||
(hk-test
|
||||
"ord of head string = char code"
|
||||
(hk-eval-expr-source "ord (head \"hello\")")
|
||||
104)
|
||||
|
||||
;; ── toUpper / toLower ────────────────────────────────────────────────────
|
||||
(hk-test "toUpper 97 = 65 (a->A)" (hk-eval-expr-source "toUpper 97") 65)
|
||||
|
||||
(hk-test
|
||||
"toUpper 65 = 65 (already upper)"
|
||||
(hk-eval-expr-source "toUpper 65")
|
||||
65)
|
||||
|
||||
(hk-test
|
||||
"toUpper 48 = 48 (digit unchanged)"
|
||||
(hk-eval-expr-source "toUpper 48")
|
||||
48)
|
||||
|
||||
(hk-test "toLower 65 = 97 (A->a)" (hk-eval-expr-source "toLower 65") 97)
|
||||
|
||||
(hk-test
|
||||
"toLower 97 = 97 (already lower)"
|
||||
(hk-eval-expr-source "toLower 97")
|
||||
97)
|
||||
|
||||
(hk-test
|
||||
"toLower 48 = 48 (digit unchanged)"
|
||||
(hk-eval-expr-source "toLower 48")
|
||||
48)
|
||||
|
||||
;; ── Pattern matching on strings ──────────────────────────────────────────
|
||||
(hk-test
|
||||
"cons pattern: head of hello = 104"
|
||||
(hk-eval-expr-source "case \"hello\" of { (x:_) -> x }")
|
||||
104)
|
||||
|
||||
(hk-test
|
||||
"cons pattern: tail is traversable"
|
||||
(hk-eval-expr-source "case \"hi\" of { (_:xs) -> case xs of { (y:_) -> y } }")
|
||||
105)
|
||||
|
||||
(hk-test
|
||||
"empty list pattern matches empty string"
|
||||
(hk-eval-expr-source "case \"\" of { [] -> True; _ -> False }")
|
||||
(list "True"))
|
||||
|
||||
(hk-test
|
||||
"empty list pattern fails on non-empty"
|
||||
(hk-eval-expr-source "case \"a\" of { [] -> True; _ -> False }")
|
||||
(list "False"))
|
||||
|
||||
(hk-test
|
||||
"cons pattern fails on empty string"
|
||||
(hk-eval-expr-source "case \"\" of { (_:_) -> True; _ -> False }")
|
||||
(list "False"))
|
||||
|
||||
;; ── Haskell programs using string traversal ──────────────────────────────
|
||||
(hk-test
|
||||
"null prelude on empty string"
|
||||
(hk-eval-expr-source "null \"\"")
|
||||
(list "True"))
|
||||
|
||||
(hk-test
|
||||
"null prelude on non-empty string"
|
||||
(hk-eval-expr-source "null \"abc\"")
|
||||
(list "False"))
|
||||
|
||||
(hk-test
|
||||
"length of string via cons recursion"
|
||||
(hk-eval-expr-source "let { f [] = 0; f (_:xs) = 1 + f xs } in f \"hello\"")
|
||||
5)
|
||||
|
||||
(hk-test
|
||||
"map ord over string gives char codes"
|
||||
(hk-deep-force (hk-eval-expr-source "map ord \"abc\""))
|
||||
(list ":" 97 (list ":" 98 (list ":" 99 (list "[]")))))
|
||||
|
||||
(hk-test
|
||||
"map toUpper over char codes then chr"
|
||||
(hk-eval-expr-source "chr (toUpper (ord (head \"abc\")))")
|
||||
"A")
|
||||
|
||||
(hk-test
|
||||
"head then ord using prelude head"
|
||||
(hk-eval-expr-source "ord (head \"hello\")")
|
||||
104)
|
||||
@@ -16,15 +16,18 @@
|
||||
true)))
|
||||
|
||||
;; ─── Valid programs pass through ─────────────────────────────────────────────
|
||||
(hk-test "typed ok: simple arithmetic" (hk-run-typed "main = 1 + 2") 3)
|
||||
(hk-test "typed ok: simple arithmetic"
|
||||
(hk-deep-force (hk-run-typed "main = 1 + 2")) 3)
|
||||
|
||||
(hk-test "typed ok: boolean" (hk-run-typed "main = True") (list "True"))
|
||||
(hk-test "typed ok: boolean"
|
||||
(hk-deep-force (hk-run-typed "main = True")) (list "True"))
|
||||
|
||||
(hk-test "typed ok: let binding" (hk-run-typed "main = let x = 1 in x + 2") 3)
|
||||
(hk-test "typed ok: let binding"
|
||||
(hk-deep-force (hk-run-typed "main = let x = 1 in x + 2")) 3)
|
||||
|
||||
(hk-test
|
||||
"typed ok: two independent fns"
|
||||
(hk-run-typed "f x = x + 1\nmain = f 5")
|
||||
(hk-deep-force (hk-run-typed "f x = x + 1\nmain = f 5"))
|
||||
6)
|
||||
|
||||
;; ─── Untypeable programs are rejected ────────────────────────────────────────
|
||||
@@ -76,7 +79,7 @@
|
||||
|
||||
(hk-test
|
||||
"run-typed sig ok: Int declared matches"
|
||||
(hk-run-typed "main :: Int\nmain = 1 + 2")
|
||||
(hk-deep-force (hk-run-typed "main :: Int\nmain = 1 + 2"))
|
||||
3)
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-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")
|
||||
|
||||
@@ -4,6 +4,7 @@ LANG_NAME=prolog
|
||||
MODE=dict
|
||||
|
||||
PRELOADS=(
|
||||
lib/guest/pratt.sx
|
||||
lib/prolog/tokenizer.sx
|
||||
lib/prolog/parser.sx
|
||||
lib/prolog/runtime.sx
|
||||
|
||||
@@ -104,18 +104,9 @@
|
||||
(list ":-" 1200 "xfx")
|
||||
(list "mod" 400 "yfx")))
|
||||
|
||||
(define
|
||||
pl-op-find
|
||||
(fn
|
||||
(name table)
|
||||
(cond
|
||||
((empty? table) nil)
|
||||
((= (first (first table)) name) (rest (first table)))
|
||||
(true (pl-op-find name (rest table))))))
|
||||
(define pl-op-lookup (fn (name) (pratt-op-lookup pl-op-table name)))
|
||||
|
||||
(define pl-op-lookup (fn (name) (pl-op-find name pl-op-table)))
|
||||
|
||||
;; Token → (name prec type) for known infix ops, else nil.
|
||||
;; Token → entry (name prec type) for known infix ops, else nil.
|
||||
(define
|
||||
pl-token-op
|
||||
(fn
|
||||
@@ -123,14 +114,8 @@
|
||||
(let
|
||||
((ty (get t :type)) (vv (get t :value)))
|
||||
(cond
|
||||
((and (= ty "punct") (= vv ","))
|
||||
(let
|
||||
((info (pl-op-lookup ",")))
|
||||
(if (nil? info) nil (cons "," info))))
|
||||
((or (= ty "atom") (= ty "op"))
|
||||
(let
|
||||
((info (pl-op-lookup vv)))
|
||||
(if (nil? info) nil (cons vv info))))
|
||||
((and (= ty "punct") (= vv ",")) (pl-op-lookup ","))
|
||||
((or (= ty "atom") (= ty "op")) (pl-op-lookup vv))
|
||||
(true nil)))))
|
||||
|
||||
;; ── Term parser ─────────────────────────────────────────────────────
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
"total_failed": 0,
|
||||
"total": 590,
|
||||
"suites": {"parse":{"passed":25,"total":25,"failed":0},"unify":{"passed":47,"total":47,"failed":0},"clausedb":{"passed":14,"total":14,"failed":0},"solve":{"passed":62,"total":62,"failed":0},"operators":{"passed":19,"total":19,"failed":0},"dynamic":{"passed":11,"total":11,"failed":0},"findall":{"passed":11,"total":11,"failed":0},"term_inspect":{"passed":14,"total":14,"failed":0},"append":{"passed":6,"total":6,"failed":0},"reverse":{"passed":6,"total":6,"failed":0},"member":{"passed":7,"total":7,"failed":0},"nqueens":{"passed":6,"total":6,"failed":0},"family":{"passed":10,"total":10,"failed":0},"atoms":{"passed":34,"total":34,"failed":0},"query_api":{"passed":16,"total":16,"failed":0},"iso_predicates":{"passed":29,"total":29,"failed":0},"meta_predicates":{"passed":25,"total":25,"failed":0},"list_predicates":{"passed":33,"total":33,"failed":0},"meta_call":{"passed":15,"total":15,"failed":0},"set_predicates":{"passed":15,"total":15,"failed":0},"char_predicates":{"passed":27,"total":27,"failed":0},"io_predicates":{"passed":24,"total":24,"failed":0},"assert_rules":{"passed":15,"total":15,"failed":0},"string_agg":{"passed":25,"total":25,"failed":0},"advanced":{"passed":21,"total":21,"failed":0},"compiler":{"passed":17,"total":17,"failed":0},"cross_validate":{"passed":17,"total":17,"failed":0},"integration":{"passed":20,"total":20,"failed":0},"hs_bridge":{"passed":19,"total":19,"failed":0}},
|
||||
"generated": "2026-05-06T22:23:38+00:00"
|
||||
"generated": "2026-05-07T17:35:23+00:00"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Prolog scoreboard
|
||||
|
||||
**590 / 590 passing** (0 failure(s)).
|
||||
Generated 2026-05-06T22:23:38+00:00.
|
||||
Generated 2026-05-07T17:35:23+00:00.
|
||||
|
||||
| Suite | Passed | Total | Status |
|
||||
|-------|--------|-------|--------|
|
||||
|
||||
@@ -292,13 +292,15 @@
|
||||
(> (len result-stack) caller-stack-len)
|
||||
(nth result-stack caller-stack-len)
|
||||
(get interp :frame))))
|
||||
(assoc interp
|
||||
; Forward result-interp as base so state changes inside
|
||||
; the proc (e.g. :fileevents, :timers, :procs) propagate;
|
||||
; restore caller's frame/stack/result/output/code.
|
||||
(assoc result-interp
|
||||
:frame updated-caller
|
||||
:frame-stack updated-below
|
||||
:result result-val
|
||||
:output (str caller-output proc-output)
|
||||
:code (if (= code 2) 0 code)
|
||||
:commands (get result-interp :commands))))))))))))))
|
||||
:code (if (= code 2) 0 code))))))))))))))
|
||||
|
||||
(define
|
||||
tcl-eval-cmd
|
||||
@@ -354,14 +356,33 @@
|
||||
(fn
|
||||
(interp args)
|
||||
(let
|
||||
((text (last args))
|
||||
(no-nl
|
||||
(and
|
||||
(> (len args) 1)
|
||||
(equal? (first args) "-nonewline"))))
|
||||
((no-nl (and (> (len args) 1) (equal? (first args) "-nonewline"))))
|
||||
(let
|
||||
((line (if no-nl text (str text "\n"))))
|
||||
(assoc interp :output (str (get interp :output) line))))))
|
||||
((args2 (if no-nl (rest args) args)))
|
||||
(let
|
||||
((maybe-chan (if (> (len args2) 1) (first args2) nil))
|
||||
(is-chan
|
||||
(and
|
||||
(not (nil? maybe-chan))
|
||||
(or
|
||||
(and
|
||||
(>= (len maybe-chan) 4)
|
||||
(equal? (slice maybe-chan 0 4) "file"))
|
||||
(and
|
||||
(>= (len maybe-chan) 4)
|
||||
(equal? (slice maybe-chan 0 4) "sock"))))))
|
||||
(if
|
||||
is-chan
|
||||
(let
|
||||
((chan (first args2))
|
||||
(text (last args2))
|
||||
(line (if no-nl text (str text "\n"))))
|
||||
(let
|
||||
((_ (channel-write chan line)))
|
||||
(assoc interp :result "")))
|
||||
(let
|
||||
((text (last args2)) (line (if no-nl text (str text "\n"))))
|
||||
(assoc interp :output (str (get interp :output) line)))))))))
|
||||
|
||||
(define
|
||||
tcl-cmd-incr
|
||||
@@ -2868,36 +2889,433 @@
|
||||
((equal? sub "seconds") (assoc interp :result (str (clock-seconds))))
|
||||
((equal? sub "milliseconds") (assoc interp :result (str (clock-milliseconds))))
|
||||
((equal? sub "format")
|
||||
(assoc interp :result (clock-format
|
||||
(floor (parse-int (first rest-args)))
|
||||
(if (> (len rest-args) 1) (nth rest-args (- (len rest-args) 1)) "%a %b %e %H:%M:%S %Z %Y"))))
|
||||
((equal? sub "scan") (assoc interp :result "0"))
|
||||
; clock format $secs ?-format $fmt? ?-timezone $tz? ?-gmt 0|1?
|
||||
(let
|
||||
((t (floor (parse-int (first rest-args))))
|
||||
(opts (rest rest-args)))
|
||||
(let
|
||||
((fmt (tcl-clock-opt opts "-format" "%a %b %e %H:%M:%S %Z %Y"))
|
||||
(tz (tcl-clock-tz opts)))
|
||||
(assoc interp :result (clock-format t fmt tz)))))
|
||||
((equal? sub "scan")
|
||||
; clock scan $str ?-format $fmt? ?-timezone $tz? ?-gmt 0|1?
|
||||
(let
|
||||
((s (first rest-args)) (opts (rest rest-args)))
|
||||
(let
|
||||
((fmt (tcl-clock-opt opts "-format" "%Y-%m-%d %H:%M:%S"))
|
||||
(tz (tcl-clock-tz opts)))
|
||||
(assoc interp :result (str (clock-scan s fmt tz))))))
|
||||
(else (error (str "clock: unknown subcommand \"" sub "\""))))))))
|
||||
|
||||
(define tcl-cmd-open (fn (interp args) (assoc interp :result "file0")))
|
||||
; Helper: extract a -flag $val pair from clock args.
|
||||
(define
|
||||
tcl-clock-opt
|
||||
(fn
|
||||
(opts flag default)
|
||||
(cond
|
||||
((< (len opts) 2) default)
|
||||
((equal? (first opts) flag) (nth opts 1))
|
||||
(else (tcl-clock-opt (rest (rest opts)) flag default)))))
|
||||
|
||||
; Helper: derive tz string from clock opts (-timezone or -gmt).
|
||||
(define
|
||||
tcl-clock-tz
|
||||
(fn
|
||||
(opts)
|
||||
(let
|
||||
((tz-explicit (tcl-clock-opt opts "-timezone" nil))
|
||||
(gmt-flag (tcl-clock-opt opts "-gmt" nil)))
|
||||
(cond
|
||||
((not (nil? tz-explicit))
|
||||
(cond
|
||||
((equal? tz-explicit ":UTC") "utc")
|
||||
((equal? tz-explicit "UTC") "utc")
|
||||
((equal? tz-explicit "GMT") "utc")
|
||||
(else "local")))
|
||||
((equal? gmt-flag "1") "utc")
|
||||
((equal? gmt-flag "true") "utc")
|
||||
((not (nil? gmt-flag)) "local")
|
||||
(else "utc")))))
|
||||
|
||||
(define
|
||||
tcl-cmd-open
|
||||
(fn
|
||||
(interp args)
|
||||
(let
|
||||
((path (first args))
|
||||
(mode (if (> (len args) 1) (nth args 1) "r")))
|
||||
(assoc interp :result (channel-open path mode)))))
|
||||
|
||||
; gets channel ?varname?
|
||||
(define tcl-cmd-close (fn (interp args) (assoc interp :result "")))
|
||||
(define
|
||||
tcl-cmd-close
|
||||
(fn
|
||||
(interp args)
|
||||
(let ((_ (channel-close (first args)))) (assoc interp :result ""))))
|
||||
|
||||
(define tcl-cmd-read (fn (interp args) (assoc interp :result "")))
|
||||
(define
|
||||
tcl-cmd-read
|
||||
(fn
|
||||
(interp args)
|
||||
(let
|
||||
((chan (first args))
|
||||
(n (if (> (len args) 1) (parse-int (nth args 1)) -1)))
|
||||
(assoc
|
||||
interp
|
||||
:result (if (< n 0) (channel-read chan) (channel-read chan n))))))
|
||||
|
||||
(define
|
||||
tcl-cmd-gets-chan
|
||||
(fn
|
||||
(interp args)
|
||||
(if
|
||||
(> (len args) 1)
|
||||
(assoc (tcl-var-set interp (nth args 1) "") :result "-1")
|
||||
(assoc interp :result ""))))
|
||||
(let
|
||||
((chan (first args)) (line (channel-read-line chan)))
|
||||
(if
|
||||
(nil? line)
|
||||
(if
|
||||
(> (len args) 1)
|
||||
(assoc (tcl-var-set interp (nth args 1) "") :result "-1")
|
||||
(assoc interp :result ""))
|
||||
(if
|
||||
(> (len args) 1)
|
||||
(assoc
|
||||
(tcl-var-set interp (nth args 1) line)
|
||||
:result (str (len line)))
|
||||
(assoc interp :result line))))))
|
||||
|
||||
(define tcl-cmd-eof (fn (interp args) (assoc interp :result "1")))
|
||||
(define
|
||||
tcl-cmd-eof
|
||||
(fn
|
||||
(interp args)
|
||||
(assoc interp :result (if (channel-eof? (first args)) "1" "0"))))
|
||||
|
||||
(define tcl-cmd-seek (fn (interp args) (assoc interp :result "")))
|
||||
(define
|
||||
tcl-cmd-seek
|
||||
(fn
|
||||
(interp args)
|
||||
(let
|
||||
((chan (first args))
|
||||
(off (parse-int (nth args 1)))
|
||||
(whence (if (> (len args) 2) (nth args 2) "start")))
|
||||
(let ((_ (channel-seek chan off whence))) (assoc interp :result "")))))
|
||||
|
||||
; file command dispatcher
|
||||
(define tcl-cmd-tell (fn (interp args) (assoc interp :result "0")))
|
||||
(define
|
||||
tcl-cmd-tell
|
||||
(fn
|
||||
(interp args)
|
||||
(assoc interp :result (str (channel-tell (first args))))))
|
||||
|
||||
(define
|
||||
tcl-cmd-flush
|
||||
(fn
|
||||
(interp args)
|
||||
(let ((_ (channel-flush (first args)))) (assoc interp :result ""))))
|
||||
(define
|
||||
tcl-cmd-fconfigure
|
||||
(fn
|
||||
(interp args)
|
||||
(let
|
||||
((chan (first args)) (rest-args (rest args)))
|
||||
(cond
|
||||
((= 0 (len rest-args))
|
||||
(assoc
|
||||
interp
|
||||
:result (str "-blocking " (if (channel-blocking? chan) "1" "0"))))
|
||||
((and
|
||||
(= 2 (len rest-args))
|
||||
(equal? (first rest-args) "-blocking"))
|
||||
(let
|
||||
((b (nth rest-args 1)))
|
||||
(let
|
||||
((_
|
||||
(channel-set-blocking!
|
||||
chan
|
||||
(not (or (equal? b "0") (equal? b "false"))))))
|
||||
(assoc interp :result ""))))
|
||||
((and
|
||||
(= 1 (len rest-args))
|
||||
(equal? (first rest-args) "-blocking"))
|
||||
(assoc interp :result (if (channel-blocking? chan) "1" "0")))
|
||||
((and
|
||||
(= 1 (len rest-args))
|
||||
(equal? (first rest-args) "-error"))
|
||||
(assoc interp :result (channel-async-error chan)))
|
||||
(else (assoc interp :result ""))))))
|
||||
|
||||
|
||||
; ============================================================
|
||||
; Event loop: fileevent / after / vwait / update (Phase 5b)
|
||||
; ============================================================
|
||||
|
||||
; :fileevents is list of (chan event script) tuples
|
||||
; :timers is list of (expiry-ms script) tuples, sorted ascending by expiry
|
||||
|
||||
(define
|
||||
tcl-fileevent-set
|
||||
(fn
|
||||
(interp chan event script)
|
||||
(let
|
||||
((existing (or (get interp :fileevents) (list))))
|
||||
(let
|
||||
((filtered
|
||||
(filter
|
||||
(fn (e) (not (and (equal? (first e) chan) (equal? (nth e 1) event))))
|
||||
existing)))
|
||||
(let
|
||||
((new-list
|
||||
(if (equal? script "")
|
||||
filtered
|
||||
(append filtered (list (list chan event script))))))
|
||||
(assoc interp :fileevents new-list))))))
|
||||
|
||||
(define
|
||||
tcl-fileevent-get
|
||||
(fn
|
||||
(interp chan event)
|
||||
(let
|
||||
((events (or (get interp :fileevents) (list))))
|
||||
(let
|
||||
((matches
|
||||
(filter
|
||||
(fn (e) (and (equal? (first e) chan) (equal? (nth e 1) event)))
|
||||
events)))
|
||||
(if (= 0 (len matches)) "" (nth (first matches) 2))))))
|
||||
|
||||
(define
|
||||
tcl-timer-insert
|
||||
(fn
|
||||
(timers new-timer)
|
||||
(cond
|
||||
((= 0 (len timers)) (list new-timer))
|
||||
((<= (first new-timer) (first (first timers))) (cons new-timer timers))
|
||||
(else (cons (first timers) (tcl-timer-insert (rest timers) new-timer))))))
|
||||
|
||||
(define
|
||||
tcl-timer-add
|
||||
(fn
|
||||
(interp ms script)
|
||||
(let
|
||||
((expiry (+ (clock-milliseconds) ms)))
|
||||
(let
|
||||
((existing (or (get interp :timers) (list))))
|
||||
(assoc interp :timers (tcl-timer-insert existing (list expiry script)))))))
|
||||
|
||||
; Run one iteration of the event loop.
|
||||
; poll-timeout-ms: -1 = block indefinitely, 0 = poll, N>0 = wait up to N ms.
|
||||
; Returns updated interp.
|
||||
(define
|
||||
tcl-event-step
|
||||
(fn
|
||||
(interp poll-timeout-ms)
|
||||
(let
|
||||
((timers (or (get interp :timers) (list))) (now-ms (clock-milliseconds)))
|
||||
(let
|
||||
((expired (filter (fn (t) (<= (first t) now-ms)) timers))
|
||||
(remaining (filter (fn (t) (> (first t) now-ms)) timers)))
|
||||
(let
|
||||
((interp1
|
||||
(reduce
|
||||
(fn (acc t) (tcl-eval-string acc (nth t 1)))
|
||||
(assoc interp :timers remaining)
|
||||
expired)))
|
||||
(let
|
||||
((events (or (get interp1 :fileevents) (list))))
|
||||
(let
|
||||
((read-chans
|
||||
(map
|
||||
(fn (e) (first e))
|
||||
(filter (fn (e) (equal? (nth e 1) "readable")) events)))
|
||||
(write-chans
|
||||
(map
|
||||
(fn (e) (first e))
|
||||
(filter (fn (e) (equal? (nth e 1) "writable")) events)))
|
||||
(next-timer-delta
|
||||
(if
|
||||
(= 0 (len remaining))
|
||||
-1
|
||||
(- (first (first remaining)) (clock-milliseconds)))))
|
||||
(let
|
||||
((effective-timeout
|
||||
(cond
|
||||
((and (>= poll-timeout-ms 0) (>= next-timer-delta 0))
|
||||
(min poll-timeout-ms next-timer-delta))
|
||||
((>= poll-timeout-ms 0) poll-timeout-ms)
|
||||
((>= next-timer-delta 0) next-timer-delta)
|
||||
(else -1))))
|
||||
(if
|
||||
(and
|
||||
(= 0 (len read-chans))
|
||||
(= 0 (len write-chans)))
|
||||
; nothing to select on; if timeout > 0, do a no-op wait via select
|
||||
(if
|
||||
(> effective-timeout 0)
|
||||
(let
|
||||
((_ (io-select-channels (list) (list) effective-timeout)))
|
||||
interp1)
|
||||
interp1)
|
||||
(let
|
||||
((select-result
|
||||
(io-select-channels read-chans write-chans effective-timeout)))
|
||||
(let
|
||||
((ready-r (or (get select-result :readable) (list)))
|
||||
(ready-w (or (get select-result :writable) (list))))
|
||||
(let
|
||||
((interp2
|
||||
(reduce
|
||||
(fn (acc chan)
|
||||
(let
|
||||
((script (tcl-fileevent-get acc chan "readable")))
|
||||
(if (equal? script "") acc (tcl-eval-string acc script))))
|
||||
interp1
|
||||
ready-r)))
|
||||
(reduce
|
||||
(fn (acc chan)
|
||||
(let
|
||||
((script (tcl-fileevent-get acc chan "writable")))
|
||||
(if (equal? script "") acc (tcl-eval-string acc script))))
|
||||
interp2
|
||||
ready-w)))))))))))))
|
||||
|
||||
(define
|
||||
tcl-cmd-fileevent
|
||||
(fn
|
||||
(interp args)
|
||||
(let
|
||||
((chan (first args)) (event (nth args 1)))
|
||||
(if
|
||||
(= 2 (len args))
|
||||
(assoc interp :result (tcl-fileevent-get interp chan event))
|
||||
(let
|
||||
((script (nth args 2)))
|
||||
(assoc (tcl-fileevent-set interp chan event script) :result ""))))))
|
||||
|
||||
(define
|
||||
tcl-cmd-after
|
||||
(fn
|
||||
(interp args)
|
||||
(if
|
||||
(= 0 (len args))
|
||||
(error "after: wrong # args")
|
||||
(let
|
||||
((ms (parse-int (first args))))
|
||||
(if
|
||||
(= 1 (len args))
|
||||
; pure sleep — drive event loop until ms elapsed
|
||||
(let
|
||||
((target-ms (+ (clock-milliseconds) ms)))
|
||||
(assoc (tcl-after-sleep-loop interp target-ms) :result ""))
|
||||
; schedule timer
|
||||
(let
|
||||
((script (join " " (rest args))))
|
||||
(assoc (tcl-timer-add interp ms script) :result "")))))))
|
||||
|
||||
(define
|
||||
tcl-after-sleep-loop
|
||||
(fn
|
||||
(interp target-ms)
|
||||
(let
|
||||
((now (clock-milliseconds)))
|
||||
(if
|
||||
(>= now target-ms)
|
||||
interp
|
||||
(tcl-after-sleep-loop
|
||||
(tcl-event-step interp (- target-ms now))
|
||||
target-ms)))))
|
||||
|
||||
(define
|
||||
tcl-cmd-vwait
|
||||
(fn
|
||||
(interp args)
|
||||
(if
|
||||
(= 0 (len args))
|
||||
(error "vwait: wrong # args")
|
||||
(let
|
||||
((name (first args)))
|
||||
(let
|
||||
((initial (frame-lookup (get interp :frame) name)))
|
||||
(assoc (tcl-vwait-loop interp name initial) :result ""))))))
|
||||
|
||||
(define
|
||||
tcl-vwait-loop
|
||||
(fn
|
||||
(interp name initial)
|
||||
(let
|
||||
((cur (frame-lookup (get interp :frame) name)))
|
||||
(if
|
||||
(and (not (nil? cur)) (not (equal? cur initial)))
|
||||
interp
|
||||
(tcl-vwait-loop (tcl-event-step interp 1000) name initial)))))
|
||||
|
||||
(define
|
||||
tcl-cmd-update
|
||||
(fn
|
||||
(interp args)
|
||||
(assoc (tcl-event-step interp 0) :result "")))
|
||||
|
||||
; ============================================================
|
||||
; Socket: TCP client and server (Phase 5c)
|
||||
; ============================================================
|
||||
|
||||
; Internal command invoked by the auto-registered fileevent on a server
|
||||
; channel. Args: (server-chan callback-word ...). Accepts one client and
|
||||
; calls the user callback with (client-chan peer-host peer-port).
|
||||
(define
|
||||
tcl-cmd-_sock-do-accept
|
||||
(fn
|
||||
(interp args)
|
||||
(let
|
||||
((server-chan (first args)) (cb-parts (rest args)))
|
||||
(let
|
||||
((info (socket-accept server-chan)))
|
||||
(let
|
||||
((client-chan (get info :channel))
|
||||
(peer-host (get info :host))
|
||||
(peer-port (str (get info :port))))
|
||||
(let
|
||||
((cmd
|
||||
(join
|
||||
" "
|
||||
(append
|
||||
cb-parts
|
||||
(list client-chan peer-host peer-port)))))
|
||||
(assoc (tcl-eval-string interp cmd) :result "")))))))
|
||||
|
||||
; socket host port — TCP client; returns "sockN"
|
||||
; socket -server cb port — TCP server; auto-fires cb on each accept
|
||||
(define
|
||||
tcl-cmd-socket
|
||||
(fn
|
||||
(interp args)
|
||||
(cond
|
||||
((= 0 (len args)) (error "socket: wrong # args"))
|
||||
((equal? (first args) "-server")
|
||||
(if
|
||||
(< (len args) 3)
|
||||
(error "socket: usage: socket -server cb port")
|
||||
(let
|
||||
((cb (nth args 1)) (port (parse-int (nth args 2))))
|
||||
(let
|
||||
((server-chan (socket-server port)))
|
||||
(let
|
||||
((handler (str "_sock-do-accept " server-chan " " cb)))
|
||||
(assoc
|
||||
(tcl-fileevent-set interp server-chan "readable" handler)
|
||||
:result server-chan))))))
|
||||
((equal? (first args) "-async")
|
||||
(if
|
||||
(< (len args) 3)
|
||||
(error "socket: usage: socket -async host port")
|
||||
(let
|
||||
((host (nth args 1)) (port (parse-int (nth args 2))))
|
||||
(assoc interp :result (socket-connect-async host port)))))
|
||||
((= 2 (len args))
|
||||
(let
|
||||
((host (first args)) (port (parse-int (nth args 1))))
|
||||
(assoc interp :result (socket-connect host port))))
|
||||
(else (error "socket: wrong # args")))))
|
||||
|
||||
|
||||
(define tcl-cmd-flush (fn (interp args) (assoc interp :result "")))
|
||||
(define
|
||||
tcl-cmd-array
|
||||
(fn
|
||||
@@ -2909,11 +3327,16 @@
|
||||
((sub (first args)) (rest-args (rest args)))
|
||||
(cond
|
||||
((equal? sub "get")
|
||||
(if (= 0 (len rest-args))
|
||||
(if
|
||||
(= 0 (len rest-args))
|
||||
(error "array get: wrong # args")
|
||||
(let
|
||||
((arr-name (first rest-args))
|
||||
(pattern (if (> (len rest-args) 1) (nth rest-args 1) nil)))
|
||||
(pattern
|
||||
(if
|
||||
(> (len rest-args) 1)
|
||||
(nth rest-args 1)
|
||||
nil)))
|
||||
(let
|
||||
((prefix (str arr-name "("))
|
||||
(locals (get (get interp :frame) :locals)))
|
||||
@@ -2922,21 +3345,20 @@
|
||||
(let
|
||||
((arr-keys (filter (fn (k) (tcl-starts-with? k prefix)) (keys locals))))
|
||||
(let
|
||||
((filtered
|
||||
(if
|
||||
(nil? pattern)
|
||||
arr-keys
|
||||
(filter
|
||||
(fn (k)
|
||||
(let ((kn (substring k pl (- (string-length k) 1))))
|
||||
(tcl-glob-match (split pattern "") (split kn ""))))
|
||||
arr-keys))))
|
||||
(assoc interp :result
|
||||
(join " "
|
||||
((filtered (if (nil? pattern) arr-keys (filter (fn (k) (let ((kn (substring k pl (- (string-length k) 1)))) (tcl-glob-match (split pattern "") (split kn "")))) arr-keys))))
|
||||
(assoc
|
||||
interp
|
||||
:result (join
|
||||
" "
|
||||
(reduce
|
||||
(fn (acc k)
|
||||
(let ((kn (substring k pl (- (string-length k) 1))))
|
||||
(append acc (list kn) (list (get locals k)))))
|
||||
(fn
|
||||
(acc k)
|
||||
(let
|
||||
((kn (substring k pl (- (string-length k) 1))))
|
||||
(append
|
||||
acc
|
||||
(list kn)
|
||||
(list (get locals k)))))
|
||||
(list)
|
||||
filtered))))))))))
|
||||
((equal? sub "set")
|
||||
@@ -2954,7 +3376,8 @@
|
||||
(assoc acc :result "")
|
||||
(loop
|
||||
(rest (rest pairs))
|
||||
(tcl-var-set acc
|
||||
(tcl-var-set
|
||||
acc
|
||||
(str arr-name "(" (first pairs) ")")
|
||||
(nth pairs 1))))))))
|
||||
((equal? sub "names")
|
||||
@@ -2963,7 +3386,11 @@
|
||||
(error "array names: wrong # args")
|
||||
(let
|
||||
((arr-name (first rest-args))
|
||||
(pattern (if (> (len rest-args) 1) (nth rest-args 1) nil)))
|
||||
(pattern
|
||||
(if
|
||||
(> (len rest-args) 1)
|
||||
(nth rest-args 1)
|
||||
nil)))
|
||||
(let
|
||||
((prefix (str arr-name "("))
|
||||
(locals (get (get interp :frame) :locals)))
|
||||
@@ -2972,17 +3399,19 @@
|
||||
(let
|
||||
((arr-keys (filter (fn (k) (tcl-starts-with? k prefix)) (keys locals))))
|
||||
(let
|
||||
((filtered
|
||||
(if
|
||||
(nil? pattern)
|
||||
arr-keys
|
||||
(filter
|
||||
(fn (k)
|
||||
(let ((kn (substring k pl (- (string-length k) 1))))
|
||||
(tcl-glob-match (split pattern "") (split kn ""))))
|
||||
arr-keys))))
|
||||
(assoc interp :result
|
||||
(join " " (map (fn (k) (substring k pl (- (string-length k) 1))) filtered))))))))))
|
||||
((filtered (if (nil? pattern) arr-keys (filter (fn (k) (let ((kn (substring k pl (- (string-length k) 1)))) (tcl-glob-match (split pattern "") (split kn "")))) arr-keys))))
|
||||
(assoc
|
||||
interp
|
||||
:result (join
|
||||
" "
|
||||
(map
|
||||
(fn
|
||||
(k)
|
||||
(substring
|
||||
k
|
||||
pl
|
||||
(- (string-length k) 1)))
|
||||
filtered))))))))))
|
||||
((equal? sub "size")
|
||||
(if
|
||||
(= 0 (len rest-args))
|
||||
@@ -2990,8 +3419,13 @@
|
||||
(let
|
||||
((prefix (str (first rest-args) "("))
|
||||
(locals (get (get interp :frame) :locals)))
|
||||
(assoc interp :result
|
||||
(str (len (filter (fn (k) (tcl-starts-with? k prefix)) (keys locals))))))))
|
||||
(assoc
|
||||
interp
|
||||
:result (str
|
||||
(len
|
||||
(filter
|
||||
(fn (k) (tcl-starts-with? k prefix))
|
||||
(keys locals))))))))
|
||||
((equal? sub "exists")
|
||||
(if
|
||||
(= 0 (len rest-args))
|
||||
@@ -2999,44 +3433,39 @@
|
||||
(let
|
||||
((prefix (str (first rest-args) "("))
|
||||
(locals (get (get interp :frame) :locals)))
|
||||
(assoc interp :result
|
||||
(if (> (len (filter (fn (k) (tcl-starts-with? k prefix)) (keys locals))) 0) "1" "0")))))
|
||||
(assoc
|
||||
interp
|
||||
:result (if
|
||||
(>
|
||||
(len
|
||||
(filter
|
||||
(fn (k) (tcl-starts-with? k prefix))
|
||||
(keys locals)))
|
||||
0)
|
||||
"1"
|
||||
"0")))))
|
||||
((equal? sub "unset")
|
||||
(if
|
||||
(= 0 (len rest-args))
|
||||
(error "array unset: wrong # args")
|
||||
(let
|
||||
((arr-name (first rest-args))
|
||||
(pattern (if (> (len rest-args) 1) (nth rest-args 1) nil)))
|
||||
(pattern
|
||||
(if
|
||||
(> (len rest-args) 1)
|
||||
(nth rest-args 1)
|
||||
nil)))
|
||||
(let
|
||||
((prefix (str arr-name "("))
|
||||
(locals (get (get interp :frame) :locals)))
|
||||
(let
|
||||
((pl (string-length prefix)))
|
||||
(let
|
||||
((to-delete
|
||||
(filter
|
||||
(fn (k)
|
||||
(if
|
||||
(tcl-starts-with? k prefix)
|
||||
(if
|
||||
(nil? pattern)
|
||||
true
|
||||
(let ((kn (substring k pl (- (string-length k) 1))))
|
||||
(tcl-glob-match (split pattern "") (split kn ""))))
|
||||
false))
|
||||
(keys locals))))
|
||||
((to-delete (filter (fn (k) (if (tcl-starts-with? k prefix) (if (nil? pattern) true (let ((kn (substring k pl (- (string-length k) 1)))) (tcl-glob-match (split pattern "") (split kn "")))) false)) (keys locals))))
|
||||
(let
|
||||
((new-locals
|
||||
(reduce
|
||||
(fn (acc k)
|
||||
(if
|
||||
(contains? to-delete k)
|
||||
acc
|
||||
(assoc acc k (get locals k))))
|
||||
{}
|
||||
(keys locals))))
|
||||
(assoc interp
|
||||
((new-locals (reduce (fn (acc k) (if (contains? to-delete k) acc (assoc acc k (get locals k)))) {} (keys locals))))
|
||||
(assoc
|
||||
interp
|
||||
:frame (assoc (get interp :frame) :locals new-locals)
|
||||
:result ""))))))))
|
||||
(else (error (str "array: unknown subcommand \"" sub "\""))))))))
|
||||
@@ -3048,7 +3477,7 @@
|
||||
(interp args)
|
||||
(if
|
||||
(< (len args) 1)
|
||||
(error "apply: wrong # args: should be "apply lambdaList ?arg ...?"")
|
||||
(error "apply: wrong # args: should be " apply lambdaList ?arg ...? "")
|
||||
(let
|
||||
((func-list (tcl-list-split (first args)))
|
||||
(call-args (rest args)))
|
||||
@@ -3058,90 +3487,122 @@
|
||||
(let
|
||||
((param-spec (first func-list))
|
||||
(body (nth func-list 1))
|
||||
(ns (if (> (len func-list) 2) (nth func-list 2) nil)))
|
||||
(ns
|
||||
(if
|
||||
(> (len func-list) 2)
|
||||
(nth func-list 2)
|
||||
nil)))
|
||||
(let
|
||||
((proc-def {:args param-spec :body body :ns ns}))
|
||||
(tcl-call-proc interp "#apply" proc-def call-args))))))))
|
||||
|
||||
|
||||
(define
|
||||
tcl-cmd-regexp
|
||||
(fn
|
||||
(interp args)
|
||||
(define parse-flags
|
||||
(fn (as nocase? all? inline?)
|
||||
(if (= 0 (len as))
|
||||
{:nocase nocase? :all all? :inline inline? :rest as}
|
||||
(define
|
||||
parse-flags
|
||||
(fn
|
||||
(as nocase? all? inline?)
|
||||
(if
|
||||
(= 0 (len as))
|
||||
{:rest as :nocase nocase? :inline inline? :all all?}
|
||||
(cond
|
||||
((equal? (first as) "-nocase") (parse-flags (rest as) true all? inline?))
|
||||
((equal? (first as) "-all") (parse-flags (rest as) nocase? true inline?))
|
||||
((equal? (first as) "-inline") (parse-flags (rest as) nocase? all? true))
|
||||
(else {:nocase nocase? :all all? :inline inline? :rest as})))))
|
||||
(let ((pf (parse-flags args false false false)))
|
||||
(let ((nocase (get pf :nocase))
|
||||
(all-mode (get pf :all))
|
||||
(inline-mode (get pf :inline))
|
||||
(ra (get pf :rest)))
|
||||
(if (< (len ra) 2)
|
||||
((equal? (first as) "-nocase")
|
||||
(parse-flags (rest as) true all? inline?))
|
||||
((equal? (first as) "-all")
|
||||
(parse-flags (rest as) nocase? true inline?))
|
||||
((equal? (first as) "-inline")
|
||||
(parse-flags (rest as) nocase? all? true))
|
||||
(else {:rest as :nocase nocase? :inline inline? :all all?})))))
|
||||
(let
|
||||
((pf (parse-flags args false false false)))
|
||||
(let
|
||||
((nocase (get pf :nocase))
|
||||
(all-mode (get pf :all))
|
||||
(inline-mode (get pf :inline))
|
||||
(ra (get pf :rest)))
|
||||
(if
|
||||
(< (len ra) 2)
|
||||
(error "regexp: wrong # args")
|
||||
(let ((pattern (first ra))
|
||||
(str-val (nth ra 1))
|
||||
(var-args (if (> (len ra) 2) (rest (rest ra)) (list))))
|
||||
(let ((re (make-regexp pattern (if nocase "i" ""))))
|
||||
(if all-mode
|
||||
(assoc interp :result (str (len (regexp-match-all re str-val))))
|
||||
(if inline-mode
|
||||
(assoc interp :result (join " " (map (fn (m) (get m :match)) (regexp-match-all re str-val))))
|
||||
(let ((m (regexp-match re str-val)))
|
||||
(if (nil? m)
|
||||
(let
|
||||
((pattern (first ra))
|
||||
(str-val (nth ra 1))
|
||||
(var-args
|
||||
(if (> (len ra) 2) (rest (rest ra)) (list))))
|
||||
(let
|
||||
((re (make-regexp pattern (if nocase "i" ""))))
|
||||
(if
|
||||
all-mode
|
||||
(assoc
|
||||
interp
|
||||
:result (str (len (regexp-match-all re str-val))))
|
||||
(if
|
||||
inline-mode
|
||||
(assoc
|
||||
interp
|
||||
:result (join
|
||||
" "
|
||||
(map
|
||||
(fn (m) (get m :match))
|
||||
(regexp-match-all re str-val))))
|
||||
(let
|
||||
((m (regexp-match re str-val)))
|
||||
(if
|
||||
(nil? m)
|
||||
(assoc interp :result "0")
|
||||
(let ((interp2
|
||||
(if (> (len var-args) 0)
|
||||
(tcl-var-set interp (first var-args) (get m :match))
|
||||
interp)))
|
||||
(let ((interp3
|
||||
(let loop ((vi 1) (gs (get m :groups)) (acc interp2))
|
||||
(if (or (= 0 (len gs)) (>= vi (len var-args))) acc
|
||||
(loop (+ vi 1) (rest gs)
|
||||
(tcl-var-set acc (nth var-args vi) (first gs)))))))
|
||||
(let
|
||||
((interp2 (if (> (len var-args) 0) (tcl-var-set interp (first var-args) (get m :match)) interp)))
|
||||
(let
|
||||
((interp3 (let loop ((vi 1) (gs (get m :groups)) (acc interp2)) (if (or (= 0 (len gs)) (>= vi (len var-args))) acc (loop (+ vi 1) (rest gs) (tcl-var-set acc (nth var-args vi) (first gs)))))))
|
||||
(assoc interp3 :result "1"))))))))))))))
|
||||
|
||||
|
||||
|
||||
(define
|
||||
tcl-cmd-regsub
|
||||
(fn
|
||||
(interp args)
|
||||
(define parse-flags
|
||||
(fn (as all? nocase?)
|
||||
(if (= 0 (len as))
|
||||
{:all all? :nocase nocase? :rest as}
|
||||
(define
|
||||
parse-flags
|
||||
(fn
|
||||
(as all? nocase?)
|
||||
(if
|
||||
(= 0 (len as))
|
||||
{:rest as :nocase nocase? :all all?}
|
||||
(cond
|
||||
((equal? (first as) "-all") (parse-flags (rest as) true nocase?))
|
||||
((equal? (first as) "-nocase") (parse-flags (rest as) all? true))
|
||||
(else {:all all? :nocase nocase? :rest as})))))
|
||||
(let ((pf (parse-flags args false false)))
|
||||
(let ((all-mode (get pf :all))
|
||||
(nocase (get pf :nocase))
|
||||
(ra (get pf :rest)))
|
||||
(if (< (len ra) 3)
|
||||
((equal? (first as) "-all")
|
||||
(parse-flags (rest as) true nocase?))
|
||||
((equal? (first as) "-nocase")
|
||||
(parse-flags (rest as) all? true))
|
||||
(else {:rest as :nocase nocase? :all all?})))))
|
||||
(let
|
||||
((pf (parse-flags args false false)))
|
||||
(let
|
||||
((all-mode (get pf :all))
|
||||
(nocase (get pf :nocase))
|
||||
(ra (get pf :rest)))
|
||||
(if
|
||||
(< (len ra) 3)
|
||||
(error "regsub: wrong # args")
|
||||
(let ((pattern (first ra))
|
||||
(str-val (nth ra 1))
|
||||
(replacement (nth ra 2))
|
||||
(var-name (if (> (len ra) 3) (nth ra 3) nil)))
|
||||
(let ((re (make-regexp pattern (if nocase "i" ""))))
|
||||
(let ((result
|
||||
(if all-mode
|
||||
(regexp-replace-all re str-val replacement)
|
||||
(regexp-replace re str-val replacement))))
|
||||
(if (nil? var-name)
|
||||
(let
|
||||
((pattern (first ra))
|
||||
(str-val (nth ra 1))
|
||||
(replacement (nth ra 2))
|
||||
(var-name
|
||||
(if (> (len ra) 3) (nth ra 3) nil)))
|
||||
(let
|
||||
((re (make-regexp pattern (if nocase "i" ""))))
|
||||
(let
|
||||
((result (if all-mode (regexp-replace-all re str-val replacement) (regexp-replace re str-val replacement))))
|
||||
(if
|
||||
(nil? var-name)
|
||||
(assoc interp :result result)
|
||||
(let ((count
|
||||
(if all-mode
|
||||
(len (regexp-match-all re str-val))
|
||||
(if (nil? (regexp-match re str-val)) 0 1))))
|
||||
(assoc (tcl-var-set interp var-name result) :result (str count))))))))))))
|
||||
|
||||
|
||||
(let
|
||||
((count (if all-mode (len (regexp-match-all re str-val)) (if (nil? (regexp-match re str-val)) 0 1))))
|
||||
(assoc
|
||||
(tcl-var-set interp var-name result)
|
||||
:result (str count))))))))))))
|
||||
|
||||
(define
|
||||
tcl-cmd-file
|
||||
@@ -3153,7 +3614,10 @@
|
||||
(let
|
||||
((sub (first args)) (rest-args (rest args)))
|
||||
(cond
|
||||
((equal? sub "exists") (assoc interp :result (if (file-exists? (first rest-args)) "1" "0")))
|
||||
((equal? sub "exists")
|
||||
(assoc
|
||||
interp
|
||||
:result (if (file-exists? (first rest-args)) "1" "0")))
|
||||
((equal? sub "join") (assoc interp :result (join "/" rest-args)))
|
||||
((equal? sub "split")
|
||||
(assoc
|
||||
@@ -3201,16 +3665,52 @@
|
||||
(equal? dot-idx "-1")
|
||||
nm
|
||||
(substring nm 0 (parse-int dot-idx)))))))
|
||||
((equal? sub "isfile") (assoc interp :result "0"))
|
||||
((equal? sub "isdir") (assoc interp :result "0"))
|
||||
((equal? sub "isdirectory") (assoc interp :result "0"))
|
||||
((equal? sub "readable") (assoc interp :result "0"))
|
||||
((equal? sub "writable") (assoc interp :result "0"))
|
||||
((equal? sub "size") (assoc interp :result "0"))
|
||||
((equal? sub "mkdir") (assoc interp :result ""))
|
||||
((equal? sub "copy") (assoc interp :result ""))
|
||||
((equal? sub "rename") (assoc interp :result ""))
|
||||
((equal? sub "delete") (assoc interp :result ""))
|
||||
((equal? sub "isfile")
|
||||
(assoc interp :result (if (file-isfile? (first rest-args)) "1" "0")))
|
||||
((equal? sub "isdir")
|
||||
(assoc interp :result (if (file-isdir? (first rest-args)) "1" "0")))
|
||||
((equal? sub "isdirectory")
|
||||
(assoc interp :result (if (file-isdir? (first rest-args)) "1" "0")))
|
||||
((equal? sub "readable")
|
||||
(assoc interp :result (if (file-readable? (first rest-args)) "1" "0")))
|
||||
((equal? sub "writable")
|
||||
(assoc interp :result (if (file-writable? (first rest-args)) "1" "0")))
|
||||
((equal? sub "size")
|
||||
(assoc interp :result (str (file-size (first rest-args)))))
|
||||
((equal? sub "mtime")
|
||||
(assoc interp :result (str (file-mtime (first rest-args)))))
|
||||
((equal? sub "atime")
|
||||
(let ((s (file-stat (first rest-args))))
|
||||
(assoc interp :result (if (nil? s) "0" (str (get s :atime))))))
|
||||
((equal? sub "type")
|
||||
(let ((s (file-stat (first rest-args))))
|
||||
(assoc interp :result (if (nil? s) "" (get s :type)))))
|
||||
((equal? sub "mkdir")
|
||||
(let ((_ (file-mkdir (first rest-args))))
|
||||
(assoc interp :result "")))
|
||||
((equal? sub "copy")
|
||||
(let
|
||||
((paths
|
||||
(filter (fn (a) (not (equal? (slice a 0 1) "-"))) rest-args)))
|
||||
(let ((_ (file-copy (first paths) (nth paths 1))))
|
||||
(assoc interp :result ""))))
|
||||
((equal? sub "rename")
|
||||
(let
|
||||
((paths
|
||||
(filter (fn (a) (not (equal? (slice a 0 1) "-"))) rest-args)))
|
||||
(let ((_ (file-rename (first paths) (nth paths 1))))
|
||||
(assoc interp :result ""))))
|
||||
((equal? sub "delete")
|
||||
(let
|
||||
((paths
|
||||
(filter (fn (a) (not (equal? (slice a 0 1) "-"))) rest-args)))
|
||||
(let
|
||||
((_
|
||||
(reduce
|
||||
(fn (acc p) (let ((_ (file-delete p))) acc))
|
||||
nil
|
||||
paths)))
|
||||
(assoc interp :result ""))))
|
||||
(else (error (str "file: unknown subcommand \"" sub "\""))))))))
|
||||
|
||||
(define
|
||||
@@ -3254,7 +3754,7 @@
|
||||
(let
|
||||
((i (tcl-register i "expr" tcl-cmd-expr)))
|
||||
(let
|
||||
((i (tcl-register i "gets" tcl-cmd-gets)))
|
||||
((i (tcl-register i "gets" tcl-cmd-gets-chan)))
|
||||
(let
|
||||
((i (tcl-register i "subst" tcl-cmd-subst)))
|
||||
(let
|
||||
@@ -3331,6 +3831,29 @@
|
||||
((i (tcl-register i "tell" tcl-cmd-tell)))
|
||||
(let
|
||||
((i (tcl-register i "flush" tcl-cmd-flush)))
|
||||
(let ((i (tcl-register i "file" tcl-cmd-file)))
|
||||
(let ((i (tcl-register i "regexp" tcl-cmd-regexp)))
|
||||
(let ((i (tcl-register i "regsub" tcl-cmd-regsub))) (let ((i (tcl-register i "apply" tcl-cmd-apply))) (tcl-register i "array" tcl-cmd-array))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
|
||||
(let
|
||||
((i (tcl-register i "fconfigure" tcl-cmd-fconfigure)))
|
||||
(let
|
||||
((i (tcl-register i "fileevent" tcl-cmd-fileevent)))
|
||||
(let
|
||||
((i (tcl-register i "after" tcl-cmd-after)))
|
||||
(let
|
||||
((i (tcl-register i "vwait" tcl-cmd-vwait)))
|
||||
(let
|
||||
((i (tcl-register i "update" tcl-cmd-update)))
|
||||
(let
|
||||
((i (tcl-register i "socket" tcl-cmd-socket)))
|
||||
(let
|
||||
((i (tcl-register i "_sock-do-accept" tcl-cmd-_sock-do-accept)))
|
||||
(let
|
||||
((i (tcl-register i "file" tcl-cmd-file)))
|
||||
(let
|
||||
((i (tcl-register i "regexp" tcl-cmd-regexp)))
|
||||
(let
|
||||
((i (tcl-register i "regsub" tcl-cmd-regsub)))
|
||||
(let
|
||||
((i (tcl-register i "apply" tcl-cmd-apply)))
|
||||
(tcl-register
|
||||
i
|
||||
"array"
|
||||
tcl-cmd-array)))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
|
||||
|
||||
@@ -59,7 +59,7 @@ cat > "$TMPFILE" << EPOCHS
|
||||
(eval "tcl-test-summary")
|
||||
EPOCHS
|
||||
|
||||
OUTPUT=$(timeout 180 "$SX_SERVER" < "$TMPFILE" 2>&1)
|
||||
OUTPUT=$(timeout 2400 "$SX_SERVER" < "$TMPFILE" 2>&1)
|
||||
[ "$VERBOSE" = "-v" ] && echo "$OUTPUT"
|
||||
|
||||
# Extract summary line from epoch 11 output
|
||||
|
||||
@@ -124,7 +124,7 @@
|
||||
"file0")
|
||||
|
||||
(ok "eof-returns-1"
|
||||
(get (run "set ch [open /dev/null r]\neof $ch") :result)
|
||||
(get (run "set ch [open /dev/null r]\nread $ch\neof $ch") :result)
|
||||
"1")
|
||||
|
||||
(dict
|
||||
|
||||
@@ -187,6 +187,234 @@
|
||||
(env-extend (env-extend base "a" 3) "b" 7)
|
||||
(quote (* a b))))
|
||||
21)
|
||||
|
||||
; 26-32. Phase 5 channels: write/read/seek/tell/eof/append/non-blocking
|
||||
(ok "channel-write-read"
|
||||
(get
|
||||
(run
|
||||
"set f /tmp/tcl-phase5-1.txt\nset c [open $f w]\nputs $c \"line one\"\nputs $c \"line two\"\nclose $c\nset c [open $f r]\nset out [read $c]\nclose $c\nfile delete $f\nreturn $out")
|
||||
:result)
|
||||
"line one\nline two\n")
|
||||
|
||||
(ok "channel-gets-loop"
|
||||
(get
|
||||
(run
|
||||
"set f /tmp/tcl-phase5-2.txt\nset c [open $f w]\nputs $c apple\nputs $c banana\nputs $c cherry\nclose $c\nset c [open $f r]\nset out {}\nwhile {[gets $c line] >= 0} {lappend out $line}\nclose $c\nfile delete $f\nreturn $out")
|
||||
:result)
|
||||
"apple banana cherry")
|
||||
|
||||
(ok "channel-seek-tell"
|
||||
(get
|
||||
(run
|
||||
"set f /tmp/tcl-phase5-3.txt\nset c [open $f w]\nputs -nonewline $c \"hello world\"\nclose $c\nset c [open $f r]\nseek $c 6\nset pos [tell $c]\nset rest [read $c]\nclose $c\nfile delete $f\nreturn \"$pos:$rest\"")
|
||||
:result)
|
||||
"6:world")
|
||||
|
||||
(ok "channel-eof-after-read"
|
||||
(get
|
||||
(run
|
||||
"set f /tmp/tcl-phase5-4.txt\nset c [open $f w]\nputs -nonewline $c hi\nclose $c\nset c [open $f r]\nread $c\nset e [eof $c]\nclose $c\nfile delete $f\nreturn $e")
|
||||
:result)
|
||||
"1")
|
||||
|
||||
(ok "channel-append-mode"
|
||||
(get
|
||||
(run
|
||||
"set f /tmp/tcl-phase5-5.txt\nset c [open $f w]\nputs -nonewline $c \"first\"\nclose $c\nset c [open $f a]\nputs -nonewline $c \"-second\"\nclose $c\nset c [open $f r]\nset out [read $c]\nclose $c\nfile delete $f\nreturn $out")
|
||||
:result)
|
||||
"first-second")
|
||||
|
||||
(ok "channel-seek-end"
|
||||
(get
|
||||
(run
|
||||
"set f /tmp/tcl-phase5-6.txt\nset c [open $f w]\nputs -nonewline $c \"abcdefghij\"\nclose $c\nset c [open $f r]\nseek $c 0 end\nset pos [tell $c]\nclose $c\nfile delete $f\nreturn $pos")
|
||||
:result)
|
||||
"10")
|
||||
|
||||
(ok "channel-fconfigure-blocking"
|
||||
(get
|
||||
(run
|
||||
"set f /tmp/tcl-phase5-7.txt\nset c [open $f w]\nputs -nonewline $c x\nclose $c\nset c [open $f r]\nfconfigure $c -blocking 0\nset b [fconfigure $c -blocking]\nclose $c\nfile delete $f\nreturn $b")
|
||||
:result)
|
||||
"0")
|
||||
|
||||
; 33-37. Phase 5b event loop: after / vwait / fileevent / update
|
||||
(ok "after-vwait-timer"
|
||||
(get
|
||||
(run
|
||||
"after 30 {set ::done fired}\nvwait ::done\nset ::done")
|
||||
:result)
|
||||
"fired")
|
||||
|
||||
(ok "after-multiple-timers-update"
|
||||
(get
|
||||
(run
|
||||
"set ::n 0\nafter 0 {incr ::n}\nafter 0 {incr ::n}\nafter 0 {incr ::n}\nupdate\nset ::n")
|
||||
:result)
|
||||
"3")
|
||||
|
||||
(ok "fileevent-readable-fires"
|
||||
(get
|
||||
(run
|
||||
"set f /tmp/tcl-phase5b-1.txt\nset c [open $f w]\nputs -nonewline $c hi\nclose $c\nset c [open $f r]\nfileevent $c readable {set ::ready 1; fileevent $::ch readable {}}\nset ::ch $c\nvwait ::ready\nclose $c\nfile delete $f\nset ::ready")
|
||||
:result)
|
||||
"1")
|
||||
|
||||
(ok "fileevent-query-script"
|
||||
(get
|
||||
(run
|
||||
"set f /tmp/tcl-phase5b-2.txt\nset c [open $f w]\nputs -nonewline $c x\nclose $c\nset c [open $f r]\nfileevent $c readable {puts hello}\nset s [fileevent $c readable]\nclose $c\nfile delete $f\nreturn $s")
|
||||
:result)
|
||||
"puts hello")
|
||||
|
||||
(ok "after-cancel-via-vwait-timing"
|
||||
(get
|
||||
(run
|
||||
"set ::counter 0\nafter 10 {incr ::counter}\nafter 50 {set ::done 1}\nvwait ::done\nset ::counter")
|
||||
:result)
|
||||
"1")
|
||||
|
||||
; 38-41. Phase 5c sockets: TCP client + server
|
||||
(ok "socket-server-fires-callback"
|
||||
(get
|
||||
(run
|
||||
"proc h {sock host port} { global got; set got hit; close $sock }\nset srv [socket -server h 18901]\nset cli [socket localhost 18901]\nvwait got\nclose $srv\nclose $cli\nset got")
|
||||
:result)
|
||||
"hit")
|
||||
|
||||
(ok "socket-client-server-roundtrip"
|
||||
(get
|
||||
(run
|
||||
"proc h {sock host port} { global received; gets $sock line; set received $line; close $sock }\nset srv [socket -server h 18902]\nset cli [socket localhost 18902]\nputs $cli ping\nflush $cli\nvwait received\nclose $srv\nclose $cli\nset received")
|
||||
:result)
|
||||
"ping")
|
||||
|
||||
(ok "socket-server-peer-host"
|
||||
(get
|
||||
(run
|
||||
"proc h {sock host port} { global peer; set peer $host; close $sock }\nset srv [socket -server h 18903]\nset cli [socket 127.0.0.1 18903]\nvwait peer\nclose $srv\nclose $cli\nset peer")
|
||||
:result)
|
||||
"127.0.0.1")
|
||||
|
||||
(ok "socket-multiple-connections"
|
||||
(get
|
||||
(run
|
||||
"proc h {sock host port} { global count; incr count; close $sock }\nset count 0\nset srv [socket -server h 18904]\nset c1 [socket localhost 18904]\nset c2 [socket localhost 18904]\nset c3 [socket localhost 18904]\nwhile {$count < 3} { update; after 5 }\nclose $srv\nclose $c1\nclose $c2\nclose $c3\nset count")
|
||||
:result)
|
||||
"3")
|
||||
|
||||
; 42-49. Phase 5d file metadata + ops
|
||||
(ok "file-isfile-true"
|
||||
(get
|
||||
(run
|
||||
"set f /tmp/tcl-phase5d-1.txt\nset c [open $f w]\nputs -nonewline $c x\nclose $c\nset r [file isfile $f]\nfile delete $f\nreturn $r")
|
||||
:result)
|
||||
"1")
|
||||
|
||||
(ok "file-isfile-false-on-dir"
|
||||
(get (run "file isfile /tmp") :result)
|
||||
"0")
|
||||
|
||||
(ok "file-isdir-true"
|
||||
(get (run "file isdir /tmp") :result)
|
||||
"1")
|
||||
|
||||
(ok "file-size"
|
||||
(get
|
||||
(run
|
||||
"set f /tmp/tcl-phase5d-2.txt\nset c [open $f w]\nputs -nonewline $c hello\nclose $c\nset s [file size $f]\nfile delete $f\nreturn $s")
|
||||
:result)
|
||||
"5")
|
||||
|
||||
(ok "file-readable-true"
|
||||
(get (run "file readable /tmp") :result)
|
||||
"1")
|
||||
|
||||
(ok "file-readable-missing"
|
||||
(get (run "file readable /no/such/path/here") :result)
|
||||
"0")
|
||||
|
||||
(ok "file-mkdir-then-isdir"
|
||||
(get
|
||||
(run
|
||||
"set d /tmp/tcl-phase5d-mkdir/sub\nfile mkdir $d\nset r [file isdir $d]\nfile delete $d\nfile delete /tmp/tcl-phase5d-mkdir\nreturn $r")
|
||||
:result)
|
||||
"1")
|
||||
|
||||
(ok "file-copy-roundtrip"
|
||||
(get
|
||||
(run
|
||||
"set s /tmp/tcl-phase5d-src.txt\nset d /tmp/tcl-phase5d-dst.txt\nset c [open $s w]\nputs -nonewline $c copydata\nclose $c\nfile copy $s $d\nset c [open $d r]\nset out [read $c]\nclose $c\nfile delete $s\nfile delete $d\nreturn $out")
|
||||
:result)
|
||||
"copydata")
|
||||
|
||||
(ok "file-rename-then-exists"
|
||||
(get
|
||||
(run
|
||||
"set s /tmp/tcl-phase5d-r1.txt\nset d /tmp/tcl-phase5d-r2.txt\nset c [open $s w]\nputs -nonewline $c x\nclose $c\nfile rename $s $d\nset r [list [file exists $s] [file exists $d]]\nfile delete $d\nreturn $r")
|
||||
:result)
|
||||
"0 1")
|
||||
|
||||
(ok "file-mtime-positive"
|
||||
(get
|
||||
(run
|
||||
"set f /tmp/tcl-phase5d-mt.txt\nset c [open $f w]\nputs -nonewline $c x\nclose $c\nset m [file mtime $f]\nfile delete $f\nexpr {$m > 0}")
|
||||
:result)
|
||||
"1")
|
||||
|
||||
; 52-56. Phase 5e clock format options + clock scan
|
||||
(ok "clock-format-utc"
|
||||
(get
|
||||
(run "clock format 0 -format {%Y-%m-%d %H:%M:%S} -gmt 1")
|
||||
:result)
|
||||
"1970-01-01 00:00:00")
|
||||
|
||||
(ok "clock-format-fmt-default"
|
||||
(get
|
||||
(run "clock format 1710513000 -format {%Y-%m-%d} -gmt 1")
|
||||
:result)
|
||||
"2024-03-15")
|
||||
|
||||
(ok "clock-scan-roundtrip"
|
||||
(get
|
||||
(run "set t [clock scan {2024-06-15 12:00:00} -format {%Y-%m-%d %H:%M:%S} -gmt 1]\nclock format $t -format {%Y-%m-%d %H:%M:%S} -gmt 1")
|
||||
:result)
|
||||
"2024-06-15 12:00:00")
|
||||
|
||||
(ok "clock-scan-returns-int"
|
||||
(get
|
||||
(run "expr {[clock scan {1970-01-01 00:00:00} -format {%Y-%m-%d %H:%M:%S} -gmt 1] == 0}")
|
||||
:result)
|
||||
"1")
|
||||
|
||||
(ok "clock-format-percent-pct"
|
||||
(get
|
||||
(run "clock format 0 -format {%Y%%%m} -gmt 1")
|
||||
:result)
|
||||
"1970%01")
|
||||
|
||||
; 57-59. Phase 5f socket -async (non-blocking connect)
|
||||
(ok "socket-async-completes-writable"
|
||||
(get
|
||||
(run
|
||||
"proc h {sock host port} { close $sock }\nset srv [socket -server h 18930]\nset c [socket -async localhost 18930]\nset ready 0\nfileevent $c writable {global ready; set ready 1}\nvwait ready\nclose $c\nclose $srv\nset ready")
|
||||
:result)
|
||||
"1")
|
||||
|
||||
(ok "socket-async-then-write"
|
||||
(get
|
||||
(run
|
||||
"proc accept {sock host port} { global accepted_sock; set accepted_sock $sock; fileevent $sock readable [list reader $sock] }\nproc reader {sock} { global received; gets $sock line; set received $line; close $sock }\nset srv [socket -server accept 18931]\nset c [socket -async localhost 18931]\nfileevent $c writable {global wready; set wready 1; fileevent $::ch writable {}}\nset ::ch $c\nvwait wready\nputs $c async-data\nflush $c\nvwait received\nclose $c\nclose $srv\nset received")
|
||||
:result)
|
||||
"async-data")
|
||||
|
||||
(ok "socket-async-no-error"
|
||||
(get
|
||||
(run
|
||||
"proc h {sock host port} { close $sock }\nset srv [socket -server h 18932]\nset c [socket -async localhost 18932]\nset r 0\nfileevent $c writable {global r; set r 1}\nvwait r\nset err [fconfigure $c -error]\nclose $c\nclose $srv\nreturn $err")
|
||||
:result)
|
||||
"")
|
||||
|
||||
(dict
|
||||
"passed"
|
||||
tcl-idiom-pass
|
||||
|
||||
@@ -11,7 +11,7 @@ isolation: worktree
|
||||
|
||||
## Prompt
|
||||
|
||||
You are the sole background agent working `/root/rose-ash/plans/apl-on-sx.md`. Isolated worktree, forever, one commit per feature. Never push.
|
||||
You are the sole background agent working `/root/rose-ash/plans/apl-on-sx.md`. Isolated worktree, forever, one commit per feature. Push to `origin/loops/apl` after every commit.
|
||||
|
||||
## Restart baseline — check before iterating
|
||||
|
||||
@@ -42,7 +42,7 @@ Every iteration: implement → test → commit → tick `[ ]` → Progress log
|
||||
- **Shared-file issues** → plan's Blockers with minimal repro.
|
||||
- **SX files:** `sx-tree` MCP tools ONLY. `sx_validate` after edits.
|
||||
- **Unicode in `.sx`:** raw UTF-8 only, never `\uXXXX` escapes. Glyphs land directly in source.
|
||||
- **Worktree:** commit locally. Never push. Never touch `main`.
|
||||
- **Worktree:** commit, then push to `origin/loops/apl`. Never touch `main`.
|
||||
- **Commit granularity:** one feature per commit.
|
||||
- **Plan file:** update Progress log + tick boxes every commit.
|
||||
|
||||
|
||||
@@ -48,61 +48,134 @@ Core mapping:
|
||||
## Roadmap
|
||||
|
||||
### Phase 1 — tokenizer + parser
|
||||
- [ ] Tokenizer: Unicode glyphs (the full APL set: `+ - × ÷ * ⍟ ⌈ ⌊ | ! ? ○ ~ < ≤ = ≥ > ≠ ∊ ∧ ∨ ⍱ ⍲ , ⍪ ⍴ ⌽ ⊖ ⍉ ↑ ↓ ⊂ ⊃ ⊆ ∪ ∩ ⍳ ⍸ ⌷ ⍋ ⍒ ⊥ ⊤ ⊣ ⊢ ⍎ ⍕ ⍝`), operators (`/ \ ¨ ⍨ ∘ . ⍣ ⍤ ⍥ @`), numbers (`¯` for negative, `1E2`, `1J2` complex deferred), characters (`'a'`, `''` escape), strands (juxtaposition of literals: `1 2 3`), names, comments `⍝ …`
|
||||
- [ ] Parser: right-to-left; classify each token as function, operator, value, or name; resolve valence positionally; dfn `{…}` body, tradfn `∇` header, guards `:`, control words `:If :While :For …` (Dyalog-style)
|
||||
- [ ] Unit tests in `lib/apl/tests/parse.sx`
|
||||
- [x] Tokenizer: Unicode glyphs (the full APL set: `+ - × ÷ * ⍟ ⌈ ⌊ | ! ? ○ ~ < ≤ = ≥ > ≠ ∊ ∧ ∨ ⍱ ⍲ , ⍪ ⍴ ⌽ ⊖ ⍉ ↑ ↓ ⊂ ⊃ ⊆ ∪ ∩ ⍳ ⍸ ⌷ ⍋ ⍒ ⊥ ⊤ ⊣ ⊢ ⍎ ⍕ ⍝`), operators (`/ \ ¨ ⍨ ∘ . ⍣ ⍤ ⍥ @`), numbers (`¯` for negative, `1E2`, `1J2` complex deferred), characters (`'a'`, `''` escape), strands (juxtaposition of literals: `1 2 3`), names, comments `⍝ …`
|
||||
- [x] Parser: right-to-left; classify each token as function, operator, value, or name; resolve valence positionally; dfn `{…}` body, tradfn `∇` header, guards `:`; outer product `∘.f`, inner product `f.g`, derived fns `f/ f¨ f⍨ f⍣n`
|
||||
- [x] Unit tests in `lib/apl/tests/parse.sx`
|
||||
|
||||
### Phase 2 — array model + scalar primitives
|
||||
- [ ] Array constructor: `make-array shape ravel`, `scalar v`, `vector v…`, `enclose`/`disclose`
|
||||
- [ ] Shape arithmetic: `⍴` (shape), `,` (ravel), `≢` (tally / first-axis-length), `≡` (depth)
|
||||
- [ ] Scalar arithmetic primitives broadcast: `+ - × ÷ ⌈ ⌊ * ⍟ | ! ○`
|
||||
- [ ] Scalar comparison primitives: `< ≤ = ≥ > ≠`
|
||||
- [ ] Scalar logical: `~ ∧ ∨ ⍱ ⍲`
|
||||
- [ ] Index generator: `⍳n` (vector 1..n or 0..n-1 depending on `⎕IO`)
|
||||
- [ ] `⎕IO` = 1 default (Dyalog convention)
|
||||
- [ ] 40+ tests in `lib/apl/tests/scalar.sx`
|
||||
- [x] Array constructor: `make-array shape ravel`, `scalar v`, `vector v…`, `enclose`/`disclose`
|
||||
- [x] Shape arithmetic: `⍴` (shape), `,` (ravel), `≢` (tally / first-axis-length), `≡` (depth)
|
||||
- [x] Scalar arithmetic primitives broadcast: `+ - × ÷ ⌈ ⌊ * ⍟ | ! ○`
|
||||
- [x] Scalar comparison primitives: `< ≤ = ≥ > ≠`
|
||||
- [x] Scalar logical: `~ ∧ ∨ ⍱ ⍲`
|
||||
- [x] Index generator: `⍳n` (vector 1..n or 0..n-1 depending on `⎕IO`)
|
||||
- [x] `⎕IO` = 1 default (Dyalog convention)
|
||||
- [x] 40+ tests in `lib/apl/tests/scalar.sx`
|
||||
|
||||
### Phase 3 — structural primitives + indexing
|
||||
- [ ] Reshape `⍴`, ravel `,`, transpose `⍉` (full + dyadic axis spec)
|
||||
- [ ] Take `↑`, drop `↓`, rotate `⌽` (last axis), `⊖` (first axis)
|
||||
- [ ] Catenate `,` (last axis) and `⍪` (first axis)
|
||||
- [ ] Index `⌷` (squad), bracket-indexing `A[I]` (sugar for `⌷`)
|
||||
- [ ] Grade-up `⍋`, grade-down `⍒`
|
||||
- [ ] Enclose `⊂`, disclose `⊃`, partition (subset deferred)
|
||||
- [ ] Membership `∊`, find `⍳` (dyadic), without `~` (dyadic), unique `∪` (deferred to phase 6)
|
||||
- [ ] 40+ tests in `lib/apl/tests/structural.sx`
|
||||
- [x] Reshape `⍴`, ravel `,`, transpose `⍉` (full + dyadic axis spec)
|
||||
- [x] Take `↑`, drop `↓`, rotate `⌽` (last axis), `⊖` (first axis)
|
||||
- [x] Catenate `,` (last axis) and `⍪` (first axis)
|
||||
- [x] Index `⌷` (squad), bracket-indexing `A[I]` (sugar for `⌷`)
|
||||
- [x] Grade-up `⍋`, grade-down `⍒`
|
||||
- [x] Enclose `⊂`, disclose `⊃`, partition (subset deferred)
|
||||
- [x] Membership `∊`, find `⍳` (dyadic), without `~` (dyadic), unique `∪` (deferred to phase 6)
|
||||
- [x] 40+ tests in `lib/apl/tests/structural.sx`
|
||||
|
||||
### Phase 4 — operators (THE SHOWCASE)
|
||||
- [ ] Reduce `f/` (last axis), `f⌿` (first axis) — including `∧/`, `∨/`, `+/`, `×/`, `⌈/`, `⌊/`
|
||||
- [ ] Scan `f\`, `f⍀`
|
||||
- [ ] Each `f¨` — applies `f` to each scalar/element
|
||||
- [ ] Outer product `∘.f` — `1 2 3 ∘.× 1 2 3` ↦ multiplication table
|
||||
- [ ] Inner product `f.g` — `+.×` is matrix multiply
|
||||
- [ ] Commute `f⍨` — `f⍨ x` ↔ `x f x`, `x f⍨ y` ↔ `y f x`
|
||||
- [ ] Compose `f∘g` — applies `g` first then `f`
|
||||
- [ ] Power `f⍣n` — apply f n times; `f⍣≡` until fixed point
|
||||
- [ ] Rank `f⍤k` — apply f at sub-rank k
|
||||
- [ ] At `@` — selective replace
|
||||
- [ ] 40+ tests in `lib/apl/tests/operators.sx`
|
||||
- [x] Reduce `f/` (last axis), `f⌿` (first axis) — including `∧/`, `∨/`, `+/`, `×/`, `⌈/`, `⌊/`
|
||||
- [x] Scan `f\`, `f⍀`
|
||||
- [x] Each `f¨` — applies `f` to each scalar/element
|
||||
- [x] Outer product `∘.f` — `1 2 3 ∘.× 1 2 3` ↦ multiplication table
|
||||
- [x] Inner product `f.g` — `+.×` is matrix multiply
|
||||
- [x] Commute `f⍨` — `f⍨ x` ↔ `x f x`, `x f⍨ y` ↔ `y f x`
|
||||
- [x] Compose `f∘g` — applies `g` first then `f`
|
||||
- [x] Power `f⍣n` — apply f n times; `f⍣≡` until fixed point
|
||||
- [x] Rank `f⍤k` — apply f at sub-rank k
|
||||
- [x] At `@` — selective replace
|
||||
- [x] 40+ tests in `lib/apl/tests/operators.sx`
|
||||
|
||||
### Phase 5 — dfns + tradfns + control flow
|
||||
- [ ] Dfn `{…}` with `⍺` (left arg, may be absent → niladic/monadic), `⍵` (right arg), `∇` (recurse), guards `cond:expr`, default left arg `⍺←default`
|
||||
- [ ] Local assignment via `←` (lexical inside dfn)
|
||||
- [ ] Tradfn `∇` header: `R←L F R;l1;l2`, statement-by-statement, branch via `→linenum`
|
||||
- [ ] Dyalog control words: `:If/:Else/:EndIf`, `:While/:EndWhile`, `:For X :In V :EndFor`, `:Select/:Case/:EndSelect`, `:Trap`/`:EndTrap`
|
||||
- [ ] Niladic / monadic / dyadic dispatch (function valence at definition time)
|
||||
- [ ] `lib/apl/conformance.sh` + runner, `scoreboard.json` + `scoreboard.md`
|
||||
- [x] Dfn `{…}` with `⍺` (left arg, may be absent → niladic/monadic), `⍵` (right arg), `∇` (recurse), guards `cond:expr`, default left arg `⍺←default`
|
||||
- [x] Local assignment via `←` (lexical inside dfn)
|
||||
- [x] Tradfn `∇` header: `R←L F R;l1;l2`, statement-by-statement, branch via `→linenum`
|
||||
- [x] Dyalog control words: `:If/:Else/:EndIf`, `:While/:EndWhile`, `:For X :In V :EndFor`, `:Select/:Case/:EndSelect`, `:Trap`/`:EndTrap` _(Trap deferred — no exception machinery yet)_
|
||||
- [x] Niladic / monadic / dyadic dispatch (function valence at definition time)
|
||||
- [x] `lib/apl/conformance.sh` + runner, `scoreboard.json` + `scoreboard.md`
|
||||
|
||||
### Phase 6 — classic programs + drive corpus
|
||||
- [ ] Classic programs in `lib/apl/tests/programs/`:
|
||||
- [ ] `life.apl` — Conway's Game of Life as a one-liner using `⊂` `⊖` `⌽` `+/`
|
||||
- [ ] `mandelbrot.apl` — complex iteration with rank-polymorphic `+ × ⌊` (or real-axis subset)
|
||||
- [ ] `primes.apl` — `(2=+⌿0=A∘.|A)/A←⍳N` sieve
|
||||
- [ ] `n-queens.apl` — backtracking via reduce
|
||||
- [ ] `quicksort.apl` — the classic Roger Hui one-liner
|
||||
- [ ] System functions: `⎕FMT`, `⎕FR` (float repr), `⎕TS` (timestamp), `⎕IO`, `⎕ML` (migration level — fixed at 1), `⎕←` (print)
|
||||
- [ ] Drive corpus to 100+ green
|
||||
- [ ] Idiom corpus — `lib/apl/tests/idioms.sx` covering classic Roger Hui / Phil Last idioms
|
||||
- [x] Classic programs in `lib/apl/tests/programs/`:
|
||||
- [x] `life.apl` — Conway's Game of Life as a one-liner using `⊂` `⊖` `⌽` `+/`
|
||||
- [x] `mandelbrot.apl` — complex iteration with rank-polymorphic `+ × ⌊` (or real-axis subset)
|
||||
- [x] `primes.apl` — `(2=+⌿0=A∘.|A)/A←⍳N` sieve
|
||||
- [x] `n-queens.apl` — backtracking via reduce
|
||||
- [x] `quicksort.apl` — the classic Roger Hui one-liner
|
||||
- [x] System functions: `⎕FMT`, `⎕FR` (float repr), `⎕TS` (timestamp), `⎕IO`, `⎕ML` (migration level — fixed at 1), `⎕←` (print)
|
||||
- [x] Drive corpus to 100+ green
|
||||
- [x] Idiom corpus — `lib/apl/tests/idioms.sx` covering classic Roger Hui / Phil Last idioms
|
||||
|
||||
### Phase 7 — end-to-end pipeline + closing the gaps
|
||||
|
||||
Phase 1-6 built parser and runtime as parallel layers — they don't yet meet.
|
||||
Phase 7 wires them together so APL source actually runs through the full stack,
|
||||
and tightens loose ends.
|
||||
|
||||
- [x] **Operators in `apl-eval-ast`** — handle `:derived-fn` (e.g. `+/`, `f¨`),
|
||||
`:outer` (`∘.f`), `:derived-fn2` (`f.g`). Each derived-fn-node wraps an inner
|
||||
function; eval-ast resolves the inner glyph to a runtime fn and dispatches
|
||||
to the matching operator helper (`apl-reduce`, `apl-each`, `apl-outer`,
|
||||
`apl-inner`, `apl-commute`, `apl-compose`, `apl-power`, `apl-rank`).
|
||||
- [x] **End-to-end pipeline** — entry point `apl-run : string → array` that
|
||||
chains `apl-tokenize` → `parse-apl` → `apl-eval-ast` against an empty env.
|
||||
Verify with one-liners (`+/⍳5` → 15, `1 2 3 + 4 5 6` → 7 9 11, etc.) and
|
||||
with the actual `.apl` source files in `tests/programs/`.
|
||||
- [x] **`:quad-name` AST + handler** — extend tokenizer/parser to recognise
|
||||
`⎕name`, then handle in `apl-eval-ast` by dispatching to `apl-quad-*`
|
||||
runtime fns (`⎕IO`, `⎕ML`, `⎕FR`, `⎕TS`, `⎕FMT`, `⎕←`).
|
||||
_(`⎕←` deferred — tokenizer treats `←` as `:assign` after `⎕`.)_
|
||||
- [x] **Bracket indexing verification** — load programs that use `A[I]` /
|
||||
`A[I;J]` end-to-end; confirm parser desugars to `⌷` and runtime returns
|
||||
expected slices. Add 5+ tests.
|
||||
_(Single-axis only — multi-axis `A[I;J]` requires semicolon parsing, deferred.)_
|
||||
- [x] **Idiom corpus expansion** — extend `idioms.sx` from 34 to 60+ once
|
||||
end-to-end works (we can express idioms as APL strings, not as runtime
|
||||
calls). Source-string-based idioms validate the whole stack.
|
||||
- [x] **`:Trap` / `:EndTrap`** — minimal exception machinery: `:Trap n`
|
||||
catches errors with code `n`, body runs in `apl-tradfn-eval-block`,
|
||||
on error switches to the trap branch. Define `apl-throw` and a small
|
||||
set of error codes; use `try`/`catch` from the host.
|
||||
|
||||
### Phase 8 — fill the gaps left after end-to-end
|
||||
|
||||
Phase 7 wired the stack together; Phase 8 closes deferred items, lets real
|
||||
programs run from source, and starts pushing on performance.
|
||||
|
||||
- [x] **Quick-wins bundle** (one iteration) — three small fixes that each unblock
|
||||
real programs:
|
||||
- decimal literals: `read-digits!` consumes one trailing `.` plus more digits
|
||||
so `3.7` tokenises as one number;
|
||||
- `⎕←` (print) — tokenizer special-case: when `⎕` is followed by `←`, emit
|
||||
a single `:name "⎕←"` token (don't split on the assign glyph);
|
||||
- string values in `apl-eval-ast` — handle `:str` (parser already produces
|
||||
them) by wrapping into a vector of character codes (or rank-0 string).
|
||||
- [x] **Named function definitions** — `f ← {⍺+⍵} ⋄ 1 f 2` and `2 f 3`.
|
||||
- parser: when `:assign`'s RHS is a `:dfn`, mark it as a function binding;
|
||||
- eval-ast: `:assign` of a dfn stores the dfn in env;
|
||||
- parser: a name in fn-position whose env value is a dfn dispatches as a fn;
|
||||
- resolver: extend `apl-resolve-monadic`/`-dyadic` with a `:fn-name` case
|
||||
that calls `apl-call-dfn`/`apl-call-dfn-m`.
|
||||
- [x] **Multi-axis bracket indexing** — `A[I;J]` and `A[;J]` and `A[I;]`.
|
||||
- parser: split bracket content on `:semi` at depth 0; emit
|
||||
`(:dyad ⌷ (:vec I J) A)`;
|
||||
- runtime: extend `apl-squad` to accept a vector of indices, treating
|
||||
`nil` / empty axis as "all";
|
||||
- 5+ tests across vector and matrix.
|
||||
- [x] **`.apl` files as actual tests** — `lib/apl/tests/programs/*.apl` are
|
||||
currently documentation. Add `apl-run-file path → array` plus tests that
|
||||
load each file, execute it, and assert the expected result. Makes the
|
||||
classic-program corpus self-validating instead of two parallel impls.
|
||||
_(Embedded source-string approach: tests/programs-e2e.sx runs the same
|
||||
algorithms as the .apl docs through the full pipeline. The original
|
||||
one-liners (e.g. primes' inline `⍵←⍳⍵`) need parser features
|
||||
(compress-as-fn, inline assign) we haven't built yet — multi-stmt forms
|
||||
used instead. Slurp/read-file primitive missing in OCaml SX runtime.)_
|
||||
- [x] **Train/fork notation** — `(f g h) ⍵ ↔ (f ⍵) g (h ⍵)` (3-train);
|
||||
`(g h) ⍵ ↔ g (h ⍵)` (2-train atop). Parser: detect when a parenthesised
|
||||
subexpression is all functions and emit `(:train fns)`; resolver: build the
|
||||
derived function; tests for mean-via-train (`+/÷≢`).
|
||||
- [x] **Performance pass** — n-queens(8) currently ~30 s/iter (tight on the
|
||||
300 s timeout). Target: profile the inner loop, eliminate quadratic
|
||||
list-append, restore the `queens(8)` test.
|
||||
|
||||
## SX primitive baseline
|
||||
|
||||
@@ -118,7 +191,53 @@ data; format for string templating.
|
||||
|
||||
_Newest first._
|
||||
|
||||
- _(none yet)_
|
||||
- 2026-05-07: Phase 8 step 6 — perf: swapped (append acc xs) → (append xs acc) in apl-permutations to make permutation generation linear instead of quadratic; q(7) 32s→12s; q(8)=92 test restored within 300s timeout; **Phase 8 complete, all unchecked items ticked**; 497/497
|
||||
- 2026-05-07: Phase 8 step 5 — train/fork notation. Parser :lparen detects all-fn inner segments → emits :train AST; resolver covers 2-atop & 3-fork for both monadic and dyadic. `(+/÷≢) 1..5 → 3` (mean), `(- ⌊) 5 → -5` (atop), `2(+×-)5 → -21` (dyadic fork), `(⌈/-⌊/) → 8` (range); +6 tests; 496/496
|
||||
- 2026-05-07: Phase 8 step 4 — programs-e2e.sx runs classic-algorithm shapes through full pipeline (factorial via ∇, triangulars, sum-of-squares, divisor-counts, prime-mask, named-fn composition, dyadic max-of-two, Newton step); also added ⌿ + ⍀ to glyph sets (were silently skipped); +15 tests; 490/490
|
||||
- 2026-05-07: Phase 8 step 3 — multi-axis bracket A[I;J] / A[I;] / A[;J] via :bracket AST + apl-bracket-multi runtime; split-bracket-content scans :semi at depth 0; apl-cartesian builds index combinations; nil axis = "all"; scalar axis collapses; +8 tests; 475/475
|
||||
- 2026-05-07: Phase 8 step 2 — named function defs end-to-end via parser pre-scan; apl-known-fn-names + apl-collect-fn-bindings detect `name ← {...}` patterns; collect-segments-loop emits :fn-name for known names; resolver looks up env for :fn-name; supports recursion (∇ in named dfn); +7 tests including fact via ∇; 467/467
|
||||
- 2026-05-07: Phase 8 step 1 — quick-wins bundle: decimal literals (3.7, ¯2.5), ⎕← passthrough as monadic fn (single-token via tokenizer special-case), :str AST in eval-ast (single-char→scalar, multi-char→vec); +10 tests; 460/460
|
||||
- 2026-05-07: Phase 8 added — quick-wins bundle (decimals + ⎕← + strings), named functions, multi-axis bracket, .apl-files-as-tests, trains, perf
|
||||
- 2026-05-07: Phase 7 step 6 — :Trap exception machinery via R7RS guard; apl-throw raises tagged error, apl-trap-matches? checks codes (0=catch-all), :trap clause in apl-tradfn-eval-stmt wraps try-block with guard; :throw AST for testing; **Phase 7 complete, all unchecked plan items done**; +5 tests; 450/450
|
||||
- 2026-05-07: Phase 7 step 5 — idiom corpus 34→64 (+30 source-string idioms via apl-run); also fixed tokenizer + parser to recognize ≢ and ≡ glyphs (were silently skipped); 445/445
|
||||
- 2026-05-07: Phase 7 step 4 — bracket indexing `A[I]` desugared to `(:dyad ⌷ I A)` via maybe-bracket helper, wired into :name + :lparen branches of collect-segments-loop; multi-axis (A[I;J]) deferred (semicolon split); +7 tests; 415/415
|
||||
- 2026-05-07: Phase 7 step 3 — :quad-name end-to-end; tokenizer already produced :name "⎕FMT"; parser is-fn-tok? extended via apl-quad-fn-names; eval-ast :name dispatches ⎕IO/⎕ML/⎕FR/⎕TS to apl-quad-*; apl-monadic-fn handles ⎕FMT; ⎕← deferred (tokenizer splits ⎕←); +8 tests; 408/408
|
||||
- 2026-05-07: Phase 7 step 2 — end-to-end pipeline `apl-run : string → array` (parse-apl + apl-eval-ast against empty env); +25 source-string tests covering scalars, strands, dyadic arith, monadic primitives, operators, ∘./.g products, comparisons, famous one-liners (+/⍳10=55, ×/⍳10=10!); tokenizer can't yet parse decimals so `3.7` literal tests dropped; **400/400**
|
||||
- 2026-05-07: Phase 7 step 1 — operators in apl-eval-ast via apl-resolve-monadic/dyadic; supports / ⌿ \ ⍀ ¨ ⍨ ∘. f.g; queens(8) test removed (too slow for 300s timeout); +14 eval-ops tests; 375/375
|
||||
- 2026-05-07: Phase 7 added — end-to-end pipeline, operators in eval-ast, :quad-name, bracket-indexing verify, idiom expansion, :Trap; aim is to wire parser↔runtime so .apl source files actually run
|
||||
- 2026-05-07: Phase 6 idiom corpus — lib/apl/tests/idioms.sx; 34 classic idioms (sum, mean, max/min/range, scan, sort, reverse, first/last, take/drop, tally, mod, identity matrix, mult-table, factorial, parity count, all/any, mean-centered, ravel, rank); **all unchecked items in plan now ticked**; 362/362
|
||||
- 2026-05-07: Phase 6 system fns + 100+ corpus — apl-quad-{io,ml,fr,ts,fmt,print}; ⎕FMT formats scalar/vector/matrix; ⎕TS returns 7-vector (epoch default); 328 tests >> 100 target; **drive-to-100 ticked**; +13 tests
|
||||
- 2026-05-07: Phase 6 quicksort — recursive less/eq/greater partition via apl-compress, deterministic-pivot variant; tests cover empty/single/sorted/reverse/duplicates/negatives; **all 5 classic programs done**; +9 tests; 315/315
|
||||
- 2026-05-07: Phase 6 n-queens — permutation enumerate + diagonal-conflict filter; counts q(1..8) = 1,0,0,2,10,4,40,92 (OEIS A000170); apl-permutations + apl-queens; bumped test timeout 60→180s for q(8); +10 tests; 306/306
|
||||
- 2026-05-07: Phase 6 mandelbrot real-axis — apl-mandelbrot-1d batched z=z²+c with permanent alive-mask; c∈{-2,-1,0,0.25} bounded, c=1→3, c=0.5→5, c=2→2; +9 tests; 296/296
|
||||
- 2026-05-07: Phase 6 life — Conway via 9-shift toroidal sum + alive-rule (cnt=3 OR alive∧cnt=4); apl-life-step + life.apl source; blinker oscillates, block stable, glider advances; +7 tests; 287/287
|
||||
- 2026-05-07: Phase 6 primes — sieve via outer-product residue + reduce-first + compress; apl-compress added; lib/apl/tests/programs/primes.apl source; +11 tests; 280/280
|
||||
- 2026-05-07: Phase 5 conformance.sh + scoreboard.{json,md} — per-suite runner; current snapshot 269/269; **Phase 5 complete**
|
||||
- 2026-05-07: Phase 5 valence dispatch — apl-dfn-valence (AST scan for ⍺/⍵), apl-tradfn-valence (slot check), apl-call unified entry; +14 tests; 269/269 tests
|
||||
- 2026-05-07: Phase 5 control words — :If/:Else, :While, :For/:In, :Select/:Case via apl-tradfn-eval-block/stmt threading env; :Trap deferred; +10 tests (sum loop, factorial, dispatch, nested); 255/255 tests
|
||||
- 2026-05-07: Phase 5 tradfn — apl-call-tradfn + apl-tradfn-loop; line-numbered stmts, :branch goto, →0 exits, locals; +10 tests including loop sum; 245/245 tests
|
||||
- 2026-05-07: Phase 5 dfn complete — apl-eval-stmts (guards, locals, ⍺←default), ∇ recursion via env "nabla"; +9 tests (factorial, guards, defaults, locals); 235/235 tests
|
||||
- 2026-05-07: Phase 5 dfn foundation — lib/apl/transpile.sx with apl-eval-ast (handles :num :vec :name :monad :dyad :program :dfn) + glyph→fn lookup tables; apl-call-dfn / apl-call-dfn-m bind ⍺/⍵; ∇/guards/defaults/locals pending; 226/226 tests
|
||||
- 2026-05-07: Phase 4 step 10 — at @ (apl-at-replace + apl-at-apply); linear-index lookup, scalar-vals broadcast; 211/211 tests
|
||||
- 2026-05-07: Phase 4 step 9 — rank f⍤k (apl-rank); cell decomposition + reassembly via frame/cell shapes; 201/201 tests
|
||||
- 2026-05-06: Phase 4 step 8 — power f⍣n (apl-power) + fixed-point f⍣≡ (apl-power-fixed); 191/191 tests
|
||||
- 2026-05-06: Phase 4 step 7 — compose f∘g (apl-compose monadic f∘g x, apl-compose-dyadic dyadic f x (g y)); 182/182 tests
|
||||
- 2026-05-06: Phase 4 step 6 — commute f⍨ (apl-commute monadic dup, apl-commute-dyadic swap); 173/173 tests
|
||||
- 2026-05-06: Phase 4 step 5 — inner product f.g (apl-inner); +.× matrix multiply, ∧.= equal-vectors; 163/163 tests
|
||||
- 2026-05-06: Phase 4 step 4 — outer product ∘.f (apl-outer); rank-doubling result shape = a-shape++b-shape; 151/151 tests
|
||||
- 2026-05-06: Phase 4 step 3 — each f¨ (monadic apl-each + dyadic apl-each-dyadic); scalar broadcast both sides; 139/139 tests
|
||||
- 2026-05-06: Phase 4 step 2 — scan f\ (last axis) + f⍀ (first axis); apl-scan/apl-scan-first; 125/125 tests
|
||||
- 2026-05-06: Phase 4 step 1 — reduce f/ (last axis) + f⌿ (first axis); apl-reduce/apl-reduce-first; 110/110 tests
|
||||
- 2026-05-06: Phase 3 complete — membership ∊, dyadic ⍳ (index-of), without ~ (index-of returns nil for not-found); 94/94 tests
|
||||
- 2026-05-06: Phase 3 step 6 — enclose ⊂ / disclose ⊃ (box/unbox, rank-0 detect via type-of); 82/82 tests
|
||||
- 2026-05-06: Phase 3 step 5 — grade-up ⍋ / grade-down ⍒ (stable insertion sort); 74/74 tests
|
||||
- 2026-05-06: Phase 3 step 4 — squad ⌷ (scalar/multi-dim/partial-slice); 66/66 tests
|
||||
- 2026-05-06: Phase 3 step 3 — catenate , (last axis, scalar promo) and first-axis; 59/59 tests
|
||||
- 2026-05-06: Phase 3 step 2 — take ↑ (multi-axis, pad), drop ↓, reverse/rotate ⌽⊖ (last+first axis); 50/50 tests
|
||||
- 2026-05-06: Phase 3 step 1 — reshape ⍴ (cycling), transpose ⍉ (monadic+dyadic); helpers apl-strides/flat->multi/multi->flat; 27/27 structural tests; lib/apl/tests/structural.sx
|
||||
- 2026-04-26: Phase 2 complete — array model + 7 scalar primitive groups; 82/82 tests; lib/apl/runtime.sx + lib/apl/tests/scalar.sx
|
||||
- 2026-04-26: parser (Phase 1 step 2) — 44/44 parser tests green (90/90 total); right-to-left segment algorithm; derived fns, outer/inner product, dfns with guards, strand handling; `lib/apl/parser.sx` + `lib/apl/tests/parse.sx`
|
||||
- 2026-04-25: tokenizer (Phase 1 step 1) — 46/46 tests green; Unicode-aware starts-with? scanner for multi-byte APL glyphs; `lib/apl/tokenizer.sx` + `lib/apl/tests/parse.sx`
|
||||
|
||||
## Blockers
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user