Compare commits
16 Commits
loops/ocam
...
loops/mini
| Author | SHA1 | Date | |
|---|---|---|---|
| 3d2a5b1814 | |||
| bc9261e90a | |||
| fd73f3c51b | |||
| b8a0c504bc | |||
| a038d41815 | |||
| d61b355413 | |||
| 43d58e6ca9 | |||
| 240ed90b20 | |||
| f4ab7f2534 | |||
| cae87c1e2c | |||
| 52070e07fc | |||
| 2de6727e83 | |||
| c754a8ee05 | |||
| f43ad04f91 | |||
| 0ba60d6a25 | |||
| f13e03e625 |
@@ -25,9 +25,8 @@
|
|||||||
; Glyph classification sets
|
; Glyph classification sets
|
||||||
; ============================================================
|
; ============================================================
|
||||||
|
|
||||||
(define
|
(define apl-parse-op-glyphs
|
||||||
apl-parse-op-glyphs
|
(list "/" "\\" "¨" "⍨" "∘" "." "⍣" "⍤" "⍥" "@"))
|
||||||
(list "/" "⌿" "\\" "⍀" "¨" "⍨" "∘" "." "⍣" "⍤" "⍥" "@"))
|
|
||||||
|
|
||||||
(define
|
(define
|
||||||
apl-parse-fn-glyphs
|
apl-parse-fn-glyphs
|
||||||
@@ -83,48 +82,22 @@
|
|||||||
"⍎"
|
"⍎"
|
||||||
"⍕"))
|
"⍕"))
|
||||||
|
|
||||||
(define apl-quad-fn-names (list "⎕FMT" "⎕←"))
|
(define apl-quad-fn-names (list "⎕FMT"))
|
||||||
|
|
||||||
(define apl-known-fn-names (list))
|
(define
|
||||||
|
apl-parse-op-glyph?
|
||||||
|
(fn (v) (some (fn (g) (= g v)) apl-parse-op-glyphs)))
|
||||||
|
|
||||||
; ============================================================
|
; ============================================================
|
||||||
; Token accessors
|
; 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
|
(define
|
||||||
apl-parse-fn-glyph?
|
apl-parse-fn-glyph?
|
||||||
(fn (v) (some (fn (g) (= g v)) apl-parse-fn-glyphs)))
|
(fn (v) (some (fn (g) (= g v)) apl-parse-fn-glyphs)))
|
||||||
|
|
||||||
(define tok-type (fn (tok) (get tok :type)))
|
(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 tok-val (fn (tok) (get tok :value)))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
@@ -134,8 +107,8 @@
|
|||||||
(and (= (tok-type tok) :glyph) (apl-parse-op-glyph? (tok-val tok)))))
|
(and (= (tok-type tok) :glyph) (apl-parse-op-glyph? (tok-val tok)))))
|
||||||
|
|
||||||
; ============================================================
|
; ============================================================
|
||||||
; Build a derived-fn node by chaining operators left-to-right
|
; Collect trailing operators starting at index i
|
||||||
; (+/¨ → (:derived-fn "¨" (:derived-fn "/" (:fn-glyph "+"))))
|
; Returns {:ops (op ...) :end new-i}
|
||||||
; ============================================================
|
; ============================================================
|
||||||
|
|
||||||
(define
|
(define
|
||||||
@@ -146,17 +119,15 @@
|
|||||||
(and (= (tok-type tok) :glyph) (apl-parse-fn-glyph? (tok-val tok)))
|
(and (= (tok-type tok) :glyph) (apl-parse-fn-glyph? (tok-val tok)))
|
||||||
(and
|
(and
|
||||||
(= (tok-type tok) :name)
|
(= (tok-type tok) :name)
|
||||||
(or
|
(some (fn (q) (= q (tok-val tok))) apl-quad-fn-names)))))
|
||||||
(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 (fn (tokens i) (collect-ops-loop tokens i (list))))
|
||||||
|
|
||||||
|
; ============================================================
|
||||||
|
; Build a derived-fn node by chaining operators left-to-right
|
||||||
|
; (+/¨ → (:derived-fn "¨" (:derived-fn "/" (:fn-glyph "+"))))
|
||||||
|
; ============================================================
|
||||||
|
|
||||||
(define
|
(define
|
||||||
collect-ops-loop
|
collect-ops-loop
|
||||||
(fn
|
(fn
|
||||||
@@ -172,10 +143,8 @@
|
|||||||
{:end i :ops acc})))))
|
{:end i :ops acc})))))
|
||||||
|
|
||||||
; ============================================================
|
; ============================================================
|
||||||
; Segment collection: scan tokens left-to-right, building
|
; Find matching close bracket/paren/brace
|
||||||
; a list of {:kind "val"/"fn" :node ast} segments.
|
; Returns the index of the matching close token
|
||||||
; Operators following function glyphs are merged into
|
|
||||||
; derived-fn nodes during this pass.
|
|
||||||
; ============================================================
|
; ============================================================
|
||||||
|
|
||||||
(define
|
(define
|
||||||
@@ -194,20 +163,12 @@
|
|||||||
(find-matching-close-loop tokens start open-type close-type 1)))
|
(find-matching-close-loop tokens start open-type close-type 1)))
|
||||||
|
|
||||||
; ============================================================
|
; ============================================================
|
||||||
; Build tree from segment list
|
; Segment collection: scan tokens left-to-right, building
|
||||||
;
|
; a list of {:kind "val"/"fn" :node ast} segments.
|
||||||
; The segments are in left-to-right order.
|
; Operators following function glyphs are merged into
|
||||||
; APL evaluates right-to-left, so the LEFTMOST function is
|
; derived-fn nodes during this pass.
|
||||||
; 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
|
(define
|
||||||
find-matching-close-loop
|
find-matching-close-loop
|
||||||
(fn
|
(fn
|
||||||
@@ -247,9 +208,21 @@
|
|||||||
collect-segments
|
collect-segments
|
||||||
(fn (tokens) (collect-segments-loop tokens 0 (list))))
|
(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
|
; Build tree from segment list
|
||||||
; If n>1 → return (:vec node1 node2 ...)
|
;
|
||||||
|
; 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
|
(define
|
||||||
collect-segments-loop
|
collect-segments-loop
|
||||||
(fn
|
(fn
|
||||||
@@ -269,38 +242,24 @@
|
|||||||
((= tt :str)
|
((= tt :str)
|
||||||
(collect-segments-loop tokens (+ i 1) (append acc {:kind "val" :node (list :str tv)})))
|
(collect-segments-loop tokens (+ i 1) (append acc {:kind "val" :node (list :str tv)})))
|
||||||
((= tt :name)
|
((= tt :name)
|
||||||
(cond
|
(if
|
||||||
((some (fn (q) (= q tv)) apl-quad-fn-names)
|
(some (fn (q) (= q tv)) apl-quad-fn-names)
|
||||||
|
(let
|
||||||
|
((op-result (collect-ops tokens (+ i 1))))
|
||||||
(let
|
(let
|
||||||
((op-result (collect-ops tokens (+ i 1))))
|
((ops (get op-result :ops)) (ni (get op-result :end)))
|
||||||
(let
|
(let
|
||||||
((ops (get op-result :ops))
|
((fn-node (build-derived-fn (list :fn-glyph tv) ops)))
|
||||||
(ni (get op-result :end)))
|
(collect-segments-loop
|
||||||
(let
|
tokens
|
||||||
((fn-node (build-derived-fn (list :fn-glyph tv) ops)))
|
ni
|
||||||
(collect-segments-loop
|
(append acc {:kind "fn" :node fn-node})))))
|
||||||
tokens
|
(let
|
||||||
ni
|
((br (maybe-bracket (list :name tv) tokens (+ i 1))))
|
||||||
(append acc {:kind "fn" :node fn-node}))))))
|
(collect-segments-loop
|
||||||
((some (fn (q) (= q tv)) apl-known-fn-names)
|
tokens
|
||||||
(let
|
(nth br 1)
|
||||||
((op-result (collect-ops tokens (+ i 1))))
|
(append acc {:kind "val" :node (nth br 0)})))))
|
||||||
(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)
|
((= tt :lparen)
|
||||||
(let
|
(let
|
||||||
((end (find-matching-close tokens (+ i 1) :lparen :rparen)))
|
((end (find-matching-close tokens (+ i 1) :lparen :rparen)))
|
||||||
@@ -308,23 +267,11 @@
|
|||||||
((inner-tokens (slice tokens (+ i 1) end))
|
((inner-tokens (slice tokens (+ i 1) end))
|
||||||
(after (+ end 1)))
|
(after (+ end 1)))
|
||||||
(let
|
(let
|
||||||
((inner-segs (collect-segments inner-tokens)))
|
((br (maybe-bracket (parse-apl-expr inner-tokens) tokens after)))
|
||||||
(if
|
(collect-segments-loop
|
||||||
(and
|
tokens
|
||||||
(>= (len inner-segs) 2)
|
(nth br 1)
|
||||||
(every? (fn (s) (= (get s :kind) "fn")) inner-segs))
|
(append acc {:kind "val" :node (nth br 0)}))))))
|
||||||
(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)
|
((= tt :lbrace)
|
||||||
(let
|
(let
|
||||||
((end (find-matching-close tokens (+ i 1) :lbrace :rbrace)))
|
((end (find-matching-close tokens (+ i 1) :lbrace :rbrace)))
|
||||||
@@ -399,12 +346,9 @@
|
|||||||
|
|
||||||
(define find-first-fn (fn (segs) (find-first-fn-loop segs 0)))
|
(define find-first-fn (fn (segs) (find-first-fn-loop segs 0)))
|
||||||
|
|
||||||
|
; Build an array node from 0..n value segments
|
||||||
; ============================================================
|
; If n=1 → return that segment's node
|
||||||
; Split token list on statement separators (diamond / newline)
|
; If n>1 → return (:vec node1 node2 ...)
|
||||||
; Only splits at depth 0 (ignores separators inside { } or ( ) )
|
|
||||||
; ============================================================
|
|
||||||
|
|
||||||
(define
|
(define
|
||||||
find-first-fn-loop
|
find-first-fn-loop
|
||||||
(fn
|
(fn
|
||||||
@@ -426,9 +370,10 @@
|
|||||||
(get (first segs) :node)
|
(get (first segs) :node)
|
||||||
(cons :vec (map (fn (s) (get s :node)) segs)))))
|
(cons :vec (map (fn (s) (get s :node)) segs)))))
|
||||||
|
|
||||||
|
|
||||||
; ============================================================
|
; ============================================================
|
||||||
; Parse a dfn body (tokens between { and })
|
; Split token list on statement separators (diamond / newline)
|
||||||
; Handles guard expressions: cond : expr
|
; Only splits at depth 0 (ignores separators inside { } or ( ) )
|
||||||
; ============================================================
|
; ============================================================
|
||||||
|
|
||||||
(define
|
(define
|
||||||
@@ -463,6 +408,11 @@
|
|||||||
split-statements
|
split-statements
|
||||||
(fn (tokens) (split-statements-loop tokens (list) (list) 0)))
|
(fn (tokens) (split-statements-loop tokens (list) (list) 0)))
|
||||||
|
|
||||||
|
; ============================================================
|
||||||
|
; Parse a dfn body (tokens between { and })
|
||||||
|
; Handles guard expressions: cond : expr
|
||||||
|
; ============================================================
|
||||||
|
|
||||||
(define
|
(define
|
||||||
split-statements-loop
|
split-statements-loop
|
||||||
(fn
|
(fn
|
||||||
@@ -517,10 +467,6 @@
|
|||||||
((stmt-groups (split-statements tokens)))
|
((stmt-groups (split-statements tokens)))
|
||||||
(let ((stmts (map parse-dfn-stmt stmt-groups))) (cons :dfn stmts)))))
|
(let ((stmts (map parse-dfn-stmt stmt-groups))) (cons :dfn stmts)))))
|
||||||
|
|
||||||
; ============================================================
|
|
||||||
; Parse a single statement (assignment or expression)
|
|
||||||
; ============================================================
|
|
||||||
|
|
||||||
(define
|
(define
|
||||||
parse-dfn-stmt
|
parse-dfn-stmt
|
||||||
(fn
|
(fn
|
||||||
@@ -537,17 +483,12 @@
|
|||||||
(parse-apl-expr body-tokens)))
|
(parse-apl-expr body-tokens)))
|
||||||
(parse-stmt tokens)))))
|
(parse-stmt tokens)))))
|
||||||
|
|
||||||
; ============================================================
|
|
||||||
; Parse an expression from a flat token list
|
|
||||||
; ============================================================
|
|
||||||
|
|
||||||
(define
|
(define
|
||||||
find-top-level-colon
|
find-top-level-colon
|
||||||
(fn (tokens i) (find-top-level-colon-loop tokens i 0)))
|
(fn (tokens i) (find-top-level-colon-loop tokens i 0)))
|
||||||
|
|
||||||
; ============================================================
|
; ============================================================
|
||||||
; Main entry point
|
; Parse a single statement (assignment or expression)
|
||||||
; parse-apl: string → AST
|
|
||||||
; ============================================================
|
; ============================================================
|
||||||
|
|
||||||
(define
|
(define
|
||||||
@@ -567,6 +508,10 @@
|
|||||||
((and (= tt :colon) (= depth 0)) i)
|
((and (= tt :colon) (= depth 0)) i)
|
||||||
(true (find-top-level-colon-loop tokens (+ i 1) depth)))))))
|
(true (find-top-level-colon-loop tokens (+ i 1) depth)))))))
|
||||||
|
|
||||||
|
; ============================================================
|
||||||
|
; Parse an expression from a flat token list
|
||||||
|
; ============================================================
|
||||||
|
|
||||||
(define
|
(define
|
||||||
parse-stmt
|
parse-stmt
|
||||||
(fn
|
(fn
|
||||||
@@ -581,6 +526,11 @@
|
|||||||
(parse-apl-expr (slice tokens 2)))
|
(parse-apl-expr (slice tokens 2)))
|
||||||
(parse-apl-expr tokens))))
|
(parse-apl-expr tokens))))
|
||||||
|
|
||||||
|
; ============================================================
|
||||||
|
; Main entry point
|
||||||
|
; parse-apl: string → AST
|
||||||
|
; ============================================================
|
||||||
|
|
||||||
(define
|
(define
|
||||||
parse-apl-expr
|
parse-apl-expr
|
||||||
(fn
|
(fn
|
||||||
@@ -597,52 +547,13 @@
|
|||||||
((tokens (apl-tokenize src)))
|
((tokens (apl-tokenize src)))
|
||||||
(let
|
(let
|
||||||
((stmt-groups (split-statements tokens)))
|
((stmt-groups (split-statements tokens)))
|
||||||
(begin
|
(if
|
||||||
(apl-collect-fn-bindings stmt-groups)
|
(= (len stmt-groups) 0)
|
||||||
|
nil
|
||||||
(if
|
(if
|
||||||
(= (len stmt-groups) 0)
|
(= (len stmt-groups) 1)
|
||||||
nil
|
(parse-stmt (first stmt-groups))
|
||||||
(if
|
(cons :program (map parse-stmt stmt-groups))))))))
|
||||||
(= (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
|
(define
|
||||||
maybe-bracket
|
maybe-bracket
|
||||||
@@ -658,17 +569,8 @@
|
|||||||
((inner-tokens (slice tokens (+ after 1) end))
|
((inner-tokens (slice tokens (+ after 1) end))
|
||||||
(next-after (+ end 1)))
|
(next-after (+ end 1)))
|
||||||
(let
|
(let
|
||||||
((sections (split-bracket-content inner-tokens)))
|
((idx-expr (parse-apl-expr inner-tokens)))
|
||||||
(if
|
(let
|
||||||
(= (len sections) 1)
|
((indexed (list :dyad (list :fn-glyph "⌷") idx-expr val-node)))
|
||||||
(let
|
(maybe-bracket indexed tokens next-after)))))
|
||||||
((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))))
|
(list val-node after))))
|
||||||
|
|||||||
@@ -883,7 +883,7 @@
|
|||||||
(let
|
(let
|
||||||
((sub (apl-permutations (- n 1))))
|
((sub (apl-permutations (- n 1))))
|
||||||
(reduce
|
(reduce
|
||||||
(fn (acc p) (append (apl-insert-everywhere n p) acc))
|
(fn (acc p) (append acc (apl-insert-everywhere n p)))
|
||||||
(list)
|
(list)
|
||||||
sub)))))
|
sub)))))
|
||||||
|
|
||||||
@@ -985,38 +985,6 @@
|
|||||||
(some (fn (c) (= c 0)) codes)
|
(some (fn (c) (= c 0)) codes)
|
||||||
(some (fn (c) (= c (nth e 1))) codes)))))
|
(some (fn (c) (= c (nth e 1))) codes)))))
|
||||||
|
|
||||||
(define
|
|
||||||
apl-cartesian
|
|
||||||
(fn
|
|
||||||
(lists)
|
|
||||||
(if
|
|
||||||
(= (len lists) 0)
|
|
||||||
(list (list))
|
|
||||||
(let
|
|
||||||
((rest-prods (apl-cartesian (rest lists))))
|
|
||||||
(reduce
|
|
||||||
(fn (acc x) (append acc (map (fn (p) (cons x p)) rest-prods)))
|
|
||||||
(list)
|
|
||||||
(first lists))))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
apl-bracket-multi
|
|
||||||
(fn
|
|
||||||
(axes arr)
|
|
||||||
(let
|
|
||||||
((shape (get arr :shape)) (ravel (get arr :ravel)))
|
|
||||||
(let
|
|
||||||
((rank (len shape)) (strides (apl-strides shape)))
|
|
||||||
(let
|
|
||||||
((axis-info (map (fn (i) (let ((a (nth axes i))) (cond ((= a nil) {:idxs (range 0 (nth shape i)) :scalar? false}) ((= (len (get a :shape)) 0) {:idxs (list (- (first (get a :ravel)) apl-io)) :scalar? true}) (else {:idxs (map (fn (x) (- x apl-io)) (get a :ravel)) :scalar? false})))) (range 0 rank))))
|
|
||||||
(let
|
|
||||||
((cells (apl-cartesian (map (fn (a) (get a :idxs)) axis-info))))
|
|
||||||
(let
|
|
||||||
((result-ravel (map (fn (cell) (let ((flat (reduce + 0 (map (fn (i) (* (nth cell i) (nth strides i))) (range 0 rank))))) (nth ravel flat))) cells)))
|
|
||||||
(let
|
|
||||||
((result-shape (filter (fn (x) (>= x 0)) (map (fn (i) (let ((a (nth axis-info i))) (if (get a :scalar?) -1 (len (get a :idxs))))) (range 0 rank)))))
|
|
||||||
(make-array result-shape result-ravel)))))))))
|
|
||||||
|
|
||||||
(define
|
(define
|
||||||
apl-reduce
|
apl-reduce
|
||||||
(fn
|
(fn
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ cat > "$TMPFILE" << 'EPOCHS'
|
|||||||
(load "lib/apl/tests/idioms.sx")
|
(load "lib/apl/tests/idioms.sx")
|
||||||
(load "lib/apl/tests/eval-ops.sx")
|
(load "lib/apl/tests/eval-ops.sx")
|
||||||
(load "lib/apl/tests/pipeline.sx")
|
(load "lib/apl/tests/pipeline.sx")
|
||||||
(load "lib/apl/tests/programs-e2e.sx")
|
|
||||||
(epoch 4)
|
(epoch 4)
|
||||||
(eval "(list apl-test-pass apl-test-fail)")
|
(eval "(list apl-test-pass apl-test-fail)")
|
||||||
EPOCHS
|
EPOCHS
|
||||||
|
|||||||
@@ -178,137 +178,3 @@
|
|||||||
"apl-run \"(⍳5)[3] × 7\" → 21"
|
"apl-run \"(⍳5)[3] × 7\" → 21"
|
||||||
(mkrv (apl-run "(⍳5)[3] × 7"))
|
(mkrv (apl-run "(⍳5)[3] × 7"))
|
||||||
(list 21))
|
(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))
|
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
; 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))
|
|
||||||
@@ -252,8 +252,6 @@
|
|||||||
|
|
||||||
(apl-test "queens 7 → 40 solutions" (mkrv (apl-queens 7)) (list 40))
|
(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 3 has 6" (len (apl-permutations 3)) 6)
|
||||||
|
|
||||||
(apl-test "permutations of 4 has 24" (len (apl-permutations 4)) 24)
|
(apl-test "permutations of 4 has 24" (len (apl-permutations 4)) 24)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
(list "+" "-" "×" "÷" "*" "⍟" "⌈" "⌊" "|" "!" "?" "○" "~" "<" "≤" "=" "≥" ">" "≠"
|
(list "+" "-" "×" "÷" "*" "⍟" "⌈" "⌊" "|" "!" "?" "○" "~" "<" "≤" "=" "≥" ">" "≠"
|
||||||
"≢" "≡" "∊" "∧" "∨" "⍱" "⍲" "," "⍪" "⍴" "⌽" "⊖" "⍉" "↑" "↓" "⊂" "⊃" "⊆"
|
"≢" "≡" "∊" "∧" "∨" "⍱" "⍲" "," "⍪" "⍴" "⌽" "⊖" "⍉" "↑" "↓" "⊂" "⊃" "⊆"
|
||||||
"∪" "∩" "⍳" "⍸" "⌷" "⍋" "⍒" "⊥" "⊤" "⊣" "⊢" "⍎" "⍕"
|
"∪" "∩" "⍳" "⍸" "⌷" "⍋" "⍒" "⊥" "⊤" "⊣" "⊢" "⍎" "⍕"
|
||||||
"⍺" "⍵" "∇" "/" "⌿" "\\" "⍀" "¨" "⍨" "∘" "." "⍣" "⍤" "⍥" "@" "¯"))
|
"⍺" "⍵" "∇" "/" "\\" "¨" "⍨" "∘" "." "⍣" "⍤" "⍥" "@" "¯"))
|
||||||
|
|
||||||
(define apl-glyph?
|
(define apl-glyph?
|
||||||
(fn (ch)
|
(fn (ch)
|
||||||
@@ -138,22 +138,12 @@
|
|||||||
(begin
|
(begin
|
||||||
(consume! "¯")
|
(consume! "¯")
|
||||||
(let ((digits (read-digits! "")))
|
(let ((digits (read-digits! "")))
|
||||||
(if (and (< pos src-len) (= (cur-byte) ".")
|
(tok-push! :num (- 0 (parse-int digits 0))))
|
||||||
(< (+ 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!)))
|
(scan!)))
|
||||||
((apl-digit? ch)
|
((apl-digit? ch)
|
||||||
(begin
|
(begin
|
||||||
(let ((digits (read-digits! "")))
|
(let ((digits (read-digits! "")))
|
||||||
(if (and (< pos src-len) (= (cur-byte) ".")
|
(tok-push! :num (parse-int digits 0)))
|
||||||
(< (+ 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!)))
|
(scan!)))
|
||||||
((= ch "'")
|
((= ch "'")
|
||||||
(begin
|
(begin
|
||||||
@@ -165,9 +155,7 @@
|
|||||||
(let ((start pos))
|
(let ((start pos))
|
||||||
(begin
|
(begin
|
||||||
(if (cur-sw? "⎕") (consume! "⎕") (advance!))
|
(if (cur-sw? "⎕") (consume! "⎕") (advance!))
|
||||||
(if (and (< pos src-len) (cur-sw? "←"))
|
(read-ident-cont!)
|
||||||
(consume! "←")
|
|
||||||
(read-ident-cont!))
|
|
||||||
(tok-push! :name (slice source start pos))
|
(tok-push! :name (slice source start pos))
|
||||||
(scan!))))
|
(scan!))))
|
||||||
(true
|
(true
|
||||||
|
|||||||
@@ -40,7 +40,6 @@
|
|||||||
((= g "⍋") apl-grade-up)
|
((= g "⍋") apl-grade-up)
|
||||||
((= g "⍒") apl-grade-down)
|
((= g "⍒") apl-grade-down)
|
||||||
((= g "⎕FMT") apl-quad-fmt)
|
((= g "⎕FMT") apl-quad-fmt)
|
||||||
((= g "⎕←") apl-quad-print)
|
|
||||||
(else (error "no monadic fn for glyph")))))
|
(else (error "no monadic fn for glyph")))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
@@ -98,15 +97,6 @@
|
|||||||
((tag (first node)))
|
((tag (first node)))
|
||||||
(cond
|
(cond
|
||||||
((= tag :num) (apl-scalar (nth node 1)))
|
((= 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)
|
((= tag :vec)
|
||||||
(let
|
(let
|
||||||
((items (rest node)))
|
((items (rest node)))
|
||||||
@@ -149,16 +139,6 @@
|
|||||||
(apl-eval-ast rhs env)))))
|
(apl-eval-ast rhs env)))))
|
||||||
((= tag :program) (apl-eval-stmts (rest node) env))
|
((= tag :program) (apl-eval-stmts (rest node) env))
|
||||||
((= tag :dfn) node)
|
((= 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)))))))
|
(else (error (list "apl-eval-ast: unknown node tag" tag node)))))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
@@ -439,36 +419,6 @@
|
|||||||
((f (apl-resolve-dyadic inner env)))
|
((f (apl-resolve-dyadic inner env)))
|
||||||
(fn (arr) (apl-commute f arr))))
|
(fn (arr) (apl-commute f arr))))
|
||||||
(else (error "apl-resolve-monadic: unsupported op")))))
|
(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"))))))
|
(else (error "apl-resolve-monadic: unknown fn-node tag"))))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
@@ -492,18 +442,6 @@
|
|||||||
((f (apl-resolve-dyadic inner env)))
|
((f (apl-resolve-dyadic inner env)))
|
||||||
(fn (a b) (apl-commute-dyadic f a b))))
|
(fn (a b) (apl-commute-dyadic f a b))))
|
||||||
(else (error "apl-resolve-dyadic: unsupported op")))))
|
(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)
|
((= tag :outer)
|
||||||
(let
|
(let
|
||||||
((inner (nth fn-node 2)))
|
((inner (nth fn-node 2)))
|
||||||
@@ -517,24 +455,6 @@
|
|||||||
((f (apl-resolve-dyadic f-node env))
|
((f (apl-resolve-dyadic f-node env))
|
||||||
(g (apl-resolve-dyadic g-node env)))
|
(g (apl-resolve-dyadic g-node env)))
|
||||||
(fn (a b) (apl-inner f g a b)))))
|
(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"))))))
|
(else (error "apl-resolve-dyadic: unknown fn-node tag"))))))
|
||||||
|
|
||||||
(define apl-run (fn (src) (apl-eval-ast (parse-apl src) {})))
|
(define apl-run (fn (src) (apl-eval-ast (parse-apl src) {})))
|
||||||
|
|||||||
180
lib/guest/hm.sx
180
lib/guest/hm.sx
@@ -1,180 +0,0 @@
|
|||||||
;; 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)))))))
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
;; 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))))
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
;; 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)}))
|
|
||||||
@@ -1,180 +0,0 @@
|
|||||||
;; 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)}))
|
|
||||||
42
lib/minikanren/conda.sx
Normal file
42
lib/minikanren/conda.sx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
;; lib/minikanren/conda.sx — Phase 5 piece A: `conda`, the soft-cut.
|
||||||
|
;;
|
||||||
|
;; (conda (g0 g ...) (h0 h ...) ...)
|
||||||
|
;; — first clause whose head g0 produces ANY answer wins; ALL of g0's
|
||||||
|
;; answers are then conj'd with the rest of that clause; later
|
||||||
|
;; clauses are NOT tried.
|
||||||
|
;; — differs from condu only in not wrapping g0 in onceo: condu
|
||||||
|
;; commits to the SINGLE first answer, conda lets the head's full
|
||||||
|
;; answer-set flow into the rest of the clause.
|
||||||
|
;; (Reasoned Schemer chapter 10; Byrd 5.3.)
|
||||||
|
|
||||||
|
(define
|
||||||
|
conda-try
|
||||||
|
(fn
|
||||||
|
(clauses s)
|
||||||
|
(cond
|
||||||
|
((empty? clauses) mzero)
|
||||||
|
(:else
|
||||||
|
(let
|
||||||
|
((cl (first clauses)))
|
||||||
|
(let
|
||||||
|
((head-goal (first cl)) (rest-goals (rest cl)))
|
||||||
|
(let
|
||||||
|
((peek (stream-take 1 (head-goal s))))
|
||||||
|
(if
|
||||||
|
(empty? peek)
|
||||||
|
(conda-try (rest clauses) s)
|
||||||
|
(mk-bind (head-goal s) (mk-conj-list rest-goals))))))))))
|
||||||
|
|
||||||
|
(defmacro
|
||||||
|
conda
|
||||||
|
(&rest clauses)
|
||||||
|
(quasiquote
|
||||||
|
(fn
|
||||||
|
(s)
|
||||||
|
(conda-try
|
||||||
|
(list
|
||||||
|
(splice-unquote
|
||||||
|
(map
|
||||||
|
(fn (cl) (quasiquote (list (splice-unquote cl))))
|
||||||
|
clauses)))
|
||||||
|
s))))
|
||||||
39
lib/minikanren/conde.sx
Normal file
39
lib/minikanren/conde.sx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
;; lib/minikanren/conde.sx — Phase 2 piece C: `conde`, the canonical
|
||||||
|
;; miniKanren and-or form, with implicit Zzz inverse-eta delay so recursive
|
||||||
|
;; relations like appendo terminate.
|
||||||
|
;;
|
||||||
|
;; (conde (g1a g1b ...) (g2a g2b ...) ...)
|
||||||
|
;; ≡ (mk-disj (Zzz (mk-conj g1a g1b ...))
|
||||||
|
;; (Zzz (mk-conj g2a g2b ...)) ...)
|
||||||
|
;;
|
||||||
|
;; `Zzz g` wraps a goal expression in (fn (S) (fn () (g S))) so that
|
||||||
|
;; `g`'s body isn't constructed until the surrounding fn is applied to a
|
||||||
|
;; substitution AND the returned thunk is forced. This is what gives
|
||||||
|
;; miniKanren its laziness — recursive goal definitions can be `(conde
|
||||||
|
;; ... (... (recur ...)))` without infinite descent at construction time.
|
||||||
|
;;
|
||||||
|
;; Hygiene: the substitution parameter is gensym'd so that user goal
|
||||||
|
;; expressions which themselves bind `s` (e.g. `(appendo l s ls)`) keep
|
||||||
|
;; their lexical `s` and don't accidentally reference the wrapper's
|
||||||
|
;; substitution. Without gensym, miniKanren relations that follow the
|
||||||
|
;; common (l s ls) parameter convention are silently miscompiled.
|
||||||
|
|
||||||
|
(defmacro
|
||||||
|
Zzz
|
||||||
|
(g)
|
||||||
|
(let
|
||||||
|
((s-sym (gensym "zzz-s-")))
|
||||||
|
(quasiquote
|
||||||
|
(fn ((unquote s-sym)) (fn () ((unquote g) (unquote s-sym)))))))
|
||||||
|
|
||||||
|
(defmacro
|
||||||
|
conde
|
||||||
|
(&rest clauses)
|
||||||
|
(quasiquote
|
||||||
|
(mk-disj
|
||||||
|
(splice-unquote
|
||||||
|
(map
|
||||||
|
(fn
|
||||||
|
(clause)
|
||||||
|
(quasiquote (Zzz (mk-conj (splice-unquote clause)))))
|
||||||
|
clauses)))))
|
||||||
58
lib/minikanren/condu.sx
Normal file
58
lib/minikanren/condu.sx
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
;; lib/minikanren/condu.sx — Phase 2 piece D: `condu` and `onceo`.
|
||||||
|
;;
|
||||||
|
;; Both are commitment forms (no backtracking into discarded options):
|
||||||
|
;;
|
||||||
|
;; (onceo g) — succeeds at most once: takes the first answer
|
||||||
|
;; stream-take produces from (g s).
|
||||||
|
;;
|
||||||
|
;; (condu (g0 g ...) (h0 h ...) ...)
|
||||||
|
;; — first clause whose head goal succeeds wins; only
|
||||||
|
;; the first answer of the head is propagated to the
|
||||||
|
;; rest of that clause; later clauses are not tried.
|
||||||
|
;; (Reasoned Schemer chapter 10; Byrd 5.4.)
|
||||||
|
|
||||||
|
(define
|
||||||
|
onceo
|
||||||
|
(fn
|
||||||
|
(g)
|
||||||
|
(fn
|
||||||
|
(s)
|
||||||
|
(let
|
||||||
|
((peek (stream-take 1 (g s))))
|
||||||
|
(if (empty? peek) mzero (unit (first peek)))))))
|
||||||
|
|
||||||
|
;; condu-try — runtime walker over a list of clauses (each clause a list of
|
||||||
|
;; goals). Forces the head with stream-take 1; if head fails, recurse to
|
||||||
|
;; the next clause; if head succeeds, commits its single answer through
|
||||||
|
;; the rest of the clause.
|
||||||
|
(define
|
||||||
|
condu-try
|
||||||
|
(fn
|
||||||
|
(clauses s)
|
||||||
|
(cond
|
||||||
|
((empty? clauses) mzero)
|
||||||
|
(:else
|
||||||
|
(let
|
||||||
|
((cl (first clauses)))
|
||||||
|
(let
|
||||||
|
((head-goal (first cl)) (rest-goals (rest cl)))
|
||||||
|
(let
|
||||||
|
((peek (stream-take 1 (head-goal s))))
|
||||||
|
(if
|
||||||
|
(empty? peek)
|
||||||
|
(condu-try (rest clauses) s)
|
||||||
|
((mk-conj-list rest-goals) (first peek))))))))))
|
||||||
|
|
||||||
|
(defmacro
|
||||||
|
condu
|
||||||
|
(&rest clauses)
|
||||||
|
(quasiquote
|
||||||
|
(fn
|
||||||
|
(s)
|
||||||
|
(condu-try
|
||||||
|
(list
|
||||||
|
(splice-unquote
|
||||||
|
(map
|
||||||
|
(fn (cl) (quasiquote (list (splice-unquote cl))))
|
||||||
|
clauses)))
|
||||||
|
s))))
|
||||||
25
lib/minikanren/fd.sx
Normal file
25
lib/minikanren/fd.sx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
;; lib/minikanren/fd.sx — Phase 6 piece A: minimal finite-domain helpers.
|
||||||
|
;;
|
||||||
|
;; A full CLP(FD) engine (arc consistency, native integer domains, fd-plus
|
||||||
|
;; etc.) is Phase 6 proper. For now we expose two small relations layered
|
||||||
|
;; on the existing list machinery — they're sufficient for permutation
|
||||||
|
;; puzzles, the N-queens-style core of constraint solving:
|
||||||
|
;;
|
||||||
|
;; (ino x dom) — x is a member of dom (alias for membero with the
|
||||||
|
;; constraint-store-friendly argument order).
|
||||||
|
;; (all-distincto l) — all elements of l are pairwise distinct.
|
||||||
|
;;
|
||||||
|
;; all-distincto uses nafc + membero on the tail — it requires the head
|
||||||
|
;; element of each recursive step to be ground enough for membero to be
|
||||||
|
;; finitary, so order matters: prefer (in x dom) goals BEFORE
|
||||||
|
;; (all-distincto (list x ...)) so values get committed first.
|
||||||
|
|
||||||
|
(define ino (fn (x dom) (membero x dom)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
all-distincto
|
||||||
|
(fn
|
||||||
|
(l)
|
||||||
|
(conde
|
||||||
|
((nullo l))
|
||||||
|
((fresh (a d) (conso a d l) (nafc (membero a d)) (all-distincto d))))))
|
||||||
23
lib/minikanren/fresh.sx
Normal file
23
lib/minikanren/fresh.sx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
;; lib/minikanren/fresh.sx — Phase 2 piece B: `fresh` for introducing
|
||||||
|
;; logic variables inside a goal body.
|
||||||
|
;;
|
||||||
|
;; (fresh (x y z) goal1 goal2 ...)
|
||||||
|
;; ≡ (let ((x (make-var)) (y (make-var)) (z (make-var)))
|
||||||
|
;; (mk-conj goal1 goal2 ...))
|
||||||
|
;;
|
||||||
|
;; A macro rather than a function so user-named vars are real lexical
|
||||||
|
;; bindings — which is also what miniKanren convention expects.
|
||||||
|
;; The empty-vars form (fresh () goal ...) is just a goal grouping.
|
||||||
|
|
||||||
|
(defmacro
|
||||||
|
fresh
|
||||||
|
(vars &rest goals)
|
||||||
|
(quasiquote
|
||||||
|
(let
|
||||||
|
(unquote (map (fn (v) (list v (list (quote make-var)))) vars))
|
||||||
|
(mk-conj (splice-unquote goals)))))
|
||||||
|
|
||||||
|
;; call-fresh — functional alternative for code that builds goals
|
||||||
|
;; programmatically:
|
||||||
|
;; ((call-fresh (fn (x) (== x 7))) empty-s) → ({:_.N 7})
|
||||||
|
(define call-fresh (fn (f) (fn (s) ((f (make-var)) s))))
|
||||||
58
lib/minikanren/goals.sx
Normal file
58
lib/minikanren/goals.sx
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
;; lib/minikanren/goals.sx — Phase 2 piece B: core goals.
|
||||||
|
;;
|
||||||
|
;; A goal is a function (fn (s) → stream-of-substitutions).
|
||||||
|
;; Goals built here:
|
||||||
|
;; succeed — always returns (unit s)
|
||||||
|
;; fail — always returns mzero
|
||||||
|
;; == — unifies two terms; succeeds with a singleton, else fails
|
||||||
|
;; ==-check — opt-in occurs-checked equality
|
||||||
|
;; conj2 / mk-conj — sequential conjunction of goals
|
||||||
|
;; disj2 / mk-disj — interleaved disjunction of goals (raw — `conde` adds
|
||||||
|
;; the implicit-conj-per-clause sugar in a later commit)
|
||||||
|
|
||||||
|
(define succeed (fn (s) (unit s)))
|
||||||
|
|
||||||
|
(define fail (fn (s) mzero))
|
||||||
|
|
||||||
|
(define
|
||||||
|
==
|
||||||
|
(fn
|
||||||
|
(u v)
|
||||||
|
(fn
|
||||||
|
(s)
|
||||||
|
(let ((s2 (mk-unify u v s))) (if (= s2 nil) mzero (unit s2))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
==-check
|
||||||
|
(fn
|
||||||
|
(u v)
|
||||||
|
(fn
|
||||||
|
(s)
|
||||||
|
(let ((s2 (mk-unify-check u v s))) (if (= s2 nil) mzero (unit s2))))))
|
||||||
|
|
||||||
|
(define conj2 (fn (g1 g2) (fn (s) (mk-bind (g1 s) g2))))
|
||||||
|
|
||||||
|
(define disj2 (fn (g1 g2) (fn (s) (mk-mplus (g1 s) (g2 s)))))
|
||||||
|
|
||||||
|
;; Fold goals in a list. (mk-conj-list ()) ≡ succeed; (mk-disj-list ()) ≡ fail.
|
||||||
|
(define
|
||||||
|
mk-conj-list
|
||||||
|
(fn
|
||||||
|
(gs)
|
||||||
|
(cond
|
||||||
|
((empty? gs) succeed)
|
||||||
|
((empty? (rest gs)) (first gs))
|
||||||
|
(:else (conj2 (first gs) (mk-conj-list (rest gs)))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
mk-disj-list
|
||||||
|
(fn
|
||||||
|
(gs)
|
||||||
|
(cond
|
||||||
|
((empty? gs) fail)
|
||||||
|
((empty? (rest gs)) (first gs))
|
||||||
|
(:else (disj2 (first gs) (mk-disj-list (rest gs)))))))
|
||||||
|
|
||||||
|
(define mk-conj (fn (&rest gs) (mk-conj-list gs)))
|
||||||
|
|
||||||
|
(define mk-disj (fn (&rest gs) (mk-disj-list gs)))
|
||||||
76
lib/minikanren/matche.sx
Normal file
76
lib/minikanren/matche.sx
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
;; lib/minikanren/matche.sx — Phase 5 piece D: pattern matching over terms.
|
||||||
|
;;
|
||||||
|
;; (matche TARGET
|
||||||
|
;; (PATTERN1 g1 g2 ...)
|
||||||
|
;; (PATTERN2 g1 ...)
|
||||||
|
;; ...)
|
||||||
|
;;
|
||||||
|
;; Pattern grammar:
|
||||||
|
;; _ wildcard — fresh anonymous var
|
||||||
|
;; x plain symbol — fresh var, bind by name
|
||||||
|
;; ATOM literal (number, string, boolean) — must equal
|
||||||
|
;; :keyword keyword literal — emitted bare (keywords self-evaluate
|
||||||
|
;; to their string name in SX, so quoting them changes
|
||||||
|
;; their type from string to keyword)
|
||||||
|
;; () empty list — must equal
|
||||||
|
;; (p1 p2 ... pn) list pattern — recurse on each element
|
||||||
|
;;
|
||||||
|
;; The macro expands to a `conde` whose clauses are
|
||||||
|
;; `((fresh (vars-in-pat) (== target pat-expr) body...))`.
|
||||||
|
;;
|
||||||
|
;; Repeated symbol names within a pattern produce the same fresh var, so
|
||||||
|
;; they unify by `==`. Fixed-length list patterns only — head/tail
|
||||||
|
;; destructuring uses `(fresh (a d) (conso a d target) body)` directly.
|
||||||
|
;;
|
||||||
|
;; Note: the macro builds the expansion via `cons` / `list` rather than a
|
||||||
|
;; quasiquote — quasiquote does not recurse into nested lambda bodies in
|
||||||
|
;; SX, so `\`(matche-clause (quote ,target) cl)` left literal
|
||||||
|
;; `(unquote target)` in the output.
|
||||||
|
|
||||||
|
(define matche-symbol-var? (fn (s) (symbol? s)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
matche-collect-vars-acc
|
||||||
|
(fn
|
||||||
|
(pat acc)
|
||||||
|
(cond
|
||||||
|
((matche-symbol-var? pat)
|
||||||
|
(if (some (fn (s) (= s pat)) acc) acc (append acc (list pat))))
|
||||||
|
((and (list? pat) (not (empty? pat)))
|
||||||
|
(reduce (fn (a p) (matche-collect-vars-acc p a)) acc pat))
|
||||||
|
(:else acc))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
matche-collect-vars
|
||||||
|
(fn (pat) (matche-collect-vars-acc pat (list))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
matche-pattern->expr
|
||||||
|
(fn
|
||||||
|
(pat)
|
||||||
|
(cond
|
||||||
|
((matche-symbol-var? pat) pat)
|
||||||
|
((and (list? pat) (empty? pat)) (list (quote list)))
|
||||||
|
((list? pat) (cons (quote list) (map matche-pattern->expr pat)))
|
||||||
|
((keyword? pat) pat)
|
||||||
|
(:else (list (quote quote) pat)))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
matche-clause
|
||||||
|
(fn
|
||||||
|
(target cl)
|
||||||
|
(let
|
||||||
|
((pat (first cl)) (body (rest cl)))
|
||||||
|
(let
|
||||||
|
((vars (matche-collect-vars pat)))
|
||||||
|
(let
|
||||||
|
((pat-expr (matche-pattern->expr pat)))
|
||||||
|
(list
|
||||||
|
(cons
|
||||||
|
(quote fresh)
|
||||||
|
(cons vars (cons (list (quote ==) target pat-expr) body)))))))))
|
||||||
|
|
||||||
|
(defmacro
|
||||||
|
matche
|
||||||
|
(target &rest clauses)
|
||||||
|
(cons (quote conde) (map (fn (cl) (matche-clause target cl)) clauses)))
|
||||||
24
lib/minikanren/nafc.sx
Normal file
24
lib/minikanren/nafc.sx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
;; lib/minikanren/nafc.sx — Phase 5 piece C: negation as finite failure.
|
||||||
|
;;
|
||||||
|
;; (nafc g)
|
||||||
|
;; succeeds (yields the input substitution) if g has zero answers
|
||||||
|
;; against that substitution; fails (mzero) if g has at least one.
|
||||||
|
;;
|
||||||
|
;; Caveat: `nafc` is unsound under the open-world assumption. It only
|
||||||
|
;; makes sense for goals over fully-ground terms, or with the explicit
|
||||||
|
;; understanding that adding more facts could flip the answer. Use
|
||||||
|
;; `(project (...) ...)` to ensure the relevant vars are ground first.
|
||||||
|
;;
|
||||||
|
;; Caveat 2: stream-take forces g for at least one answer; if g is
|
||||||
|
;; infinitely-ground (say, a divergent search over an unbound list),
|
||||||
|
;; nafc itself will diverge. Standard miniKanren limitation.
|
||||||
|
|
||||||
|
(define
|
||||||
|
nafc
|
||||||
|
(fn
|
||||||
|
(g)
|
||||||
|
(fn
|
||||||
|
(s)
|
||||||
|
(let
|
||||||
|
((peek (stream-take 1 (g s))))
|
||||||
|
(if (empty? peek) (unit s) mzero)))))
|
||||||
35
lib/minikanren/peano.sx
Normal file
35
lib/minikanren/peano.sx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
;; lib/minikanren/peano.sx — Peano-encoded natural-number relations.
|
||||||
|
;;
|
||||||
|
;; Same encoding as `lengtho`: zero is the keyword `:z`; successors are
|
||||||
|
;; `(:s n)`. So 3 = `(:s (:s (:s :z)))`. `(:z)` and `(:s ...)` are normal
|
||||||
|
;; SX values that unify positionally — no special primitives needed.
|
||||||
|
;;
|
||||||
|
;; Peano arithmetic is the canonical miniKanren way to test addition /
|
||||||
|
;; multiplication / less-than relationally without an FD constraint store.
|
||||||
|
;; (CLP(FD) integers come in Phase 6.)
|
||||||
|
|
||||||
|
(define zeroo (fn (n) (== n :z)))
|
||||||
|
|
||||||
|
(define succ-of (fn (n m) (== m (list :s n))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pluso
|
||||||
|
(fn
|
||||||
|
(a b c)
|
||||||
|
(conde
|
||||||
|
((== a :z) (== b c))
|
||||||
|
((fresh (a-1 c-1) (== a (list :s a-1)) (== c (list :s c-1)) (pluso a-1 b c-1))))))
|
||||||
|
|
||||||
|
(define minuso (fn (a b c) (pluso b c a)))
|
||||||
|
|
||||||
|
(define lteo (fn (a b) (fresh (k) (pluso a k b))))
|
||||||
|
|
||||||
|
(define lto (fn (a b) (fresh (sa) (succ-of a sa) (lteo sa b))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
*o
|
||||||
|
(fn
|
||||||
|
(a b c)
|
||||||
|
(conde
|
||||||
|
((== a :z) (== c :z))
|
||||||
|
((fresh (a-1 ab-1) (== a (list :s a-1)) (*o a-1 b ab-1) (pluso b ab-1 c))))))
|
||||||
25
lib/minikanren/project.sx
Normal file
25
lib/minikanren/project.sx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
;; lib/minikanren/project.sx — Phase 5 piece B: `project`.
|
||||||
|
;;
|
||||||
|
;; (project (x y) g1 g2 ...)
|
||||||
|
;; — rebinds each named var to (mk-walk* var s) within the body's
|
||||||
|
;; lexical scope, then runs the conjunction of the body goals on
|
||||||
|
;; the same substitution. Use to escape into regular SX (arithmetic,
|
||||||
|
;; string ops, host predicates) when you need a ground value.
|
||||||
|
;;
|
||||||
|
;; If any of the projected vars is still unbound at this point, the body
|
||||||
|
;; sees the raw `(:var NAME)` term — that is intentional and lets you
|
||||||
|
;; mix project with `(== ground? var)` patterns or with conda guards.
|
||||||
|
;;
|
||||||
|
;; Hygiene: substitution parameter is gensym'd so it doesn't capture user
|
||||||
|
;; vars (`s` is a popular relation parameter name).
|
||||||
|
|
||||||
|
(defmacro
|
||||||
|
project
|
||||||
|
(vars &rest goals)
|
||||||
|
(let
|
||||||
|
((s-sym (gensym "proj-s-")))
|
||||||
|
(quasiquote
|
||||||
|
(fn
|
||||||
|
((unquote s-sym))
|
||||||
|
((let (unquote (map (fn (v) (list v (list (quote mk-walk*) v s-sym))) vars)) (mk-conj (splice-unquote goals)))
|
||||||
|
(unquote s-sym))))))
|
||||||
83
lib/minikanren/relations.sx
Normal file
83
lib/minikanren/relations.sx
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
;; lib/minikanren/relations.sx — Phase 4 standard relations.
|
||||||
|
;;
|
||||||
|
;; Programs use native SX lists as data. Relations decompose lists via the
|
||||||
|
;; tagged cons-cell shape `(:cons h t)` because SX has no improper pairs;
|
||||||
|
;; the unifier treats `(:cons h t)` and the native list `(h . t)` as
|
||||||
|
;; equivalent, and `mk-walk*` flattens cons cells back to flat lists for
|
||||||
|
;; reification.
|
||||||
|
|
||||||
|
;; --- pair / list shape relations ---
|
||||||
|
|
||||||
|
(define nullo (fn (l) (== l (list))))
|
||||||
|
|
||||||
|
(define pairo (fn (p) (fresh (a d) (== p (mk-cons a d)))))
|
||||||
|
|
||||||
|
(define caro (fn (p a) (fresh (d) (== p (mk-cons a d)))))
|
||||||
|
|
||||||
|
(define cdro (fn (p d) (fresh (a) (== p (mk-cons a d)))))
|
||||||
|
|
||||||
|
(define conso (fn (a d p) (== p (mk-cons a d))))
|
||||||
|
|
||||||
|
(define firsto caro)
|
||||||
|
(define resto cdro)
|
||||||
|
|
||||||
|
(define
|
||||||
|
listo
|
||||||
|
(fn (l) (conde ((nullo l)) ((fresh (a d) (conso a d l) (listo d))))))
|
||||||
|
|
||||||
|
;; --- appendo: the canary ---
|
||||||
|
;;
|
||||||
|
;; (appendo l s ls) — `ls` is the concatenation of `l` and `s`.
|
||||||
|
;; Runs forwards (l, s known → ls), backwards (ls known → all (l, s) pairs),
|
||||||
|
;; and bidirectionally (mix of bound + unbound).
|
||||||
|
|
||||||
|
(define
|
||||||
|
appendo
|
||||||
|
(fn
|
||||||
|
(l s ls)
|
||||||
|
(conde
|
||||||
|
((nullo l) (== s ls))
|
||||||
|
((fresh (a d res) (conso a d l) (conso a res ls) (appendo d s res))))))
|
||||||
|
|
||||||
|
;; --- membero ---
|
||||||
|
;; (membero x l) — x appears (at least once) in l.
|
||||||
|
|
||||||
|
(define
|
||||||
|
membero
|
||||||
|
(fn
|
||||||
|
(x l)
|
||||||
|
(conde
|
||||||
|
((fresh (d) (conso x d l)))
|
||||||
|
((fresh (a d) (conso a d l) (membero x d))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
reverseo
|
||||||
|
(fn
|
||||||
|
(l r)
|
||||||
|
(conde
|
||||||
|
((nullo l) (nullo r))
|
||||||
|
((fresh (a d res-rev) (conso a d l) (reverseo d res-rev) (appendo res-rev (list a) r))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
lengtho
|
||||||
|
(fn
|
||||||
|
(l n)
|
||||||
|
(conde
|
||||||
|
((nullo l) (== n :z))
|
||||||
|
((fresh (a d n-1) (conso a d l) (== n (list :s n-1)) (lengtho d n-1))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
inserto
|
||||||
|
(fn
|
||||||
|
(a l p)
|
||||||
|
(conde
|
||||||
|
((conso a l p))
|
||||||
|
((fresh (h t pt) (conso h t l) (conso h pt p) (inserto a t pt))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
permuteo
|
||||||
|
(fn
|
||||||
|
(l p)
|
||||||
|
(conde
|
||||||
|
((nullo l) (nullo p))
|
||||||
|
((fresh (a d perm-d) (conso a d l) (permuteo d perm-d) (inserto a perm-d p))))))
|
||||||
56
lib/minikanren/run.sx
Normal file
56
lib/minikanren/run.sx
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
;; lib/minikanren/run.sx — Phase 3: drive a goal + reify the query var.
|
||||||
|
;;
|
||||||
|
;; reify-name N — make the canonical "_.N" reified symbol.
|
||||||
|
;; reify-s term rs — walk term in rs, add a mapping from each fresh
|
||||||
|
;; unbound var to its _.N name (left-to-right order).
|
||||||
|
;; reify q s — walk* q in s, build reify-s, walk* again to
|
||||||
|
;; substitute reified names in.
|
||||||
|
;; run-n n q-name g... — defmacro: bind q-name to a fresh var, conj goals,
|
||||||
|
;; take ≤ n answers from the stream, reify each
|
||||||
|
;; through q-name. n = -1 takes all (used by run*).
|
||||||
|
;; run* — defmacro: (run* q g...) ≡ (run-n -1 q g...)
|
||||||
|
;; run — defmacro: (run n q g...) ≡ (run-n n q g...)
|
||||||
|
;; The two-segment form is the standard TRS API.
|
||||||
|
|
||||||
|
(define reify-name (fn (n) (make-symbol (str "_." n))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
reify-s
|
||||||
|
(fn
|
||||||
|
(term rs)
|
||||||
|
(let
|
||||||
|
((w (mk-walk term rs)))
|
||||||
|
(cond
|
||||||
|
((is-var? w) (extend (var-name w) (reify-name (len rs)) rs))
|
||||||
|
((mk-list-pair? w) (reduce (fn (acc a) (reify-s a acc)) rs w))
|
||||||
|
(:else rs)))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
reify
|
||||||
|
(fn
|
||||||
|
(term s)
|
||||||
|
(let
|
||||||
|
((w (mk-walk* term s)))
|
||||||
|
(let ((rs (reify-s w (empty-subst)))) (mk-walk* w rs)))))
|
||||||
|
|
||||||
|
(defmacro
|
||||||
|
run-n
|
||||||
|
(n q-name &rest goals)
|
||||||
|
(quasiquote
|
||||||
|
(let
|
||||||
|
(((unquote q-name) (make-var)))
|
||||||
|
(map
|
||||||
|
(fn (s) (reify (unquote q-name) s))
|
||||||
|
(stream-take
|
||||||
|
(unquote n)
|
||||||
|
((mk-conj (splice-unquote goals)) empty-s))))))
|
||||||
|
|
||||||
|
(defmacro
|
||||||
|
run*
|
||||||
|
(q-name &rest goals)
|
||||||
|
(quasiquote (run-n -1 (unquote q-name) (splice-unquote goals))))
|
||||||
|
|
||||||
|
(defmacro
|
||||||
|
run
|
||||||
|
(n q-name &rest goals)
|
||||||
|
(quasiquote (run-n (unquote n) (unquote q-name) (splice-unquote goals))))
|
||||||
66
lib/minikanren/stream.sx
Normal file
66
lib/minikanren/stream.sx
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
;; lib/minikanren/stream.sx — Phase 2 piece A: lazy streams of substitutions.
|
||||||
|
;;
|
||||||
|
;; SX has no improper pairs (cons requires a list cdr), so we use a
|
||||||
|
;; tagged stream-cell shape for mature stream elements:
|
||||||
|
;;
|
||||||
|
;; stream ::= mzero empty (the SX empty list)
|
||||||
|
;; | (:s HEAD TAIL) mature cell, TAIL is a stream
|
||||||
|
;; | thunk (fn () ...) → stream when forced
|
||||||
|
;;
|
||||||
|
;; HEAD is a substitution dict. TAIL is again a stream (possibly a thunk),
|
||||||
|
;; which is what gives us laziness — mk-mplus can return a mature head with
|
||||||
|
;; a thunk in the tail, deferring the rest of the search.
|
||||||
|
|
||||||
|
(define mzero (list))
|
||||||
|
|
||||||
|
(define s-cons (fn (h t) (list :s h t)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
s-cons?
|
||||||
|
(fn (s) (and (list? s) (not (empty? s)) (= (first s) :s))))
|
||||||
|
|
||||||
|
(define s-car (fn (s) (nth s 1)))
|
||||||
|
(define s-cdr (fn (s) (nth s 2)))
|
||||||
|
|
||||||
|
(define unit (fn (s) (s-cons s mzero)))
|
||||||
|
|
||||||
|
(define stream-pause? (fn (s) (and (not (list? s)) (callable? s))))
|
||||||
|
|
||||||
|
;; mk-mplus — interleave two streams. If s1 is paused we suspend and
|
||||||
|
;; swap (Reasoned Schemer "interleave"); otherwise mature-cons head with
|
||||||
|
;; mk-mplus of the rest.
|
||||||
|
(define
|
||||||
|
mk-mplus
|
||||||
|
(fn
|
||||||
|
(s1 s2)
|
||||||
|
(cond
|
||||||
|
((empty? s1) s2)
|
||||||
|
((stream-pause? s1) (fn () (mk-mplus s2 (s1))))
|
||||||
|
(:else (s-cons (s-car s1) (mk-mplus (s-cdr s1) s2))))))
|
||||||
|
|
||||||
|
;; mk-bind — apply goal g to every substitution in stream s, mk-mplus-ing.
|
||||||
|
(define
|
||||||
|
mk-bind
|
||||||
|
(fn
|
||||||
|
(s g)
|
||||||
|
(cond
|
||||||
|
((empty? s) mzero)
|
||||||
|
((stream-pause? s) (fn () (mk-bind (s) g)))
|
||||||
|
(:else (mk-mplus (g (s-car s)) (mk-bind (s-cdr s) g))))))
|
||||||
|
|
||||||
|
;; stream-take — force up to n results out of a (possibly lazy) stream
|
||||||
|
;; into a flat SX list of substitutions. n = -1 means take all.
|
||||||
|
(define
|
||||||
|
stream-take
|
||||||
|
(fn
|
||||||
|
(n s)
|
||||||
|
(cond
|
||||||
|
((= n 0) (list))
|
||||||
|
((empty? s) (list))
|
||||||
|
((stream-pause? s) (stream-take n (s)))
|
||||||
|
(:else
|
||||||
|
(cons
|
||||||
|
(s-car s)
|
||||||
|
(stream-take
|
||||||
|
(if (= n -1) -1 (- n 1))
|
||||||
|
(s-cdr s)))))))
|
||||||
87
lib/minikanren/tests/classics.sx
Normal file
87
lib/minikanren/tests/classics.sx
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
;; lib/minikanren/tests/classics.sx — small classic-style puzzles that
|
||||||
|
;; exercise the full system end to end (relations + conde + matche +
|
||||||
|
;; fresh + run*). Each test is a self-contained miniKanren program.
|
||||||
|
|
||||||
|
;; -----------------------------------------------------------------------
|
||||||
|
;; Pet puzzle (3 friends, 3 pets, 1-each).
|
||||||
|
;; -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"classics-pet-puzzle"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(fresh
|
||||||
|
(a b c)
|
||||||
|
(== q (list a b c))
|
||||||
|
(permuteo (list :dog :cat :fish) (list a b c))
|
||||||
|
(== b :fish)
|
||||||
|
(conde ((== a :cat)) ((== a :fish)))))
|
||||||
|
(list (list :cat :fish :dog)))
|
||||||
|
|
||||||
|
;; -----------------------------------------------------------------------
|
||||||
|
;; Family-relations puzzle (uses membero on a fact list).
|
||||||
|
;; -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
(define
|
||||||
|
parent-facts
|
||||||
|
(list
|
||||||
|
(list "alice" "bob")
|
||||||
|
(list "alice" "carol")
|
||||||
|
(list "bob" "dave")
|
||||||
|
(list "carol" "eve")
|
||||||
|
(list "dave" "frank")))
|
||||||
|
|
||||||
|
(define parento (fn (x y) (membero (list x y) parent-facts)))
|
||||||
|
|
||||||
|
(define grandparento (fn (x z) (fresh (y) (parento x y) (parento y z))))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"classics-grandparents-of-frank"
|
||||||
|
(run* q (grandparento q "frank"))
|
||||||
|
(list "bob"))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"classics-grandchildren-of-alice"
|
||||||
|
(run* q (grandparento "alice" q))
|
||||||
|
(list "dave" "eve"))
|
||||||
|
|
||||||
|
;; -----------------------------------------------------------------------
|
||||||
|
;; Symbolic differentiation, matche-driven.
|
||||||
|
;; Variable :x: d/dx x = 1
|
||||||
|
;; Sum (:+ a b): d/dx (a+b) = (da + db)
|
||||||
|
;; Product (:* a b): d/dx (a*b) = (da*b + a*db)
|
||||||
|
;; -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
(define
|
||||||
|
diffo
|
||||||
|
(fn
|
||||||
|
(expr var d)
|
||||||
|
(matche
|
||||||
|
expr
|
||||||
|
(:x (== d 1))
|
||||||
|
((:+ a b)
|
||||||
|
(fresh
|
||||||
|
(da db)
|
||||||
|
(== d (list :+ da db))
|
||||||
|
(diffo a var da)
|
||||||
|
(diffo b var db)))
|
||||||
|
((:* a b)
|
||||||
|
(fresh
|
||||||
|
(da db)
|
||||||
|
(== d (list :+ (list :* da b) (list :* a db)))
|
||||||
|
(diffo a var da)
|
||||||
|
(diffo b var db))))))
|
||||||
|
|
||||||
|
(mk-test "classics-diff-of-x" (run* q (diffo :x :x q)) (list 1))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"classics-diff-of-x-plus-x"
|
||||||
|
(run* q (diffo (list :+ :x :x) :x q))
|
||||||
|
(list (list :+ 1 1)))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"classics-diff-of-x-times-x"
|
||||||
|
(run* q (diffo (list :* :x :x) :x q))
|
||||||
|
(list (list :+ (list :* 1 :x) (list :* :x 1))))
|
||||||
|
|
||||||
|
(mk-tests-run!)
|
||||||
75
lib/minikanren/tests/conda.sx
Normal file
75
lib/minikanren/tests/conda.sx
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
;; lib/minikanren/tests/conda.sx — Phase 5 piece A tests for `conda`.
|
||||||
|
|
||||||
|
;; --- conda commits to first non-failing head, keeps ALL its answers ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"conda-first-clause-keeps-all"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(conda
|
||||||
|
((mk-disj (== q 1) (== q 2)))
|
||||||
|
((== q 100))))
|
||||||
|
(list 1 2))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"conda-skips-failing-head"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(conda
|
||||||
|
((== 1 2))
|
||||||
|
((mk-disj (== q 10) (== q 20)))))
|
||||||
|
(list 10 20))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"conda-all-fail"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(conda ((== 1 2)) ((== 3 4))))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(mk-test "conda-no-clauses" (run* q (conda)) (list))
|
||||||
|
|
||||||
|
;; --- conda DIFFERS from condu: conda keeps all head answers ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"conda-vs-condu-divergence"
|
||||||
|
(list
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(conda
|
||||||
|
((mk-disj (== q 1) (== q 2)))
|
||||||
|
((== q 100))))
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(condu
|
||||||
|
((mk-disj (== q 1) (== q 2)))
|
||||||
|
((== q 100)))))
|
||||||
|
(list (list 1 2) (list 1)))
|
||||||
|
|
||||||
|
;; --- conda head's rest-goals run on every head answer ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"conda-rest-goals-run-on-all-answers"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(fresh
|
||||||
|
(x r)
|
||||||
|
(conda
|
||||||
|
((mk-disj (== x 1) (== x 2))
|
||||||
|
(== r (list :tag x))))
|
||||||
|
(== q r)))
|
||||||
|
(list (list :tag 1) (list :tag 2)))
|
||||||
|
|
||||||
|
;; --- if rest-goals fail on a head answer, that head answer is filtered;
|
||||||
|
;; the clause does not fall through to next clauses (per soft-cut). ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"conda-rest-fails-no-fallthrough"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(conda
|
||||||
|
((mk-disj (== q 1) (== q 2)) (== q 99))
|
||||||
|
((== q 200))))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(mk-tests-run!)
|
||||||
89
lib/minikanren/tests/conde.sx
Normal file
89
lib/minikanren/tests/conde.sx
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
;; lib/minikanren/tests/conde.sx — Phase 2 piece C tests for `conde`.
|
||||||
|
;;
|
||||||
|
;; Note on ordering: conde clauses are wrapped in Zzz (inverse-eta delay),
|
||||||
|
;; so applying the conde goal to a substitution returns thunks. mk-mplus
|
||||||
|
;; suspends-and-swaps when its left operand is paused, giving fair
|
||||||
|
;; interleaving — this is exactly what makes recursive relations work,
|
||||||
|
;; but it does mean conde answers can interleave rather than appear in
|
||||||
|
;; strict left-to-right clause order.
|
||||||
|
|
||||||
|
;; --- single-clause conde ≡ conj of clause body ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"conde-one-clause"
|
||||||
|
(let ((q (mk-var "q"))) (run* q (conde ((== q 7)))))
|
||||||
|
(list 7))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"conde-one-clause-multi-goals"
|
||||||
|
(let
|
||||||
|
((q (mk-var "q")))
|
||||||
|
(run* q (conde ((fresh (x) (== x 5) (== q (list x x)))))))
|
||||||
|
(list (list 5 5)))
|
||||||
|
|
||||||
|
;; --- multi-clause: produces one row per clause (interleaved) ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"conde-three-clauses-as-set"
|
||||||
|
(let
|
||||||
|
((qs (run* q (conde ((== q 1)) ((== q 2)) ((== q 3))))))
|
||||||
|
(and
|
||||||
|
(= (len qs) 3)
|
||||||
|
(and
|
||||||
|
(some (fn (x) (= x 1)) qs)
|
||||||
|
(and
|
||||||
|
(some (fn (x) (= x 2)) qs)
|
||||||
|
(some (fn (x) (= x 3)) qs)))))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"conde-mixed-success-failure-as-set"
|
||||||
|
(let
|
||||||
|
((qs (run* q (conde ((== q "a")) ((== 1 2)) ((== q "b"))))))
|
||||||
|
(and
|
||||||
|
(= (len qs) 2)
|
||||||
|
(and (some (fn (x) (= x "a")) qs) (some (fn (x) (= x "b")) qs))))
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; --- conde with conjuncts inside clauses ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"conde-clause-conj-as-set"
|
||||||
|
(let
|
||||||
|
((rows (run* q (fresh (x y) (conde ((== x 1) (== y 10)) ((== x 2) (== y 20))) (== q (list x y))))))
|
||||||
|
(and
|
||||||
|
(= (len rows) 2)
|
||||||
|
(and
|
||||||
|
(some (fn (r) (= r (list 1 10))) rows)
|
||||||
|
(some (fn (r) (= r (list 2 20))) rows))))
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; --- nested conde ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"conde-nested-yields-three"
|
||||||
|
(let
|
||||||
|
((qs (run* q (conde ((conde ((== q 1)) ((== q 2)))) ((== q 3))))))
|
||||||
|
(and
|
||||||
|
(= (len qs) 3)
|
||||||
|
(and
|
||||||
|
(some (fn (x) (= x 1)) qs)
|
||||||
|
(and
|
||||||
|
(some (fn (x) (= x 2)) qs)
|
||||||
|
(some (fn (x) (= x 3)) qs)))))
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; --- conde all clauses fail → empty stream ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"conde-all-fail"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(conde ((== 1 2)) ((== 3 4))))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
;; --- empty conde: no clauses ⇒ fail ---
|
||||||
|
|
||||||
|
(mk-test "conde-no-clauses" (run* q (conde)) (list))
|
||||||
|
|
||||||
|
(mk-tests-run!)
|
||||||
86
lib/minikanren/tests/condu.sx
Normal file
86
lib/minikanren/tests/condu.sx
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
;; lib/minikanren/tests/condu.sx — Phase 2 piece D tests for `onceo` and `condu`.
|
||||||
|
|
||||||
|
;; --- onceo: at most one answer ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"onceo-single-success-passes-through"
|
||||||
|
(let
|
||||||
|
((q (mk-var "q")))
|
||||||
|
(let
|
||||||
|
((res (stream-take 5 ((onceo (== q 7)) empty-s))))
|
||||||
|
(map (fn (s) (mk-walk q s)) res)))
|
||||||
|
(list 7))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"onceo-multi-success-trimmed-to-one"
|
||||||
|
(let
|
||||||
|
((q (mk-var "q")))
|
||||||
|
(let
|
||||||
|
((res (stream-take 5 ((onceo (mk-disj (== q 1) (== q 2) (== q 3))) empty-s))))
|
||||||
|
(map (fn (s) (mk-walk q s)) res)))
|
||||||
|
(list 1))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"onceo-failure-stays-failure"
|
||||||
|
((onceo (== 1 2)) empty-s)
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"onceo-conde-trimmed"
|
||||||
|
(let
|
||||||
|
((q (mk-var "q")))
|
||||||
|
(let
|
||||||
|
((res (stream-take 5 ((onceo (conde ((== q "a")) ((== q "b")))) empty-s))))
|
||||||
|
(map (fn (s) (mk-walk q s)) res)))
|
||||||
|
(list "a"))
|
||||||
|
|
||||||
|
;; --- condu: first clause with successful head wins ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"condu-first-clause-wins"
|
||||||
|
(let
|
||||||
|
((q (mk-var "q")))
|
||||||
|
(let
|
||||||
|
((res (stream-take 10 ((condu ((== q 1)) ((== q 2))) empty-s))))
|
||||||
|
(map (fn (s) (mk-walk q s)) res)))
|
||||||
|
(list 1))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"condu-skips-failing-head"
|
||||||
|
(let
|
||||||
|
((q (mk-var "q")))
|
||||||
|
(let
|
||||||
|
((res (stream-take 10 ((condu ((== 1 2)) ((== q 100)) ((== q 200))) empty-s))))
|
||||||
|
(map (fn (s) (mk-walk q s)) res)))
|
||||||
|
(list 100))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"condu-all-fail-empty"
|
||||||
|
((condu ((== 1 2)) ((== 3 4)))
|
||||||
|
empty-s)
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(mk-test "condu-empty-clauses-fail" ((condu) empty-s) (list))
|
||||||
|
|
||||||
|
;; --- condu commits head's first answer; rest-goals can still backtrack
|
||||||
|
;; within that committed substitution but cannot revisit other heads. ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"condu-head-onceo-rest-runs"
|
||||||
|
(let
|
||||||
|
((q (mk-var "q")) (r (mk-var "r")))
|
||||||
|
(let
|
||||||
|
((res (stream-take 10 ((condu ((mk-disj (== q 1) (== q 2)) (== r 99))) empty-s))))
|
||||||
|
(map (fn (s) (list (mk-walk q s) (mk-walk r s))) res)))
|
||||||
|
(list (list 1 99)))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"condu-rest-goals-can-fail-the-clause"
|
||||||
|
(let
|
||||||
|
((q (mk-var "q")))
|
||||||
|
(let
|
||||||
|
((res (stream-take 10 ((condu ((== q 1) (== 2 3)) ((== q 99))) empty-s))))
|
||||||
|
(map (fn (s) (mk-walk q s)) res)))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(mk-tests-run!)
|
||||||
75
lib/minikanren/tests/fd.sx
Normal file
75
lib/minikanren/tests/fd.sx
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
;; lib/minikanren/tests/fd.sx — Phase 6 piece A: ino + all-distincto.
|
||||||
|
|
||||||
|
;; --- ino ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"ino-element-in-domain"
|
||||||
|
(run* q (ino q (list 1 2 3)))
|
||||||
|
(list 1 2 3))
|
||||||
|
|
||||||
|
(mk-test "ino-empty-domain" (run* q (ino q (list))) (list))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"ino-singleton-domain"
|
||||||
|
(run* q (ino q (list 42)))
|
||||||
|
(list 42))
|
||||||
|
|
||||||
|
;; --- all-distincto ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"all-distincto-empty"
|
||||||
|
(run* q (all-distincto (list)))
|
||||||
|
(list (make-symbol "_.0")))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"all-distincto-singleton"
|
||||||
|
(run* q (all-distincto (list 1)))
|
||||||
|
(list (make-symbol "_.0")))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"all-distincto-distinct-three"
|
||||||
|
(run* q (all-distincto (list 1 2 3)))
|
||||||
|
(list (make-symbol "_.0")))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"all-distincto-duplicate-fails"
|
||||||
|
(run* q (all-distincto (list 1 2 1)))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"all-distincto-adjacent-duplicate-fails"
|
||||||
|
(run* q (all-distincto (list 1 1 2)))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
;; --- ino + all-distincto: classic enumerate-all-permutations ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"fd-puzzle-three-distinct-from-domain"
|
||||||
|
(let
|
||||||
|
((perms (run* q (fresh (a b c) (== q (list a b c)) (ino a (list 1 2 3)) (ino b (list 1 2 3)) (ino c (list 1 2 3)) (all-distincto (list a b c))))))
|
||||||
|
(and
|
||||||
|
(= (len perms) 6)
|
||||||
|
(and
|
||||||
|
(some (fn (p) (= p (list 1 2 3))) perms)
|
||||||
|
(and
|
||||||
|
(some
|
||||||
|
(fn (p) (= p (list 1 3 2)))
|
||||||
|
perms)
|
||||||
|
(and
|
||||||
|
(some
|
||||||
|
(fn (p) (= p (list 2 1 3)))
|
||||||
|
perms)
|
||||||
|
(and
|
||||||
|
(some
|
||||||
|
(fn (p) (= p (list 2 3 1)))
|
||||||
|
perms)
|
||||||
|
(and
|
||||||
|
(some
|
||||||
|
(fn (p) (= p (list 3 1 2)))
|
||||||
|
perms)
|
||||||
|
(some
|
||||||
|
(fn (p) (= p (list 3 2 1)))
|
||||||
|
perms))))))))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(mk-tests-run!)
|
||||||
101
lib/minikanren/tests/fresh.sx
Normal file
101
lib/minikanren/tests/fresh.sx
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
;; lib/minikanren/tests/fresh.sx — Phase 2 piece B tests for `fresh`.
|
||||||
|
|
||||||
|
;; --- empty fresh: pure goal grouping ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"fresh-empty-vars-equiv-conj"
|
||||||
|
(stream-take 5 ((fresh () (== 1 1)) empty-s))
|
||||||
|
(list empty-s))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"fresh-empty-vars-no-goals-is-succeed"
|
||||||
|
(stream-take 5 ((fresh ()) empty-s))
|
||||||
|
(list empty-s))
|
||||||
|
|
||||||
|
;; --- single var ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"fresh-one-var-bound"
|
||||||
|
(let
|
||||||
|
((s (first (stream-take 5 ((fresh (x) (== x 7)) empty-s)))))
|
||||||
|
(first (vals s)))
|
||||||
|
7)
|
||||||
|
|
||||||
|
;; --- multiple vars + multiple goals ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"fresh-two-vars-three-goals"
|
||||||
|
(let
|
||||||
|
((q (mk-var "q"))
|
||||||
|
(g
|
||||||
|
(fresh
|
||||||
|
(x y)
|
||||||
|
(== x 10)
|
||||||
|
(== y 20)
|
||||||
|
(== q (list x y)))))
|
||||||
|
(mk-walk* q (first (stream-take 5 (g empty-s)))))
|
||||||
|
(list 10 20))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"fresh-three-vars"
|
||||||
|
(let
|
||||||
|
((q (mk-var "q"))
|
||||||
|
(g
|
||||||
|
(fresh
|
||||||
|
(a b c)
|
||||||
|
(== a 1)
|
||||||
|
(== b 2)
|
||||||
|
(== c 3)
|
||||||
|
(== q (list a b c)))))
|
||||||
|
(mk-walk* q (first (stream-take 5 (g empty-s)))))
|
||||||
|
(list 1 2 3))
|
||||||
|
|
||||||
|
;; --- fresh interacts with disj ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"fresh-with-disj"
|
||||||
|
(let
|
||||||
|
((q (mk-var "q")))
|
||||||
|
(let
|
||||||
|
((g (fresh (x) (mk-disj (== x 1) (== x 2)) (== q x))))
|
||||||
|
(let
|
||||||
|
((res (stream-take 5 (g empty-s))))
|
||||||
|
(map (fn (s) (mk-walk q s)) res))))
|
||||||
|
(list 1 2))
|
||||||
|
|
||||||
|
;; --- nested fresh ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"fresh-nested"
|
||||||
|
(let
|
||||||
|
((q (mk-var "q"))
|
||||||
|
(g
|
||||||
|
(fresh
|
||||||
|
(x)
|
||||||
|
(fresh
|
||||||
|
(y)
|
||||||
|
(== x 1)
|
||||||
|
(== y 2)
|
||||||
|
(== q (list x y))))))
|
||||||
|
(mk-walk* q (first (stream-take 5 (g empty-s)))))
|
||||||
|
(list 1 2))
|
||||||
|
|
||||||
|
;; --- call-fresh (functional alternative) ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"call-fresh-binds-and-walks"
|
||||||
|
(let
|
||||||
|
((s (first (stream-take 5 ((call-fresh (fn (x) (== x 99))) empty-s)))))
|
||||||
|
(first (vals s)))
|
||||||
|
99)
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"call-fresh-distinct-from-outer-vars"
|
||||||
|
(let
|
||||||
|
((q (mk-var "q")))
|
||||||
|
(let
|
||||||
|
((g (call-fresh (fn (x) (mk-conj (== x 5) (== q (list x x)))))))
|
||||||
|
(mk-walk* q (first (stream-take 5 (g empty-s))))))
|
||||||
|
(list 5 5))
|
||||||
|
|
||||||
|
(mk-tests-run!)
|
||||||
260
lib/minikanren/tests/goals.sx
Normal file
260
lib/minikanren/tests/goals.sx
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
;; lib/minikanren/tests/goals.sx — Phase 2 tests for stream.sx + goals.sx.
|
||||||
|
;;
|
||||||
|
;; Streams use a tagged shape internally (`(:s head tail)`) so that mature
|
||||||
|
;; cells can have thunk tails — SX has no improper pairs. Test assertions
|
||||||
|
;; therefore stream-take into a plain SX list, or check goal effects via
|
||||||
|
;; mk-walk on the resulting subst, instead of inspecting raw streams.
|
||||||
|
|
||||||
|
;; --- stream-take base cases (input streams use s-cons / mzero) ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"stream-take-zero-from-mature"
|
||||||
|
(stream-take 0 (s-cons (empty-subst) mzero))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(mk-test "stream-take-from-mzero" (stream-take 5 mzero) (list))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"stream-take-mature-pair"
|
||||||
|
(stream-take 5 (s-cons :a (s-cons :b mzero)))
|
||||||
|
(list :a :b))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"stream-take-fewer-than-available"
|
||||||
|
(stream-take 1 (s-cons :a (s-cons :b mzero)))
|
||||||
|
(list :a))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"stream-take-all-with-neg-1"
|
||||||
|
(stream-take -1 (s-cons :a (s-cons :b (s-cons :c mzero))))
|
||||||
|
(list :a :b :c))
|
||||||
|
|
||||||
|
;; --- stream-take forces immature thunks ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"stream-take-forces-thunk"
|
||||||
|
(stream-take 5 (fn () (s-cons :x mzero)))
|
||||||
|
(list :x))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"stream-take-forces-nested-thunks"
|
||||||
|
(stream-take 5 (fn () (fn () (s-cons :y mzero))))
|
||||||
|
(list :y))
|
||||||
|
|
||||||
|
;; --- mk-mplus interleaves ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"mplus-empty-left"
|
||||||
|
(stream-take 5 (mk-mplus mzero (s-cons :r mzero)))
|
||||||
|
(list :r))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"mplus-empty-right"
|
||||||
|
(stream-take 5 (mk-mplus (s-cons :l mzero) mzero))
|
||||||
|
(list :l))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"mplus-mature-mature"
|
||||||
|
(stream-take
|
||||||
|
5
|
||||||
|
(mk-mplus (s-cons :a (s-cons :b mzero)) (s-cons :c (s-cons :d mzero))))
|
||||||
|
(list :a :b :c :d))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"mplus-with-paused-left-swaps"
|
||||||
|
(stream-take
|
||||||
|
5
|
||||||
|
(mk-mplus
|
||||||
|
(fn () (s-cons :a (s-cons :b mzero)))
|
||||||
|
(s-cons :c (s-cons :d mzero))))
|
||||||
|
(list :c :d :a :b))
|
||||||
|
|
||||||
|
;; --- mk-bind ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"bind-empty-stream"
|
||||||
|
(stream-take 5 (mk-bind mzero (fn (s) (unit s))))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"bind-singleton-identity"
|
||||||
|
(stream-take
|
||||||
|
5
|
||||||
|
(mk-bind (s-cons 5 mzero) (fn (x) (unit x))))
|
||||||
|
(list 5))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"bind-flat-multi"
|
||||||
|
(stream-take
|
||||||
|
10
|
||||||
|
(mk-bind
|
||||||
|
(s-cons 1 (s-cons 2 mzero))
|
||||||
|
(fn (x) (s-cons x (s-cons (* x 10) mzero)))))
|
||||||
|
(list 1 10 2 20))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"bind-fail-prunes-some"
|
||||||
|
(stream-take
|
||||||
|
10
|
||||||
|
(mk-bind
|
||||||
|
(s-cons 1 (s-cons 2 (s-cons 3 mzero)))
|
||||||
|
(fn (x) (if (= x 2) mzero (unit x)))))
|
||||||
|
(list 1 3))
|
||||||
|
|
||||||
|
;; --- core goals: succeed / fail ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"succeed-yields-singleton"
|
||||||
|
(stream-take 5 (succeed empty-s))
|
||||||
|
(list empty-s))
|
||||||
|
|
||||||
|
(mk-test "fail-yields-mzero" (stream-take 5 (fail empty-s)) (list))
|
||||||
|
|
||||||
|
;; --- == ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"eq-ground-success"
|
||||||
|
(stream-take 5 ((== 1 1) empty-s))
|
||||||
|
(list empty-s))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"eq-ground-failure"
|
||||||
|
(stream-take 5 ((== 1 2) empty-s))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"eq-binds-var"
|
||||||
|
(let
|
||||||
|
((x (mk-var "x")))
|
||||||
|
(mk-walk
|
||||||
|
x
|
||||||
|
(first (stream-take 5 ((== x 7) empty-s)))))
|
||||||
|
7)
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"eq-list-success"
|
||||||
|
(let
|
||||||
|
((x (mk-var "x")))
|
||||||
|
(mk-walk
|
||||||
|
x
|
||||||
|
(first
|
||||||
|
(stream-take
|
||||||
|
5
|
||||||
|
((== x (list 1 2)) empty-s)))))
|
||||||
|
(list 1 2))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"eq-list-mismatch-fails"
|
||||||
|
(stream-take
|
||||||
|
5
|
||||||
|
((== (list 1 2) (list 1 3)) empty-s))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
;; --- conj2 / mk-conj ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"conj2-both-bind"
|
||||||
|
(let
|
||||||
|
((x (mk-var "x")) (y (mk-var "y")))
|
||||||
|
(let
|
||||||
|
((s (first (stream-take 5 ((conj2 (== x 1) (== y 2)) empty-s)))))
|
||||||
|
(list (mk-walk x s) (mk-walk y s))))
|
||||||
|
(list 1 2))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"conj2-conflict-empty"
|
||||||
|
(let
|
||||||
|
((x (mk-var "x")))
|
||||||
|
(stream-take
|
||||||
|
5
|
||||||
|
((conj2 (== x 1) (== x 2)) empty-s)))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"conj-empty-is-succeed"
|
||||||
|
(stream-take 5 ((mk-conj) empty-s))
|
||||||
|
(list empty-s))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"conj-single-is-goal"
|
||||||
|
(let
|
||||||
|
((x (mk-var "x")))
|
||||||
|
(mk-walk
|
||||||
|
x
|
||||||
|
(first
|
||||||
|
(stream-take 5 ((mk-conj (== x 99)) empty-s)))))
|
||||||
|
99)
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"conj-three-bindings"
|
||||||
|
(let
|
||||||
|
((x (mk-var "x")) (y (mk-var "y")) (z (mk-var "z")))
|
||||||
|
(let
|
||||||
|
((s (first (stream-take 5 ((mk-conj (== x 1) (== y 2) (== z 3)) empty-s)))))
|
||||||
|
(list (mk-walk x s) (mk-walk y s) (mk-walk z s))))
|
||||||
|
(list 1 2 3))
|
||||||
|
|
||||||
|
;; --- disj2 / mk-disj ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"disj2-both-succeed"
|
||||||
|
(let
|
||||||
|
((q (mk-var "q")))
|
||||||
|
(let
|
||||||
|
((res (stream-take 5 ((disj2 (== q 1) (== q 2)) empty-s))))
|
||||||
|
(map (fn (s) (mk-walk q s)) res)))
|
||||||
|
(list 1 2))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"disj2-fail-or-succeed"
|
||||||
|
(let
|
||||||
|
((q (mk-var "q")))
|
||||||
|
(let
|
||||||
|
((res (stream-take 5 ((disj2 fail (== q 5)) empty-s))))
|
||||||
|
(map (fn (s) (mk-walk q s)) res)))
|
||||||
|
(list 5))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"disj-empty-is-fail"
|
||||||
|
(stream-take 5 ((mk-disj) empty-s))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"disj-three-clauses"
|
||||||
|
(let
|
||||||
|
((q (mk-var "q")))
|
||||||
|
(let
|
||||||
|
((res (stream-take 5 ((mk-disj (== q "a") (== q "b") (== q "c")) empty-s))))
|
||||||
|
(map (fn (s) (mk-walk q s)) res)))
|
||||||
|
(list "a" "b" "c"))
|
||||||
|
|
||||||
|
;; --- conj/disj nesting ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"disj-of-conj"
|
||||||
|
(let
|
||||||
|
((x (mk-var "x")) (y (mk-var "y")))
|
||||||
|
(let
|
||||||
|
((res (stream-take 5 ((mk-disj (mk-conj (== x 1) (== y 2)) (mk-conj (== x 3) (== y 4))) empty-s))))
|
||||||
|
(map (fn (s) (list (mk-walk x s) (mk-walk y s))) res)))
|
||||||
|
(list (list 1 2) (list 3 4)))
|
||||||
|
|
||||||
|
;; --- ==-check ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"eq-check-no-occurs-fails"
|
||||||
|
(let
|
||||||
|
((x (mk-var "x")))
|
||||||
|
(stream-take 5 ((==-check x (list 1 x)) empty-s)))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"eq-check-no-occurs-non-occurring-succeeds"
|
||||||
|
(let
|
||||||
|
((x (mk-var "x")))
|
||||||
|
(mk-walk
|
||||||
|
x
|
||||||
|
(first (stream-take 5 ((==-check x 5) empty-s)))))
|
||||||
|
5)
|
||||||
|
|
||||||
|
(mk-tests-run!)
|
||||||
138
lib/minikanren/tests/matche.sx
Normal file
138
lib/minikanren/tests/matche.sx
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
;; lib/minikanren/tests/matche.sx — Phase 5 piece D tests for `matche`.
|
||||||
|
|
||||||
|
;; --- literal patterns ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"matche-literal-number"
|
||||||
|
(run* q (matche q (1 (== q 1))))
|
||||||
|
(list 1))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"matche-literal-string"
|
||||||
|
(run* q (matche q ("hello" (== q "hello"))))
|
||||||
|
(list "hello"))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"matche-literal-no-clause-matches"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(matche 7 (1 (== q :a)) (2 (== q :b))))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
;; --- variable patterns ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"matche-symbol-pattern"
|
||||||
|
(run* q (fresh (x) (== x 99) (matche x (a (== q a)))))
|
||||||
|
(list 99))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"matche-wildcard"
|
||||||
|
(run* q (fresh (x) (== x 7) (matche x (_ (== q :any)))))
|
||||||
|
(list :any))
|
||||||
|
|
||||||
|
;; --- list patterns ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"matche-empty-list"
|
||||||
|
(run* q (matche (list) (() (== q :ok))))
|
||||||
|
(list :ok))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"matche-pair-binds"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(fresh
|
||||||
|
(x)
|
||||||
|
(== x (list 1 2))
|
||||||
|
(matche x ((a b) (== q (list b a))))))
|
||||||
|
(list (list 2 1)))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"matche-triple-binds"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(fresh
|
||||||
|
(x)
|
||||||
|
(== x (list 1 2 3))
|
||||||
|
(matche x ((a b c) (== q (list :sum a b c))))))
|
||||||
|
(list (list :sum 1 2 3)))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"matche-mixed-literal-and-var"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(fresh
|
||||||
|
(x)
|
||||||
|
(== x (list 1 99 3))
|
||||||
|
(matche x ((1 m 3) (== q m)))))
|
||||||
|
(list 99))
|
||||||
|
|
||||||
|
;; --- multi-clause dispatch ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"matche-multi-clause-shape"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(fresh
|
||||||
|
(x)
|
||||||
|
(== x (list 5 6))
|
||||||
|
(matche
|
||||||
|
x
|
||||||
|
(() (== q :empty))
|
||||||
|
((a) (== q (list :one a)))
|
||||||
|
((a b) (== q (list :two a b))))))
|
||||||
|
(list (list :two 5 6)))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"matche-three-shapes-via-fresh"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(fresh
|
||||||
|
(x)
|
||||||
|
(matche
|
||||||
|
x
|
||||||
|
(() (== q :empty))
|
||||||
|
((a) (== q (list :one a)))
|
||||||
|
((a b) (== q (list :two a b))))))
|
||||||
|
(list
|
||||||
|
:empty (list :one (make-symbol "_.0"))
|
||||||
|
(list :two (make-symbol "_.0") (make-symbol "_.1"))))
|
||||||
|
|
||||||
|
;; --- nested patterns ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"matche-nested"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(fresh
|
||||||
|
(x)
|
||||||
|
(==
|
||||||
|
x
|
||||||
|
(list (list 1 2) (list 3 4)))
|
||||||
|
(matche x (((a b) (c d)) (== q (list a b c d))))))
|
||||||
|
(list (list 1 2 3 4)))
|
||||||
|
|
||||||
|
;; --- repeated var names create the same fresh var → must unify ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"matche-repeated-var-implies-equality"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(fresh
|
||||||
|
(x)
|
||||||
|
(== x (list 7 7))
|
||||||
|
(matche x ((a a) (== q a)))))
|
||||||
|
(list 7))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"matche-repeated-var-mismatch-fails"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(fresh
|
||||||
|
(x)
|
||||||
|
(== x (list 7 8))
|
||||||
|
(matche x ((a a) (== q a)))))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(mk-tests-run!)
|
||||||
50
lib/minikanren/tests/nafc.sx
Normal file
50
lib/minikanren/tests/nafc.sx
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
;; lib/minikanren/tests/nafc.sx — Phase 5 piece C tests for `nafc`.
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"nafc-failed-goal-succeeds"
|
||||||
|
(run* q (nafc (== 1 2)))
|
||||||
|
(list (make-symbol "_.0")))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"nafc-successful-goal-fails"
|
||||||
|
(run* q (nafc (== 1 1)))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"nafc-double-negation"
|
||||||
|
(run* q (nafc (nafc (== 1 1))))
|
||||||
|
(list (make-symbol "_.0")))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"nafc-with-conde-no-clauses-succeed"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(nafc
|
||||||
|
(conde ((== 1 2)) ((== 3 4)))))
|
||||||
|
(list (make-symbol "_.0")))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"nafc-with-conde-some-clause-succeeds-fails"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(nafc
|
||||||
|
(conde ((== 1 1)) ((== 3 4)))))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
;; --- composing nafc with == as a guard ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"nafc-as-guard"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(fresh (x) (== x 5) (nafc (== x 99)) (== q x)))
|
||||||
|
(list 5))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"nafc-guard-blocking"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(fresh (x) (== x 5) (nafc (== x 5)) (== q x)))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(mk-tests-run!)
|
||||||
119
lib/minikanren/tests/peano.sx
Normal file
119
lib/minikanren/tests/peano.sx
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
;; lib/minikanren/tests/peano.sx — Peano arithmetic.
|
||||||
|
;;
|
||||||
|
;; Builds Peano numbers via a host-side helper so tests stay readable.
|
||||||
|
;; (mk-nat 3) → (:s (:s (:s :z))).
|
||||||
|
|
||||||
|
(define
|
||||||
|
mk-nat
|
||||||
|
(fn (n) (if (= n 0) :z (list :s (mk-nat (- n 1))))))
|
||||||
|
|
||||||
|
;; --- zeroo ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"zeroo-zero-succeeds"
|
||||||
|
(run* q (zeroo :z))
|
||||||
|
(list (make-symbol "_.0")))
|
||||||
|
(mk-test
|
||||||
|
"zeroo-non-zero-fails"
|
||||||
|
(run* q (zeroo (mk-nat 1)))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
;; --- pluso forward ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"pluso-forward-2-3"
|
||||||
|
(run* q (pluso (mk-nat 2) (mk-nat 3) q))
|
||||||
|
(list (mk-nat 5)))
|
||||||
|
|
||||||
|
(mk-test "pluso-forward-zero-zero" (run* q (pluso :z :z q)) (list :z))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"pluso-forward-zero-n"
|
||||||
|
(run* q (pluso :z (mk-nat 4) q))
|
||||||
|
(list (mk-nat 4)))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"pluso-forward-n-zero"
|
||||||
|
(run* q (pluso (mk-nat 4) :z q))
|
||||||
|
(list (mk-nat 4)))
|
||||||
|
|
||||||
|
;; --- pluso backward ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"pluso-recover-augend"
|
||||||
|
(run* q (pluso q (mk-nat 2) (mk-nat 5)))
|
||||||
|
(list (mk-nat 3)))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"pluso-recover-addend"
|
||||||
|
(run* q (pluso (mk-nat 2) q (mk-nat 5)))
|
||||||
|
(list (mk-nat 3)))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"pluso-enumerate-pairs-summing-to-3"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(fresh (a b) (pluso a b (mk-nat 3)) (== q (list a b))))
|
||||||
|
(list
|
||||||
|
(list :z (mk-nat 3))
|
||||||
|
(list (mk-nat 1) (mk-nat 2))
|
||||||
|
(list (mk-nat 2) (mk-nat 1))
|
||||||
|
(list (mk-nat 3) :z)))
|
||||||
|
|
||||||
|
;; --- minuso ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"minuso-5-2-3"
|
||||||
|
(run* q (minuso (mk-nat 5) (mk-nat 2) q))
|
||||||
|
(list (mk-nat 3)))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"minuso-n-n-zero"
|
||||||
|
(run* q (minuso (mk-nat 7) (mk-nat 7) q))
|
||||||
|
(list :z))
|
||||||
|
|
||||||
|
;; --- *o ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"times-2-3"
|
||||||
|
(run* q (*o (mk-nat 2) (mk-nat 3) q))
|
||||||
|
(list (mk-nat 6)))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"times-zero-anything-zero"
|
||||||
|
(run* q (*o :z (mk-nat 99) q))
|
||||||
|
(list :z))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"times-3-4"
|
||||||
|
(run* q (*o (mk-nat 3) (mk-nat 4) q))
|
||||||
|
(list (mk-nat 12)))
|
||||||
|
|
||||||
|
;; --- lteo / lto ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"lteo-success"
|
||||||
|
(run 1 q (lteo (mk-nat 2) (mk-nat 5)))
|
||||||
|
(list (make-symbol "_.0")))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"lteo-equal-success"
|
||||||
|
(run 1 q (lteo (mk-nat 3) (mk-nat 3)))
|
||||||
|
(list (make-symbol "_.0")))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"lteo-greater-fails"
|
||||||
|
(run* q (lteo (mk-nat 5) (mk-nat 2)))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"lto-strict-success"
|
||||||
|
(run 1 q (lto (mk-nat 2) (mk-nat 5)))
|
||||||
|
(list (make-symbol "_.0")))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"lto-equal-fails"
|
||||||
|
(run* q (lto (mk-nat 3) (mk-nat 3)))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(mk-tests-run!)
|
||||||
60
lib/minikanren/tests/project.sx
Normal file
60
lib/minikanren/tests/project.sx
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
;; lib/minikanren/tests/project.sx — Phase 5 piece B tests for `project`.
|
||||||
|
|
||||||
|
;; --- project rebinds vars to ground values for SX use ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"project-square-via-host"
|
||||||
|
(run* q (fresh (n) (== n 5) (project (n) (== q (* n n)))))
|
||||||
|
(list 25))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"project-multi-vars"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(fresh
|
||||||
|
(a b)
|
||||||
|
(== a 3)
|
||||||
|
(== b 4)
|
||||||
|
(project (a b) (== q (+ a b)))))
|
||||||
|
(list 7))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"project-with-string-host-op"
|
||||||
|
(run* q (fresh (s) (== s "hello") (project (s) (== q (str s "!")))))
|
||||||
|
(list "hello!"))
|
||||||
|
|
||||||
|
;; --- project nested inside conde ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"project-inside-conde"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(fresh
|
||||||
|
(n)
|
||||||
|
(conde ((== n 3)) ((== n 4)))
|
||||||
|
(project (n) (== q (* n 10)))))
|
||||||
|
(list 30 40))
|
||||||
|
|
||||||
|
;; --- project body can be multiple goals (mk-conj'd) ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"project-multi-goal-body"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(fresh
|
||||||
|
(n)
|
||||||
|
(== n 7)
|
||||||
|
(project (n) (== q (+ n 1)) (== q (+ n 1)))))
|
||||||
|
(list 8))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"project-multi-goal-body-conflict"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(fresh
|
||||||
|
(n)
|
||||||
|
(== n 7)
|
||||||
|
(project (n) (== q (+ n 1)) (== q (+ n 2)))))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(mk-tests-run!)
|
||||||
291
lib/minikanren/tests/relations.sx
Normal file
291
lib/minikanren/tests/relations.sx
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
;; lib/minikanren/tests/relations.sx — Phase 4 standard relations.
|
||||||
|
;;
|
||||||
|
;; Includes the classic miniKanren canaries: appendo forwards / backwards /
|
||||||
|
;; bidirectionally, membero, listo enumeration.
|
||||||
|
|
||||||
|
;; --- nullo / pairo ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"nullo-empty-succeeds"
|
||||||
|
(run* q (nullo (list)))
|
||||||
|
(list (make-symbol "_.0")))
|
||||||
|
|
||||||
|
(mk-test "nullo-non-empty-fails" (run* q (nullo (list 1))) (list))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"pairo-non-empty-succeeds"
|
||||||
|
(run* q (pairo (list 1 2)))
|
||||||
|
(list (make-symbol "_.0")))
|
||||||
|
|
||||||
|
(mk-test "pairo-empty-fails" (run* q (pairo (list))) (list))
|
||||||
|
|
||||||
|
;; --- caro / cdro / firsto / resto ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"caro-extracts-head"
|
||||||
|
(run* q (caro (list 1 2 3) q))
|
||||||
|
(list 1))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"cdro-extracts-tail"
|
||||||
|
(run* q (cdro (list 1 2 3) q))
|
||||||
|
(list (list 2 3)))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"firsto-alias-of-caro"
|
||||||
|
(run* q (firsto (list 10 20) q))
|
||||||
|
(list 10))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"resto-alias-of-cdro"
|
||||||
|
(run* q (resto (list 10 20) q))
|
||||||
|
(list (list 20)))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"caro-cdro-build"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(fresh
|
||||||
|
(h t)
|
||||||
|
(caro (list 1 2 3) h)
|
||||||
|
(cdro (list 1 2 3) t)
|
||||||
|
(== q (list h t))))
|
||||||
|
(list (list 1 (list 2 3))))
|
||||||
|
|
||||||
|
;; --- conso ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"conso-forward"
|
||||||
|
(run* q (conso 0 (list 1 2 3) q))
|
||||||
|
(list (list 0 1 2 3)))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"conso-extract-head"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(conso
|
||||||
|
q
|
||||||
|
(list 2 3)
|
||||||
|
(list 1 2 3)))
|
||||||
|
(list 1))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"conso-extract-tail"
|
||||||
|
(run* q (conso 1 q (list 1 2 3)))
|
||||||
|
(list (list 2 3)))
|
||||||
|
|
||||||
|
;; --- listo ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"listo-empty-succeeds"
|
||||||
|
(run* q (listo (list)))
|
||||||
|
(list (make-symbol "_.0")))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"listo-finite-list-succeeds"
|
||||||
|
(run* q (listo (list 1 2 3)))
|
||||||
|
(list (make-symbol "_.0")))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"listo-enumerates-shapes"
|
||||||
|
(run 3 q (listo q))
|
||||||
|
(list
|
||||||
|
(list)
|
||||||
|
(list (make-symbol "_.0"))
|
||||||
|
(list (make-symbol "_.0") (make-symbol "_.1"))))
|
||||||
|
|
||||||
|
;; --- appendo: the canary ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"appendo-forward-simple"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(appendo (list 1 2) (list 3 4) q))
|
||||||
|
(list (list 1 2 3 4)))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"appendo-forward-empty-l"
|
||||||
|
(run* q (appendo (list) (list 3 4) q))
|
||||||
|
(list (list 3 4)))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"appendo-forward-empty-s"
|
||||||
|
(run* q (appendo (list 1 2) (list) q))
|
||||||
|
(list (list 1 2)))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"appendo-recovers-tail"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(appendo
|
||||||
|
(list 1 2)
|
||||||
|
q
|
||||||
|
(list 1 2 3 4)))
|
||||||
|
(list (list 3 4)))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"appendo-recovers-prefix"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(appendo
|
||||||
|
q
|
||||||
|
(list 3 4)
|
||||||
|
(list 1 2 3 4)))
|
||||||
|
(list (list 1 2)))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"appendo-backward-all-splits"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(fresh
|
||||||
|
(l s)
|
||||||
|
(appendo l s (list 1 2 3))
|
||||||
|
(== q (list l s))))
|
||||||
|
(list
|
||||||
|
(list (list) (list 1 2 3))
|
||||||
|
(list (list 1) (list 2 3))
|
||||||
|
(list (list 1 2) (list 3))
|
||||||
|
(list (list 1 2 3) (list))))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"appendo-empty-empty-empty"
|
||||||
|
(run* q (appendo (list) (list) q))
|
||||||
|
(list (list)))
|
||||||
|
|
||||||
|
;; --- membero ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"membero-element-present"
|
||||||
|
(run
|
||||||
|
1
|
||||||
|
q
|
||||||
|
(membero 2 (list 1 2 3)))
|
||||||
|
(list (make-symbol "_.0")))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"membero-element-absent-empty"
|
||||||
|
(run* q (membero 99 (list 1 2 3)))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"membero-enumerates"
|
||||||
|
(run* q (membero q (list "a" "b" "c")))
|
||||||
|
(list "a" "b" "c"))
|
||||||
|
|
||||||
|
;; --- reverseo ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"reverseo-forward"
|
||||||
|
(run* q (reverseo (list 1 2 3) q))
|
||||||
|
(list (list 3 2 1)))
|
||||||
|
|
||||||
|
(mk-test "reverseo-empty" (run* q (reverseo (list) q)) (list (list)))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"reverseo-singleton"
|
||||||
|
(run* q (reverseo (list 42) q))
|
||||||
|
(list (list 42)))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"reverseo-five"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(reverseo (list 1 2 3 4 5) q))
|
||||||
|
(list (list 5 4 3 2 1)))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"reverseo-backward-one"
|
||||||
|
(run 1 q (reverseo q (list 1 2 3)))
|
||||||
|
(list (list 3 2 1)))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"reverseo-round-trip"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(fresh (mid) (reverseo (list "a" "b" "c") mid) (reverseo mid q)))
|
||||||
|
(list (list "a" "b" "c")))
|
||||||
|
|
||||||
|
;; --- lengtho (Peano-style) ---
|
||||||
|
|
||||||
|
(mk-test "lengtho-empty-is-z" (run* q (lengtho (list) q)) (list :z))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"lengtho-of-3"
|
||||||
|
(run* q (lengtho (list "a" "b" "c") q))
|
||||||
|
(list (list :s (list :s (list :s :z)))))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"lengtho-empty-from-zero"
|
||||||
|
(run 1 q (lengtho q :z))
|
||||||
|
(list (list)))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"lengtho-enumerates-of-length-2"
|
||||||
|
(run 1 q (lengtho q (list :s (list :s :z))))
|
||||||
|
(list (list (make-symbol "_.0") (make-symbol "_.1"))))
|
||||||
|
|
||||||
|
;; --- inserto ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"inserto-front"
|
||||||
|
(run* q (inserto 0 (list 1 2 3) q))
|
||||||
|
(list
|
||||||
|
(list 0 1 2 3)
|
||||||
|
(list 1 0 2 3)
|
||||||
|
(list 1 2 0 3)
|
||||||
|
(list 1 2 3 0)))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"inserto-empty"
|
||||||
|
(run* q (inserto 0 (list) q))
|
||||||
|
(list (list 0)))
|
||||||
|
|
||||||
|
;; --- permuteo ---
|
||||||
|
|
||||||
|
(mk-test "permuteo-empty" (run* q (permuteo (list) q)) (list (list)))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"permuteo-singleton"
|
||||||
|
(run* q (permuteo (list 42) q))
|
||||||
|
(list (list 42)))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"permuteo-two"
|
||||||
|
(run* q (permuteo (list 1 2) q))
|
||||||
|
(list (list 1 2) (list 2 1)))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"permuteo-three-as-set"
|
||||||
|
(let
|
||||||
|
((perms (run* q (permuteo (list 1 2 3) q))))
|
||||||
|
(and
|
||||||
|
(= (len perms) 6)
|
||||||
|
(and
|
||||||
|
(some (fn (p) (= p (list 1 2 3))) perms)
|
||||||
|
(and
|
||||||
|
(some
|
||||||
|
(fn (p) (= p (list 2 1 3)))
|
||||||
|
perms)
|
||||||
|
(and
|
||||||
|
(some
|
||||||
|
(fn (p) (= p (list 1 3 2)))
|
||||||
|
perms)
|
||||||
|
(and
|
||||||
|
(some
|
||||||
|
(fn (p) (= p (list 2 3 1)))
|
||||||
|
perms)
|
||||||
|
(and
|
||||||
|
(some
|
||||||
|
(fn (p) (= p (list 3 1 2)))
|
||||||
|
perms)
|
||||||
|
(some
|
||||||
|
(fn (p) (= p (list 3 2 1)))
|
||||||
|
perms))))))))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"permuteo-backward-finds-input"
|
||||||
|
(run 1 q (permuteo q (list "a" "b" "c")))
|
||||||
|
(list (list "a" "b" "c")))
|
||||||
|
|
||||||
|
(mk-tests-run!)
|
||||||
114
lib/minikanren/tests/run.sx
Normal file
114
lib/minikanren/tests/run.sx
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
;; lib/minikanren/tests/run.sx — Phase 3 tests for run* / run / reify.
|
||||||
|
|
||||||
|
;; --- canonical TRS one-liners ---
|
||||||
|
|
||||||
|
(mk-test "run*-eq-one" (run* q (== q 1)) (list 1))
|
||||||
|
(mk-test "run*-eq-string" (run* q (== q "hello")) (list "hello"))
|
||||||
|
(mk-test "run*-eq-symbol" (run* q (== q (quote sym))) (list (quote sym)))
|
||||||
|
(mk-test "run*-fail-empty" (run* q (== 1 2)) (list))
|
||||||
|
|
||||||
|
;; --- run with a count ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"run-3-of-many"
|
||||||
|
(run
|
||||||
|
3
|
||||||
|
q
|
||||||
|
(conde
|
||||||
|
((== q 1))
|
||||||
|
((== q 2))
|
||||||
|
((== q 3))
|
||||||
|
((== q 4))
|
||||||
|
((== q 5))))
|
||||||
|
(list 1 2 3))
|
||||||
|
|
||||||
|
(mk-test "run-zero-empty" (run 0 q (== q 1)) (list))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"run-1-takes-one"
|
||||||
|
(run 1 q (conde ((== q "a")) ((== q "b"))))
|
||||||
|
(list "a"))
|
||||||
|
|
||||||
|
;; --- reification: unbound vars get _.N left-to-right ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"reify-single-unbound"
|
||||||
|
(run* q (fresh (x) (== q x)))
|
||||||
|
(list (make-symbol "_.0")))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"reify-pair-unbound"
|
||||||
|
(run* q (fresh (x y) (== q (list x y))))
|
||||||
|
(list (list (make-symbol "_.0") (make-symbol "_.1"))))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"reify-mixed-bound-unbound"
|
||||||
|
(run* q (fresh (x y) (== q (list 1 x 2 y))))
|
||||||
|
(list
|
||||||
|
(list 1 (make-symbol "_.0") 2 (make-symbol "_.1"))))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"reify-shared-unbound-same-name"
|
||||||
|
(run* q (fresh (x) (== q (list x x))))
|
||||||
|
(list (list (make-symbol "_.0") (make-symbol "_.0"))))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"reify-distinct-unbound-distinct-names"
|
||||||
|
(run* q (fresh (x y) (== q (list x y x y))))
|
||||||
|
(list
|
||||||
|
(list
|
||||||
|
(make-symbol "_.0")
|
||||||
|
(make-symbol "_.1")
|
||||||
|
(make-symbol "_.0")
|
||||||
|
(make-symbol "_.1"))))
|
||||||
|
|
||||||
|
;; --- conde + run* ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"run*-conde-three"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(conde ((== q 1)) ((== q 2)) ((== q 3))))
|
||||||
|
(list 1 2 3))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"run*-conde-fresh-mix"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(conde ((fresh (x) (== q (list 1 x)))) ((== q "ground"))))
|
||||||
|
(list (list 1 (make-symbol "_.0")) "ground"))
|
||||||
|
|
||||||
|
;; --- run* + conjunction ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"run*-conj-binds-q"
|
||||||
|
(run* q (fresh (x) (== x 5) (== q (list x x))))
|
||||||
|
(list (list 5 5)))
|
||||||
|
|
||||||
|
;; --- run* + condu ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"run*-condu-first-wins"
|
||||||
|
(run* q (condu ((== q 1)) ((== q 2))))
|
||||||
|
(list 1))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"run*-onceo-trim"
|
||||||
|
(run* q (onceo (conde ((== q "a")) ((== q "b")))))
|
||||||
|
(list "a"))
|
||||||
|
|
||||||
|
;; --- multi-goal run ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"run*-three-goals"
|
||||||
|
(run*
|
||||||
|
q
|
||||||
|
(fresh
|
||||||
|
(x y z)
|
||||||
|
(== x 1)
|
||||||
|
(== y 2)
|
||||||
|
(== z 3)
|
||||||
|
(== q (list x y z))))
|
||||||
|
(list (list 1 2 3)))
|
||||||
|
|
||||||
|
(mk-tests-run!)
|
||||||
293
lib/minikanren/tests/unify.sx
Normal file
293
lib/minikanren/tests/unify.sx
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
;; lib/minikanren/tests/unify.sx — Phase 1 tests for unify.sx.
|
||||||
|
;;
|
||||||
|
;; Loads into a session that already has lib/guest/match.sx and
|
||||||
|
;; lib/minikanren/unify.sx defined. Tests are top-level forms.
|
||||||
|
;; Call (mk-tests-run!) afterwards to get the totals.
|
||||||
|
;;
|
||||||
|
;; Note: SX dict equality is reference-based, so tests check the *effect*
|
||||||
|
;; of a unification (success/failure flag, or walked bindings) rather than
|
||||||
|
;; the raw substitution dict.
|
||||||
|
|
||||||
|
(define mk-test-pass 0)
|
||||||
|
(define mk-test-fail 0)
|
||||||
|
(define mk-test-fails (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
mk-test
|
||||||
|
(fn
|
||||||
|
(name actual expected)
|
||||||
|
(if
|
||||||
|
(= actual expected)
|
||||||
|
(set! mk-test-pass (+ mk-test-pass 1))
|
||||||
|
(begin
|
||||||
|
(set! mk-test-fail (+ mk-test-fail 1))
|
||||||
|
(append! mk-test-fails {:name name :expected expected :actual actual})))))
|
||||||
|
|
||||||
|
(define mk-tests-run! (fn () {:total (+ mk-test-pass mk-test-fail) :passed mk-test-pass :failed mk-test-fail :fails mk-test-fails}))
|
||||||
|
|
||||||
|
(define mk-unified? (fn (s) (if (= s nil) false true)))
|
||||||
|
|
||||||
|
;; --- fresh variable construction ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"make-var-distinct"
|
||||||
|
(let ((a (make-var)) (b (make-var))) (= (var-name a) (var-name b)))
|
||||||
|
false)
|
||||||
|
|
||||||
|
(mk-test "make-var-is-var" (mk-var? (make-var)) true)
|
||||||
|
(mk-test "var?-num" (mk-var? 5) false)
|
||||||
|
(mk-test "var?-list" (mk-var? (list 1 2)) false)
|
||||||
|
(mk-test "var?-string" (mk-var? "hi") false)
|
||||||
|
(mk-test "var?-empty" (mk-var? (list)) false)
|
||||||
|
(mk-test "var?-bool" (mk-var? true) false)
|
||||||
|
|
||||||
|
;; --- empty substitution ---
|
||||||
|
|
||||||
|
(mk-test "empty-s-walk-num" (mk-walk 5 empty-s) 5)
|
||||||
|
(mk-test "empty-s-walk-str" (mk-walk "x" empty-s) "x")
|
||||||
|
(mk-test
|
||||||
|
"empty-s-walk-list"
|
||||||
|
(mk-walk (list 1 2) empty-s)
|
||||||
|
(list 1 2))
|
||||||
|
(mk-test
|
||||||
|
"empty-s-walk-unbound-var"
|
||||||
|
(let ((x (make-var))) (= (mk-walk x empty-s) x))
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; --- walk: top-level chain resolution ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"walk-direct-binding"
|
||||||
|
(mk-walk (mk-var "x") (extend "x" 7 empty-s))
|
||||||
|
7)
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"walk-two-step-chain"
|
||||||
|
(mk-walk
|
||||||
|
(mk-var "x")
|
||||||
|
(extend "x" (mk-var "y") (extend "y" 9 empty-s)))
|
||||||
|
9)
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"walk-three-step-chain"
|
||||||
|
(mk-walk
|
||||||
|
(mk-var "a")
|
||||||
|
(extend
|
||||||
|
"a"
|
||||||
|
(mk-var "b")
|
||||||
|
(extend "b" (mk-var "c") (extend "c" 42 empty-s))))
|
||||||
|
42)
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"walk-stops-at-list"
|
||||||
|
(mk-walk (list 1 (mk-var "x")) (extend "x" 5 empty-s))
|
||||||
|
(list 1 (mk-var "x")))
|
||||||
|
|
||||||
|
;; --- walk*: deep walk into lists ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"walk*-flat-list-with-vars"
|
||||||
|
(mk-walk*
|
||||||
|
(list (mk-var "x") 2 (mk-var "y"))
|
||||||
|
(extend "x" 1 (extend "y" 3 empty-s)))
|
||||||
|
(list 1 2 3))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"walk*-nested-list"
|
||||||
|
(mk-walk*
|
||||||
|
(list 1 (mk-var "x") (list 2 (mk-var "y")))
|
||||||
|
(extend "x" 5 (extend "y" 6 empty-s)))
|
||||||
|
(list 1 5 (list 2 6)))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"walk*-unbound-stays-var"
|
||||||
|
(let
|
||||||
|
((x (mk-var "x")))
|
||||||
|
(= (mk-walk* (list 1 x) empty-s) (list 1 x)))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(mk-test "walk*-atom" (mk-walk* 5 empty-s) 5)
|
||||||
|
|
||||||
|
;; --- unify atoms (success / failure semantics, not dict shape) ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"unify-num-eq-succeeds"
|
||||||
|
(mk-unified? (mk-unify 5 5 empty-s))
|
||||||
|
true)
|
||||||
|
(mk-test "unify-num-neq-fails" (mk-unify 5 6 empty-s) nil)
|
||||||
|
(mk-test
|
||||||
|
"unify-str-eq-succeeds"
|
||||||
|
(mk-unified? (mk-unify "a" "a" empty-s))
|
||||||
|
true)
|
||||||
|
(mk-test "unify-str-neq-fails" (mk-unify "a" "b" empty-s) nil)
|
||||||
|
(mk-test
|
||||||
|
"unify-bool-eq-succeeds"
|
||||||
|
(mk-unified? (mk-unify true true empty-s))
|
||||||
|
true)
|
||||||
|
(mk-test "unify-bool-neq-fails" (mk-unify true false empty-s) nil)
|
||||||
|
(mk-test
|
||||||
|
"unify-nil-eq-succeeds"
|
||||||
|
(mk-unified? (mk-unify nil nil empty-s))
|
||||||
|
true)
|
||||||
|
(mk-test
|
||||||
|
"unify-empty-list-succeeds"
|
||||||
|
(mk-unified? (mk-unify (list) (list) empty-s))
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; --- unify var with anything (walk to verify binding) ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"unify-var-num-binds"
|
||||||
|
(mk-walk (mk-var "x") (mk-unify (mk-var "x") 5 empty-s))
|
||||||
|
5)
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"unify-num-var-binds"
|
||||||
|
(mk-walk (mk-var "x") (mk-unify 5 (mk-var "x") empty-s))
|
||||||
|
5)
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"unify-var-list-binds"
|
||||||
|
(mk-walk
|
||||||
|
(mk-var "x")
|
||||||
|
(mk-unify (mk-var "x") (list 1 2) empty-s))
|
||||||
|
(list 1 2))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"unify-var-var-same-no-extend"
|
||||||
|
(mk-unified? (mk-unify (mk-var "x") (mk-var "x") empty-s))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"unify-var-var-different-walks-equal"
|
||||||
|
(let
|
||||||
|
((s (mk-unify (mk-var "x") (mk-var "y") empty-s)))
|
||||||
|
(= (mk-walk (mk-var "x") s) (mk-walk (mk-var "y") s)))
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; --- unify lists positionally ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"unify-list-equal-succeeds"
|
||||||
|
(mk-unified?
|
||||||
|
(mk-unify
|
||||||
|
(list 1 2 3)
|
||||||
|
(list 1 2 3)
|
||||||
|
empty-s))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"unify-list-different-length-fails-1"
|
||||||
|
(mk-unify
|
||||||
|
(list 1 2)
|
||||||
|
(list 1 2 3)
|
||||||
|
empty-s)
|
||||||
|
nil)
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"unify-list-different-length-fails-2"
|
||||||
|
(mk-unify
|
||||||
|
(list 1 2 3)
|
||||||
|
(list 1 2)
|
||||||
|
empty-s)
|
||||||
|
nil)
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"unify-list-mismatch-fails"
|
||||||
|
(mk-unify
|
||||||
|
(list 1 2)
|
||||||
|
(list 1 3)
|
||||||
|
empty-s)
|
||||||
|
nil)
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"unify-list-vs-atom-fails"
|
||||||
|
(mk-unify (list 1 2) 5 empty-s)
|
||||||
|
nil)
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"unify-empty-vs-non-empty-fails"
|
||||||
|
(mk-unify (list) (list 1) empty-s)
|
||||||
|
nil)
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"unify-list-with-vars-walks"
|
||||||
|
(mk-walk*
|
||||||
|
(list (mk-var "x") (mk-var "y"))
|
||||||
|
(mk-unify
|
||||||
|
(list (mk-var "x") (mk-var "y"))
|
||||||
|
(list 1 2)
|
||||||
|
empty-s))
|
||||||
|
(list 1 2))
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"unify-nested-lists-with-vars-walks"
|
||||||
|
(mk-walk*
|
||||||
|
(list (mk-var "x") (list (mk-var "y") 3))
|
||||||
|
(mk-unify
|
||||||
|
(list (mk-var "x") (list (mk-var "y") 3))
|
||||||
|
(list 1 (list 2 3))
|
||||||
|
empty-s))
|
||||||
|
(list 1 (list 2 3)))
|
||||||
|
|
||||||
|
;; --- unify chained substitutions ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"unify-chain-var-var-then-atom"
|
||||||
|
(let
|
||||||
|
((x (mk-var "x")) (y (mk-var "y")))
|
||||||
|
(let
|
||||||
|
((s1 (mk-unify x y empty-s)))
|
||||||
|
(mk-walk x (mk-unify y 7 s1))))
|
||||||
|
7)
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"unify-already-bound-consistent"
|
||||||
|
(let
|
||||||
|
((s (extend "x" 5 empty-s)))
|
||||||
|
(mk-unified? (mk-unify (mk-var "x") 5 s)))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"unify-already-bound-conflict-fails"
|
||||||
|
(let
|
||||||
|
((s (extend "x" 5 empty-s)))
|
||||||
|
(mk-unify (mk-var "x") 6 s))
|
||||||
|
nil)
|
||||||
|
|
||||||
|
;; --- occurs check (opt-in) ---
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"unify-no-occurs-default-succeeds"
|
||||||
|
(let
|
||||||
|
((x (mk-var "x")))
|
||||||
|
(mk-unified? (mk-unify x (list 1 x) empty-s)))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"unify-occurs-direct-fails"
|
||||||
|
(let ((x (mk-var "x"))) (mk-unify-check x (list 1 x) empty-s))
|
||||||
|
nil)
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"unify-occurs-nested-fails"
|
||||||
|
(let
|
||||||
|
((x (mk-var "x")))
|
||||||
|
(mk-unify-check x (list 1 (list 2 x)) empty-s))
|
||||||
|
nil)
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"unify-occurs-non-occurring-succeeds"
|
||||||
|
(let
|
||||||
|
((x (mk-var "x")))
|
||||||
|
(mk-unified? (mk-unify-check x 5 empty-s)))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(mk-test
|
||||||
|
"unify-occurs-via-chain-fails"
|
||||||
|
(let
|
||||||
|
((x (mk-var "x")) (y (mk-var "y")))
|
||||||
|
(let ((s (extend "y" (list x) empty-s))) (mk-unify-check x y s)))
|
||||||
|
nil)
|
||||||
|
|
||||||
|
(mk-tests-run!)
|
||||||
82
lib/minikanren/unify.sx
Normal file
82
lib/minikanren/unify.sx
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
;; lib/minikanren/unify.sx — Phase 1 + cons-cell extension.
|
||||||
|
;;
|
||||||
|
;; miniKanren-on-SX, built on lib/guest/match.sx. The kit ships the heavy
|
||||||
|
;; lifting (walk-with, unify-with, occurs-with, extend, empty-subst,
|
||||||
|
;; mk-var/is-var?/var-name); this file supplies a miniKanren-shaped cfg
|
||||||
|
;; and a thin public API.
|
||||||
|
;;
|
||||||
|
;; Term shapes:
|
||||||
|
;; logic var : (:var NAME) — kit's mk-var
|
||||||
|
;; cons cell : (:cons HEAD TAIL) — for relational programming
|
||||||
|
;; (built by mk-cons; lets relations decompose lists by
|
||||||
|
;; head/tail without proper improper pairs in the host)
|
||||||
|
;; native list : SX list (a b c) — also unifies pair-style:
|
||||||
|
;; args = (head, tail) so (1 2 3) ≡ (:cons 1 (:cons 2 (:cons 3 ())))
|
||||||
|
;; atom : number / string / symbol / boolean / nil / ()
|
||||||
|
;;
|
||||||
|
;; Substitution: SX dict mapping VAR-NAME → term. Empty = (empty-subst).
|
||||||
|
|
||||||
|
(define mk-cons (fn (h t) (list :cons h t)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
mk-cons-cell?
|
||||||
|
(fn (t) (and (list? t) (not (empty? t)) (= (first t) :cons))))
|
||||||
|
|
||||||
|
(define mk-cons-head (fn (t) (nth t 1)))
|
||||||
|
(define mk-cons-tail (fn (t) (nth t 2)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
mk-list-pair?
|
||||||
|
(fn (t) (and (list? t) (not (empty? t)) (not (is-var? t)))))
|
||||||
|
|
||||||
|
(define mk-list-pair-head (fn (t) :pair))
|
||||||
|
|
||||||
|
(define
|
||||||
|
mk-list-pair-args
|
||||||
|
(fn
|
||||||
|
(t)
|
||||||
|
(cond
|
||||||
|
((mk-cons-cell? t) (list (mk-cons-head t) (mk-cons-tail t)))
|
||||||
|
(:else (list (first t) (rest t))))))
|
||||||
|
|
||||||
|
(define mk-cfg {:ctor-head mk-list-pair-head :var? is-var? :ctor? mk-list-pair? :occurs-check? false :var-name var-name :ctor-args mk-list-pair-args})
|
||||||
|
|
||||||
|
(define mk-cfg-occurs {:ctor-head mk-list-pair-head :var? is-var? :ctor? mk-list-pair? :occurs-check? true :var-name var-name :ctor-args mk-list-pair-args})
|
||||||
|
|
||||||
|
(define empty-s (empty-subst))
|
||||||
|
|
||||||
|
(define mk-fresh-counter 0)
|
||||||
|
|
||||||
|
(define
|
||||||
|
make-var
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(begin
|
||||||
|
(set! mk-fresh-counter (+ mk-fresh-counter 1))
|
||||||
|
(mk-var (str "_." mk-fresh-counter)))))
|
||||||
|
|
||||||
|
(define mk-var? is-var?)
|
||||||
|
|
||||||
|
(define mk-walk (fn (t s) (walk-with mk-cfg t s)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
mk-walk*
|
||||||
|
(fn
|
||||||
|
(t s)
|
||||||
|
(let
|
||||||
|
((w (mk-walk t s)))
|
||||||
|
(cond
|
||||||
|
((mk-cons-cell? w)
|
||||||
|
(let
|
||||||
|
((h (mk-walk* (mk-cons-head w) s))
|
||||||
|
(tl (mk-walk* (mk-cons-tail w) s)))
|
||||||
|
(cond
|
||||||
|
((empty? tl) (list h))
|
||||||
|
((mk-cons-cell? tl) tl)
|
||||||
|
((list? tl) (cons h tl))
|
||||||
|
(:else (mk-cons h tl)))))
|
||||||
|
((mk-list-pair? w) (map (fn (a) (mk-walk* a s)) w))
|
||||||
|
(:else w)))))
|
||||||
|
|
||||||
|
(define mk-unify (fn (u v s) (unify-with mk-cfg u v s)))
|
||||||
|
(define mk-unify-check (fn (u v s) (unify-with mk-cfg-occurs u v s)))
|
||||||
@@ -1,432 +0,0 @@
|
|||||||
;; lib/ocaml/eval.sx — OCaml AST evaluator (Phase 2 slice).
|
|
||||||
;;
|
|
||||||
;; Walks the AST produced by ocaml-parse / ocaml-parse-program and yields
|
|
||||||
;; SX values.
|
|
||||||
;;
|
|
||||||
;; Coverage in this slice:
|
|
||||||
;; atoms int/float/string/char/bool/unit
|
|
||||||
;; :var env lookup
|
|
||||||
;; :app curried application
|
|
||||||
;; :op arithmetic, comparison, boolean, ^ string concat, mod, ::
|
|
||||||
;; :neg unary minus
|
|
||||||
;; :not boolean negation
|
|
||||||
;; :if conditional
|
|
||||||
;; :seq sequence — discard all but last
|
|
||||||
;; :tuple SX (:tuple v1 v2 …)
|
|
||||||
;; :list SX list
|
|
||||||
;; :fun closure (auto-curried via host SX lambda)
|
|
||||||
;; :let non-recursive binding
|
|
||||||
;; :let-rec recursive binding for function values (mutable ref cell)
|
|
||||||
;;
|
|
||||||
;; Out of scope: pattern matching, refs (`ref`/`!`/`:=`), modules, ADTs,
|
|
||||||
;; mutable records, for/while, try/with.
|
|
||||||
;;
|
|
||||||
;; Environment representation: an assoc list of (name value) pairs. Most
|
|
||||||
;; recent binding shadows older ones.
|
|
||||||
|
|
||||||
;; Initial environment provides OCaml stdlib functions that are values,
|
|
||||||
;; not language keywords (e.g. `not`, `succ`, `pred`). Phase 6 adds the
|
|
||||||
;; full stdlib slice; this just unblocks Phase 2 tests.
|
|
||||||
(define ocaml-empty-env
|
|
||||||
(fn ()
|
|
||||||
(list
|
|
||||||
(list "not" (fn (x) (not x)))
|
|
||||||
(list "succ" (fn (x) (+ x 1)))
|
|
||||||
(list "pred" (fn (x) (- x 1)))
|
|
||||||
(list "abs" (fn (x) (if (< x 0) (- 0 x) x)))
|
|
||||||
(list "max" (fn (a) (fn (b) (if (> a b) a b))))
|
|
||||||
(list "min" (fn (a) (fn (b) (if (< a b) a b))))
|
|
||||||
(list "fst" (fn (p) (nth p 1)))
|
|
||||||
(list "snd" (fn (p) (nth p 2)))
|
|
||||||
(list "ignore" (fn (x) nil))
|
|
||||||
;; References. A ref cell is a one-element list; ! reads it and
|
|
||||||
;; := mutates it via set-nth!.
|
|
||||||
(list "ref" (fn (x) (list x))))))
|
|
||||||
|
|
||||||
(define ocaml-env-lookup
|
|
||||||
(fn (env name)
|
|
||||||
(cond
|
|
||||||
((= env (list)) nil)
|
|
||||||
((= (first (first env)) name) (nth (first env) 1))
|
|
||||||
(else (ocaml-env-lookup (rest env) name)))))
|
|
||||||
|
|
||||||
(define ocaml-env-has?
|
|
||||||
(fn (env name)
|
|
||||||
(cond
|
|
||||||
((= env (list)) false)
|
|
||||||
((= (first (first env)) name) true)
|
|
||||||
(else (ocaml-env-has? (rest env) name)))))
|
|
||||||
|
|
||||||
(define ocaml-env-extend
|
|
||||||
(fn (env name val)
|
|
||||||
(cons (list name val) env)))
|
|
||||||
|
|
||||||
(define ocaml-tag-of (fn (ast) (nth ast 0)))
|
|
||||||
|
|
||||||
(define ocaml-eval (fn (ast env) nil))
|
|
||||||
|
|
||||||
;; Pattern matcher — returns the extended env on success, or :fail on
|
|
||||||
;; mismatch (using the keyword :fail so nil values don't ambiguate).
|
|
||||||
;;
|
|
||||||
;; Pattern shapes (from parser):
|
|
||||||
;; (:pwild) match anything, no binding
|
|
||||||
;; (:pvar NAME) match anything, bind NAME → val
|
|
||||||
;; (:plit LITAST) literal compare
|
|
||||||
;; (:pcon NAME PATS...) ctor: val must be (NAME ARGS...) and arity match
|
|
||||||
;; (:pcons HEAD TAIL) non-empty list: match head + tail
|
|
||||||
;; (:plist PATS...) list of exact length, item-wise match
|
|
||||||
;; (:ptuple PATS...) val must be ("tuple" ITEMS...) of same arity
|
|
||||||
(define ocaml-match-fail :fail)
|
|
||||||
|
|
||||||
(define ocaml-eval-lit
|
|
||||||
(fn (lit-ast)
|
|
||||||
(let ((tag (nth lit-ast 0)))
|
|
||||||
(cond
|
|
||||||
((= tag "int") (nth lit-ast 1))
|
|
||||||
((= tag "float") (nth lit-ast 1))
|
|
||||||
((= tag "string") (nth lit-ast 1))
|
|
||||||
((= tag "char") (nth lit-ast 1))
|
|
||||||
((= tag "bool") (nth lit-ast 1))
|
|
||||||
((= tag "unit") nil)
|
|
||||||
(else (error (str "ocaml-eval-lit: bad literal " tag)))))))
|
|
||||||
|
|
||||||
(define ocaml-match-pat (fn (pat val env) ocaml-match-fail))
|
|
||||||
|
|
||||||
(define ocaml-match-list
|
|
||||||
(fn (pats vals env)
|
|
||||||
(cond
|
|
||||||
((and (= (len pats) 0) (= (len vals) 0)) env)
|
|
||||||
((or (= (len pats) 0) (= (len vals) 0)) ocaml-match-fail)
|
|
||||||
(else
|
|
||||||
(let ((env2 (ocaml-match-pat (first pats) (first vals) env)))
|
|
||||||
(cond
|
|
||||||
((= env2 ocaml-match-fail) ocaml-match-fail)
|
|
||||||
(else (ocaml-match-list (rest pats) (rest vals) env2))))))))
|
|
||||||
|
|
||||||
(set! ocaml-match-pat
|
|
||||||
(fn (pat val env)
|
|
||||||
(let ((tag (nth pat 0)))
|
|
||||||
(cond
|
|
||||||
((= tag "pwild") env)
|
|
||||||
((= tag "pvar")
|
|
||||||
(ocaml-env-extend env (nth pat 1) val))
|
|
||||||
((= tag "plit")
|
|
||||||
(if (= (ocaml-eval-lit (nth pat 1)) val) env ocaml-match-fail))
|
|
||||||
((= tag "pcon")
|
|
||||||
;; (:pcon NAME PATS...) — val must be (NAME VALS...) with same arity.
|
|
||||||
(let ((name (nth pat 1)) (arg-pats (rest (rest pat))))
|
|
||||||
(cond
|
|
||||||
((and (list? val) (not (empty? val)) (= (first val) name)
|
|
||||||
(= (len (rest val)) (len arg-pats)))
|
|
||||||
(ocaml-match-list arg-pats (rest val) env))
|
|
||||||
(else ocaml-match-fail))))
|
|
||||||
((= tag "pcons")
|
|
||||||
;; (:pcons HEAD TAIL) — val must be a non-empty list.
|
|
||||||
(cond
|
|
||||||
((and (list? val) (not (empty? val))
|
|
||||||
(not (and (not (empty? val)) (string? (first val)))))
|
|
||||||
;; OCaml lists are SX lists (not tagged like ctors). Match
|
|
||||||
;; head pattern against (first val), tail against (rest val).
|
|
||||||
(let ((env2 (ocaml-match-pat (nth pat 1) (first val) env)))
|
|
||||||
(cond
|
|
||||||
((= env2 ocaml-match-fail) ocaml-match-fail)
|
|
||||||
(else (ocaml-match-pat (nth pat 2) (rest val) env2)))))
|
|
||||||
;; Allow lists whose first element happens to be a string —
|
|
||||||
;; ambiguous with ctors; treat them as plain lists.
|
|
||||||
((and (list? val) (not (empty? val)))
|
|
||||||
(let ((env2 (ocaml-match-pat (nth pat 1) (first val) env)))
|
|
||||||
(cond
|
|
||||||
((= env2 ocaml-match-fail) ocaml-match-fail)
|
|
||||||
(else (ocaml-match-pat (nth pat 2) (rest val) env2)))))
|
|
||||||
(else ocaml-match-fail)))
|
|
||||||
((= tag "plist")
|
|
||||||
;; (:plist PATS...) — val must be a list of exact length.
|
|
||||||
(let ((item-pats (rest pat)))
|
|
||||||
(cond
|
|
||||||
((and (list? val) (= (len val) (len item-pats)))
|
|
||||||
(ocaml-match-list item-pats val env))
|
|
||||||
(else ocaml-match-fail))))
|
|
||||||
((= tag "ptuple")
|
|
||||||
(let ((item-pats (rest pat)))
|
|
||||||
(cond
|
|
||||||
((and (list? val) (not (empty? val))
|
|
||||||
(= (first val) "tuple")
|
|
||||||
(= (len (rest val)) (len item-pats)))
|
|
||||||
(ocaml-match-list item-pats (rest val) env))
|
|
||||||
(else ocaml-match-fail))))
|
|
||||||
(else (error (str "ocaml-match-pat: unknown pattern tag " tag)))))))
|
|
||||||
|
|
||||||
(define ocaml-match-eval
|
|
||||||
(fn (scrut-ast clauses env)
|
|
||||||
(let ((val (ocaml-eval scrut-ast env)))
|
|
||||||
(begin
|
|
||||||
(define try-clauses
|
|
||||||
(fn (cs)
|
|
||||||
(cond
|
|
||||||
((empty? cs)
|
|
||||||
(error (str "ocaml-eval: match failure on " val)))
|
|
||||||
(else
|
|
||||||
(let ((clause (first cs)))
|
|
||||||
(let ((pat (nth clause 1)) (body (nth clause 2)))
|
|
||||||
(let ((env2 (ocaml-match-pat pat val env)))
|
|
||||||
(cond
|
|
||||||
((= env2 ocaml-match-fail) (try-clauses (rest cs)))
|
|
||||||
(else (ocaml-eval body env2))))))))))
|
|
||||||
(try-clauses clauses)))))
|
|
||||||
|
|
||||||
;; Auto-curry: (:fun ("x" "y" "z") body) → (fn (x) (fn (y) (fn (z) body))).
|
|
||||||
;; A zero-param lambda evaluates the body immediately on first call —
|
|
||||||
;; OCaml does not have nullary functions; `()`-taking functions still
|
|
||||||
;; receive the unit argument via a one-param lambda.
|
|
||||||
(define ocaml-make-curried
|
|
||||||
(fn (params body env)
|
|
||||||
(cond
|
|
||||||
((= (len params) 0)
|
|
||||||
(ocaml-eval body env))
|
|
||||||
((= (len params) 1)
|
|
||||||
(fn (arg)
|
|
||||||
(ocaml-eval body
|
|
||||||
(ocaml-env-extend env (nth params 0) arg))))
|
|
||||||
(else
|
|
||||||
(fn (arg)
|
|
||||||
(ocaml-make-curried
|
|
||||||
(rest params)
|
|
||||||
body
|
|
||||||
(ocaml-env-extend env (nth params 0) arg)))))))
|
|
||||||
|
|
||||||
(define ocaml-eval-op
|
|
||||||
(fn (op lhs rhs)
|
|
||||||
(cond
|
|
||||||
((= op "+") (+ lhs rhs))
|
|
||||||
((= op "-") (- lhs rhs))
|
|
||||||
((= op "*") (* lhs rhs))
|
|
||||||
((= op "/") (/ lhs rhs))
|
|
||||||
((= op "mod") (mod lhs rhs))
|
|
||||||
((= op "%") (mod lhs rhs))
|
|
||||||
((= op "**") (pow lhs rhs))
|
|
||||||
((= op "^") (str lhs rhs))
|
|
||||||
((= op "@") (concat lhs rhs))
|
|
||||||
((= op "::") (cons lhs rhs))
|
|
||||||
((= op "=") (= lhs rhs))
|
|
||||||
((= op "<>") (not (= lhs rhs)))
|
|
||||||
((= op "==") (= lhs rhs))
|
|
||||||
((= op "!=") (not (= lhs rhs)))
|
|
||||||
((= op "<") (< lhs rhs))
|
|
||||||
((= op ">") (> lhs rhs))
|
|
||||||
((= op "<=") (<= lhs rhs))
|
|
||||||
((= op ">=") (>= lhs rhs))
|
|
||||||
((= op "&&") (and lhs rhs))
|
|
||||||
((= op "||") (or lhs rhs))
|
|
||||||
((= op "or") (or lhs rhs))
|
|
||||||
((= op "|>") (rhs lhs))
|
|
||||||
(else (error (str "ocaml-eval: unknown operator " op))))))
|
|
||||||
|
|
||||||
(set! ocaml-eval
|
|
||||||
(fn (ast env)
|
|
||||||
(let ((tag (ocaml-tag-of ast)))
|
|
||||||
(cond
|
|
||||||
((= tag "int") (nth ast 1))
|
|
||||||
((= tag "float") (nth ast 1))
|
|
||||||
((= tag "string") (nth ast 1))
|
|
||||||
((= tag "char") (nth ast 1))
|
|
||||||
((= tag "bool") (nth ast 1))
|
|
||||||
((= tag "unit") nil)
|
|
||||||
((= tag "var")
|
|
||||||
(let ((name (nth ast 1)))
|
|
||||||
(cond
|
|
||||||
((ocaml-env-has? env name) (ocaml-env-lookup env name))
|
|
||||||
(else (error (str "ocaml-eval: unbound variable " name))))))
|
|
||||||
((= tag "neg") (- 0 (ocaml-eval (nth ast 1) env)))
|
|
||||||
((= tag "not") (not (ocaml-eval (nth ast 1) env)))
|
|
||||||
((= tag "deref")
|
|
||||||
(let ((cell (ocaml-eval (nth ast 1) env)))
|
|
||||||
(nth cell 0)))
|
|
||||||
((= tag "op")
|
|
||||||
(let ((op (nth ast 1)))
|
|
||||||
(cond
|
|
||||||
;; := mutates the lhs cell — short-circuit before generic
|
|
||||||
;; eval-op so we still evaluate lhs (to obtain the cell).
|
|
||||||
((= op ":=")
|
|
||||||
(let ((cell (ocaml-eval (nth ast 2) env))
|
|
||||||
(new-val (ocaml-eval (nth ast 3) env)))
|
|
||||||
(begin (set-nth! cell 0 new-val) nil)))
|
|
||||||
(else
|
|
||||||
(ocaml-eval-op op
|
|
||||||
(ocaml-eval (nth ast 2) env)
|
|
||||||
(ocaml-eval (nth ast 3) env))))))
|
|
||||||
((= tag "if")
|
|
||||||
(if (ocaml-eval (nth ast 1) env)
|
|
||||||
(ocaml-eval (nth ast 2) env)
|
|
||||||
(ocaml-eval (nth ast 3) env)))
|
|
||||||
((= tag "seq")
|
|
||||||
(let ((items (rest ast)) (last nil))
|
|
||||||
(begin
|
|
||||||
(define loop
|
|
||||||
(fn (xs)
|
|
||||||
(when (not (= xs (list)))
|
|
||||||
(begin
|
|
||||||
(set! last (ocaml-eval (first xs) env))
|
|
||||||
(loop (rest xs))))))
|
|
||||||
(loop items)
|
|
||||||
last)))
|
|
||||||
((= tag "tuple")
|
|
||||||
(cons :tuple
|
|
||||||
(map (fn (e) (ocaml-eval e env)) (rest ast))))
|
|
||||||
((= tag "list")
|
|
||||||
(map (fn (e) (ocaml-eval e env)) (rest ast)))
|
|
||||||
((= tag "fun")
|
|
||||||
(ocaml-make-curried (nth ast 1) (nth ast 2) env))
|
|
||||||
((= tag "con")
|
|
||||||
;; Standalone ctor — produces a nullary tagged value.
|
|
||||||
(list (nth ast 1)))
|
|
||||||
((= tag "app")
|
|
||||||
(let ((fn-ast (nth ast 1)))
|
|
||||||
(cond
|
|
||||||
;; Constructor application: build a tagged value, flattening
|
|
||||||
;; a tuple arg into multiple ctor args (so `Pair (a, b)`
|
|
||||||
;; becomes ("Pair" va vb) — matches the parser's pattern
|
|
||||||
;; flattening).
|
|
||||||
((= (ocaml-tag-of fn-ast) "con")
|
|
||||||
(let ((name (nth fn-ast 1))
|
|
||||||
(arg-val (ocaml-eval (nth ast 2) env)))
|
|
||||||
(cond
|
|
||||||
((and (list? arg-val) (not (empty? arg-val))
|
|
||||||
(= (first arg-val) "tuple"))
|
|
||||||
(cons name (rest arg-val)))
|
|
||||||
(else (list name arg-val)))))
|
|
||||||
(else
|
|
||||||
(let ((fn-val (ocaml-eval fn-ast env))
|
|
||||||
(arg-val (ocaml-eval (nth ast 2) env)))
|
|
||||||
(fn-val arg-val))))))
|
|
||||||
((= tag "match")
|
|
||||||
(ocaml-match-eval (nth ast 1) (nth ast 2) env))
|
|
||||||
((= tag "for")
|
|
||||||
;; (:for NAME LO HI DIR BODY) — DIR is "ascend" or "descend".
|
|
||||||
(let ((name (nth ast 1))
|
|
||||||
(lo (ocaml-eval (nth ast 2) env))
|
|
||||||
(hi (ocaml-eval (nth ast 3) env))
|
|
||||||
(dir (nth ast 4))
|
|
||||||
(body (nth ast 5)))
|
|
||||||
(begin
|
|
||||||
(cond
|
|
||||||
((= dir "ascend")
|
|
||||||
(let ((i lo))
|
|
||||||
(begin
|
|
||||||
(define loop
|
|
||||||
(fn ()
|
|
||||||
(when (<= i hi)
|
|
||||||
(begin
|
|
||||||
(ocaml-eval body
|
|
||||||
(ocaml-env-extend env name i))
|
|
||||||
(set! i (+ i 1))
|
|
||||||
(loop)))))
|
|
||||||
(loop))))
|
|
||||||
((= dir "descend")
|
|
||||||
(let ((i lo))
|
|
||||||
(begin
|
|
||||||
(define loop
|
|
||||||
(fn ()
|
|
||||||
(when (>= i hi)
|
|
||||||
(begin
|
|
||||||
(ocaml-eval body
|
|
||||||
(ocaml-env-extend env name i))
|
|
||||||
(set! i (- i 1))
|
|
||||||
(loop)))))
|
|
||||||
(loop)))))
|
|
||||||
nil)))
|
|
||||||
((= tag "while")
|
|
||||||
(let ((cond-ast (nth ast 1)) (body (nth ast 2)))
|
|
||||||
(begin
|
|
||||||
(define loop
|
|
||||||
(fn ()
|
|
||||||
(when (ocaml-eval cond-ast env)
|
|
||||||
(begin
|
|
||||||
(ocaml-eval body env)
|
|
||||||
(loop)))))
|
|
||||||
(loop)
|
|
||||||
nil)))
|
|
||||||
((= tag "let")
|
|
||||||
(let ((name (nth ast 1)) (params (nth ast 2))
|
|
||||||
(rhs (nth ast 3)) (body (nth ast 4)))
|
|
||||||
(let ((rhs-val
|
|
||||||
(if (= (len params) 0)
|
|
||||||
(ocaml-eval rhs env)
|
|
||||||
(ocaml-make-curried params rhs env))))
|
|
||||||
(ocaml-eval body (ocaml-env-extend env name rhs-val)))))
|
|
||||||
((= tag "let-rec")
|
|
||||||
;; For function bindings: tie the knot via a mutable cell. The
|
|
||||||
;; placeholder closure that's bound first dereferences the cell
|
|
||||||
;; on each call, so the function can call itself once the cell
|
|
||||||
;; is set to the real closure.
|
|
||||||
(let ((name (nth ast 1)) (params (nth ast 2))
|
|
||||||
(rhs (nth ast 3)) (body (nth ast 4)))
|
|
||||||
(cond
|
|
||||||
((= (len params) 0)
|
|
||||||
;; Non-functional let-rec — OCaml only allows this when the
|
|
||||||
;; rhs is "syntactically a function or constructor". For the
|
|
||||||
;; common case of a value, evaluate non-recursively.
|
|
||||||
(let ((rhs-val (ocaml-eval rhs env)))
|
|
||||||
(ocaml-eval body (ocaml-env-extend env name rhs-val))))
|
|
||||||
(else
|
|
||||||
;; Use a one-element list as a mutable cell to tie the
|
|
||||||
;; recursive knot. The placeholder closure dereferences
|
|
||||||
;; the cell on each call.
|
|
||||||
(let ((cell (list nil)))
|
|
||||||
(let ((env2 (ocaml-env-extend env name
|
|
||||||
(fn (arg) ((nth cell 0) arg)))))
|
|
||||||
(let ((rhs-val (ocaml-make-curried params rhs env2)))
|
|
||||||
(begin
|
|
||||||
(set-nth! cell 0 rhs-val)
|
|
||||||
(ocaml-eval body env2)))))))))
|
|
||||||
(else (error
|
|
||||||
(str "ocaml-eval: unknown AST tag " tag)))))))
|
|
||||||
|
|
||||||
;; ocaml-run — convenience wrapper: parse + eval.
|
|
||||||
(define ocaml-run
|
|
||||||
(fn (src)
|
|
||||||
(ocaml-eval (ocaml-parse src) (ocaml-empty-env))))
|
|
||||||
|
|
||||||
;; ocaml-run-program — evaluate a program (sequence of decls + bare exprs).
|
|
||||||
;; Threads an env through decls; returns the value of the last form.
|
|
||||||
(define ocaml-run-program
|
|
||||||
(fn (src)
|
|
||||||
(let ((prog (ocaml-parse-program src)) (env (ocaml-empty-env)) (last nil))
|
|
||||||
(begin
|
|
||||||
(define run-decl
|
|
||||||
(fn (decl)
|
|
||||||
(let ((tag (ocaml-tag-of decl)))
|
|
||||||
(cond
|
|
||||||
((= tag "def")
|
|
||||||
(let ((name (nth decl 1)) (params (nth decl 2)) (rhs (nth decl 3)))
|
|
||||||
(let ((v (if (= (len params) 0)
|
|
||||||
(ocaml-eval rhs env)
|
|
||||||
(ocaml-make-curried params rhs env))))
|
|
||||||
(begin
|
|
||||||
(set! env (ocaml-env-extend env name v))
|
|
||||||
(set! last v)))))
|
|
||||||
((= tag "def-rec")
|
|
||||||
(let ((name (nth decl 1)) (params (nth decl 2)) (rhs (nth decl 3)))
|
|
||||||
(cond
|
|
||||||
((= (len params) 0)
|
|
||||||
(let ((v (ocaml-eval rhs env)))
|
|
||||||
(begin
|
|
||||||
(set! env (ocaml-env-extend env name v))
|
|
||||||
(set! last v))))
|
|
||||||
(else
|
|
||||||
(let ((cell (list nil)))
|
|
||||||
(let ((env2 (ocaml-env-extend env name
|
|
||||||
(fn (arg) ((nth cell 0) arg)))))
|
|
||||||
(let ((v (ocaml-make-curried params rhs env2)))
|
|
||||||
(begin
|
|
||||||
(set-nth! cell 0 v)
|
|
||||||
(set! env env2)
|
|
||||||
(set! last v)))))))))
|
|
||||||
((= tag "expr")
|
|
||||||
(set! last (ocaml-eval (nth decl 1) env)))
|
|
||||||
(else (error (str "ocaml-run-program: bad decl " tag)))))))
|
|
||||||
(define loop
|
|
||||||
(fn (xs)
|
|
||||||
(when (not (= xs (list)))
|
|
||||||
(begin (run-decl (first xs)) (loop (rest xs))))))
|
|
||||||
(loop (rest prog))
|
|
||||||
last))))
|
|
||||||
@@ -1,789 +0,0 @@
|
|||||||
;; lib/ocaml/parser.sx — OCaml expression parser.
|
|
||||||
;;
|
|
||||||
;; Input: token list from (ocaml-tokenize src).
|
|
||||||
;; Output: an OCaml AST. Nodes are plain lists tagged by a keyword head;
|
|
||||||
;; keywords serialize to their string name so `(list :var "x")` is the
|
|
||||||
;; same value as `(list "var" "x")` at runtime.
|
|
||||||
;;
|
|
||||||
;; Expression scope:
|
|
||||||
;; atoms int/float/string/char/bool, unit (), var, con, list literal
|
|
||||||
;; application left-associative, f x y z
|
|
||||||
;; prefix -E unary minus, not E
|
|
||||||
;; infix 29 ops via lib/guest/pratt.sx
|
|
||||||
;; tuple a, b, c (lower than infix, higher than let/if)
|
|
||||||
;; parens (e)
|
|
||||||
;; if if c then t else e (else optional → unit)
|
|
||||||
;; fun fun x y -> body
|
|
||||||
;; let let x = e in body (no rec, function shorthand)
|
|
||||||
;; let rec f x = e in body
|
|
||||||
;; match match e with [|] p -> body | p -> body | ...
|
|
||||||
;; sequence e1 ; e2 → (:seq e1 e2 …) (lowest-precedence binary)
|
|
||||||
;;
|
|
||||||
;; Pattern scope:
|
|
||||||
;; _ (wildcard), int/string/char/bool literals, ident (var binding),
|
|
||||||
;; ctor (no args), ctor pat, (), parens, tuple (pat,pat,…),
|
|
||||||
;; list literal [pat;pat;…], cons p1 :: p2.
|
|
||||||
;;
|
|
||||||
;; AST shapes:
|
|
||||||
;; (:int N) (:float N) (:string S) (:char C) (:bool B) (:unit)
|
|
||||||
;; (:var NAME) (:con NAME)
|
|
||||||
;; (:app FN ARG)
|
|
||||||
;; (:op OP LHS RHS)
|
|
||||||
;; (:neg E) (:not E)
|
|
||||||
;; (:tuple ITEMS) (:list ITEMS)
|
|
||||||
;; (:seq EXPRS)
|
|
||||||
;; (:if C T E)
|
|
||||||
;; (:fun PARAMS BODY)
|
|
||||||
;; (:let NAME PARAMS EXPR BODY) (:let-rec NAME PARAMS EXPR BODY)
|
|
||||||
;; (:match SCRUTINEE CLAUSES) CLAUSES = ((:case PAT BODY) ...)
|
|
||||||
;;
|
|
||||||
;; (:pwild) (:pvar N) (:plit LIT)
|
|
||||||
;; (:pcon NAME ARG-PATS) — ARG-PATS empty for nullary
|
|
||||||
;; (:ptuple PATS) (:plist PATS) (:pcons HEAD TAIL)
|
|
||||||
|
|
||||||
(define ocaml-tok-type (fn (t) (if (= t nil) "eof" (get t :type))))
|
|
||||||
|
|
||||||
(define ocaml-tok-value (fn (t) (if (= t nil) nil (get t :value))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
ocaml-op-table
|
|
||||||
(list
|
|
||||||
(list ":=" 1 :right)
|
|
||||||
(list "||" 2 :right)
|
|
||||||
(list "or" 2 :right)
|
|
||||||
(list "&&" 3 :right)
|
|
||||||
(list "&" 3 :right)
|
|
||||||
(list "=" 4 :left)
|
|
||||||
(list "<" 4 :left)
|
|
||||||
(list ">" 4 :left)
|
|
||||||
(list "<=" 4 :left)
|
|
||||||
(list ">=" 4 :left)
|
|
||||||
(list "<>" 4 :left)
|
|
||||||
(list "==" 4 :left)
|
|
||||||
(list "!=" 4 :left)
|
|
||||||
(list "|>" 4 :left)
|
|
||||||
(list "@" 5 :right)
|
|
||||||
(list "^" 5 :right)
|
|
||||||
(list "::" 6 :right)
|
|
||||||
(list "+" 7 :left)
|
|
||||||
(list "-" 7 :left)
|
|
||||||
(list "*" 8 :left)
|
|
||||||
(list "/" 8 :left)
|
|
||||||
(list "%" 8 :left)
|
|
||||||
(list "mod" 8 :left)
|
|
||||||
(list "land" 8 :left)
|
|
||||||
(list "lor" 8 :left)
|
|
||||||
(list "lxor" 8 :left)
|
|
||||||
(list "**" 9 :right)
|
|
||||||
(list "lsl" 9 :right)
|
|
||||||
(list "lsr" 9 :right)
|
|
||||||
(list "asr" 9 :right)))
|
|
||||||
|
|
||||||
(define
|
|
||||||
ocaml-binop-prec
|
|
||||||
(fn
|
|
||||||
(op)
|
|
||||||
(let
|
|
||||||
((entry (pratt-op-lookup ocaml-op-table op)))
|
|
||||||
(if (= entry nil) 0 (pratt-op-prec entry)))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
ocaml-binop-right?
|
|
||||||
(fn
|
|
||||||
(op)
|
|
||||||
(let
|
|
||||||
((entry (pratt-op-lookup ocaml-op-table op)))
|
|
||||||
(and (not (= entry nil)) (= (pratt-op-assoc entry) :right)))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
ocaml-tok-is-binop?
|
|
||||||
(fn
|
|
||||||
(tok)
|
|
||||||
(let
|
|
||||||
((tt (ocaml-tok-type tok)) (tv (ocaml-tok-value tok)))
|
|
||||||
(cond
|
|
||||||
((= tt "op") (not (= (ocaml-binop-prec tv) 0)))
|
|
||||||
((= tt "keyword") (not (= (ocaml-binop-prec tv) 0)))
|
|
||||||
(else false)))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
ocaml-parse
|
|
||||||
(fn
|
|
||||||
(src)
|
|
||||||
(let
|
|
||||||
((tokens (ocaml-tokenize src)) (idx 0) (tok-len 0))
|
|
||||||
(begin
|
|
||||||
(set! tok-len (len tokens))
|
|
||||||
(define peek-tok (fn () (nth tokens idx)))
|
|
||||||
(define advance-tok! (fn () (set! idx (+ idx 1))))
|
|
||||||
(define
|
|
||||||
check-tok?
|
|
||||||
(fn
|
|
||||||
(type value)
|
|
||||||
(let
|
|
||||||
((t (peek-tok)))
|
|
||||||
(and
|
|
||||||
(= (ocaml-tok-type t) type)
|
|
||||||
(or (= value nil) (= (ocaml-tok-value t) value))))))
|
|
||||||
(define
|
|
||||||
consume!
|
|
||||||
(fn
|
|
||||||
(type value)
|
|
||||||
(if
|
|
||||||
(check-tok? type value)
|
|
||||||
(let ((t (peek-tok))) (begin (advance-tok!) t))
|
|
||||||
(error
|
|
||||||
(str
|
|
||||||
"ocaml-parse: expected "
|
|
||||||
type
|
|
||||||
" "
|
|
||||||
value
|
|
||||||
" got "
|
|
||||||
(ocaml-tok-type (peek-tok))
|
|
||||||
" "
|
|
||||||
(ocaml-tok-value (peek-tok)))))))
|
|
||||||
(define at-kw? (fn (kw) (check-tok? "keyword" kw)))
|
|
||||||
(define at-op? (fn (op) (check-tok? "op" op)))
|
|
||||||
(define parse-pattern (fn () nil))
|
|
||||||
(define parse-pattern-cons (fn () nil))
|
|
||||||
(define parse-pattern-app (fn () nil))
|
|
||||||
(define parse-pattern-atom (fn () nil))
|
|
||||||
(define
|
|
||||||
at-pattern-atom?
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(let
|
|
||||||
((tt (ocaml-tok-type (peek-tok)))
|
|
||||||
(tv (ocaml-tok-value (peek-tok))))
|
|
||||||
(cond
|
|
||||||
((= tt "number") true)
|
|
||||||
((= tt "string") true)
|
|
||||||
((= tt "char") true)
|
|
||||||
((= tt "ident") true)
|
|
||||||
((= tt "ctor") true)
|
|
||||||
((and (= tt "keyword") (or (= tv "true") (= tv "false")))
|
|
||||||
true)
|
|
||||||
((and (= tt "op") (or (= tv "(") (= tv "["))) true)
|
|
||||||
(else false)))))
|
|
||||||
(set!
|
|
||||||
parse-pattern-atom
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(let
|
|
||||||
((tt (ocaml-tok-type (peek-tok)))
|
|
||||||
(tv (ocaml-tok-value (peek-tok))))
|
|
||||||
(cond
|
|
||||||
((= tt "number")
|
|
||||||
(begin
|
|
||||||
(advance-tok!)
|
|
||||||
(if
|
|
||||||
(= (round tv) tv)
|
|
||||||
(list :plit (list :int tv))
|
|
||||||
(list :plit (list :float tv)))))
|
|
||||||
((= tt "string")
|
|
||||||
(begin (advance-tok!) (list :plit (list :string tv))))
|
|
||||||
((= tt "char")
|
|
||||||
(begin (advance-tok!) (list :plit (list :char tv))))
|
|
||||||
((and (= tt "keyword") (= tv "true"))
|
|
||||||
(begin (advance-tok!) (list :plit (list :bool true))))
|
|
||||||
((and (= tt "keyword") (= tv "false"))
|
|
||||||
(begin (advance-tok!) (list :plit (list :bool false))))
|
|
||||||
((and (= tt "ident") (= tv "_"))
|
|
||||||
(begin (advance-tok!) (list :pwild)))
|
|
||||||
((= tt "ident") (begin (advance-tok!) (list :pvar tv)))
|
|
||||||
((= tt "ctor") (begin (advance-tok!) (list :pcon tv)))
|
|
||||||
((and (= tt "op") (= tv "("))
|
|
||||||
(begin
|
|
||||||
(advance-tok!)
|
|
||||||
(cond
|
|
||||||
((at-op? ")")
|
|
||||||
(begin (advance-tok!) (list :plit (list :unit))))
|
|
||||||
(else
|
|
||||||
(let
|
|
||||||
((first (parse-pattern)))
|
|
||||||
(cond
|
|
||||||
((at-op? ",")
|
|
||||||
(let
|
|
||||||
((items (list first)))
|
|
||||||
(begin
|
|
||||||
(define
|
|
||||||
loop
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(when
|
|
||||||
(at-op? ",")
|
|
||||||
(begin
|
|
||||||
(advance-tok!)
|
|
||||||
(append! items (parse-pattern))
|
|
||||||
(loop)))))
|
|
||||||
(loop)
|
|
||||||
(consume! "op" ")")
|
|
||||||
(cons :ptuple items))))
|
|
||||||
(else (begin (consume! "op" ")") first))))))))
|
|
||||||
((and (= tt "op") (= tv "["))
|
|
||||||
(begin
|
|
||||||
(advance-tok!)
|
|
||||||
(cond
|
|
||||||
((at-op? "]") (begin (advance-tok!) (list :plist)))
|
|
||||||
(else
|
|
||||||
(let
|
|
||||||
((items (list)))
|
|
||||||
(begin
|
|
||||||
(append! items (parse-pattern))
|
|
||||||
(define
|
|
||||||
loop
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(when
|
|
||||||
(at-op? ";")
|
|
||||||
(begin
|
|
||||||
(advance-tok!)
|
|
||||||
(when
|
|
||||||
(not (at-op? "]"))
|
|
||||||
(begin
|
|
||||||
(append! items (parse-pattern))
|
|
||||||
(loop)))))))
|
|
||||||
(loop)
|
|
||||||
(consume! "op" "]")
|
|
||||||
(cons :plist items)))))))
|
|
||||||
(else
|
|
||||||
(error
|
|
||||||
(str
|
|
||||||
"ocaml-parse: unexpected pattern token "
|
|
||||||
tt
|
|
||||||
" "
|
|
||||||
tv
|
|
||||||
" at idx "
|
|
||||||
idx)))))))
|
|
||||||
(set!
|
|
||||||
parse-pattern-app
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(let
|
|
||||||
((head (parse-pattern-atom)))
|
|
||||||
(cond
|
|
||||||
((and (= (nth head 0) :pcon) (at-pattern-atom?))
|
|
||||||
(let
|
|
||||||
((arg (parse-pattern-atom)))
|
|
||||||
(let
|
|
||||||
((args (cond ((= (nth arg 0) :ptuple) (rest arg)) (else (list arg)))))
|
|
||||||
(concat (list :pcon (nth head 1)) args))))
|
|
||||||
(else head)))))
|
|
||||||
(set!
|
|
||||||
parse-pattern-cons
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(let
|
|
||||||
((lhs (parse-pattern-app)))
|
|
||||||
(cond
|
|
||||||
((at-op? "::")
|
|
||||||
(begin
|
|
||||||
(advance-tok!)
|
|
||||||
(list :pcons lhs (parse-pattern-cons))))
|
|
||||||
(else lhs)))))
|
|
||||||
(set! parse-pattern (fn () (parse-pattern-cons)))
|
|
||||||
(define parse-expr (fn () nil))
|
|
||||||
(define parse-expr-no-seq (fn () nil))
|
|
||||||
(define parse-tuple (fn () nil))
|
|
||||||
(define parse-binop-rhs (fn (lhs min-prec) lhs))
|
|
||||||
(define parse-prefix (fn () nil))
|
|
||||||
(define parse-app (fn () nil))
|
|
||||||
(define parse-atom (fn () nil))
|
|
||||||
(set!
|
|
||||||
parse-atom
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(let
|
|
||||||
((t (peek-tok))
|
|
||||||
(tt (ocaml-tok-type (peek-tok)))
|
|
||||||
(tv (ocaml-tok-value (peek-tok))))
|
|
||||||
(cond
|
|
||||||
((= tt "number")
|
|
||||||
(begin
|
|
||||||
(advance-tok!)
|
|
||||||
(if (= (round tv) tv) (list :int tv) (list :float tv))))
|
|
||||||
((= tt "string") (begin (advance-tok!) (list :string tv)))
|
|
||||||
((= tt "char") (begin (advance-tok!) (list :char tv)))
|
|
||||||
((and (= tt "keyword") (= tv "true"))
|
|
||||||
(begin (advance-tok!) (list :bool true)))
|
|
||||||
((and (= tt "keyword") (= tv "false"))
|
|
||||||
(begin (advance-tok!) (list :bool false)))
|
|
||||||
((= tt "ident") (begin (advance-tok!) (list :var tv)))
|
|
||||||
((= tt "ctor") (begin (advance-tok!) (list :con tv)))
|
|
||||||
((and (= tt "op") (= tv "("))
|
|
||||||
(begin
|
|
||||||
(advance-tok!)
|
|
||||||
(cond
|
|
||||||
((at-op? ")") (begin (advance-tok!) (list :unit)))
|
|
||||||
(else
|
|
||||||
(let
|
|
||||||
((e (parse-expr)))
|
|
||||||
(begin (consume! "op" ")") e))))))
|
|
||||||
((and (= tt "op") (= tv "["))
|
|
||||||
(begin
|
|
||||||
(advance-tok!)
|
|
||||||
(cond
|
|
||||||
((at-op? "]") (begin (advance-tok!) (list :list)))
|
|
||||||
(else
|
|
||||||
(let
|
|
||||||
((items (list)))
|
|
||||||
(begin
|
|
||||||
(append! items (parse-expr-no-seq))
|
|
||||||
(define
|
|
||||||
loop
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(when
|
|
||||||
(at-op? ";")
|
|
||||||
(begin
|
|
||||||
(advance-tok!)
|
|
||||||
(when
|
|
||||||
(not (at-op? "]"))
|
|
||||||
(begin
|
|
||||||
(append! items (parse-expr-no-seq))
|
|
||||||
(loop)))))))
|
|
||||||
(loop)
|
|
||||||
(consume! "op" "]")
|
|
||||||
(cons :list items)))))))
|
|
||||||
((at-kw? "begin")
|
|
||||||
(begin
|
|
||||||
(advance-tok!)
|
|
||||||
(let
|
|
||||||
((e (parse-expr)))
|
|
||||||
(begin (consume! "keyword" "end") e))))
|
|
||||||
(else
|
|
||||||
(error
|
|
||||||
(str
|
|
||||||
"ocaml-parse: unexpected token "
|
|
||||||
tt
|
|
||||||
" "
|
|
||||||
tv
|
|
||||||
" at idx "
|
|
||||||
idx)))))))
|
|
||||||
(define
|
|
||||||
at-app-start?
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(let
|
|
||||||
((tt (ocaml-tok-type (peek-tok)))
|
|
||||||
(tv (ocaml-tok-value (peek-tok))))
|
|
||||||
(cond
|
|
||||||
((= tt "number") true)
|
|
||||||
((= tt "string") true)
|
|
||||||
((= tt "char") true)
|
|
||||||
((= tt "ident") true)
|
|
||||||
((= tt "ctor") true)
|
|
||||||
((and (= tt "keyword") (or (= tv "true") (= tv "false") (= tv "begin")))
|
|
||||||
true)
|
|
||||||
((and (= tt "op") (or (= tv "(") (= tv "["))) true)
|
|
||||||
(else false)))))
|
|
||||||
(set!
|
|
||||||
parse-app
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(let
|
|
||||||
((head (parse-atom)))
|
|
||||||
(begin
|
|
||||||
(define
|
|
||||||
loop
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(when
|
|
||||||
(at-app-start?)
|
|
||||||
(let
|
|
||||||
((arg (parse-atom)))
|
|
||||||
(begin (set! head (list :app head arg)) (loop))))))
|
|
||||||
(loop)
|
|
||||||
head))))
|
|
||||||
(set!
|
|
||||||
parse-prefix
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(cond
|
|
||||||
((at-op? "-")
|
|
||||||
(begin (advance-tok!) (list :neg (parse-prefix))))
|
|
||||||
((at-op? "!")
|
|
||||||
(begin (advance-tok!) (list :deref (parse-prefix))))
|
|
||||||
((at-kw? "not")
|
|
||||||
(begin (advance-tok!) (list :not (parse-prefix))))
|
|
||||||
(else (parse-app)))))
|
|
||||||
(set!
|
|
||||||
parse-binop-rhs
|
|
||||||
(fn
|
|
||||||
(lhs min-prec)
|
|
||||||
(let
|
|
||||||
((tok (peek-tok)))
|
|
||||||
(cond
|
|
||||||
((not (ocaml-tok-is-binop? tok)) lhs)
|
|
||||||
(else
|
|
||||||
(let
|
|
||||||
((op (ocaml-tok-value tok))
|
|
||||||
(prec (ocaml-binop-prec (ocaml-tok-value tok))))
|
|
||||||
(cond
|
|
||||||
((< prec min-prec) lhs)
|
|
||||||
(else
|
|
||||||
(begin
|
|
||||||
(advance-tok!)
|
|
||||||
(let
|
|
||||||
((rhs (parse-prefix))
|
|
||||||
(next-min
|
|
||||||
(if
|
|
||||||
(ocaml-binop-right? op)
|
|
||||||
prec
|
|
||||||
(+ prec 1))))
|
|
||||||
(begin
|
|
||||||
(set! rhs (parse-binop-rhs rhs next-min))
|
|
||||||
(parse-binop-rhs (list :op op lhs rhs) min-prec))))))))))))
|
|
||||||
(define
|
|
||||||
parse-binary
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(let ((lhs (parse-prefix))) (parse-binop-rhs lhs 1))))
|
|
||||||
(set!
|
|
||||||
parse-tuple
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(let
|
|
||||||
((first (parse-binary)))
|
|
||||||
(cond
|
|
||||||
((at-op? ",")
|
|
||||||
(let
|
|
||||||
((items (list first)))
|
|
||||||
(begin
|
|
||||||
(define
|
|
||||||
loop
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(when
|
|
||||||
(at-op? ",")
|
|
||||||
(begin
|
|
||||||
(advance-tok!)
|
|
||||||
(append! items (parse-binary))
|
|
||||||
(loop)))))
|
|
||||||
(loop)
|
|
||||||
(cons :tuple items))))
|
|
||||||
(else first)))))
|
|
||||||
(define
|
|
||||||
parse-fun
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(let
|
|
||||||
((params (list)))
|
|
||||||
(begin
|
|
||||||
(define
|
|
||||||
collect-params
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(when
|
|
||||||
(check-tok? "ident" nil)
|
|
||||||
(begin
|
|
||||||
(append! params (ocaml-tok-value (peek-tok)))
|
|
||||||
(advance-tok!)
|
|
||||||
(collect-params)))))
|
|
||||||
(collect-params)
|
|
||||||
(when
|
|
||||||
(= (len params) 0)
|
|
||||||
(error "ocaml-parse: fun expects at least one parameter"))
|
|
||||||
(consume! "op" "->")
|
|
||||||
(let ((body (parse-expr))) (list :fun params body))))))
|
|
||||||
(define
|
|
||||||
parse-let
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(let
|
|
||||||
((reccy false))
|
|
||||||
(begin
|
|
||||||
(when
|
|
||||||
(at-kw? "rec")
|
|
||||||
(begin (advance-tok!) (set! reccy true)))
|
|
||||||
(let
|
|
||||||
((name (ocaml-tok-value (consume! "ident" nil)))
|
|
||||||
(params (list)))
|
|
||||||
(begin
|
|
||||||
(define
|
|
||||||
collect-params
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(when
|
|
||||||
(check-tok? "ident" nil)
|
|
||||||
(begin
|
|
||||||
(append! params (ocaml-tok-value (peek-tok)))
|
|
||||||
(advance-tok!)
|
|
||||||
(collect-params)))))
|
|
||||||
(collect-params)
|
|
||||||
(consume! "op" "=")
|
|
||||||
(let
|
|
||||||
((rhs (parse-expr)))
|
|
||||||
(begin
|
|
||||||
(consume! "keyword" "in")
|
|
||||||
(let
|
|
||||||
((body (parse-expr)))
|
|
||||||
(if
|
|
||||||
reccy
|
|
||||||
(list :let-rec name params rhs body)
|
|
||||||
(list :let name params rhs body)))))))))))
|
|
||||||
(define
|
|
||||||
parse-if
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(let
|
|
||||||
((cond-expr (parse-expr-no-seq)))
|
|
||||||
(begin
|
|
||||||
(consume! "keyword" "then")
|
|
||||||
(let
|
|
||||||
((then-expr (parse-expr-no-seq)))
|
|
||||||
(cond
|
|
||||||
((at-kw? "else")
|
|
||||||
(begin
|
|
||||||
(advance-tok!)
|
|
||||||
(let
|
|
||||||
((else-expr (parse-expr-no-seq)))
|
|
||||||
(list :if cond-expr then-expr else-expr))))
|
|
||||||
(else (list :if cond-expr then-expr (list :unit)))))))))
|
|
||||||
(define
|
|
||||||
parse-match
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(let
|
|
||||||
((scrut (parse-expr-no-seq)))
|
|
||||||
(begin
|
|
||||||
(consume! "keyword" "with")
|
|
||||||
(when (at-op? "|") (advance-tok!))
|
|
||||||
(let
|
|
||||||
((cases (list)))
|
|
||||||
(begin
|
|
||||||
(define
|
|
||||||
one
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(let
|
|
||||||
((p (parse-pattern)))
|
|
||||||
(begin
|
|
||||||
(consume! "op" "->")
|
|
||||||
(let
|
|
||||||
((body (parse-expr)))
|
|
||||||
(append! cases (list :case p body)))))))
|
|
||||||
(one)
|
|
||||||
(define
|
|
||||||
loop
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(when
|
|
||||||
(at-op? "|")
|
|
||||||
(begin (advance-tok!) (one) (loop)))))
|
|
||||||
(loop)
|
|
||||||
(cons :match (cons scrut (list cases)))))))))
|
|
||||||
(define parse-for
|
|
||||||
(fn ()
|
|
||||||
(let ((name (ocaml-tok-value (consume! "ident" nil))))
|
|
||||||
(begin
|
|
||||||
(consume! "op" "=")
|
|
||||||
(let ((lo (parse-expr-no-seq)))
|
|
||||||
(let ((dir
|
|
||||||
(cond
|
|
||||||
((at-kw? "to") (begin (advance-tok!) :ascend))
|
|
||||||
((at-kw? "downto") (begin (advance-tok!) :descend))
|
|
||||||
(else (error "ocaml-parse: expected to/downto in for")))))
|
|
||||||
(let ((hi (parse-expr-no-seq)))
|
|
||||||
(begin
|
|
||||||
(consume! "keyword" "do")
|
|
||||||
(let ((body (parse-expr)))
|
|
||||||
(begin
|
|
||||||
(consume! "keyword" "done")
|
|
||||||
(list :for name lo hi dir body)))))))))))
|
|
||||||
(define parse-while
|
|
||||||
(fn ()
|
|
||||||
(let ((cond-expr (parse-expr-no-seq)))
|
|
||||||
(begin
|
|
||||||
(consume! "keyword" "do")
|
|
||||||
(let ((body (parse-expr)))
|
|
||||||
(begin
|
|
||||||
(consume! "keyword" "done")
|
|
||||||
(list :while cond-expr body)))))))
|
|
||||||
(set!
|
|
||||||
parse-expr-no-seq
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(cond
|
|
||||||
((at-kw? "fun") (begin (advance-tok!) (parse-fun)))
|
|
||||||
((at-kw? "let") (begin (advance-tok!) (parse-let)))
|
|
||||||
((at-kw? "if") (begin (advance-tok!) (parse-if)))
|
|
||||||
((at-kw? "match") (begin (advance-tok!) (parse-match)))
|
|
||||||
((at-kw? "for") (begin (advance-tok!) (parse-for)))
|
|
||||||
((at-kw? "while") (begin (advance-tok!) (parse-while)))
|
|
||||||
(else (parse-tuple)))))
|
|
||||||
(set!
|
|
||||||
parse-expr
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(let
|
|
||||||
((lhs (parse-expr-no-seq)))
|
|
||||||
(cond
|
|
||||||
((at-op? ";")
|
|
||||||
(let
|
|
||||||
((items (list lhs)))
|
|
||||||
(begin
|
|
||||||
(define
|
|
||||||
loop
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(when
|
|
||||||
(at-op? ";")
|
|
||||||
(begin
|
|
||||||
(advance-tok!)
|
|
||||||
(cond
|
|
||||||
((at-kw? "end") nil)
|
|
||||||
((at-op? ")") nil)
|
|
||||||
((at-op? "|") nil)
|
|
||||||
((at-kw? "in") nil)
|
|
||||||
((at-kw? "then") nil)
|
|
||||||
((at-kw? "else") nil)
|
|
||||||
((= (ocaml-tok-type (peek-tok)) "eof") nil)
|
|
||||||
(else
|
|
||||||
(begin
|
|
||||||
(append! items (parse-expr-no-seq))
|
|
||||||
(loop))))))))
|
|
||||||
(loop)
|
|
||||||
(cons :seq items))))
|
|
||||||
(else lhs)))))
|
|
||||||
(let
|
|
||||||
((result (parse-expr)))
|
|
||||||
(begin
|
|
||||||
(when
|
|
||||||
(not (= (ocaml-tok-type (peek-tok)) "eof"))
|
|
||||||
(error
|
|
||||||
(str
|
|
||||||
"ocaml-parse: trailing tokens at idx "
|
|
||||||
idx
|
|
||||||
" — got "
|
|
||||||
(ocaml-tok-type (peek-tok))
|
|
||||||
" "
|
|
||||||
(ocaml-tok-value (peek-tok)))))
|
|
||||||
result))))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
ocaml-parse-program
|
|
||||||
(fn
|
|
||||||
(src)
|
|
||||||
(let
|
|
||||||
((tokens (ocaml-tokenize src))
|
|
||||||
(idx 0)
|
|
||||||
(tok-len 0)
|
|
||||||
(decls (list)))
|
|
||||||
(begin
|
|
||||||
(set! tok-len (len tokens))
|
|
||||||
(define peek-tok (fn () (nth tokens idx)))
|
|
||||||
(define advance-tok! (fn () (set! idx (+ idx 1))))
|
|
||||||
(define
|
|
||||||
check-tok?
|
|
||||||
(fn
|
|
||||||
(type value)
|
|
||||||
(let
|
|
||||||
((t (peek-tok)))
|
|
||||||
(and
|
|
||||||
(= (ocaml-tok-type t) type)
|
|
||||||
(or (= value nil) (= (ocaml-tok-value t) value))))))
|
|
||||||
(define
|
|
||||||
consume!
|
|
||||||
(fn
|
|
||||||
(type value)
|
|
||||||
(if
|
|
||||||
(check-tok? type value)
|
|
||||||
(let ((t (peek-tok))) (begin (advance-tok!) t))
|
|
||||||
(error
|
|
||||||
(str
|
|
||||||
"ocaml-parse-program: expected "
|
|
||||||
type
|
|
||||||
" "
|
|
||||||
value
|
|
||||||
" got "
|
|
||||||
(ocaml-tok-type (peek-tok))
|
|
||||||
" "
|
|
||||||
(ocaml-tok-value (peek-tok)))))))
|
|
||||||
(define at-kw? (fn (kw) (check-tok? "keyword" kw)))
|
|
||||||
(define at-op? (fn (op) (check-tok? "op" op)))
|
|
||||||
(define
|
|
||||||
skip-double-semi!
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(when (at-op? ";;") (begin (advance-tok!) (skip-double-semi!)))))
|
|
||||||
(define
|
|
||||||
cur-pos
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(let ((t (peek-tok))) (if (= t nil) (len src) (get t :pos)))))
|
|
||||||
(define
|
|
||||||
skip-to-boundary!
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(cond
|
|
||||||
((>= idx tok-len) nil)
|
|
||||||
((= (ocaml-tok-type (peek-tok)) "eof") nil)
|
|
||||||
((at-op? ";;") nil)
|
|
||||||
((at-kw? "let") nil)
|
|
||||||
(else (begin (advance-tok!) (skip-to-boundary!))))))
|
|
||||||
(define
|
|
||||||
parse-decl-let
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(advance-tok!)
|
|
||||||
(let
|
|
||||||
((reccy false))
|
|
||||||
(begin
|
|
||||||
(when
|
|
||||||
(at-kw? "rec")
|
|
||||||
(begin (advance-tok!) (set! reccy true)))
|
|
||||||
(let
|
|
||||||
((name (ocaml-tok-value (consume! "ident" nil)))
|
|
||||||
(params (list)))
|
|
||||||
(begin
|
|
||||||
(define
|
|
||||||
collect-params
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(when
|
|
||||||
(check-tok? "ident" nil)
|
|
||||||
(begin
|
|
||||||
(append! params (ocaml-tok-value (peek-tok)))
|
|
||||||
(advance-tok!)
|
|
||||||
(collect-params)))))
|
|
||||||
(collect-params)
|
|
||||||
(consume! "op" "=")
|
|
||||||
(let
|
|
||||||
((expr-start (cur-pos)))
|
|
||||||
(begin
|
|
||||||
(skip-to-boundary!)
|
|
||||||
(let
|
|
||||||
((expr-src (slice src expr-start (cur-pos))))
|
|
||||||
(let
|
|
||||||
((expr (ocaml-parse expr-src)))
|
|
||||||
(if
|
|
||||||
reccy
|
|
||||||
(list :def-rec name params expr)
|
|
||||||
(list :def name params expr))))))))))))
|
|
||||||
(define
|
|
||||||
parse-decl-expr
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(let
|
|
||||||
((expr-start (cur-pos)))
|
|
||||||
(begin
|
|
||||||
(skip-to-boundary!)
|
|
||||||
(let
|
|
||||||
((expr-src (slice src expr-start (cur-pos))))
|
|
||||||
(let ((expr (ocaml-parse expr-src))) (list :expr expr)))))))
|
|
||||||
(define
|
|
||||||
loop
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(begin
|
|
||||||
(skip-double-semi!)
|
|
||||||
(when
|
|
||||||
(< idx tok-len)
|
|
||||||
(cond
|
|
||||||
((= (ocaml-tok-type (peek-tok)) "eof") nil)
|
|
||||||
((at-kw? "let")
|
|
||||||
(begin (append! decls (parse-decl-let)) (loop)))
|
|
||||||
(else (begin (append! decls (parse-decl-expr)) (loop))))))))
|
|
||||||
(loop)
|
|
||||||
(cons :program decls)))))
|
|
||||||
@@ -1,820 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Fast OCaml-on-SX test runner — epoch protocol direct to sx_server.exe.
|
|
||||||
# Mirrors lib/lua/test.sh.
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# bash lib/ocaml/test.sh # run all tests
|
|
||||||
# bash lib/ocaml/test.sh -v # verbose
|
|
||||||
|
|
||||||
set -uo pipefail
|
|
||||||
cd "$(git rev-parse --show-toplevel)"
|
|
||||||
|
|
||||||
SX_SERVER="${SX_SERVER:-hosts/ocaml/_build/default/bin/sx_server.exe}"
|
|
||||||
if [ ! -x "$SX_SERVER" ]; then
|
|
||||||
SX_SERVER="/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe"
|
|
||||||
fi
|
|
||||||
if [ ! -x "$SX_SERVER" ]; then
|
|
||||||
echo "ERROR: sx_server.exe not found. Run: cd hosts/ocaml && dune build"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
VERBOSE="${1:-}"
|
|
||||||
PASS=0
|
|
||||||
FAIL=0
|
|
||||||
ERRORS=""
|
|
||||||
TMPFILE=$(mktemp)
|
|
||||||
trap "rm -f $TMPFILE" EXIT
|
|
||||||
|
|
||||||
cat > "$TMPFILE" << 'EPOCHS'
|
|
||||||
(epoch 1)
|
|
||||||
(load "lib/guest/lex.sx")
|
|
||||||
(load "lib/guest/prefix.sx")
|
|
||||||
(load "lib/guest/pratt.sx")
|
|
||||||
(load "lib/ocaml/tokenizer.sx")
|
|
||||||
(load "lib/ocaml/parser.sx")
|
|
||||||
(load "lib/ocaml/eval.sx")
|
|
||||||
(load "lib/ocaml/tests/tokenize.sx")
|
|
||||||
|
|
||||||
;; ── empty / eof ────────────────────────────────────────────────
|
|
||||||
(epoch 100)
|
|
||||||
(eval "(ocaml-test-tok-count \"\")")
|
|
||||||
(epoch 101)
|
|
||||||
(eval "(ocaml-test-tok-type \"\" 0)")
|
|
||||||
|
|
||||||
;; ── numbers ────────────────────────────────────────────────────
|
|
||||||
(epoch 110)
|
|
||||||
(eval "(ocaml-test-tok-type \"42\" 0)")
|
|
||||||
(epoch 111)
|
|
||||||
(eval "(ocaml-test-tok-value \"42\" 0)")
|
|
||||||
(epoch 112)
|
|
||||||
(eval "(ocaml-test-tok-value \"3.14\" 0)")
|
|
||||||
(epoch 113)
|
|
||||||
(eval "(ocaml-test-tok-value \"0xff\" 0)")
|
|
||||||
(epoch 114)
|
|
||||||
(eval "(ocaml-test-tok-value \"1e3\" 0)")
|
|
||||||
(epoch 115)
|
|
||||||
(eval "(ocaml-test-tok-value \"1_000_000\" 0)")
|
|
||||||
(epoch 116)
|
|
||||||
(eval "(ocaml-test-tok-value \"3.14e-2\" 0)")
|
|
||||||
|
|
||||||
;; ── identifiers / constructors / keywords ─────────────────────
|
|
||||||
(epoch 120)
|
|
||||||
(eval "(ocaml-test-tok-type \"foo\" 0)")
|
|
||||||
(epoch 121)
|
|
||||||
(eval "(ocaml-test-tok-value \"foo_bar1\" 0)")
|
|
||||||
(epoch 122)
|
|
||||||
(eval "(ocaml-test-tok-type \"Some\" 0)")
|
|
||||||
(epoch 123)
|
|
||||||
(eval "(ocaml-test-tok-value \"Some\" 0)")
|
|
||||||
(epoch 124)
|
|
||||||
(eval "(ocaml-test-tok-type \"let\" 0)")
|
|
||||||
(epoch 125)
|
|
||||||
(eval "(ocaml-test-tok-value \"match\" 0)")
|
|
||||||
(epoch 126)
|
|
||||||
(eval "(ocaml-test-tok-type \"true\" 0)")
|
|
||||||
(epoch 127)
|
|
||||||
(eval "(ocaml-test-tok-value \"false\" 0)")
|
|
||||||
(epoch 128)
|
|
||||||
(eval "(ocaml-test-tok-value \"name'\" 0)")
|
|
||||||
|
|
||||||
;; ── strings ────────────────────────────────────────────────────
|
|
||||||
(epoch 130)
|
|
||||||
(eval "(ocaml-test-tok-type \"\\\"hi\\\"\" 0)")
|
|
||||||
(epoch 131)
|
|
||||||
(eval "(ocaml-test-tok-value \"\\\"hi\\\"\" 0)")
|
|
||||||
(epoch 132)
|
|
||||||
(eval "(ocaml-test-tok-value \"\\\"a\\\\nb\\\"\" 0)")
|
|
||||||
|
|
||||||
;; ── chars ──────────────────────────────────────────────────────
|
|
||||||
(epoch 140)
|
|
||||||
(eval "(ocaml-test-tok-type \"'a'\" 0)")
|
|
||||||
(epoch 141)
|
|
||||||
(eval "(ocaml-test-tok-value \"'a'\" 0)")
|
|
||||||
(epoch 142)
|
|
||||||
(eval "(ocaml-test-tok-value \"'\\\\n'\" 0)")
|
|
||||||
|
|
||||||
;; ── type variables ─────────────────────────────────────────────
|
|
||||||
(epoch 145)
|
|
||||||
(eval "(ocaml-test-tok-type \"'a\" 0)")
|
|
||||||
(epoch 146)
|
|
||||||
(eval "(ocaml-test-tok-value \"'a\" 0)")
|
|
||||||
|
|
||||||
;; ── multi-char operators ───────────────────────────────────────
|
|
||||||
(epoch 150)
|
|
||||||
(eval "(ocaml-test-tok-value \"->\" 0)")
|
|
||||||
(epoch 151)
|
|
||||||
(eval "(ocaml-test-tok-value \"|>\" 0)")
|
|
||||||
(epoch 152)
|
|
||||||
(eval "(ocaml-test-tok-value \"<-\" 0)")
|
|
||||||
(epoch 153)
|
|
||||||
(eval "(ocaml-test-tok-value \":=\" 0)")
|
|
||||||
(epoch 154)
|
|
||||||
(eval "(ocaml-test-tok-value \"::\" 0)")
|
|
||||||
(epoch 155)
|
|
||||||
(eval "(ocaml-test-tok-value \";;\" 0)")
|
|
||||||
(epoch 156)
|
|
||||||
(eval "(ocaml-test-tok-value \"@@\" 0)")
|
|
||||||
(epoch 157)
|
|
||||||
(eval "(ocaml-test-tok-value \"<>\" 0)")
|
|
||||||
(epoch 158)
|
|
||||||
(eval "(ocaml-test-tok-value \"&&\" 0)")
|
|
||||||
(epoch 159)
|
|
||||||
(eval "(ocaml-test-tok-value \"||\" 0)")
|
|
||||||
|
|
||||||
;; ── single-char punctuation ────────────────────────────────────
|
|
||||||
(epoch 160)
|
|
||||||
(eval "(ocaml-test-tok-value \"+\" 0)")
|
|
||||||
(epoch 161)
|
|
||||||
(eval "(ocaml-test-tok-value \"|\" 0)")
|
|
||||||
(epoch 162)
|
|
||||||
(eval "(ocaml-test-tok-value \";\" 0)")
|
|
||||||
(epoch 163)
|
|
||||||
(eval "(ocaml-test-tok-value \"(\" 0)")
|
|
||||||
(epoch 164)
|
|
||||||
(eval "(ocaml-test-tok-value \"!\" 0)")
|
|
||||||
(epoch 165)
|
|
||||||
(eval "(ocaml-test-tok-value \"@\" 0)")
|
|
||||||
|
|
||||||
;; ── comments ───────────────────────────────────────────────────
|
|
||||||
(epoch 170)
|
|
||||||
(eval "(ocaml-test-tok-count \"(* hi *)\")")
|
|
||||||
(epoch 171)
|
|
||||||
(eval "(ocaml-test-tok-value \"(* c *) 42\" 0)")
|
|
||||||
(epoch 172)
|
|
||||||
(eval "(ocaml-test-tok-count \"(* outer (* inner *) end *) 1\")")
|
|
||||||
(epoch 173)
|
|
||||||
(eval "(ocaml-test-tok-value \"(* outer (* inner *) end *) 1\" 0)")
|
|
||||||
|
|
||||||
;; ── compound expressions ───────────────────────────────────────
|
|
||||||
(epoch 180)
|
|
||||||
(eval "(ocaml-test-tok-count \"let x = 1\")")
|
|
||||||
(epoch 181)
|
|
||||||
(eval "(ocaml-test-tok-type \"let x = 1\" 0)")
|
|
||||||
(epoch 182)
|
|
||||||
(eval "(ocaml-test-tok-value \"let x = 1\" 0)")
|
|
||||||
(epoch 183)
|
|
||||||
(eval "(ocaml-test-tok-type \"let x = 1\" 1)")
|
|
||||||
(epoch 184)
|
|
||||||
(eval "(ocaml-test-tok-value \"let x = 1\" 2)")
|
|
||||||
(epoch 185)
|
|
||||||
(eval "(ocaml-test-tok-value \"let x = 1\" 3)")
|
|
||||||
|
|
||||||
(epoch 190)
|
|
||||||
(eval "(ocaml-test-tok-count \"match x with | None -> 0 | Some y -> y\")")
|
|
||||||
(epoch 191)
|
|
||||||
(eval "(ocaml-test-tok-value \"fun x -> x + 1\" 2)")
|
|
||||||
(epoch 192)
|
|
||||||
(eval "(ocaml-test-tok-type \"fun x -> x + 1\" 2)")
|
|
||||||
(epoch 193)
|
|
||||||
(eval "(ocaml-test-tok-type \"Some 42\" 0)")
|
|
||||||
(epoch 194)
|
|
||||||
(eval "(ocaml-test-tok-value \"a |> f |> g\" 1)")
|
|
||||||
(epoch 195)
|
|
||||||
(eval "(ocaml-test-tok-value \"x := !y\" 1)")
|
|
||||||
|
|
||||||
;; ── Phase 1.parse: parser ──────────────────────────────────────
|
|
||||||
;; Atoms
|
|
||||||
(epoch 200)
|
|
||||||
(eval "(ocaml-parse \"42\")")
|
|
||||||
(epoch 201)
|
|
||||||
(eval "(ocaml-parse \"3.14\")")
|
|
||||||
(epoch 202)
|
|
||||||
(eval "(ocaml-parse \"\\\"hi\\\"\")")
|
|
||||||
(epoch 203)
|
|
||||||
(eval "(ocaml-parse \"'a'\")")
|
|
||||||
(epoch 204)
|
|
||||||
(eval "(ocaml-parse \"true\")")
|
|
||||||
(epoch 205)
|
|
||||||
(eval "(ocaml-parse \"false\")")
|
|
||||||
(epoch 206)
|
|
||||||
(eval "(ocaml-parse \"x\")")
|
|
||||||
(epoch 207)
|
|
||||||
(eval "(ocaml-parse \"Some\")")
|
|
||||||
(epoch 208)
|
|
||||||
(eval "(ocaml-parse \"()\")")
|
|
||||||
|
|
||||||
;; Application (left-assoc)
|
|
||||||
(epoch 210)
|
|
||||||
(eval "(ocaml-parse \"f x\")")
|
|
||||||
(epoch 211)
|
|
||||||
(eval "(ocaml-parse \"f x y\")")
|
|
||||||
(epoch 212)
|
|
||||||
(eval "(ocaml-parse \"f (g x)\")")
|
|
||||||
(epoch 213)
|
|
||||||
(eval "(ocaml-parse \"Some 42\")")
|
|
||||||
|
|
||||||
;; Binops with precedence
|
|
||||||
(epoch 220)
|
|
||||||
(eval "(ocaml-parse \"1 + 2\")")
|
|
||||||
(epoch 221)
|
|
||||||
(eval "(ocaml-parse \"a + b * c\")")
|
|
||||||
(epoch 222)
|
|
||||||
(eval "(ocaml-parse \"a * b + c\")")
|
|
||||||
(epoch 223)
|
|
||||||
(eval "(ocaml-parse \"a && b || c\")")
|
|
||||||
(epoch 224)
|
|
||||||
(eval "(ocaml-parse \"a = b\")")
|
|
||||||
(epoch 225)
|
|
||||||
(eval "(ocaml-parse \"a ^ b ^ c\")")
|
|
||||||
(epoch 226)
|
|
||||||
(eval "(ocaml-parse \"a :: b :: []\")")
|
|
||||||
(epoch 227)
|
|
||||||
(eval "(ocaml-parse \"(a + b) * c\")")
|
|
||||||
(epoch 228)
|
|
||||||
(eval "(ocaml-parse \"a |> f |> g\")")
|
|
||||||
(epoch 229)
|
|
||||||
(eval "(ocaml-parse \"x mod 2\")")
|
|
||||||
|
|
||||||
;; Prefix
|
|
||||||
(epoch 230)
|
|
||||||
(eval "(ocaml-parse \"-x\")")
|
|
||||||
(epoch 231)
|
|
||||||
(eval "(ocaml-parse \"-1 + 2\")")
|
|
||||||
|
|
||||||
;; Tuples & lists
|
|
||||||
(epoch 240)
|
|
||||||
(eval "(ocaml-parse \"(1, 2, 3)\")")
|
|
||||||
(epoch 241)
|
|
||||||
(eval "(ocaml-parse \"[1; 2; 3]\")")
|
|
||||||
(epoch 242)
|
|
||||||
(eval "(ocaml-parse \"[]\")")
|
|
||||||
|
|
||||||
;; if / fun / let / let rec
|
|
||||||
(epoch 250)
|
|
||||||
(eval "(ocaml-parse \"if x then 1 else 2\")")
|
|
||||||
(epoch 251)
|
|
||||||
(eval "(ocaml-parse \"if c then x\")")
|
|
||||||
(epoch 252)
|
|
||||||
(eval "(ocaml-parse \"fun x -> x + 1\")")
|
|
||||||
(epoch 253)
|
|
||||||
(eval "(ocaml-parse \"fun x y -> x + y\")")
|
|
||||||
(epoch 254)
|
|
||||||
(eval "(ocaml-parse \"let x = 1 in x\")")
|
|
||||||
(epoch 255)
|
|
||||||
(eval "(ocaml-parse \"let f x = x + 1 in f 2\")")
|
|
||||||
(epoch 256)
|
|
||||||
(eval "(ocaml-parse \"let rec f x = f x in f 1\")")
|
|
||||||
(epoch 257)
|
|
||||||
(eval "(ocaml-parse \"let f x y = x + y in f 1 2\")")
|
|
||||||
|
|
||||||
;; begin/end
|
|
||||||
(epoch 260)
|
|
||||||
(eval "(ocaml-parse \"begin 1 + 2 end\")")
|
|
||||||
|
|
||||||
;; ── Top-level decls ────────────────────────────────────────────
|
|
||||||
(epoch 270)
|
|
||||||
(eval "(ocaml-parse-program \"let x = 1\")")
|
|
||||||
(epoch 271)
|
|
||||||
(eval "(ocaml-parse-program \"let x = 1 ;;\")")
|
|
||||||
(epoch 272)
|
|
||||||
(eval "(ocaml-parse-program \"let f x = x + 1\")")
|
|
||||||
(epoch 273)
|
|
||||||
(eval "(ocaml-parse-program \"let rec fact n = if n = 0 then 1 else n * fact (n - 1)\")")
|
|
||||||
(epoch 274)
|
|
||||||
(eval "(ocaml-parse-program \"let x = 1 let y = 2\")")
|
|
||||||
(epoch 275)
|
|
||||||
(eval "(ocaml-parse-program \"1 + 2 ;;\")")
|
|
||||||
(epoch 276)
|
|
||||||
(eval "(ocaml-parse-program \"let x = 1 ;; let y = 2 ;; x + y\")")
|
|
||||||
(epoch 277)
|
|
||||||
(eval "(len (ocaml-parse-program \"let x = 1 ;; let y = 2 ;; x + y\"))")
|
|
||||||
(epoch 278)
|
|
||||||
(eval "(ocaml-parse-program \"\")")
|
|
||||||
|
|
||||||
;; ── Match / patterns ───────────────────────────────────────────
|
|
||||||
(epoch 300)
|
|
||||||
(eval "(ocaml-parse \"match x with | None -> 0 | Some y -> y\")")
|
|
||||||
(epoch 301)
|
|
||||||
(eval "(ocaml-parse \"match x with None -> 0 | Some y -> y\")")
|
|
||||||
(epoch 302)
|
|
||||||
(eval "(ocaml-parse \"match l with | [] -> 0 | h :: t -> 1\")")
|
|
||||||
(epoch 303)
|
|
||||||
(eval "(ocaml-parse \"match p with | (a, b) -> a + b\")")
|
|
||||||
(epoch 304)
|
|
||||||
(eval "(ocaml-parse \"match n with | 0 -> 1 | _ -> n\")")
|
|
||||||
(epoch 305)
|
|
||||||
(eval "(ocaml-parse \"match x with | true -> 1 | false -> 0\")")
|
|
||||||
(epoch 306)
|
|
||||||
(eval "(ocaml-parse \"match x with | Pair (a, b) -> a + b\")")
|
|
||||||
(epoch 307)
|
|
||||||
(eval "(ocaml-parse \"match x with | \\\"hi\\\" -> 1 | _ -> 0\")")
|
|
||||||
(epoch 308)
|
|
||||||
(eval "(ocaml-parse \"match x with | () -> 0\")")
|
|
||||||
|
|
||||||
;; ── Sequences (;) ──────────────────────────────────────────────
|
|
||||||
(epoch 320)
|
|
||||||
(eval "(ocaml-parse \"1; 2\")")
|
|
||||||
(epoch 321)
|
|
||||||
(eval "(ocaml-parse \"1; 2; 3\")")
|
|
||||||
(epoch 322)
|
|
||||||
(eval "(ocaml-parse \"(1; 2)\")")
|
|
||||||
(epoch 323)
|
|
||||||
(eval "(ocaml-parse \"begin a; b; c end\")")
|
|
||||||
(epoch 324)
|
|
||||||
(eval "(ocaml-parse \"let x = 1 in x; x\")")
|
|
||||||
(epoch 325)
|
|
||||||
(eval "(ocaml-parse \"if c then (a; b) else c\")")
|
|
||||||
(epoch 326)
|
|
||||||
(eval "(ocaml-parse \"[1; 2; 3]\")")
|
|
||||||
(epoch 327)
|
|
||||||
(eval "(ocaml-parse \"1; 2;\")")
|
|
||||||
(epoch 328)
|
|
||||||
(eval "(ocaml-parse \"begin a; end\")")
|
|
||||||
(epoch 329)
|
|
||||||
(eval "(ocaml-parse \"match x with | _ -> a; b\")")
|
|
||||||
|
|
||||||
;; ── Phase 2: evaluator ─────────────────────────────────────────
|
|
||||||
;; Atoms
|
|
||||||
(epoch 400)
|
|
||||||
(eval "(ocaml-run \"42\")")
|
|
||||||
(epoch 401)
|
|
||||||
(eval "(ocaml-run \"3.14\")")
|
|
||||||
(epoch 402)
|
|
||||||
(eval "(ocaml-run \"true\")")
|
|
||||||
(epoch 403)
|
|
||||||
(eval "(ocaml-run \"false\")")
|
|
||||||
(epoch 404)
|
|
||||||
(eval "(ocaml-run \"\\\"hi\\\"\")")
|
|
||||||
|
|
||||||
;; Arithmetic
|
|
||||||
(epoch 410)
|
|
||||||
(eval "(ocaml-run \"1 + 2\")")
|
|
||||||
(epoch 411)
|
|
||||||
(eval "(ocaml-run \"10 - 3\")")
|
|
||||||
(epoch 412)
|
|
||||||
(eval "(ocaml-run \"4 * 5\")")
|
|
||||||
(epoch 413)
|
|
||||||
(eval "(ocaml-run \"20 / 4\")")
|
|
||||||
(epoch 414)
|
|
||||||
(eval "(ocaml-run \"10 mod 3\")")
|
|
||||||
(epoch 415)
|
|
||||||
(eval "(ocaml-run \"2 ** 10\")")
|
|
||||||
(epoch 416)
|
|
||||||
(eval "(ocaml-run \"(1 + 2) * 3\")")
|
|
||||||
(epoch 417)
|
|
||||||
(eval "(ocaml-run \"1 + 2 * 3\")")
|
|
||||||
(epoch 418)
|
|
||||||
(eval "(ocaml-run \"-5 + 10\")")
|
|
||||||
|
|
||||||
;; Comparison & boolean
|
|
||||||
(epoch 420)
|
|
||||||
(eval "(ocaml-run \"1 < 2\")")
|
|
||||||
(epoch 421)
|
|
||||||
(eval "(ocaml-run \"3 > 2\")")
|
|
||||||
(epoch 422)
|
|
||||||
(eval "(ocaml-run \"2 = 2\")")
|
|
||||||
(epoch 423)
|
|
||||||
(eval "(ocaml-run \"1 <> 2\")")
|
|
||||||
(epoch 424)
|
|
||||||
(eval "(ocaml-run \"true && false\")")
|
|
||||||
(epoch 425)
|
|
||||||
(eval "(ocaml-run \"true || false\")")
|
|
||||||
(epoch 426)
|
|
||||||
(eval "(ocaml-run \"not false\")")
|
|
||||||
|
|
||||||
;; String
|
|
||||||
(epoch 430)
|
|
||||||
(eval "(ocaml-run \"\\\"a\\\" ^ \\\"b\\\"\")")
|
|
||||||
(epoch 431)
|
|
||||||
(eval "(ocaml-run \"\\\"hello\\\" ^ \\\" \\\" ^ \\\"world\\\"\")")
|
|
||||||
|
|
||||||
;; Conditional
|
|
||||||
(epoch 440)
|
|
||||||
(eval "(ocaml-run \"if true then 1 else 2\")")
|
|
||||||
(epoch 441)
|
|
||||||
(eval "(ocaml-run \"if 1 > 2 then 100 else 200\")")
|
|
||||||
|
|
||||||
;; Let / lambda / app
|
|
||||||
(epoch 450)
|
|
||||||
(eval "(ocaml-run \"let x = 5 in x * 2\")")
|
|
||||||
(epoch 451)
|
|
||||||
(eval "(ocaml-run \"let f x = x + 1 in f 41\")")
|
|
||||||
(epoch 452)
|
|
||||||
(eval "(ocaml-run \"let f x y = x + y in f 3 4\")")
|
|
||||||
(epoch 453)
|
|
||||||
(eval "(ocaml-run \"(fun x -> x * x) 7\")")
|
|
||||||
(epoch 454)
|
|
||||||
(eval "(ocaml-run \"(fun x -> fun y -> x + y) 10 20\")")
|
|
||||||
(epoch 455)
|
|
||||||
(eval "(ocaml-run \"let f = fun x -> x + 1 in f 9\")")
|
|
||||||
|
|
||||||
;; Closures capture
|
|
||||||
(epoch 460)
|
|
||||||
(eval "(ocaml-run \"let x = 10 in let f y = x + y in f 5\")")
|
|
||||||
(epoch 461)
|
|
||||||
(eval "(ocaml-run \"let make_adder n = fun x -> n + x in (make_adder 100) 1\")")
|
|
||||||
|
|
||||||
;; Recursion
|
|
||||||
(epoch 470)
|
|
||||||
(eval "(ocaml-run \"let rec fact n = if n = 0 then 1 else n * fact (n - 1) in fact 5\")")
|
|
||||||
(epoch 471)
|
|
||||||
(eval "(ocaml-run \"let rec fib n = if n < 2 then n else fib (n - 1) + fib (n - 2) in fib 10\")")
|
|
||||||
(epoch 472)
|
|
||||||
(eval "(ocaml-run \"let rec sum n = if n = 0 then 0 else n + sum (n - 1) in sum 100\")")
|
|
||||||
|
|
||||||
;; Sequence
|
|
||||||
(epoch 480)
|
|
||||||
(eval "(ocaml-run \"1; 2; 3\")")
|
|
||||||
(epoch 481)
|
|
||||||
(eval "(ocaml-run \"begin 10 end\")")
|
|
||||||
|
|
||||||
;; Programs (top-level decls)
|
|
||||||
(epoch 490)
|
|
||||||
(eval "(ocaml-run-program \"let x = 1;; let y = 2;; x + y\")")
|
|
||||||
(epoch 491)
|
|
||||||
(eval "(ocaml-run-program \"let rec fact n = if n = 0 then 1 else n * fact (n - 1);; fact 6\")")
|
|
||||||
(epoch 492)
|
|
||||||
(eval "(ocaml-run-program \"let inc x = x + 1;; let double x = x * 2;; double (inc 4)\")")
|
|
||||||
|
|
||||||
;; Pipe
|
|
||||||
(epoch 495)
|
|
||||||
(eval "(ocaml-run \"let f x = x * 2 in 5 |> f\")")
|
|
||||||
|
|
||||||
;; ── Phase 3: ADTs + match (eval) ───────────────────────────────
|
|
||||||
;; Constructors
|
|
||||||
(epoch 500)
|
|
||||||
(eval "(ocaml-run \"None\")")
|
|
||||||
(epoch 501)
|
|
||||||
(eval "(ocaml-run \"Some 42\")")
|
|
||||||
(epoch 502)
|
|
||||||
(eval "(ocaml-run \"Some (1, 2)\")")
|
|
||||||
|
|
||||||
;; Match — option
|
|
||||||
(epoch 510)
|
|
||||||
(eval "(ocaml-run \"match Some 5 with | None -> 0 | Some y -> y\")")
|
|
||||||
(epoch 511)
|
|
||||||
(eval "(ocaml-run \"match None with | None -> 0 | Some y -> y\")")
|
|
||||||
|
|
||||||
;; Match — literals
|
|
||||||
(epoch 520)
|
|
||||||
(eval "(ocaml-run \"match 3 with | 1 -> 100 | 2 -> 200 | _ -> 999\")")
|
|
||||||
(epoch 521)
|
|
||||||
(eval "(ocaml-run \"match true with | true -> 1 | false -> 0\")")
|
|
||||||
(epoch 522)
|
|
||||||
(eval "(ocaml-run \"match \\\"hi\\\" with | \\\"hi\\\" -> 1 | _ -> 0\")")
|
|
||||||
|
|
||||||
;; Match — tuples
|
|
||||||
(epoch 530)
|
|
||||||
(eval "(ocaml-run \"match (1, 2) with | (a, b) -> a + b\")")
|
|
||||||
(epoch 531)
|
|
||||||
(eval "(ocaml-run \"match (1, 2, 3) with | (a, b, c) -> a * b * c\")")
|
|
||||||
|
|
||||||
;; Match — list cons / nil
|
|
||||||
(epoch 540)
|
|
||||||
(eval "(ocaml-run \"match [1; 2; 3] with | [] -> 0 | h :: _ -> h\")")
|
|
||||||
(epoch 541)
|
|
||||||
(eval "(ocaml-run \"match [] with | [] -> 0 | h :: _ -> h\")")
|
|
||||||
(epoch 542)
|
|
||||||
(eval "(ocaml-run \"match [1; 2; 3] with | [a; b; c] -> a + b + c | _ -> 0\")")
|
|
||||||
(epoch 543)
|
|
||||||
(eval "(ocaml-run \"let rec len lst = match lst with | [] -> 0 | _ :: t -> 1 + len t in len [1; 2; 3; 4; 5]\")")
|
|
||||||
(epoch 544)
|
|
||||||
(eval "(ocaml-run \"let rec sum lst = match lst with | [] -> 0 | h :: t -> h + sum t in sum [1; 2; 3; 4; 5]\")")
|
|
||||||
|
|
||||||
;; Match — wildcard + var
|
|
||||||
(epoch 550)
|
|
||||||
(eval "(ocaml-run \"match 99 with | _ -> 1\")")
|
|
||||||
(epoch 551)
|
|
||||||
(eval "(ocaml-run \"match 99 with | x -> x + 1\")")
|
|
||||||
|
|
||||||
;; Constructors with tuple args
|
|
||||||
(epoch 560)
|
|
||||||
(eval "(ocaml-run \"match Pair (1, 2) with | Pair (a, b) -> a * b\")")
|
|
||||||
|
|
||||||
;; ── References (ref / ! / :=) ──────────────────────────────────
|
|
||||||
(epoch 600)
|
|
||||||
(eval "(ocaml-run \"let r = ref 5 in !r\")")
|
|
||||||
(epoch 601)
|
|
||||||
(eval "(ocaml-run \"let r = ref 5 in r := 10; !r\")")
|
|
||||||
(epoch 602)
|
|
||||||
(eval "(ocaml-run \"let r = ref 0 in r := !r + 1; r := !r + 1; !r\")")
|
|
||||||
(epoch 603)
|
|
||||||
(eval "(ocaml-run \"let r = ref 100 in let f x = r := !r + x in f 5; f 10; !r\")")
|
|
||||||
(epoch 604)
|
|
||||||
(eval "(ocaml-run \"let r = ref \\\"a\\\" in r := \\\"b\\\"; !r\")")
|
|
||||||
(epoch 605)
|
|
||||||
(eval "(ocaml-run \"let count = ref 0 in let rec loop n = if n = 0 then !count else (count := !count + n; loop (n - 1)) in loop 5\")")
|
|
||||||
|
|
||||||
;; ── for / while loops ──────────────────────────────────────────
|
|
||||||
(epoch 620)
|
|
||||||
(eval "(ocaml-run \"let s = ref 0 in for i = 1 to 5 do s := !s + i done; !s\")")
|
|
||||||
(epoch 621)
|
|
||||||
(eval "(ocaml-run \"let s = ref 0 in for i = 5 downto 1 do s := !s + i done; !s\")")
|
|
||||||
(epoch 622)
|
|
||||||
(eval "(ocaml-run \"let i = ref 0 in let s = ref 0 in while !i < 5 do i := !i + 1; s := !s + !i done; !s\")")
|
|
||||||
(epoch 623)
|
|
||||||
(eval "(ocaml-run \"let s = ref 0 in for i = 1 to 100 do s := !s + i done; !s\")")
|
|
||||||
(epoch 624)
|
|
||||||
(eval "(ocaml-run \"let p = ref 1 in for i = 1 to 5 do p := !p * i done; !p\")")
|
|
||||||
|
|
||||||
EPOCHS
|
|
||||||
|
|
||||||
OUTPUT=$(timeout 60 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
|
|
||||||
|
|
||||||
check() {
|
|
||||||
local epoch="$1" desc="$2" expected="$3"
|
|
||||||
local actual
|
|
||||||
actual=$(echo "$OUTPUT" | grep -A1 "^(ok-len $epoch " | tail -1)
|
|
||||||
if [ -z "$actual" ]; then
|
|
||||||
actual=$(echo "$OUTPUT" | grep "^(ok $epoch " || true)
|
|
||||||
fi
|
|
||||||
if [ -z "$actual" ]; then
|
|
||||||
actual=$(echo "$OUTPUT" | grep "^(error $epoch " || true)
|
|
||||||
fi
|
|
||||||
if [ -z "$actual" ]; then
|
|
||||||
actual="<no output for epoch $epoch>"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if echo "$actual" | grep -qF -- "$expected"; then
|
|
||||||
PASS=$((PASS + 1))
|
|
||||||
[ "$VERBOSE" = "-v" ] && echo " ok $desc"
|
|
||||||
else
|
|
||||||
FAIL=$((FAIL + 1))
|
|
||||||
ERRORS+=" FAIL $desc (epoch $epoch)
|
|
||||||
expected: $expected
|
|
||||||
actual: $actual
|
|
||||||
"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# empty / eof
|
|
||||||
check 100 "empty tokens length" '1'
|
|
||||||
check 101 "empty first is eof" '"eof"'
|
|
||||||
|
|
||||||
# numbers
|
|
||||||
check 110 "int type" '"number"'
|
|
||||||
check 111 "int value" '42'
|
|
||||||
check 112 "float value" '3.14'
|
|
||||||
check 113 "hex value" '255'
|
|
||||||
check 114 "exponent" '1000'
|
|
||||||
check 115 "underscored int" '1000000'
|
|
||||||
check 116 "neg exponent" '0.0314'
|
|
||||||
|
|
||||||
# idents / ctors / keywords
|
|
||||||
check 120 "ident type" '"ident"'
|
|
||||||
check 121 "ident value" '"foo_bar1"'
|
|
||||||
check 122 "ctor type" '"ctor"'
|
|
||||||
check 123 "ctor value" '"Some"'
|
|
||||||
check 124 "let keyword type" '"keyword"'
|
|
||||||
check 125 "match keyword value" '"match"'
|
|
||||||
check 126 "true is keyword" '"keyword"'
|
|
||||||
check 127 "false value" '"false"'
|
|
||||||
check 128 "primed ident" "\"name'\""
|
|
||||||
|
|
||||||
# strings
|
|
||||||
check 130 "string type" '"string"'
|
|
||||||
check 131 "string value" '"hi"'
|
|
||||||
check 132 "escape sequence" '"a'
|
|
||||||
|
|
||||||
# chars
|
|
||||||
check 140 "char type" '"char"'
|
|
||||||
check 141 "char value" '"a"'
|
|
||||||
check 142 "char escape" '"'
|
|
||||||
|
|
||||||
# tyvars
|
|
||||||
check 145 "tyvar type" '"tyvar"'
|
|
||||||
check 146 "tyvar value" '"a"'
|
|
||||||
|
|
||||||
# multi-char ops
|
|
||||||
check 150 "->" '"->"'
|
|
||||||
check 151 "|>" '"|>"'
|
|
||||||
check 152 "<-" '"<-"'
|
|
||||||
check 153 ":=" '":="'
|
|
||||||
check 154 "::" '"::"'
|
|
||||||
check 155 ";;" '";;"'
|
|
||||||
check 156 "@@" '"@@"'
|
|
||||||
check 157 "<>" '"<>"'
|
|
||||||
check 158 "&&" '"&&"'
|
|
||||||
check 159 "||" '"||"'
|
|
||||||
|
|
||||||
# single ops
|
|
||||||
check 160 "+" '"+"'
|
|
||||||
check 161 "|" '"|"'
|
|
||||||
check 162 ";" '";"'
|
|
||||||
check 163 "(" '"("'
|
|
||||||
check 164 "!" '"!"'
|
|
||||||
check 165 "@" '"@"'
|
|
||||||
|
|
||||||
# comments
|
|
||||||
check 170 "block comment alone -> eof" '1'
|
|
||||||
check 171 "num after block comment" '42'
|
|
||||||
check 172 "nested comment count" '2'
|
|
||||||
check 173 "nested comment value" '1'
|
|
||||||
|
|
||||||
# compound
|
|
||||||
check 180 "let x = 1 count" '5'
|
|
||||||
check 181 "let is keyword" '"keyword"'
|
|
||||||
check 182 "let value" '"let"'
|
|
||||||
check 183 "x is ident" '"ident"'
|
|
||||||
check 184 "= value" '"="'
|
|
||||||
check 185 "1 value" '1'
|
|
||||||
|
|
||||||
check 190 "match expr count" '13'
|
|
||||||
check 191 "fun -> arrow value" '"->"'
|
|
||||||
check 192 "fun -> arrow type" '"op"'
|
|
||||||
check 193 "Some is ctor" '"ctor"'
|
|
||||||
check 194 "first |> value" '"|>"'
|
|
||||||
check 195 "ref assign :=" '":="'
|
|
||||||
|
|
||||||
# ── Parser tests ────────────────────────────────────────────────
|
|
||||||
check 200 "parse int" '("int" 42)'
|
|
||||||
check 201 "parse float" '("float" 3.14)'
|
|
||||||
check 202 "parse string" '("string" "hi")'
|
|
||||||
check 203 "parse char" '("char" "a")'
|
|
||||||
check 204 "parse true" '("bool" true)'
|
|
||||||
check 205 "parse false" '("bool" false)'
|
|
||||||
check 206 "parse var" '("var" "x")'
|
|
||||||
check 207 "parse ctor" '("con" "Some")'
|
|
||||||
check 208 "parse unit" '("unit")'
|
|
||||||
|
|
||||||
check 210 "parse f x" '("app" ("var" "f") ("var" "x"))'
|
|
||||||
check 211 "parse f x y left-assoc" '("app" ("app" ("var" "f") ("var" "x")) ("var" "y"))'
|
|
||||||
check 212 "parse f (g x)" '("app" ("var" "f") ("app" ("var" "g") ("var" "x")))'
|
|
||||||
check 213 "parse Some 42" '("app" ("con" "Some") ("int" 42))'
|
|
||||||
|
|
||||||
check 220 "parse 1+2" '("op" "+" ("int" 1) ("int" 2))'
|
|
||||||
check 221 "parse a + b * c prec" '("op" "+" ("var" "a") ("op" "*"'
|
|
||||||
check 222 "parse a*b + c prec" '("op" "+" ("op" "*"'
|
|
||||||
check 223 "parse && / || prec" '("op" "||" ("op" "&&"'
|
|
||||||
check 224 "parse a = b" '("op" "=" ("var" "a") ("var" "b"))'
|
|
||||||
check 225 "parse ^ right-assoc" '("op" "^" ("var" "a") ("op" "^"'
|
|
||||||
check 226 "parse :: right-assoc" '("op" "::" ("var" "a") ("op" "::"'
|
|
||||||
check 227 "parse parens override" '("op" "*" ("op" "+"'
|
|
||||||
check 228 "parse |> chain" '("op" "|>" ("op" "|>"'
|
|
||||||
check 229 "parse mod kw-binop" '("op" "mod" ("var" "x") ("int" 2))'
|
|
||||||
|
|
||||||
check 230 "parse -x" '("neg" ("var" "x"))'
|
|
||||||
check 231 "parse -1+2" '("op" "+" ("neg" ("int" 1)) ("int" 2))'
|
|
||||||
|
|
||||||
check 240 "parse tuple" '("tuple" ("int" 1) ("int" 2) ("int" 3))'
|
|
||||||
check 241 "parse list literal" '("list" ("int" 1) ("int" 2) ("int" 3))'
|
|
||||||
check 242 "parse []" '("list")'
|
|
||||||
|
|
||||||
check 250 "parse if/then/else" '("if" ("var" "x") ("int" 1) ("int" 2))'
|
|
||||||
check 251 "parse if w/o else" '("if" ("var" "c") ("var" "x") ("unit"))'
|
|
||||||
check 252 "parse fun x -> ..." '("fun" ("x") ("op" "+" ("var" "x") ("int" 1)))'
|
|
||||||
check 253 "parse fun x y ->" '("fun" ("x" "y")'
|
|
||||||
check 254 "parse let x = 1 in x" '("let" "x" () ("int" 1) ("var" "x"))'
|
|
||||||
check 255 "parse let f x =" '("let" "f" ("x") ("op" "+"'
|
|
||||||
check 256 "parse let rec f x =" '("let-rec" "f" ("x")'
|
|
||||||
check 257 "parse let f x y =" '("let" "f" ("x" "y")'
|
|
||||||
|
|
||||||
check 260 "parse begin/end" '("op" "+" ("int" 1) ("int" 2))'
|
|
||||||
|
|
||||||
# ── Top-level decls ─────────────────────────────────────────────
|
|
||||||
check 270 "program: let x = 1" '("program" ("def" "x" () ("int" 1)))'
|
|
||||||
check 271 "program: let x = 1 ;;" '("program" ("def" "x" () ("int" 1)))'
|
|
||||||
check 272 "program: let f x = x+1" '("program" ("def" "f" ("x") ("op" "+"'
|
|
||||||
check 273 "program: let rec fact" '("def-rec" "fact" ("n")'
|
|
||||||
check 274 "program: two decls" '("def" "x" () ("int" 1)) ("def" "y"'
|
|
||||||
check 275 "program: bare expr" '("program" ("expr" ("op" "+" ("int" 1) ("int" 2))))'
|
|
||||||
check 276 "program: mixed decls + expr" '("def" "y" () ("int" 2)) ("expr"'
|
|
||||||
check 277 "program: 4 forms incl head" '4'
|
|
||||||
check 278 "program: empty" '("program")'
|
|
||||||
|
|
||||||
# ── Match / patterns ────────────────────────────────────────────
|
|
||||||
check 300 "match Some/None" '("match" ("var" "x") (("case" ("pcon" "None") ("int" 0)) ("case" ("pcon" "Some" ("pvar" "y")) ("var" "y")))'
|
|
||||||
check 301 "match no leading bar" '("match" ("var" "x") (("case" ("pcon" "None") ("int" 0)) ("case" ("pcon" "Some"'
|
|
||||||
check 302 "match list cons" '("case" ("plist") ("int" 0)) ("case" ("pcons" ("pvar" "h") ("pvar" "t")) ("int" 1))'
|
|
||||||
check 303 "match tuple pat" '("ptuple" ("pvar" "a") ("pvar" "b"))'
|
|
||||||
check 304 "match int + wildcard" '("case" ("plit" ("int" 0)) ("int" 1)) ("case" ("pwild")'
|
|
||||||
check 305 "match bool literals" '("plit" ("bool" true))'
|
|
||||||
check 306 "match ctor with tuple arg" '("pcon" "Pair" ("pvar" "a") ("pvar" "b"))'
|
|
||||||
check 307 "match string literal" '("plit" ("string" "hi"))'
|
|
||||||
check 308 "match unit pattern" '("plit" ("unit"))'
|
|
||||||
|
|
||||||
# ── Sequences ───────────────────────────────────────────────────
|
|
||||||
check 320 "seq 1;2" '("seq" ("int" 1) ("int" 2))'
|
|
||||||
check 321 "seq 1;2;3" '("seq" ("int" 1) ("int" 2) ("int" 3))'
|
|
||||||
check 322 "seq in parens" '("seq" ("int" 1) ("int" 2))'
|
|
||||||
check 323 "seq in begin/end" '("seq" ("var" "a") ("var" "b") ("var" "c"))'
|
|
||||||
check 324 "let body absorbs seq" '("let" "x" () ("int" 1) ("seq" ("var" "x") ("var" "x")))'
|
|
||||||
check 325 "if-branch parens for seq" '("if" ("var" "c") ("seq" ("var" "a") ("var" "b"))'
|
|
||||||
check 326 "list ; is separator" '("list" ("int" 1) ("int" 2) ("int" 3))'
|
|
||||||
check 327 "trailing ; OK" '("seq" ("int" 1) ("int" 2))'
|
|
||||||
check 328 "begin a; end singleton seq" '("seq" ("var" "a"))'
|
|
||||||
check 329 "match clause body absorbs ;" '("case" ("pwild") ("seq" ("var" "a") ("var" "b")))'
|
|
||||||
|
|
||||||
# ── Phase 2: evaluator ──────────────────────────────────────────
|
|
||||||
# atoms
|
|
||||||
check 400 "eval int" '42'
|
|
||||||
check 401 "eval float" '3.14'
|
|
||||||
check 402 "eval true" 'true'
|
|
||||||
check 403 "eval false" 'false'
|
|
||||||
check 404 "eval string" '"hi"'
|
|
||||||
|
|
||||||
# arithmetic
|
|
||||||
check 410 "eval 1+2" '3'
|
|
||||||
check 411 "eval 10-3" '7'
|
|
||||||
check 412 "eval 4*5" '20'
|
|
||||||
check 413 "eval 20/4" '5'
|
|
||||||
check 414 "eval 10 mod 3" '1'
|
|
||||||
check 415 "eval 2 ** 10" '1024'
|
|
||||||
check 416 "eval (1+2)*3" '9'
|
|
||||||
check 417 "eval 1+2*3 prec" '7'
|
|
||||||
check 418 "eval -5+10" '5'
|
|
||||||
|
|
||||||
# comparison & boolean
|
|
||||||
check 420 "eval 1<2" 'true'
|
|
||||||
check 421 "eval 3>2" 'true'
|
|
||||||
check 422 "eval 2=2" 'true'
|
|
||||||
check 423 "eval 1<>2" 'true'
|
|
||||||
check 424 "eval true && false" 'false'
|
|
||||||
check 425 "eval true || false" 'true'
|
|
||||||
check 426 "eval not false" 'true'
|
|
||||||
|
|
||||||
# string
|
|
||||||
check 430 'eval "a" ^ "b"' '"ab"'
|
|
||||||
check 431 "eval string concat 3" '"hello world"'
|
|
||||||
|
|
||||||
# conditional
|
|
||||||
check 440 "eval if true 1 else 2" '1'
|
|
||||||
check 441 "eval if 1>2 100 else 200" '200'
|
|
||||||
|
|
||||||
# let / lambda / app
|
|
||||||
check 450 "eval let x=5 x*2" '10'
|
|
||||||
check 451 "eval let f x = x+1; f 41" '42'
|
|
||||||
check 452 "eval let f x y = x+y; f 3 4" '7'
|
|
||||||
check 453 "eval (fun x -> x*x) 7" '49'
|
|
||||||
check 454 "eval curried lambdas" '30'
|
|
||||||
check 455 "eval named lambda" '10'
|
|
||||||
|
|
||||||
# closures
|
|
||||||
check 460 "eval closure capture" '15'
|
|
||||||
check 461 "eval make_adder" '101'
|
|
||||||
|
|
||||||
# recursion
|
|
||||||
check 470 "eval fact 5" '120'
|
|
||||||
check 471 "eval fib 10" '55'
|
|
||||||
check 472 "eval sum 100" '5050'
|
|
||||||
|
|
||||||
# sequence
|
|
||||||
check 480 "eval 1; 2; 3 → 3" '3'
|
|
||||||
check 481 "eval begin 10 end" '10'
|
|
||||||
|
|
||||||
# programs
|
|
||||||
check 490 "run-prog x+y" '3'
|
|
||||||
check 491 "run-prog fact 6" '720'
|
|
||||||
check 492 "run-prog inc + double" '10'
|
|
||||||
|
|
||||||
# pipe
|
|
||||||
check 495 "eval x |> f" '10'
|
|
||||||
|
|
||||||
# ── Phase 3: ADTs + match (eval) ────────────────────────────────
|
|
||||||
# constructors
|
|
||||||
check 500 "eval None" '("None")'
|
|
||||||
check 501 "eval Some 42" '("Some" 42)'
|
|
||||||
check 502 "eval Pair tuple-arg" '("Some" 1 2)'
|
|
||||||
|
|
||||||
# option match
|
|
||||||
check 510 "match Some 5 -> 5" '5'
|
|
||||||
check 511 "match None -> 0" '0'
|
|
||||||
|
|
||||||
# literal match
|
|
||||||
check 520 "match 3 -> _ -> 999" '999'
|
|
||||||
check 521 "match bool true" '1'
|
|
||||||
check 522 "match string lit" '1'
|
|
||||||
|
|
||||||
# tuple match
|
|
||||||
check 530 "match (1,2)" '3'
|
|
||||||
check 531 "match (1,2,3)" '6'
|
|
||||||
|
|
||||||
# list match
|
|
||||||
check 540 "match list cons head" '1'
|
|
||||||
check 541 "match empty list" '0'
|
|
||||||
check 542 "match list literal pat" '6'
|
|
||||||
check 543 "match recursive len" '5'
|
|
||||||
check 544 "match recursive sum" '15'
|
|
||||||
|
|
||||||
# wildcard + var
|
|
||||||
check 550 "match _ -> 1" '1'
|
|
||||||
check 551 "match x -> x+1" '100'
|
|
||||||
|
|
||||||
# ctor with tuple arg
|
|
||||||
check 560 "Pair(a,b) → a*b" '2'
|
|
||||||
|
|
||||||
# ── References ──────────────────────────────────────────────────
|
|
||||||
check 600 "deref new ref" '5'
|
|
||||||
check 601 ":= then deref" '10'
|
|
||||||
check 602 "increment cell twice" '2'
|
|
||||||
check 603 "ref captured by closure" '115'
|
|
||||||
check 604 "ref of string" '"b"'
|
|
||||||
check 605 "ref + recursion" '15'
|
|
||||||
|
|
||||||
# ── for / while ─────────────────────────────────────────────────
|
|
||||||
check 620 "for 1..5 sum" '15'
|
|
||||||
check 621 "for 5 downto 1 sum" '15'
|
|
||||||
check 622 "while loop" '15'
|
|
||||||
check 623 "for 1..100 sum" '5050'
|
|
||||||
check 624 "for 1..5 product = 120" '120'
|
|
||||||
|
|
||||||
TOTAL=$((PASS + FAIL))
|
|
||||||
if [ $FAIL -eq 0 ]; then
|
|
||||||
echo "ok $PASS/$TOTAL OCaml-on-SX tests passed"
|
|
||||||
else
|
|
||||||
echo "FAIL $PASS/$TOTAL passed, $FAIL failed:"
|
|
||||||
echo ""
|
|
||||||
echo "$ERRORS"
|
|
||||||
fi
|
|
||||||
|
|
||||||
[ $FAIL -eq 0 ]
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
;; lib/ocaml/tests/tokenize.sx — smoke-test helpers.
|
|
||||||
;;
|
|
||||||
;; Tests are exercised via lib/ocaml/test.sh, which drives sx_server.exe
|
|
||||||
;; over the epoch protocol. This file provides small accessors so the
|
|
||||||
;; bash runner can grep short diagnostic values out of one batched run.
|
|
||||||
|
|
||||||
(define
|
|
||||||
ocaml-test-tok-type
|
|
||||||
(fn (src i) (get (nth (ocaml-tokenize src) i) :type)))
|
|
||||||
|
|
||||||
(define
|
|
||||||
ocaml-test-tok-value
|
|
||||||
(fn (src i) (get (nth (ocaml-tokenize src) i) :value)))
|
|
||||||
|
|
||||||
(define ocaml-test-tok-count (fn (src) (len (ocaml-tokenize src))))
|
|
||||||
|
|
||||||
(define ocaml-test-parse-str (fn (src) (ocaml-parse src)))
|
|
||||||
|
|
||||||
(define
|
|
||||||
ocaml-test-parse-head
|
|
||||||
(fn (src) (nth (ocaml-parse src) 0)))
|
|
||||||
@@ -1,382 +0,0 @@
|
|||||||
;; lib/ocaml/tokenizer.sx — OCaml lexer.
|
|
||||||
;;
|
|
||||||
;; Tokens: ident, ctor (uppercase ident), keyword, number, string, char, op, eof.
|
|
||||||
;; Token shape: {:type :value :pos} via lex-make-token.
|
|
||||||
;; OCaml is not indentation-sensitive — no layout pass.
|
|
||||||
;; Block comments (* ... *) nest. There is no line-comment syntax.
|
|
||||||
|
|
||||||
(prefix-rename
|
|
||||||
"ocaml-"
|
|
||||||
(quote
|
|
||||||
((make-token lex-make-token)
|
|
||||||
(digit? lex-digit?)
|
|
||||||
(hex-digit? lex-hex-digit?)
|
|
||||||
(alpha? lex-alpha?)
|
|
||||||
(alnum? lex-alnum?)
|
|
||||||
(ident-start? lex-ident-start?)
|
|
||||||
(ident-char? lex-ident-char?)
|
|
||||||
(ws? lex-whitespace?))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
ocaml-keywords
|
|
||||||
(list
|
|
||||||
"and"
|
|
||||||
"as"
|
|
||||||
"assert"
|
|
||||||
"begin"
|
|
||||||
"class"
|
|
||||||
"constraint"
|
|
||||||
"do"
|
|
||||||
"done"
|
|
||||||
"downto"
|
|
||||||
"else"
|
|
||||||
"end"
|
|
||||||
"exception"
|
|
||||||
"external"
|
|
||||||
"false"
|
|
||||||
"for"
|
|
||||||
"fun"
|
|
||||||
"function"
|
|
||||||
"functor"
|
|
||||||
"if"
|
|
||||||
"in"
|
|
||||||
"include"
|
|
||||||
"inherit"
|
|
||||||
"initializer"
|
|
||||||
"lazy"
|
|
||||||
"let"
|
|
||||||
"match"
|
|
||||||
"method"
|
|
||||||
"module"
|
|
||||||
"mutable"
|
|
||||||
"new"
|
|
||||||
"nonrec"
|
|
||||||
"object"
|
|
||||||
"of"
|
|
||||||
"open"
|
|
||||||
"or"
|
|
||||||
"private"
|
|
||||||
"rec"
|
|
||||||
"sig"
|
|
||||||
"struct"
|
|
||||||
"then"
|
|
||||||
"to"
|
|
||||||
"true"
|
|
||||||
"try"
|
|
||||||
"type"
|
|
||||||
"val"
|
|
||||||
"virtual"
|
|
||||||
"when"
|
|
||||||
"while"
|
|
||||||
"with"
|
|
||||||
"land"
|
|
||||||
"lor"
|
|
||||||
"lxor"
|
|
||||||
"lsl"
|
|
||||||
"lsr"
|
|
||||||
"asr"
|
|
||||||
"mod"))
|
|
||||||
|
|
||||||
(define ocaml-keyword? (fn (word) (contains? ocaml-keywords word)))
|
|
||||||
|
|
||||||
(define
|
|
||||||
ocaml-upper?
|
|
||||||
(fn (c) (and (not (= c nil)) (>= c "A") (<= c "Z"))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
ocaml-tokenize
|
|
||||||
(fn
|
|
||||||
(src)
|
|
||||||
(let
|
|
||||||
((tokens (list)) (pos 0) (src-len (len src)))
|
|
||||||
(define
|
|
||||||
ocaml-peek
|
|
||||||
(fn
|
|
||||||
(offset)
|
|
||||||
(if (< (+ pos offset) src-len) (nth src (+ pos offset)) nil)))
|
|
||||||
(define cur (fn () (ocaml-peek 0)))
|
|
||||||
(define advance! (fn (n) (set! pos (+ pos n))))
|
|
||||||
(define
|
|
||||||
push!
|
|
||||||
(fn
|
|
||||||
(type value start)
|
|
||||||
(append! tokens (ocaml-make-token type value start))))
|
|
||||||
(define
|
|
||||||
skip-block-comment!
|
|
||||||
(fn
|
|
||||||
(depth)
|
|
||||||
(cond
|
|
||||||
((>= pos src-len) nil)
|
|
||||||
((and (= (cur) "*") (= (ocaml-peek 1) ")"))
|
|
||||||
(begin
|
|
||||||
(advance! 2)
|
|
||||||
(when
|
|
||||||
(> depth 1)
|
|
||||||
(skip-block-comment! (- depth 1)))))
|
|
||||||
((and (= (cur) "(") (= (ocaml-peek 1) "*"))
|
|
||||||
(begin
|
|
||||||
(advance! 2)
|
|
||||||
(skip-block-comment! (+ depth 1))))
|
|
||||||
(else (begin (advance! 1) (skip-block-comment! depth))))))
|
|
||||||
(define
|
|
||||||
skip-ws!
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(cond
|
|
||||||
((>= pos src-len) nil)
|
|
||||||
((ocaml-ws? (cur)) (begin (advance! 1) (skip-ws!)))
|
|
||||||
((and (= (cur) "(") (= (ocaml-peek 1) "*"))
|
|
||||||
(begin
|
|
||||||
(advance! 2)
|
|
||||||
(skip-block-comment! 1)
|
|
||||||
(skip-ws!)))
|
|
||||||
(else nil))))
|
|
||||||
(define
|
|
||||||
read-ident
|
|
||||||
(fn
|
|
||||||
(start)
|
|
||||||
(begin
|
|
||||||
(when
|
|
||||||
(and (< pos src-len) (ocaml-ident-char? (cur)))
|
|
||||||
(begin (advance! 1) (read-ident start)))
|
|
||||||
(when
|
|
||||||
(and (< pos src-len) (= (cur) "'"))
|
|
||||||
(begin (advance! 1) (read-ident start)))
|
|
||||||
(slice src start pos))))
|
|
||||||
(define
|
|
||||||
read-decimal-digits!
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(when
|
|
||||||
(and (< pos src-len) (or (ocaml-digit? (cur)) (= (cur) "_")))
|
|
||||||
(begin (advance! 1) (read-decimal-digits!)))))
|
|
||||||
(define
|
|
||||||
read-hex-digits!
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(when
|
|
||||||
(and
|
|
||||||
(< pos src-len)
|
|
||||||
(or (ocaml-hex-digit? (cur)) (= (cur) "_")))
|
|
||||||
(begin (advance! 1) (read-hex-digits!)))))
|
|
||||||
(define
|
|
||||||
read-exp-part!
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(when
|
|
||||||
(and (< pos src-len) (or (= (cur) "e") (= (cur) "E")))
|
|
||||||
(let
|
|
||||||
((p1 (ocaml-peek 1)))
|
|
||||||
(when
|
|
||||||
(or
|
|
||||||
(and (not (= p1 nil)) (ocaml-digit? p1))
|
|
||||||
(and
|
|
||||||
(or (= p1 "+") (= p1 "-"))
|
|
||||||
(< (+ pos 2) src-len)
|
|
||||||
(ocaml-digit? (ocaml-peek 2))))
|
|
||||||
(begin
|
|
||||||
(advance! 1)
|
|
||||||
(when
|
|
||||||
(and
|
|
||||||
(< pos src-len)
|
|
||||||
(or (= (cur) "+") (= (cur) "-")))
|
|
||||||
(advance! 1))
|
|
||||||
(read-decimal-digits!)))))))
|
|
||||||
(define
|
|
||||||
strip-underscores
|
|
||||||
(fn
|
|
||||||
(s)
|
|
||||||
(let
|
|
||||||
((out (list)) (i 0) (n (len s)))
|
|
||||||
(begin
|
|
||||||
(define
|
|
||||||
loop
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(when
|
|
||||||
(< i n)
|
|
||||||
(begin
|
|
||||||
(when
|
|
||||||
(not (= (nth s i) "_"))
|
|
||||||
(append! out (nth s i)))
|
|
||||||
(set! i (+ i 1))
|
|
||||||
(loop)))))
|
|
||||||
(loop)
|
|
||||||
(join "" out)))))
|
|
||||||
(define
|
|
||||||
read-number
|
|
||||||
(fn
|
|
||||||
(start)
|
|
||||||
(cond
|
|
||||||
((and (= (cur) "0") (< (+ pos 1) src-len) (or (= (ocaml-peek 1) "x") (= (ocaml-peek 1) "X")))
|
|
||||||
(begin
|
|
||||||
(advance! 2)
|
|
||||||
(read-hex-digits!)
|
|
||||||
(let
|
|
||||||
((raw (slice src (+ start 2) pos)))
|
|
||||||
(parse-number (str "0x" (strip-underscores raw))))))
|
|
||||||
(else
|
|
||||||
(begin
|
|
||||||
(read-decimal-digits!)
|
|
||||||
(when
|
|
||||||
(and
|
|
||||||
(< pos src-len)
|
|
||||||
(= (cur) ".")
|
|
||||||
(or
|
|
||||||
(>= (+ pos 1) src-len)
|
|
||||||
(not (= (ocaml-peek 1) "."))))
|
|
||||||
(begin (advance! 1) (read-decimal-digits!)))
|
|
||||||
(read-exp-part!)
|
|
||||||
(parse-number (strip-underscores (slice src start pos))))))))
|
|
||||||
(define
|
|
||||||
read-string-literal
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(let
|
|
||||||
((chars (list)))
|
|
||||||
(begin
|
|
||||||
(advance! 1)
|
|
||||||
(define
|
|
||||||
loop
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(cond
|
|
||||||
((>= pos src-len) nil)
|
|
||||||
((= (cur) "\\")
|
|
||||||
(begin
|
|
||||||
(advance! 1)
|
|
||||||
(when
|
|
||||||
(< pos src-len)
|
|
||||||
(let
|
|
||||||
((ch (cur)))
|
|
||||||
(begin
|
|
||||||
(cond
|
|
||||||
((= ch "n") (append! chars "\n"))
|
|
||||||
((= ch "t") (append! chars "\t"))
|
|
||||||
((= ch "r") (append! chars "\r"))
|
|
||||||
((= ch "b") (append! chars "\\b"))
|
|
||||||
((= ch "\\") (append! chars "\\"))
|
|
||||||
((= ch "'") (append! chars "'"))
|
|
||||||
((= ch "\"") (append! chars "\""))
|
|
||||||
((= ch " ") nil)
|
|
||||||
(else (append! chars ch)))
|
|
||||||
(advance! 1))))
|
|
||||||
(loop)))
|
|
||||||
((= (cur) "\"") (advance! 1))
|
|
||||||
(else
|
|
||||||
(begin
|
|
||||||
(append! chars (cur))
|
|
||||||
(advance! 1)
|
|
||||||
(loop))))))
|
|
||||||
(loop)
|
|
||||||
(join "" chars)))))
|
|
||||||
(define
|
|
||||||
read-char-literal
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(begin
|
|
||||||
(advance! 1)
|
|
||||||
(let
|
|
||||||
((value (cond ((= (cur) "\\") (begin (advance! 1) (let ((ch (cur))) (begin (advance! 1) (cond ((= ch "n") "\n") ((= ch "t") "\t") ((= ch "r") "\r") ((= ch "b") "\\b") ((= ch "\\") "\\") ((= ch "'") "'") ((= ch "\"") "\"") (else ch)))))) (else (let ((ch (cur))) (begin (advance! 1) ch))))))
|
|
||||||
(begin
|
|
||||||
(when
|
|
||||||
(and (< pos src-len) (= (cur) "'"))
|
|
||||||
(advance! 1))
|
|
||||||
value)))))
|
|
||||||
(define
|
|
||||||
try-punct
|
|
||||||
(fn
|
|
||||||
(start)
|
|
||||||
(let
|
|
||||||
((c (cur))
|
|
||||||
(c1 (ocaml-peek 1))
|
|
||||||
(c2 (ocaml-peek 2)))
|
|
||||||
(cond
|
|
||||||
((and (= c ";") (= c1 ";"))
|
|
||||||
(begin (advance! 2) (push! "op" ";;" start) true))
|
|
||||||
((and (= c "-") (= c1 ">"))
|
|
||||||
(begin (advance! 2) (push! "op" "->" start) true))
|
|
||||||
((and (= c "<") (= c1 "-"))
|
|
||||||
(begin (advance! 2) (push! "op" "<-" start) true))
|
|
||||||
((and (= c ":") (= c1 "="))
|
|
||||||
(begin (advance! 2) (push! "op" ":=" start) true))
|
|
||||||
((and (= c ":") (= c1 ":"))
|
|
||||||
(begin (advance! 2) (push! "op" "::" start) true))
|
|
||||||
((and (= c "|") (= c1 "|"))
|
|
||||||
(begin (advance! 2) (push! "op" "||" start) true))
|
|
||||||
((and (= c "&") (= c1 "&"))
|
|
||||||
(begin (advance! 2) (push! "op" "&&" start) true))
|
|
||||||
((and (= c "<") (= c1 "="))
|
|
||||||
(begin (advance! 2) (push! "op" "<=" start) true))
|
|
||||||
((and (= c ">") (= c1 "="))
|
|
||||||
(begin (advance! 2) (push! "op" ">=" start) true))
|
|
||||||
((and (= c "<") (= c1 ">"))
|
|
||||||
(begin (advance! 2) (push! "op" "<>" start) true))
|
|
||||||
((and (= c "=") (= c1 "="))
|
|
||||||
(begin (advance! 2) (push! "op" "==" start) true))
|
|
||||||
((and (= c "!") (= c1 "="))
|
|
||||||
(begin (advance! 2) (push! "op" "!=" start) true))
|
|
||||||
((and (= c "|") (= c1 ">"))
|
|
||||||
(begin (advance! 2) (push! "op" "|>" start) true))
|
|
||||||
((and (= c "<") (= c1 "|"))
|
|
||||||
(begin (advance! 2) (push! "op" "<|" start) true))
|
|
||||||
((and (= c "@") (= c1 "@"))
|
|
||||||
(begin (advance! 2) (push! "op" "@@" start) true))
|
|
||||||
((and (= c "*") (= c1 "*"))
|
|
||||||
(begin (advance! 2) (push! "op" "**" start) true))
|
|
||||||
((or (= c "+") (= c "-") (= c "*") (= c "/") (= c "%") (= c "^") (= c "<") (= c ">") (= c "=") (= c "(") (= c ")") (= c "{") (= c "}") (= c "[") (= c "]") (= c ";") (= c ":") (= c ",") (= c ".") (= c "|") (= c "!") (= c "&") (= c "@") (= c "?") (= c "~") (= c "#"))
|
|
||||||
(begin (advance! 1) (push! "op" c start) true))
|
|
||||||
(else false)))))
|
|
||||||
(define
|
|
||||||
step
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(begin
|
|
||||||
(skip-ws!)
|
|
||||||
(when
|
|
||||||
(< pos src-len)
|
|
||||||
(let
|
|
||||||
((start pos) (c (cur)))
|
|
||||||
(cond
|
|
||||||
((ocaml-ident-start? c)
|
|
||||||
(let
|
|
||||||
((word (read-ident start)))
|
|
||||||
(begin
|
|
||||||
(cond
|
|
||||||
((ocaml-keyword? word)
|
|
||||||
(push! "keyword" word start))
|
|
||||||
((ocaml-upper? c) (push! "ctor" word start))
|
|
||||||
(else (push! "ident" word start)))
|
|
||||||
(step))))
|
|
||||||
((ocaml-digit? c)
|
|
||||||
(let
|
|
||||||
((v (read-number start)))
|
|
||||||
(begin (push! "number" v start) (step))))
|
|
||||||
((= c "\"")
|
|
||||||
(let
|
|
||||||
((s (read-string-literal)))
|
|
||||||
(begin (push! "string" s start) (step))))
|
|
||||||
((and (= c "'") (< (+ pos 1) src-len) (or (and (= (ocaml-peek 1) "\\") (< (+ pos 3) src-len) (= (ocaml-peek 3) "'")) (and (not (= (ocaml-peek 1) "\\")) (< (+ pos 2) src-len) (= (ocaml-peek 2) "'"))))
|
|
||||||
(let
|
|
||||||
((v (read-char-literal)))
|
|
||||||
(begin (push! "char" v start) (step))))
|
|
||||||
((= c "'")
|
|
||||||
(begin
|
|
||||||
(advance! 1)
|
|
||||||
(when
|
|
||||||
(and (< pos src-len) (ocaml-ident-start? (cur)))
|
|
||||||
(begin
|
|
||||||
(advance! 1)
|
|
||||||
(read-ident (+ start 1))))
|
|
||||||
(push!
|
|
||||||
"tyvar"
|
|
||||||
(slice src (+ start 1) pos)
|
|
||||||
start)
|
|
||||||
(step)))
|
|
||||||
((try-punct start) (step))
|
|
||||||
(else
|
|
||||||
(error
|
|
||||||
(str "ocaml-tokenize: unexpected char " c " at " pos)))))))))
|
|
||||||
(step)
|
|
||||||
(push! "eof" nil pos)
|
|
||||||
tokens)))
|
|
||||||
@@ -135,48 +135,6 @@ and tightens loose ends.
|
|||||||
on error switches to the trap branch. Define `apl-throw` and a small
|
on error switches to the trap branch. Define `apl-throw` and a small
|
||||||
set of error codes; use `try`/`catch` from the host.
|
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
|
## SX primitive baseline
|
||||||
|
|
||||||
Use vectors for arrays; numeric tower + rationals for numbers; ADTs for tagged data;
|
Use vectors for arrays; numeric tower + rationals for numbers; ADTs for tagged data;
|
||||||
@@ -191,13 +149,6 @@ data; format for string templating.
|
|||||||
|
|
||||||
_Newest first._
|
_Newest first._
|
||||||
|
|
||||||
- 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 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 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 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
|
||||||
|
|||||||
@@ -158,8 +158,8 @@ Extract from `haskell/infer.sx`. Algorithm W or J, generalisation, instantiation
|
|||||||
| 4 — pratt.sx (lua + prolog) | [done] | da27958d | Extracted operator-table format + lookup only — climbing loops stay per-language because lua and prolog use opposite prec conventions. lua/parser.sx: 18-clause cond → 15-entry table. prolog/parser.sx: pl-op-find deleted, pl-op-lookup wraps pratt-op-lookup. lua 185/185, prolog 590/590 — both = baseline. |
|
| 4 — pratt.sx (lua + prolog) | [done] | da27958d | Extracted operator-table format + lookup only — climbing loops stay per-language because lua and prolog use opposite prec conventions. lua/parser.sx: 18-clause cond → 15-entry table. prolog/parser.sx: pl-op-find deleted, pl-op-lookup wraps pratt-op-lookup. lua 185/185, prolog 590/590 — both = baseline. |
|
||||||
| 5 — ast.sx (lua + prolog) | [partial — pending real consumers] | a774cd26 | Kit + 33 self-tests shipped (10 canonical kinds, predicates, accessors). Step is "Optional" per brief; lua/prolog parsers untouched (185/185 + 590/590). Datalog-on-sx will be the natural first real consumer; lua/prolog converters can land later. |
|
| 5 — ast.sx (lua + prolog) | [partial — pending real consumers] | a774cd26 | Kit + 33 self-tests shipped (10 canonical kinds, predicates, accessors). Step is "Optional" per brief; lua/prolog parsers untouched (185/185 + 590/590). Datalog-on-sx will be the natural first real consumer; lua/prolog converters can land later. |
|
||||||
| 6 — match.sx (haskell + prolog) | [partial — kit shipped; ports deferred] | 863e9d93 | Pure-functional unify + match kit (canonical wire format + cfg-driven adapters) + 25 self-tests. Existing prolog/haskell engines untouched (structurally divergent — mutating-symmetric vs pure-asymmetric — would risk 746 passing tests under brief's revert-on-regression rule). Real consumer is minikraken/datalog work in flight. |
|
| 6 — match.sx (haskell + prolog) | [partial — kit shipped; ports deferred] | 863e9d93 | Pure-functional unify + match kit (canonical wire format + cfg-driven adapters) + 25 self-tests. Existing prolog/haskell engines untouched (structurally divergent — mutating-symmetric vs pure-asymmetric — would risk 746 passing tests under brief's revert-on-regression rule). Real consumer is minikraken/datalog work in flight. |
|
||||||
| 7 — layout.sx (haskell + synthetic) | [partial — haskell port deferred] | d75c61d4 | Configurable kit (haskell-style keyword-opens + python-style trailing-`:`-opens) + 6 self-tests covering both flavours. Synthetic Python-ish fixture passes; haskell/layout.sx untouched (kit not yet a drop-in for Haskell 98 Note 5 etc.; haskell still 156/156 baseline). |
|
| 7 — layout.sx (haskell + synthetic) | [in-progress] | — | — |
|
||||||
| 8 — hm.sx (haskell + TBD) | [partial — algebra shipped; assembly deferred] | ab2c40c1 | HM foundations: types/schemes/ftv/apply/compose/generalize/instantiate/fresh-tv on top of match.sx unify, plus literal inference rule. 24/24 self-tests. Algorithm W lambda/app/let assembly deferred to host code — paired sequencing per brief: lib/ocaml/types.sx (OCaml-on-SX Phase 5) + haskell/infer.sx port. Haskell still 156/156 baseline. |
|
| 8 — hm.sx (haskell + TBD) | [ ] | — | — |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -50,68 +50,102 @@ Key semantic mappings:
|
|||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
### Phase 1 — variables + unification
|
### Phase 1 — variables + unification
|
||||||
- [ ] `make-var` → fresh logic variable (unique mutable box)
|
- [x] `make-var` → fresh logic variable (unique mutable box)
|
||||||
- [ ] `var?` `v` → bool — is this a logic variable?
|
- [x] `var?` `v` → bool — is this a logic variable?
|
||||||
- [ ] `walk` `term` `subst` → follow substitution chain to ground term or unbound var
|
- [x] `walk` `term` `subst` → follow substitution chain to ground term or unbound var
|
||||||
- [ ] `walk*` `term` `subst` → deep walk (recurse into lists/dicts)
|
- [x] `walk*` `term` `subst` → deep walk (recurse into lists/dicts)
|
||||||
- [ ] `unify` `u` `v` `subst` → extended substitution or `#f` (failure)
|
- [x] `unify` `u` `v` `subst` → extended substitution or `#f` (failure)
|
||||||
Handles: var/var, var/term, term/var, list unification, number/string/symbol equality.
|
Handles: var/var, var/term, term/var, list unification, number/string/symbol equality.
|
||||||
No occurs check by default; `unify-check` with occurs check as opt-in.
|
No occurs check by default; `unify-check` with occurs check as opt-in.
|
||||||
- [ ] Empty substitution `empty-s` = `(list)` (empty assoc list)
|
- [x] Empty substitution `empty-s` (dict-based via kit's `empty-subst` — assoc list was a sketch; kit ships dict, kept it)
|
||||||
- [ ] Tests in `lib/minikanren/tests/unify.sx`: ground terms, vars, lists, failure, occurs
|
- [x] Tests in `lib/minikanren/tests/unify.sx`: ground terms, vars, lists, failure, occurs
|
||||||
|
|
||||||
### Phase 2 — streams + goals
|
### Phase 2 — streams + goals
|
||||||
- [ ] Stream type: `mzero` (empty stream = `nil`), `unit s` (singleton = `(list s)`),
|
- [x] Stream type: `mzero` (empty), `unit s` (singleton), `mk-mplus` (interleave),
|
||||||
`mplus` (interleave two streams), `bind` (apply goal to stream)
|
`mk-bind` (apply goal to stream). Names mk-prefixed because SX has a host
|
||||||
- [ ] Lazy streams via `delay`/`force` — mature pairs for depth-first, immature for lazy
|
`bind` primitive that silently shadows user defines.
|
||||||
- [ ] `==` goal: `(fn (s) (let ((s2 (unify u v s))) (if s2 (unit s2) mzero)))`
|
- [x] Lazy streams via thunks: a paused stream is a zero-arg fn; mk-mplus suspends
|
||||||
- [ ] `succeed` / `fail` — trivial goals
|
and swaps when its left operand is paused, giving fair interleaving.
|
||||||
- [ ] `fresh` — `(fn (f) (fn (s) ((f (make-var)) s)))` — introduces one var; `fresh*` for many
|
- [x] `==` goal: `(fn (s) (let ((s2 (mk-unify u v s))) (if s2 (unit s2) mzero)))`
|
||||||
- [ ] `conde` — interleaving disjunction of goal lists
|
- [x] `==-check` — opt-in occurs-checked equality goal
|
||||||
- [ ] `condu` — committed choice (soft-cut): only explores first successful clause
|
- [x] `succeed` / `fail` — trivial goals
|
||||||
- [ ] `onceo` — succeeds at most once
|
- [x] `conj2` / `mk-conj` (variadic) — sequential conjunction
|
||||||
- [ ] Tests: basic goal composition, backtracking, interleaving
|
- [x] `disj2` / `mk-disj` (variadic) — interleaved disjunction (raw — `conde`
|
||||||
|
adds the implicit-conj-per-clause sugar later)
|
||||||
|
- [x] `fresh` — introduces logic variables inside a goal body. Implemented as a
|
||||||
|
defmacro: `(fresh (x y) g1 g2 ...)` ⇒ `(let ((x (make-var)) (y (make-var)))
|
||||||
|
(mk-conj g1 g2 ...))`. Also `call-fresh` for programmatic goal building.
|
||||||
|
- [x] `conde` — sugar over disj+conj, one row per clause; defmacro that
|
||||||
|
wraps each clause body in `mk-conj` and folds via `mk-disj`. Notes:
|
||||||
|
with eager streams ordering is left-clause-first DFS; true interleaving
|
||||||
|
requires paused thunks (Phase 4 recursive relations).
|
||||||
|
- [x] `condu` — committed choice. defmacro folding clauses into a runtime
|
||||||
|
`condu-try` walker; first clause whose head goal yields a non-empty
|
||||||
|
stream commits its first answer, rest-goals run on that single subst.
|
||||||
|
- [x] `onceo` — `(stream-take 1 (g s))`; trims a goal's stream to ≤1 answer.
|
||||||
|
- [x] Tests: basic goal composition, backtracking, interleaving (110 cumulative)
|
||||||
|
|
||||||
### Phase 3 — run + reification
|
### Phase 3 — run + reification
|
||||||
- [ ] `run*` `goal` → list of all answers (reified)
|
- [x] `run*` `goal` → list of all answers (reified). defmacro: bind q-name as
|
||||||
- [ ] `run n` `goal` → list of first n answers
|
fresh var, conj goals, take all from stream, reify each.
|
||||||
- [ ] `reify` `term` `subst` → replace unbound vars with `_0`, `_1`, ... names
|
- [x] `run n` `goal` → list of first n answers (defmacro; n = -1 means all)
|
||||||
- [ ] `reify-s` → builds reification substitution for naming unbound vars consistently
|
- [x] `reify` `term` `subst` → walk* + build reification subst + walk* again
|
||||||
- [ ] `fresh` with multiple variables: `(fresh (x y z) goal)` sugar
|
- [x] `reify-s` → maps each unbound var (in left-to-right walk order) to a
|
||||||
- [ ] Query variable conventions: `q` as canonical query variable
|
`_.N` symbol via `(make-symbol (str "_." n))`
|
||||||
- [ ] Tests: classic miniKanren programs — `(run* q (== q 1))` → `(1)`,
|
- [x] `fresh` with multiple variables — already shipped Phase 2B.
|
||||||
|
- [x] Query variable conventions: `q` as canonical query variable (matches TRS)
|
||||||
|
- [x] Tests: classic miniKanren programs — `(run* q (== q 1))` → `(1)`,
|
||||||
`(run* q (conde ((== q 1)) ((== q 2))))` → `(1 2)`,
|
`(run* q (conde ((== q 1)) ((== q 2))))` → `(1 2)`,
|
||||||
Peano arithmetic, `appendo` preview
|
`(run* q (fresh (x y) (== q (list x y))))` → `((_.0 _.1))`. Peano +
|
||||||
|
`appendo` deferred to Phase 4.
|
||||||
|
|
||||||
### Phase 4 — standard relations
|
### Phase 4 — standard relations
|
||||||
- [ ] `appendo` `l` `s` `ls` — list append, runs forwards and backwards
|
- [x] `appendo` `l` `s` `ls` — list append, runs forwards AND backwards.
|
||||||
- [ ] `membero` `x` `l` — x is a member of l
|
Canary green: `(run* q (appendo (1 2) (3 4) q))` → `((1 2 3 4))`;
|
||||||
- [ ] `listo` `l` — l is a proper list
|
`(run* q (fresh (l s) (appendo l s (1 2 3)) (== q (list l s))))` →
|
||||||
- [ ] `nullo` `l` — l is empty
|
all four splits.
|
||||||
- [ ] `pairo` `p` — p is a pair (cons cell)
|
- [x] `membero` `x` `l` — enumerates: `(run* q (membero q (a b c)))` → `(a b c)`
|
||||||
- [ ] `caro` `p` `a` — car of pair
|
- [x] `listo` `l` — l is a proper list; enumerates list shapes with laziness
|
||||||
- [ ] `cdro` `p` `d` — cdr of pair
|
- [x] `nullo` `l` — l is empty
|
||||||
- [ ] `conso` `a` `d` `p` — cons
|
- [x] `pairo` `p` — p is a (non-empty) cons-cell / list
|
||||||
- [ ] `firsto` / `resto` — aliases for caro/cdro
|
- [x] `caro` / `cdro` / `conso` / `firsto` / `resto`
|
||||||
- [ ] `reverseo` `l` `r` — reverse of list
|
- [x] `reverseo` `l` `r` — reverse of list. Forward is fast; backward is `run 1`-clean,
|
||||||
- [ ] `flatteno` `l` `f` — flatten nested lists
|
`run*` diverges due to interleaved unbounded list search (canonical TRS issue).
|
||||||
- [ ] `permuteo` `l` `p` — permutation of list
|
- [ ] `flatteno` `l` `f` — flatten nested lists (deferred — needs atom predicate)
|
||||||
- [ ] `lengtho` `l` `n` — length as a relation (Peano or integer)
|
- [x] `permuteo` `l` `p` — permutation of list. Built on `inserto` (insert at any
|
||||||
- [ ] Tests: run each relation forwards and backwards; generate from partial inputs
|
position) and recursive permutation of tail. All 6 perms of (1 2 3) generated
|
||||||
|
forward; backward `run 1 q (permuteo q (a b c))` finds the input.
|
||||||
|
- [x] `lengtho` `l` `n` — length as a relation, Peano-encoded:
|
||||||
|
`:z` / `(:s :z)` / `(:s (:s :z))` ... matches TRS. Forward is direct;
|
||||||
|
backward enumerates lists of a given length.
|
||||||
|
- [x] Tests: run each relation forwards and backwards (so far 25 in
|
||||||
|
`tests/relations.sx`; reverseo/flatteno/permuteo/lengtho deferred)
|
||||||
|
|
||||||
### Phase 5 — `project` + `matche` + negation
|
### Phase 5 — `project` + `matche` + negation
|
||||||
- [ ] `project` `(x ...) body` — access reified values of logic vars inside a goal;
|
- [x] `project` `(x ...) body` — defmacro: rebinds named vars to `(mk-walk* var s)`
|
||||||
escapes to ground values for arithmetic or string ops
|
in the body's lexical scope, then runs `(mk-conj body...)` on the same
|
||||||
- [ ] `matche` — pattern matching over logic terms (extension from core.logic)
|
substitution. Hygienic via gensym'd `s`-param. (`Phase 5 piece B`)
|
||||||
`(matche l ((head . tail) goal) (() goal))`
|
- [x] `matche` — pattern matching over logic terms. Pattern grammar: `_` /
|
||||||
- [ ] `conda` — soft-cut disjunction (like Prolog `->`)
|
symbol / atom / `()` / `(p1 p2 ... pn)`. Each clause becomes
|
||||||
- [ ] `condu` — committed choice (already in phase 2; refine semantics here)
|
`(fresh (vars-in-pat) (== target pat-expr) body...)`. Repeated symbol
|
||||||
- [ ] `nafc` — negation as finite failure with constraint
|
names in a pattern produce the same fresh var, so they unify (== check).
|
||||||
|
Built without quasiquote (the expander does not recurse into lambda
|
||||||
|
bodies). Fixed-length list patterns only — head/tail destructuring uses
|
||||||
|
`(fresh (a d) (conso a d target) body)` directly.
|
||||||
|
- [x] `conda` — soft-cut: first non-failing head wins; ALL of head's answers
|
||||||
|
flow through rest-goals; later clauses not tried (`Phase 5 piece A`)
|
||||||
|
- [x] `condu` — committed choice (Phase 2)
|
||||||
|
- [x] `nafc` — negation as finite failure: `(nafc g)` yields the input subst
|
||||||
|
iff g has zero answers. Standard caveats apply (open-world unsoundness;
|
||||||
|
diverges if g is infinite). `Phase 5 piece C`.
|
||||||
- [ ] Tests: Zebra puzzle, N-queens, Sudoku via `project`, family relations via `matche`
|
- [ ] Tests: Zebra puzzle, N-queens, Sudoku via `project`, family relations via `matche`
|
||||||
|
|
||||||
### Phase 6 — arithmetic constraints CLP(FD)
|
### Phase 6 — arithmetic constraints CLP(FD)
|
||||||
- [ ] Finite domain variables: `fd-var` with domain `[lo..hi]`
|
- [ ] Finite domain variables: `fd-var` with domain `[lo..hi]`
|
||||||
- [ ] `in` `x` `domain` — constrain x to domain
|
- [x] `ino` `x` `domain` — alias for `(membero x domain)` with the
|
||||||
|
constraint-store-friendly argument order. Sufficient for the
|
||||||
|
enumerate-then-filter style of finite-domain solving.
|
||||||
|
- [x] `all-distincto` `l` — pairwise-distinct elements via `nafc + membero`.
|
||||||
- [ ] `fd-eq` `x` `y` — x = y (constraint propagation)
|
- [ ] `fd-eq` `x` `y` — x = y (constraint propagation)
|
||||||
- [ ] `fd-neq` `x` `y` — x ≠ y
|
- [ ] `fd-neq` `x` `y` — x ≠ y
|
||||||
- [ ] `fd-lt` `fd-lte` `fd-gt` `fd-gte` — ordering constraints
|
- [ ] `fd-lt` `fd-lte` `fd-gt` `fd-gte` — ordering constraints
|
||||||
@@ -135,4 +169,96 @@ _(none yet)_
|
|||||||
|
|
||||||
_Newest first._
|
_Newest first._
|
||||||
|
|
||||||
_(awaiting phase 1)_
|
- **2026-05-08** — **Phase 6 piece A — minimal FD (ino + all-distincto)**:
|
||||||
|
`lib/minikanren/fd.sx`. `ino` is `membero` with the FD-style argument
|
||||||
|
order; `all-distincto` is `nafc + membero` walking the list. Together
|
||||||
|
they cover the enumerate-then-filter style of finite-domain solving —
|
||||||
|
`(fresh (a b c) (ino a D) (ino b D) (ino c D) (all-distincto (list a b c)))`
|
||||||
|
enumerates all distinct triples from D. Full FD with arc-consistency,
|
||||||
|
fd-plus etc. is still pending. 9 new tests, 237/237 cumulative.
|
||||||
|
- **2026-05-08** — **Classic puzzles + matche keyword fix**: matche now emits
|
||||||
|
keywords bare in the pattern->expr conversion so they self-evaluate to their
|
||||||
|
string name and unify with the same-keyword target value (instead of becoming
|
||||||
|
a quoted-keyword type). New `tests/classics.sx`: pet permutation puzzle,
|
||||||
|
parent/grandparent inference over a fact list, symbolic differentiation
|
||||||
|
driven by matche dispatch on `:x` / `(:+ a b)` / `(:* a b)` patterns.
|
||||||
|
6 new tests, 228/228 cumulative.
|
||||||
|
- **2026-05-08** — **Phase 5 piece D — matche, Phase 5 done**: pattern matching
|
||||||
|
macro (`lib/minikanren/matche.sx`) — symbols become fresh vars, atoms become
|
||||||
|
literals, lists recurse positionally, repeated names unify. 14 new tests
|
||||||
|
(literals, vars, wildcards, list patterns, multi-clause dispatch, nested
|
||||||
|
patterns, repeated-var-implies-eq). Built via `cons`/`list` rather than
|
||||||
|
quasiquote because SX's quasiquote does not recurse into lambda bodies — a
|
||||||
|
worth-knowing gotcha. 222/222 cumulative.
|
||||||
|
- **2026-05-08** — **Phase 4 piece C — permuteo + inserto**: standard recursive
|
||||||
|
insert-at-any-position + permute-tail. 7 new tests, including all-6-perms-of-3
|
||||||
|
as a set check. 208/208 cumulative.
|
||||||
|
- **2026-05-07** — **Phase 5 piece C — nafc**: `lib/minikanren/nafc.sx`. Three-line
|
||||||
|
primitive: stream-take 1; if empty, `(unit s)`, else `mzero`. 7 tests including
|
||||||
|
double-negation and use as a guard. 201/201 cumulative.
|
||||||
|
- **2026-05-07** — **Phase 5 piece B — project**: `lib/minikanren/project.sx` —
|
||||||
|
defmacro that walks each named var, rebinds them, and runs the body's mk-conj.
|
||||||
|
Demonstrated escape into host arithmetic / string ops (`(* n n)`, `(str s "!")`).
|
||||||
|
Hygienic gensym'd s-param. 6 new tests, 194/194 cumulative.
|
||||||
|
- **2026-05-07** — **Peano arithmetic** (`lib/minikanren/peano.sx`): zeroo, pluso,
|
||||||
|
minuso, lteo, lto, *o on Peano-encoded naturals (`:z` / `(:s n)`). pluso runs
|
||||||
|
forward, backward, and enumerates: `(run* q (fresh (a b) (pluso a b 3)
|
||||||
|
(== q (list a b))))` → all 4 pairs summing to 3. *o uses repeated pluso —
|
||||||
|
works for small inputs, slower for larger. 19 new tests, 188/188 cumulative.
|
||||||
|
- **2026-05-07** — **Phase 5 piece A — conda**: soft-cut. Mirrors `condu` minus
|
||||||
|
the `onceo` on the head: all head answers are conjuncted through the rest of
|
||||||
|
the chosen clause. 7 new tests including the conda-vs-condu divergence test.
|
||||||
|
169/169 cumulative.
|
||||||
|
- **2026-05-07** — **Phase 4 piece B — reverseo + lengtho**: reverseo runs forward
|
||||||
|
cleanly and `run 1`-cleanly backward; lengtho uses Peano-encoded lengths so it
|
||||||
|
works as a true relation in both directions (tests use the encoding directly).
|
||||||
|
10 new tests, 162/162 cumulative.
|
||||||
|
- **2026-05-07** — **Phase 4 piece A — appendo canary green**: cons-cell support
|
||||||
|
in `unify.sx` + `(:s head tail)` lazy stream refactor in `stream.sx` + hygienic
|
||||||
|
`Zzz` (gensym'd subst-name) wrapping each `conde` clause + `lib/minikanren/
|
||||||
|
relations.sx` with `nullo` / `pairo` / `caro` / `cdro` / `conso` / `firsto` /
|
||||||
|
`resto` / `listo` / `appendo` / `membero`. 25 new tests in `tests/relations.sx`,
|
||||||
|
152/152 cumulative.
|
||||||
|
- **Three deep fixes shipped together**, all required to make `appendo`
|
||||||
|
terminate in both directions:
|
||||||
|
1. SX has no improper pairs, so a stream cell of mature subst + thunk
|
||||||
|
tail can't use `cons` — moved to a `(:s head tail)` tagged shape.
|
||||||
|
2. `(Zzz g)` wrapped its inner fn in a parameter named `s`, capturing
|
||||||
|
the user goal's own `s` binding (the `(appendo l s ls)` convention).
|
||||||
|
Replaced with `(gensym "zzz-s-")` for hygiene.
|
||||||
|
3. SX cons cells `(:cons h t)` for relational decomposition (so
|
||||||
|
`(conso a d l)` can split a list by head/tail without proper
|
||||||
|
improper pairs); `mk-walk*` re-flattens cons cells back to native
|
||||||
|
lists for clean reification output.
|
||||||
|
- **2026-05-07** — **Phase 3 done** (run + reification): `lib/minikanren/run.sx` (~28 lines).
|
||||||
|
`reify`/`reify-s`/`reify-name` for canonical `_.N` rendering of unbound vars in
|
||||||
|
left-to-right occurrence order; `run*` / `run` / `run-n` defmacros. 18 new tests
|
||||||
|
in `tests/run.sx`, including the **first classic miniKanren tests green**:
|
||||||
|
`(run* q (== q 1))` → `(1)`; `(run* q (fresh (x y) (== q (list x y))))` →
|
||||||
|
`((_.0 _.1))`. 128/128 cumulative.
|
||||||
|
- **2026-05-07** — **Phase 2 piece D + done** (`condu` / `onceo`): `lib/minikanren/condu.sx`.
|
||||||
|
Both are commitment forms: `onceo` is `(stream-take 1 ...)`; `condu` walks clauses
|
||||||
|
and commits the first one whose head produces an answer. 10 tests in `tests/condu.sx`,
|
||||||
|
110/110 cumulative. Phase 2 complete — ready for Phase 3 (run + reification).
|
||||||
|
- **2026-05-07** — **Phase 2 piece C** (`conde`): `lib/minikanren/conde.sx` — single
|
||||||
|
defmacro folding clauses through `mk-disj` with internal `mk-conj`. 9 tests in
|
||||||
|
`tests/conde.sx`, 100/100 cumulative. Confirmed eager DFS ordering for ==-only
|
||||||
|
streams; true interleaving is a Phase 4 concern (paused thunks under recursion).
|
||||||
|
- **2026-05-07** — **Phase 2 piece B** (`fresh`): `lib/minikanren/fresh.sx` (~10 lines).
|
||||||
|
defmacro form for nice user-facing syntax + `call-fresh` for programmatic use.
|
||||||
|
9 new tests in `tests/fresh.sx`, 91/91 cumulative.
|
||||||
|
- **2026-05-07** — **Phase 2 piece A** (streams + ==/conj/disj): `lib/minikanren/stream.sx`
|
||||||
|
(mzero/unit/mk-mplus/mk-bind/stream-take, ~25 lines of code) + `lib/minikanren/goals.sx`
|
||||||
|
(succeed/fail/==/==-check/conj2/disj2/mk-conj/mk-disj, ~30 lines). Found and noted
|
||||||
|
a host-primitive name clash: `bind` is built in and silently shadows user defines —
|
||||||
|
must use `mk-bind`/`mk-mplus` etc. throughout. 34 tests in `tests/goals.sx`,
|
||||||
|
82/82 cumulative all green. fresh/conde/condu/onceo still pending.
|
||||||
|
- **2026-05-07** — **Phase 1 done**: `lib/minikanren/unify.sx` (53 lines, ~22 lines of actual code) +
|
||||||
|
`lib/minikanren/tests/unify.sx` (48 tests, all green). Kit consumption: `walk-with`,
|
||||||
|
`unify-with`, `occurs-with`, `extend`, `empty-subst`, `mk-var`, `is-var?`, `var-name`
|
||||||
|
all supplied by `lib/guest/match.sx`. Local additions: a miniKanren-flavoured cfg
|
||||||
|
(treats native SX lists as cons-pairs via `:ctor-head = :pair`, occurs-check off),
|
||||||
|
`make-var` fresh-counter, deep `mk-walk*` (kit's `walk*` only recurses into `:ctor`
|
||||||
|
form, not native lists), and `mk-unify` / `mk-unify-check` thin wrappers. The kit
|
||||||
|
earns its keep ~3× over by line count — confirms lib-guest match kit is reusable
|
||||||
|
for logic-language hosts as designed.
|
||||||
|
|||||||
@@ -116,60 +116,47 @@ SX CEK evaluator (both JS and OCaml hosts)
|
|||||||
|
|
||||||
### Phase 1 — Tokenizer + parser
|
### Phase 1 — Tokenizer + parser
|
||||||
|
|
||||||
- [x] **Tokenizer:** keywords (`let`, `rec`, `in`, `fun`, `function`, `match`, `with`,
|
- [ ] **Tokenizer:** keywords (`let`, `rec`, `in`, `fun`, `function`, `match`, `with`,
|
||||||
`type`, `of`, `module`, `struct`, `end`, `functor`, `sig`, `open`, `include`,
|
`type`, `of`, `module`, `struct`, `end`, `functor`, `sig`, `open`, `include`,
|
||||||
`if`, `then`, `else`, `begin`, `try`, `exception`, `raise`, `mutable`,
|
`if`, `then`, `else`, `begin`, `try`, `exception`, `raise`, `mutable`,
|
||||||
`for`, `while`, `do`, `done`, `and`, `as`, `when`), operators (`->`, `|>`,
|
`for`, `while`, `do`, `done`, `and`, `as`, `when`), operators (`->`, `|>`,
|
||||||
`<|`, `@@`, `@`, `:=`, `!`, `::`, `**`, `:`, `;`, `;;`), identifiers (lower,
|
`<|`, `@@`, `@`, `:=`, `!`, `::`, `**`, `:`, `;`, `;;`), identifiers (lower,
|
||||||
upper/ctor), char literals `'c'`, string literals (escaped),
|
upper/ctor, labels `~label:`, optional `?label:`), char literals `'c'`,
|
||||||
int/float literals (incl. hex, exponent, underscores), nested block
|
string literals (escaped + heredoc `{|...|}`), int/float literals,
|
||||||
comments `(* ... *)`. _(labels `~label:` / `?label:` and heredoc `{|...|}`
|
line comments `(*` nested block comments `*)`.
|
||||||
deferred — surface tokens already work via `~`/`?` punct + `{`/`|` punct.)_
|
- [ ] **Parser:** top-level `let`/`let rec`/`type`/`module`/`exception`/`open`/`include`
|
||||||
- [~] **Parser:** expressions: literals, identifiers, constructor application,
|
declarations; expressions: literals, identifiers, constructor application,
|
||||||
lambda, application (left-assoc), binary ops with precedence (29 ops via
|
lambda, application (left-assoc), binary ops with precedence table,
|
||||||
`lib/guest/pratt.sx`), `if`/`then`/`else`, `let`/`in`, `let rec`,
|
`if`/`then`/`else`, `match`/`with`, `try`/`with`, `let`/`in`, `begin`/`end`,
|
||||||
`fun`/`->`, `match`/`with`, tuples, list literals, sequences `;`,
|
`fun`/`function`, tuples, list literals, record literals/updates, field access,
|
||||||
`begin`/`end`, unit `()`. Top-level decls: `let [rec] name params* = expr`
|
sequences `;`, unit `()`.
|
||||||
and bare expressions, `;;`-separated via `ocaml-parse-program`. _(Pending:
|
- [ ] **Patterns:** constructor, literal, variable, wildcard `_`, tuple, list cons `::`,
|
||||||
`type`/`module`/`exception`/`open`/`include` decls, `try`/`with`,
|
list literal, record, `as`, or-pattern `P1 | P2`, `when` guard.
|
||||||
`function`, record literals/updates, field access, `and` mutually-recursive
|
|
||||||
bindings.)_
|
|
||||||
- [~] **Patterns:** constructor (nullary + with args, incl. flattened tuple
|
|
||||||
args `Pair (a, b)` → `(:pcon "Pair" PA PB)`), literal (int/string/char/
|
|
||||||
bool/unit), variable, wildcard `_`, tuple, list cons `::`, list literal.
|
|
||||||
_(Pending: record patterns, `as` binding, or-pattern `P1 | P2`, `when`
|
|
||||||
guard.)_
|
|
||||||
- [ ] OCaml is **not** indentation-sensitive — no layout algorithm needed.
|
- [ ] OCaml is **not** indentation-sensitive — no layout algorithm needed.
|
||||||
- [ ] Tests in `lib/ocaml/tests/parse.sx` — 50+ round-trip parse tests.
|
- [ ] Tests in `lib/ocaml/tests/parse.sx` — 50+ round-trip parse tests.
|
||||||
|
|
||||||
### Phase 2 — Core evaluator (untyped)
|
### Phase 2 — Core evaluator (untyped)
|
||||||
|
|
||||||
- [x] `ocaml-eval` entry: walks OCaml AST, produces SX values.
|
- [ ] `ocaml-eval` entry: walks OCaml AST, produces SX values.
|
||||||
- [~] `let`/`let rec`/`let ... in` (single-binding done; mutually recursive
|
- [ ] `let`/`let rec`/`let ... in` (mutually recursive with `and`).
|
||||||
`and` deferred).
|
- [ ] Lambda + application (curried by default — auto-curry multi-param defs).
|
||||||
- [x] Lambda + application (curried by default — auto-curry multi-param defs).
|
- [ ] `fun`/`function` (single-arg lambda with immediate match on arg).
|
||||||
- [ ] `fun`/`function` (single-arg lambda with immediate match on arg). _(`fun`
|
- [ ] `if`/`then`/`else`, `begin`/`end`, sequence `;`.
|
||||||
done; `function` blocked on parser support.)_
|
- [ ] Arithmetic, comparison, boolean ops, string `^`, `mod`.
|
||||||
- [x] `if`/`then`/`else`, `begin`/`end`, sequence `;`.
|
- [ ] Unit `()` value; `ignore`.
|
||||||
- [x] Arithmetic, comparison, boolean ops, string `^`, `mod`.
|
- [ ] References: `ref`, `!`, `:=`.
|
||||||
- [x] Unit `()` value; `ignore`.
|
|
||||||
- [x] References: `ref`, `!`, `:=`.
|
|
||||||
- [ ] Mutable record fields.
|
- [ ] Mutable record fields.
|
||||||
- [x] `for i = lo to hi do ... done` loop; `while cond do ... done` (incl.
|
- [ ] `for i = lo to hi do ... done` loop; `while cond do ... done`.
|
||||||
`downto` direction).
|
|
||||||
- [ ] `try`/`with` — maps to SX `guard`; `raise` via perform.
|
- [ ] `try`/`with` — maps to SX `guard`; `raise` via perform.
|
||||||
- [ ] Tests in `lib/ocaml/tests/eval.sx` — 50+ tests, pure + imperative.
|
- [ ] Tests in `lib/ocaml/tests/eval.sx` — 50+ tests, pure + imperative.
|
||||||
|
|
||||||
### Phase 3 — ADTs + pattern matching
|
### Phase 3 — ADTs + pattern matching
|
||||||
|
|
||||||
- [ ] `type` declarations: `type t = A | B of t1 * t2 | C of { x: int }`.
|
- [ ] `type` declarations: `type t = A | B of t1 * t2 | C of { x: int }`.
|
||||||
_(Parser + evaluator currently inferred-arity at runtime; type decls
|
- [ ] Constructors as tagged lists: `A` → `(:A)`, `B(1, "x")` → `(:B 1 "x")`.
|
||||||
pending.)_
|
- [ ] `match`/`with`: constructor, literal, variable, wildcard, tuple, list cons/nil,
|
||||||
- [x] Constructors as tagged lists: `A` → `("A")`, `B(1, "x")` → `("B" 1 "x")`.
|
`as` binding, or-patterns, nested patterns, `when` guard.
|
||||||
- [~] `match`/`with`: constructor, literal, variable, wildcard, tuple, list
|
- [ ] Exhaustiveness: runtime error on incomplete match (no compile-time check yet).
|
||||||
cons/nil, nested patterns. _(Pending: `as` binding, or-patterns,
|
|
||||||
`when` guard.)_
|
|
||||||
- [x] Exhaustiveness: runtime error on incomplete match (no compile-time check yet).
|
|
||||||
- [ ] Built-in types: `option` (`None`/`Some`), `result` (`Ok`/`Error`),
|
- [ ] Built-in types: `option` (`None`/`Some`), `result` (`Ok`/`Error`),
|
||||||
`list` (nil/cons), `bool`, `unit`, `exn`.
|
`list` (nil/cons), `bool`, `unit`, `exn`.
|
||||||
- [ ] `exception` declarations; built-in: `Not_found`, `Invalid_argument`,
|
- [ ] `exception` declarations; built-in: `Not_found`, `Invalid_argument`,
|
||||||
@@ -321,75 +308,7 @@ the "mother tongue" closure: OCaml → SX → OCaml. This means:
|
|||||||
|
|
||||||
_Newest first._
|
_Newest first._
|
||||||
|
|
||||||
- 2026-05-08 Phase 2 — `for`/`while` loops. `(:for NAME LO HI DIR BODY)`
|
_(awaiting phase 1)_
|
||||||
with `:ascend`/`:descend` direction (`to`/`downto`); `(:while COND BODY)`.
|
|
||||||
Both eval to unit and re-bind the loop var per iteration. 194/194 (+5).
|
|
||||||
- 2026-05-08 Phase 2 — references (`ref`/`!`/`:=`). `ref` is a builtin
|
|
||||||
that boxes its argument in a one-element list (the mutable cell);
|
|
||||||
prefix `!` parses to `(:deref EXPR)` and reads `(nth cell 0)`; `:=`
|
|
||||||
joins the precedence table at the lowest binop level (right-assoc) and
|
|
||||||
short-circuits in eval to mutate via `set-nth!`. Closures capture refs
|
|
||||||
by sharing the underlying list. 189/189 (+6).
|
|
||||||
- 2026-05-08 Phase 3 — pattern matching evaluator + constructors (+18
|
|
||||||
tests, 183 total). Constructor application: `(:app (:con NAME) arg)`
|
|
||||||
builds a tagged list `(NAME …args)` with tuple args flattened (so
|
|
||||||
`Pair (a, b)` → `("Pair" a b)` matches the parser's pattern flatten).
|
|
||||||
Standalone ctor `(:con NAME)` → `(NAME)` (nullary). Pattern matcher:
|
|
||||||
:pwild / :pvar / :plit (unboxed compare) / :pcon (head + arity match) /
|
|
||||||
:pcons (cons-decompose) / :plist (length+items) / :ptuple (after `tuple`
|
|
||||||
tag). Match drives clauses until first success; runtime error on
|
|
||||||
exhaustion. Tested with option match, literal match, tuple match,
|
|
||||||
recursive list functions (`len`, `sum`), nested ctor (`Pair(a,b)`).
|
|
||||||
Note: arity flattening happens for any tuple-arg ctor — without ADT
|
|
||||||
declarations there's no way to distinguish `Some (1,2)` (single tuple
|
|
||||||
payload) from `Pair (1,2)` (two-arg ctor). All-flatten convention is
|
|
||||||
consistent across parser + evaluator.
|
|
||||||
- 2026-05-08 Phase 2 — `lib/ocaml/eval.sx`: ocaml-eval + ocaml-run +
|
|
||||||
ocaml-run-program. Coverage: atoms, var lookup, :app (curried),
|
|
||||||
:op (arithmetic/comparison/boolean/^/mod/::/|>), :neg, :not, :if,
|
|
||||||
:seq, :tuple, :list, :fun (auto-curried host-SX closures), :let,
|
|
||||||
:let-rec (recursive knot via one-element-list mutable cell). Initial
|
|
||||||
env exposes `not`/`succ`/`pred`/`abs`/`max`/`min`/`fst`/`snd`/`ignore`
|
|
||||||
as host-SX functions. Tests: literals, arithmetic, comparison, boolean,
|
|
||||||
string concat, conditionals, lambda + closures + recursion (fact 5,
|
|
||||||
fib 10, sum 100), sequences, top-level program decls, |> pipe. 165/165
|
|
||||||
passing (+42).
|
|
||||||
- 2026-05-07 Phase 1 — sequence operator `;`. Lowest-precedence binary;
|
|
||||||
`e1; e2; e3` → `(:seq e1 e2 e3)`. Two-phase grammar: `parse-expr-no-seq`
|
|
||||||
is the prior expression entry point; new `parse-expr` wraps it with
|
|
||||||
`;` chaining. List-literal items still use `parse-expr-no-seq` so `;`
|
|
||||||
retains its separator role inside `[…]`. Match-clause bodies use the
|
|
||||||
seq variant and stop at `|`, matching real OCaml semantics. Trailing `;`
|
|
||||||
before `end`/`)`/`|`/`in`/`then`/`else`/eof is permitted. 123/123 tests
|
|
||||||
passing (+10).
|
|
||||||
- 2026-05-07 Phase 1 — `match`/`with` + pattern parser. Patterns: wildcard,
|
|
||||||
literal, var, ctor (nullary + with arg, with tuple-arg flattening so
|
|
||||||
`Pair (a, b)` → `(:pcon "Pair" PA PB)`), tuple, list literal, cons `::`
|
|
||||||
(right-assoc), parens, unit. Match clauses: leading `|` optional, body
|
|
||||||
parsed via `parse-expr`. AST: `(:match SCRUT CLAUSES)` where each clause
|
|
||||||
is `(:case PAT BODY)`. 113/113 tests passing (+9). Note: parse-expr is
|
|
||||||
used for case bodies, so a trailing `| pat -> body` after a complex body
|
|
||||||
will be reached because `|` is not in the binop table for level 1.
|
|
||||||
- 2026-05-07 Phase 1 — top-level program parser `ocaml-parse-program`. Parses
|
|
||||||
a sequence of `let [rec] name params* = expr` decls and bare expressions
|
|
||||||
separated by `;;`. Output `(:program DECLS)` with each decl one of `(:def …)`,
|
|
||||||
`(:def-rec …)`, `(:expr E)`. Decl bodies parsed by re-feeding the source
|
|
||||||
slice through `ocaml-parse` (cheap stand-in until shared-state refactor).
|
|
||||||
104/104 tests now passing (+9).
|
|
||||||
- 2026-05-07 Phase 1 — `lib/ocaml/parser.sx` expression parser consuming
|
|
||||||
`lib/guest/pratt.sx` for binop precedence (29 operators across 8 levels,
|
|
||||||
incl. keyword-spelled binops `mod`/`land`/`lor`/`lxor`/`lsl`/`lsr`/`asr`).
|
|
||||||
Atoms (literals + var/con/unit/list), application (left-assoc), prefix
|
|
||||||
`-`/`not`, tuples, parens, `if`/`then`/`else`, `fun x y -> body`,
|
|
||||||
`let`/`let rec` with function shorthand. AST shapes match Haskell-on-SX
|
|
||||||
conventions (`(:int N)` `(:op OP L R)` `(:fun PARAMS BODY)` etc.). Total
|
|
||||||
95/95 tests now passing via `lib/ocaml/test.sh`.
|
|
||||||
- 2026-05-07 Phase 1 — `lib/ocaml/tokenizer.sx` consuming `lib/guest/lex.sx`
|
|
||||||
via `prefix-rename`. Covers idents, ctors, 51 keywords, numbers (int / float
|
|
||||||
/ hex / exponent / underscored), strings (with escapes), chars (with escapes),
|
|
||||||
type variables (`'a`), nested block comments, and 26 operator/punct tokens
|
|
||||||
(incl. `->` `|>` `<-` `:=` `::` `;;` `@@` `<>` `&&` `||` `**` etc.). 58/58
|
|
||||||
tokenizer tests pass via `lib/ocaml/test.sh` driving `sx_server.exe`.
|
|
||||||
|
|
||||||
## Blockers
|
## Blockers
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user