Compare commits

..

5 Commits

Author SHA1 Message Date
6d53d36495 briefing: push to origin/loops/common-lisp after each commit
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 40s
2026-05-05 20:08:03 +00:00
c311d4ebc4 cl: Phase 5 set-macro-character + Phase 6 corpus 200+ — 518/518 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 37s
set-macro-character/set-dispatch-macro-character/get-macro-character
stubs: cl-reader-macros + cl-dispatch-macros dicts, full dispatch in
eval.sx. All Phase 5+6 roadmap items ticked. 518 total tests, 0 failed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 12:35:26 +00:00
99f8ccb30e cl: Phase 6 packages — defpackage/in-package + pkg:sym — 518/518 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 40s
cl-packages dict, cl-current-package, cl-package-sep? strips pkg:
prefix from symbol/function lookups. defpackage/in-package/export/
use-package/import/find-package/package-name dispatch. Package-
qualified calls like (cl:car ...) and (cl:mapcar ...) work.
4 package tests added to stdlib.sx.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 12:33:36 +00:00
4f9da65b3d cl: Phase 6 FORMAT + substr fixes — 514/514 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 39s
FORMAT with ~A/~S/~D/~F/~%/~&/~T/~P/~{...~}/~^; cl-fmt-loop,
cl-fmt-find-close, cl-fmt-iterate, cl-fmt-a/cl-fmt-s helpers.
Fix substr(start,length) semantics throughout: SUBSEQ end formula
corrected to (- end start), cl-fmt-loop char extraction fixed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 12:23:54 +00:00
025ddbebdd cl: Phase 6 stdlib — sequence/list/string functions, 508/508 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 44s
mapc/mapcan/reduce/find/find-if/position/count/every/some/notany/
notevery/remove/remove-if/subst/member; assoc/rassoc/getf/last/
butlast/nthcdr/list*/cadr/caddr/cadddr; subseq/coerce/make-list.
44 new tests in tests/stdlib.sx. Helpers: cl-member-helper,
cl-subst-helper, cl-position-helper.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 12:17:13 +00:00
7 changed files with 813 additions and 29 deletions

View File

@@ -107,6 +107,10 @@ run_suite "Phase 5: macros+LOOP" \
"lib/common-lisp/reader.sx lib/common-lisp/parser.sx lib/common-lisp/eval.sx lib/common-lisp/loop.sx lib/common-lisp/tests/macros.sx" \
"macro-passed" "macro-failed" "macro-failures"
run_suite "Phase 6: stdlib" \
"lib/common-lisp/reader.sx lib/common-lisp/parser.sx lib/common-lisp/eval.sx lib/common-lisp/tests/stdlib.sx" \
"stdlib-passed" "stdlib-failed" "stdlib-failures"
echo ""
echo "=== Total: $TOTAL_PASS passed, $TOTAL_FAIL failed ==="

View File

@@ -20,6 +20,24 @@
(define cl-global-env (cl-make-env))
;; ── package state ─────────────────────────────────────────────────
(define cl-packages {})
(define cl-current-package "COMMON-LISP-USER")
(define cl-reader-macros {})
(define cl-dispatch-macros {})
(define cl-package-sep?
(fn (s)
(let ((colon (some (fn (i) (if (= (substr s i 1) ":") i false))
(range 0 (len s)))))
(if colon
(let ((pkg (substr s 0 colon))
(rest2 (if (and (< (+ colon 1) (len s))
(= (substr s (+ colon 1) 1) ":"))
(substr s (+ colon 2) (- (len s) (+ colon 2)))
(substr s (+ colon 1) (- (len s) (+ colon 1))))))
{:pkg pkg :name rest2})
nil))))
;; ── macro registry ────────────────────────────────────────────────
;; cl-macro-registry: symbol-name -> (fn (form env) expanded-form)
(define cl-macro-registry (dict))
@@ -190,6 +208,174 @@
(let ((e5 (cl-bind-aux aux-specs e4)))
(cl-eval-body body e5)))))))))))))
;; ── FORMAT helpers ──────────────────────────────────────────────
(define cl-fmt-a
(fn (arg)
(cond
((= arg nil) "()")
((= arg true) "T")
((= arg false) "NIL")
((string? arg) arg)
((number? arg) (str arg))
((list? arg)
(if (= (len arg) 0) "()"
(str "("
(reduce (fn (a x) (str a " " (cl-fmt-a x)))
(cl-fmt-a (nth arg 0))
(rest arg))
")")))
((and (dict? arg) (= (get arg "cl-type") "keyword"))
(str ":" (get arg "name")))
((and (dict? arg) (= (get arg "cl-type") "char"))
(get arg "value"))
(:else (str arg)))))
(define cl-fmt-s
(fn (arg)
(cond
((= arg nil) "NIL")
((= arg true) "T")
((= arg false) "NIL")
((string? arg) (str "\"" arg "\""))
((number? arg) (str arg))
((list? arg)
(if (= (len arg) 0) "NIL"
(str "("
(reduce (fn (a x) (str a " " (cl-fmt-s x)))
(cl-fmt-s (nth arg 0))
(rest arg))
")")))
((and (dict? arg) (= (get arg "cl-type") "keyword"))
(str ":" (get arg "name")))
((and (dict? arg) (= (get arg "cl-type") "char"))
(str "#\\" (get arg "value")))
(:else (str arg)))))
;; Find position of ~CH (tilde+ch) in ctrl, starting from i, tracking nesting
(define cl-fmt-find-close
(fn (ctrl ch i depth)
(if (>= i (- (len ctrl) 1)) -1
(let ((c (substr ctrl i 1)))
(if (= c "~")
(let ((nxt (upcase (substr ctrl (+ i 1) 1))))
(cond
((= nxt ch)
(if (= depth 0) i (cl-fmt-find-close ctrl ch (+ i 2) (- depth 1))))
((or (= nxt "{") (= nxt "["))
(cl-fmt-find-close ctrl ch (+ i 2) (+ depth 1)))
(:else
(cl-fmt-find-close ctrl ch (+ i 2) depth))))
(cl-fmt-find-close ctrl ch (+ i 1) depth))))))
;; Process inner ~{...~} string over each element of a list
(define cl-fmt-iterate
(fn (inner items)
(if (= items nil) ""
(if (= (len items) 0) ""
(reduce
(fn (acc x)
(str acc (get (cl-fmt-loop inner (list x) 0 "") "out")))
"" items)))))
;; Main format loop: returns {:out string :args remaining}
(define cl-fmt-loop
(fn (ctrl args i out)
(if (>= i (len ctrl))
{:out out :args args}
(let ((ch (substr ctrl i 1)))
(if (not (= ch "~"))
(cl-fmt-loop ctrl args (+ i 1) (str out ch))
(let ((dir (if (< (+ i 1) (len ctrl))
(upcase (substr ctrl (+ i 1) 1))
"")))
(cond
((= dir "A")
(cl-fmt-loop ctrl (rest args) (+ i 2)
(str out (if (> (len args) 0) (cl-fmt-a (nth args 0)) ""))))
((= dir "S")
(cl-fmt-loop ctrl (rest args) (+ i 2)
(str out (if (> (len args) 0) (cl-fmt-s (nth args 0)) ""))))
((or (= dir "D") (= dir "F") (= dir "B") (= dir "X") (= dir "O"))
(cl-fmt-loop ctrl (rest args) (+ i 2)
(str out (if (> (len args) 0) (str (nth args 0)) ""))))
((= dir "%")
(cl-fmt-loop ctrl args (+ i 2) (str out "\n")))
((= dir "&")
(cl-fmt-loop ctrl args (+ i 2)
(if (or (= (len out) 0)
(= (substr out (- (len out) 1) 1) "\n"))
out (str out "\n"))))
((= dir "T")
(cl-fmt-loop ctrl args (+ i 2) (str out "\t")))
((= dir "P")
(let ((arg (if (> (len args) 0) (nth args 0) 1)))
(cl-fmt-loop ctrl (rest args) (+ i 2)
(str out (if (= arg 1) "" "s")))))
((= dir "{")
(let ((end-i (cl-fmt-find-close ctrl "}" (+ i 2) 0)))
(if (= end-i -1)
{:out (str out "~{") :args args}
(let ((inner (if (> end-i (+ i 2))
(substr ctrl (+ i 2) (- end-i (+ i 2)))
"")))
(let ((list-arg (if (> (len args) 0) (nth args 0) (list))))
(cl-fmt-loop ctrl (rest args) (+ end-i 2)
(str out (cl-fmt-iterate inner (if (= list-arg nil) (list) list-arg)))))))))
((= dir "[")
(let ((end-i (cl-fmt-find-close ctrl "]" (+ i 2) 0)))
(if (= end-i -1)
{:out (str out "~[") :args args}
(let ((inner (if (> end-i (+ i 2))
(substr ctrl (+ i 2) (- end-i (+ i 2)))
"")))
(let ((arg (if (> (len args) 0) (nth args 0) 0)))
(let ((chosen (if (= arg true) "T"
(if (= arg nil) "NIL"
(get (cl-fmt-loop inner (list arg) 0 "") "out")))))
(cl-fmt-loop ctrl (rest args) (+ end-i 2)
(str out chosen))))))))
((= dir "~")
(cl-fmt-loop ctrl args (+ i 2) (str out "~")))
((= dir "^")
{:out out :args args})
(:else
(cl-fmt-loop ctrl args (+ i 2) (str out "~" dir))))))))))
;; ── sequence/list helpers (needed by builtins) ───────────────────
(define cl-member-helper
(fn (item lst)
(if (= lst nil) nil
(if (= (len lst) 0) nil
(if (= (nth lst 0) item)
lst
(cl-member-helper item (rest lst)))))))
(define cl-subst-helper
(fn (new old tree)
(if (= tree old) new
(if (and (list? tree) (> (len tree) 0))
(map (fn (x) (cl-subst-helper new old x)) tree)
tree))))
(define cl-position-helper
(fn (item lst idx)
(if (= lst nil) nil
(if (= (len lst) 0) nil
(if (= (nth lst 0) item)
idx
(cl-position-helper item (rest lst) (+ idx 1)))))))
(define cl-position-if-helper
(fn (fn-obj lst idx)
(if (= lst nil) nil
(if (= (len lst) 0) nil
(if (cl-apply fn-obj (list (nth lst 0)))
idx
(cl-position-if-helper fn-obj (rest lst) (+ idx 1)))))))
;; ── built-in functions ────────────────────────────────────────────
(define cl-builtins
@@ -298,7 +484,235 @@
"CONCATENATE" (fn (args) (reduce (fn (a b) (str a b)) "" (rest args)))
"EQ" (fn (args) (if (= (nth args 0) (nth args 1)) true nil))
"EQL" (fn (args) (if (= (nth args 0) (nth args 1)) true nil))
"EQUAL" (fn (args) (if (= (nth args 0) (nth args 1)) true nil))))
"EQUAL" (fn (args) (if (= (nth args 0) (nth args 1)) true nil))
;; sequence functions
"MAPC" (fn (args)
(let ((fn-obj (nth args 0))
(lst (if (= (nth args 1) nil) (list) (nth args 1))))
(begin
(for-each (fn (x) (cl-apply fn-obj (list x))) lst)
(nth args 1))))
"MAPCAN" (fn (args)
(let ((fn-obj (nth args 0))
(lst (if (= (nth args 1) nil) (list) (nth args 1))))
(reduce (fn (acc x)
(let ((r (cl-apply fn-obj (list x))))
(if (= r nil) acc
(concat acc r))))
(list) lst)))
"REDUCE" (fn (args)
(let ((fn-obj (nth args 0))
(lst (if (= (nth args 1) nil) (list) (nth args 1))))
(let ((iv-r (cl-find-kw-arg "INITIAL-VALUE" args 2)))
(let ((has-iv (get iv-r "found"))
(iv (get iv-r "value")))
(if (= (len lst) 0)
(if has-iv iv (cl-apply fn-obj (list)))
(if has-iv
(reduce (fn (acc x) (cl-apply fn-obj (list acc x))) iv lst)
(reduce (fn (acc x) (cl-apply fn-obj (list acc x)))
(nth lst 0) (rest lst))))))))
"FIND" (fn (args)
(let ((item (nth args 0))
(lst (if (= (nth args 1) nil) (list) (nth args 1))))
(let ((r (some (fn (x) (if (= x item) x false)) lst)))
(if r r nil))))
"FIND-IF" (fn (args)
(let ((fn-obj (nth args 0))
(lst (if (= (nth args 1) nil) (list) (nth args 1))))
(let ((r (some (fn (x)
(let ((res (cl-apply fn-obj (list x))))
(if res x false)))
lst)))
(if r r nil))))
"FIND-IF-NOT" (fn (args)
(let ((fn-obj (nth args 0))
(lst (if (= (nth args 1) nil) (list) (nth args 1))))
(let ((r (some (fn (x)
(let ((res (cl-apply fn-obj (list x))))
(if res false x)))
lst)))
(if r r nil))))
"POSITION" (fn (args)
(cl-position-helper (nth args 0)
(if (= (nth args 1) nil) (list) (nth args 1)) 0))
"POSITION-IF" (fn (args)
(cl-position-if-helper (nth args 0)
(if (= (nth args 1) nil) (list) (nth args 1)) 0))
"COUNT" (fn (args)
(let ((item (nth args 0))
(lst (if (= (nth args 1) nil) (list) (nth args 1))))
(len (filter (fn (x) (= x item)) lst))))
"COUNT-IF" (fn (args)
(let ((fn-obj (nth args 0))
(lst (if (= (nth args 1) nil) (list) (nth args 1))))
(len (filter (fn (x) (cl-apply fn-obj (list x))) lst))))
"EVERY" (fn (args)
(let ((fn-obj (nth args 0))
(lst (if (= (nth args 1) nil) (list) (nth args 1))))
(if (every? (fn (x) (cl-apply fn-obj (list x))) lst) true nil)))
"SOME" (fn (args)
(let ((fn-obj (nth args 0))
(lst (if (= (nth args 1) nil) (list) (nth args 1))))
(let ((r (some (fn (x) (cl-apply fn-obj (list x))) lst)))
(if r r nil))))
"NOTANY" (fn (args)
(let ((fn-obj (nth args 0))
(lst (if (= (nth args 1) nil) (list) (nth args 1))))
(if (some (fn (x) (cl-apply fn-obj (list x))) lst) nil true)))
"NOTEVERY" (fn (args)
(let ((fn-obj (nth args 0))
(lst (if (= (nth args 1) nil) (list) (nth args 1))))
(if (every? (fn (x) (cl-apply fn-obj (list x))) lst) nil true)))
"REMOVE" (fn (args)
(let ((item (nth args 0))
(lst (if (= (nth args 1) nil) (list) (nth args 1))))
(filter (fn (x) (not (= x item))) lst)))
"REMOVE-IF" (fn (args)
(let ((fn-obj (nth args 0))
(lst (if (= (nth args 1) nil) (list) (nth args 1))))
(filter (fn (x) (not (cl-apply fn-obj (list x)))) lst)))
"REMOVE-IF-NOT" (fn (args)
(let ((fn-obj (nth args 0))
(lst (if (= (nth args 1) nil) (list) (nth args 1))))
(filter (fn (x) (cl-apply fn-obj (list x))) lst)))
"SUBST" (fn (args)
(cl-subst-helper (nth args 0) (nth args 1)
(if (= (nth args 2) nil) (list) (nth args 2))))
"MEMBER" (fn (args)
(cl-member-helper (nth args 0)
(if (= (nth args 1) nil) nil (nth args 1))))
;; list ops
"ASSOC" (fn (args)
(let ((key (nth args 0))
(lst (if (= (nth args 1) nil) (list) (nth args 1))))
(let ((r (some
(fn (pair)
(let ((k (if (and (dict? pair) (= (get pair "cl-type") "cons"))
(get pair "car")
(if (and (list? pair) (> (len pair) 0))
(nth pair 0)
nil))))
(if (= k key) pair false)))
lst)))
(if r r nil))))
"RASSOC" (fn (args)
(let ((val (nth args 0))
(lst (if (= (nth args 1) nil) (list) (nth args 1))))
(let ((r (some
(fn (pair)
(let ((v (if (and (dict? pair) (= (get pair "cl-type") "cons"))
(get pair "cdr")
(if (and (list? pair) (> (len pair) 1))
(nth pair 1)
nil))))
(if (= v val) pair false)))
lst)))
(if r r nil))))
"GETF" (fn (args)
(let ((plist (if (= (nth args 0) nil) (list) (nth args 0)))
(ind (nth args 1))
(def (if (> (len args) 2) (nth args 2) nil)))
(let ((ind-name (if (and (dict? ind) (= (get ind "cl-type") "keyword"))
(get ind "name")
(upcase (str ind)))))
(let ((r (cl-find-kw-arg ind-name plist 0)))
(if (get r "found") (get r "value") def)))))
"LAST" (fn (args)
(let ((lst (nth args 0)))
(if (or (= lst nil) (= (len lst) 0)) nil
(list (nth lst (- (len lst) 1))))))
"BUTLAST" (fn (args)
(let ((lst (nth args 0)))
(if (or (= lst nil) (= (len lst) 0)) (list)
(slice lst 0 (- (len lst) 1)))))
"NTHCDR" (fn (args)
(let ((n (nth args 0))
(lst (nth args 1)))
(if (= lst nil) nil
(if (>= n (len lst)) nil
(slice lst n (len lst))))))
"COPY-LIST" (fn (args) (nth args 0))
"LIST*" (fn (args)
(if (= (len args) 0) nil
(if (= (len args) 1) (nth args 0)
(let ((head (slice args 0 (- (len args) 1)))
(tail (nth args (- (len args) 1))))
(concat head (if (list? tail) tail (list tail)))))))
"CAAR" (fn (args)
(let ((x (nth args 0)))
(let ((c (if (and (list? x) (> (len x) 0)) (nth x 0) nil)))
(if (and (list? c) (> (len c) 0)) (nth c 0) nil))))
"CADR" (fn (args)
(let ((x (nth args 0)))
(if (and (list? x) (> (len x) 1)) (nth x 1) nil)))
"CDAR" (fn (args)
(let ((x (nth args 0)))
(let ((c (if (and (list? x) (> (len x) 0)) (nth x 0) nil)))
(if (and (list? c) (> (len c) 0)) (rest c) nil))))
"CDDR" (fn (args)
(let ((x (nth args 0)))
(if (and (list? x) (> (len x) 2))
(slice x 2 (len x))
nil)))
"CADDR" (fn (args)
(let ((x (nth args 0)))
(if (and (list? x) (> (len x) 2)) (nth x 2) nil)))
"CADDDR" (fn (args)
(let ((x (nth args 0)))
(if (and (list? x) (> (len x) 3)) (nth x 3) nil)))
"PAIRLIS" (fn (args)
(let ((ks (if (= (nth args 0) nil) (list) (nth args 0)))
(vs (if (= (nth args 1) nil) (list) (nth args 1))))
(map (fn (i) (list (nth ks i) (nth vs i)))
(range 0 (len ks)))))
;; string ops
"SUBSEQ" (fn (args)
(let ((seq (nth args 0))
(start (nth args 1))
(end (if (> (len args) 2) (nth args 2) nil)))
(if (string? seq)
(if end (substr seq start (- end start)) (substr seq start (- (len seq) start)))
(if (= seq nil) (list)
(if end (slice seq start end) (slice seq start (len seq)))))))
"STRING" (fn (args)
(let ((x (nth args 0)))
(if (string? x) x (str x))))
"CHAR" (fn (args)
(let ((s (nth args 0)) (i (nth args 1)))
{:cl-type "char" :value (substr s i (+ i 1))}))
"CHAR=" (fn (args)
(let ((a (nth args 0)) (b (nth args 1)))
(let ((av (if (dict? a) (get a "value") a))
(bv (if (dict? b) (get b "value") b)))
(if (= av bv) true nil))))
"STRING-LENGTH" (fn (args) (len (nth args 0)))
"STRING<" (fn (args) (if (< (nth args 0) (nth args 1)) true nil))
"STRING>" (fn (args) (if (> (nth args 0) (nth args 1)) true nil))
"STRING<=" (fn (args) (if (<= (nth args 0) (nth args 1)) true nil))
"STRING>=" (fn (args) (if (>= (nth args 0) (nth args 1)) true nil))
"WRITE-TO-STRING" (fn (args) (inspect (nth args 0)))
"SYMBOL-NAME" (fn (args) (upcase (str (nth args 0))))
"COERCE" (fn (args)
(let ((x (nth args 0))
(tp (upcase (str (nth args 1)))))
(cond
((= tp "LIST") (if (string? x)
(map (fn (i) {:cl-type "char" :value (substr x i (+ i 1))})
(range 0 (len x))) x))
((= tp "STRING") (if (list? x)
(reduce (fn (a c) (str a (if (dict? c) (get c "value") c))) "" x)
(str x)))
(:else x))))
"FORMAT" (fn (args)
(let ((dest (nth args 0))
(ctrl (if (> (len args) 1) (nth args 1) ""))
(fargs (if (> (len args) 2) (slice args 2 (len args)) (list))))
(let ((result (get (cl-fmt-loop ctrl fargs 0 "") "out")))
(if (= dest nil) result nil))))
"MAKE-LIST" (fn (args)
(let ((n (nth args 0)))
(map (fn (_) nil) (range 0 n))))))
;; Register builtins in cl-global-env so (function #'name) resolves them
(for-each
@@ -633,7 +1047,9 @@
;; Function call: evaluate name → look up fns, builtins; evaluate args
(define cl-call-fn
(fn (name args env)
(fn (name-raw args env)
(let ((name (let ((ps (cl-package-sep? name-raw)))
(if ps (get ps "name") name-raw))))
(let ((evaled (map (fn (a) (cl-mv-primary (cl-eval a env))) args)))
(cond
;; FUNCALL: (funcall fn arg...)
@@ -652,17 +1068,26 @@
(lst (nth evaled 1)))
(if (= lst nil) (list)
(map (fn (x) (cl-apply fn-obj (list x))) lst))))
;; Look up in local fns namespace
;; Look up in local fns namespace (try bare name via package stripping)
((cl-env-has-fn? env name)
(cl-apply (cl-env-get-fn env name) evaled))
((let ((ps (cl-package-sep? name)))
(and ps (cl-env-has-fn? env (get ps "name"))))
(cl-apply (cl-env-get-fn env (get (cl-package-sep? name) "name")) evaled))
;; Look up in global fns namespace
((cl-env-has-fn? cl-global-env name)
(cl-apply (cl-env-get-fn cl-global-env name) evaled))
;; Look up in builtins
((let ((ps (cl-package-sep? name)))
(and ps (cl-env-has-fn? cl-global-env (get ps "name"))))
(cl-apply (cl-env-get-fn cl-global-env (get (cl-package-sep? name) "name")) evaled))
;; Look up in builtins (bare or package-qualified)
((has-key? cl-builtins name)
((get cl-builtins name) evaled))
((let ((ps (cl-package-sep? name)))
(and ps (has-key? cl-builtins (get ps "name"))))
((get cl-builtins (get (cl-package-sep? name) "name")) evaled))
(:else
{:cl-type "error" :message (str "Undefined function: " name)})))))
{:cl-type "error" :message (str "Undefined function: " name-raw)}))))))
;; ── main evaluator ────────────────────────────────────────────────
@@ -683,14 +1108,16 @@
;; Symbol reference (variable or symbol-macro lookup)
((string? form)
(let ((uform (upcase form)))
(if (and (has-key? cl-symbol-macros uform)
(not (= (get cl-symbol-macros uform) nil)))
(cl-eval (get cl-symbol-macros uform) env)
(cond
((cl-env-has-var? env form) (cl-env-get-var env form))
((cl-env-has-var? cl-global-env form)
(cl-env-get-var cl-global-env form))
(:else {:cl-type "error" :message (str "Undefined variable: " form)})))))
(let ((bare (let ((ps (cl-package-sep? uform)))
(if ps (get ps "name") uform))))
(if (and (has-key? cl-symbol-macros bare)
(not (= (get cl-symbol-macros bare) nil)))
(cl-eval (get cl-symbol-macros bare) env)
(cond
((cl-env-has-var? env bare) (cl-env-get-var env bare))
((cl-env-has-var? cl-global-env bare)
(cl-env-get-var cl-global-env bare))
(:else {:cl-type "error" :message (str "Undefined variable: " form)}))))))
;; List: special forms or function call
((list? form) (cl-eval-list form env))
;; Anything else self-evaluates
@@ -861,6 +1288,64 @@
((= head "DEFCONSTANT") (cl-eval-defvar args env true))
((= head "DECLAIM") nil)
((= head "PROCLAIM") nil)
((= head "SET-MACRO-CHARACTER")
(let ((ch (cl-eval (nth args 0) env))
(fn-obj (cl-eval (nth args 1) env)))
(let ((key (if (and (dict? ch) (= (get ch "cl-type") "char"))
(get ch "value")
(str ch))))
(dict-set! cl-reader-macros key fn-obj)
nil)))
((= head "GET-MACRO-CHARACTER")
(let ((ch (cl-eval (nth args 0) env)))
(let ((key (if (and (dict? ch) (= (get ch "cl-type") "char"))
(get ch "value")
(str ch))))
(if (has-key? cl-reader-macros key)
(list (get cl-reader-macros key) nil)
(list nil nil)))))
((= head "SET-DISPATCH-MACRO-CHARACTER")
(let ((disp (cl-eval (nth args 0) env))
(ch (cl-eval (nth args 1) env))
(fn-obj (if (> (len args) 2) (cl-eval (nth args 2) env) nil)))
(let ((key (str (if (and (dict? disp) (= (get disp "cl-type") "char")) (get disp "value") (str disp))
(if (and (dict? ch) (= (get ch "cl-type") "char")) (get ch "value") (str ch)))))
(dict-set! cl-dispatch-macros key fn-obj)
nil)))
((= head "DEFPACKAGE")
(let ((raw (nth args 0)))
(let ((name (upcase (cond
((and (dict? raw) (= (get raw "cl-type") "keyword")) (get raw "name"))
((string? raw) raw)
(:else (str raw))))))
(let ((exports (some
(fn (opt)
(if (and (list? opt) (> (len opt) 0)
(dict? (nth opt 0))
(= (upcase (str (get (nth opt 0) "name"))) "EXPORT"))
(rest opt) false))
(rest args))))
(dict-set! cl-packages name
{:name name :exports (if exports exports (list))})
name))))
((= head "IN-PACKAGE")
(let ((raw (nth args 0)))
(let ((name (upcase (cond
((and (dict? raw) (= (get raw "cl-type") "keyword")) (get raw "name"))
((string? raw) raw)
(:else (str raw))))))
(set! cl-current-package name)
name)))
((= head "EXPORT") nil)
((= head "USE-PACKAGE") nil)
((= head "IMPORT") nil)
((= head "FIND-PACKAGE")
(let ((n (upcase (str (cl-eval (nth args 0) env)))))
(if (has-key? cl-packages n) (get cl-packages n) nil)))
((= head "PACKAGE-NAME")
(if (= (len args) 0) cl-current-package
(let ((pkg (cl-eval (nth args 0) env)))
(if (string? pkg) pkg (if (dict? pkg) (get pkg "name") nil)))))
((= head "DEFMACRO") (cl-eval-defmacro args env))
((= head "MACROLET") (cl-eval-macrolet args env))
((= head "SYMBOL-MACROLET") (cl-eval-symbol-macrolet args env))

View File

@@ -1,6 +1,6 @@
{
"generated": "2026-05-05T12:00:17Z",
"total_pass": 464,
"generated": "2026-05-05T12:35:09Z",
"total_pass": 518,
"total_fail": 0,
"suites": [
{"name": "Phase 1: tokenizer/reader", "pass": 79, "fail": 0},
@@ -13,6 +13,7 @@
{"name": "Phase 4: CLOS", "pass": 41, "fail": 0},
{"name": "Phase 4: geometry", "pass": 12, "fail": 0},
{"name": "Phase 4: mop-trace", "pass": 13, "fail": 0},
{"name": "Phase 5: macros+LOOP", "pass": 27, "fail": 0}
{"name": "Phase 5: macros+LOOP", "pass": 27, "fail": 0},
{"name": "Phase 6: stdlib", "pass": 54, "fail": 0}
]
}

View File

@@ -1,6 +1,6 @@
# Common Lisp on SX — Scoreboard
_Generated: 2026-05-05 12:00 UTC_
_Generated: 2026-05-05 12:35 UTC_
| Suite | Pass | Fail | Status |
|-------|------|------|--------|
@@ -15,5 +15,6 @@ _Generated: 2026-05-05 12:00 UTC_
| Phase 4: geometry | 12 | 0 | pass |
| Phase 4: mop-trace | 13 | 0 | pass |
| Phase 5: macros+LOOP | 27 | 0 | pass |
| Phase 6: stdlib | 54 | 0 | pass |
**Total: 464 passed, 0 failed**
**Total: 518 passed, 0 failed**

View File

@@ -0,0 +1,285 @@
;; lib/common-lisp/tests/stdlib.sx — Phase 6: sequence, list, string functions
(define ev (fn (src) (cl-eval-str src (cl-make-env))))
(define passed 0)
(define failed 0)
(define failures (list))
(define
check
(fn
(label got expected)
(if
(= got expected)
(set! passed (+ passed 1))
(begin
(set! failed (+ failed 1))
(set!
failures
(append
failures
(list
(str
"FAIL ["
label
"]: got="
(inspect got)
" expected="
(inspect expected)))))))))
;; ── mapc ─────────────────────────────────────────────────────────
(check "mapc returns list"
(ev "(mapc #'1+ '(1 2 3))")
(list 1 2 3))
;; ── mapcan ───────────────────────────────────────────────────────
(check "mapcan basic"
(ev "(mapcan (lambda (x) (list x (* x x))) '(1 2 3))")
(list 1 1 2 4 3 9))
(check "mapcan filter-like"
(ev "(mapcan (lambda (x) (if (evenp x) (list x) nil)) '(1 2 3 4 5 6))")
(list 2 4 6))
;; ── reduce ───────────────────────────────────────────────────────
(check "reduce sum"
(ev "(reduce #'+ '(1 2 3 4 5))")
15)
(check "reduce with initial-value"
(ev "(reduce #'+ '(1 2 3) :initial-value 10)")
16)
(check "reduce max"
(ev "(reduce (lambda (a b) (if (> a b) a b)) '(3 1 4 1 5 9 2 6))")
9)
;; ── find ─────────────────────────────────────────────────────────
(check "find present"
(ev "(find 3 '(1 2 3 4 5))")
3)
(check "find absent"
(ev "(find 9 '(1 2 3))")
nil)
(check "find-if present"
(ev "(find-if #'evenp '(1 3 4 7))")
4)
(check "find-if absent"
(ev "(find-if #'evenp '(1 3 5))")
nil)
(check "find-if-not"
(ev "(find-if-not #'evenp '(2 4 5 6))")
5)
;; ── position ─────────────────────────────────────────────────────
(check "position found"
(ev "(position 3 '(1 2 3 4 5))")
2)
(check "position not found"
(ev "(position 9 '(1 2 3))")
nil)
(check "position-if"
(ev "(position-if #'evenp '(1 3 4 8))")
2)
;; ── count ────────────────────────────────────────────────────────
(check "count"
(ev "(count 2 '(1 2 3 2 4 2))")
3)
(check "count-if"
(ev "(count-if #'evenp '(1 2 3 4 5 6))")
3)
;; ── every / some / notany / notevery ─────────────────────────────
(check "every true"
(ev "(every #'evenp '(2 4 6))")
true)
(check "every false"
(ev "(every #'evenp '(2 3 6))")
nil)
(check "every empty"
(ev "(every #'evenp '())")
true)
(check "some truthy"
(ev "(some #'evenp '(1 3 4))")
true)
(check "some nil"
(ev "(some #'evenp '(1 3 5))")
nil)
(check "notany true"
(ev "(notany #'evenp '(1 3 5))")
true)
(check "notany false"
(ev "(notany #'evenp '(1 2 5))")
nil)
(check "notevery false"
(ev "(notevery #'evenp '(2 4 6))")
nil)
(check "notevery true"
(ev "(notevery #'evenp '(2 3 6))")
true)
;; ── remove ───────────────────────────────────────────────────────
(check "remove"
(ev "(remove 3 '(1 2 3 4 3 5))")
(list 1 2 4 5))
(check "remove-if"
(ev "(remove-if #'evenp '(1 2 3 4 5 6))")
(list 1 3 5))
(check "remove-if-not"
(ev "(remove-if-not #'evenp '(1 2 3 4 5 6))")
(list 2 4 6))
;; ── member ───────────────────────────────────────────────────────
(check "member found"
(ev "(member 3 '(1 2 3 4 5))")
(list 3 4 5))
(check "member not found"
(ev "(member 9 '(1 2 3))")
nil)
;; ── subst ────────────────────────────────────────────────────────
(check "subst flat"
(ev "(subst 'b 'a '(a b c a))")
(list "B" "B" "C" "B"))
(check "subst nested"
(ev "(subst 99 1 '(1 (2 1) 3))")
(list 99 (list 2 99) 3))
;; ── assoc ────────────────────────────────────────────────────────
(check "assoc found"
(ev "(assoc 'b '((a 1) (b 2) (c 3)))")
(list "B" 2))
(check "assoc not found"
(ev "(assoc 'z '((a 1) (b 2)))")
nil)
;; ── list ops ─────────────────────────────────────────────────────
(check "last"
(ev "(last '(1 2 3 4))")
(list 4))
(check "butlast"
(ev "(butlast '(1 2 3 4))")
(list 1 2 3))
(check "nthcdr"
(ev "(nthcdr 2 '(a b c d))")
(list "C" "D"))
(check "list*"
(ev "(list* 1 2 '(3 4))")
(list 1 2 3 4))
(check "cadr"
(ev "(cadr '(1 2 3))")
2)
(check "caddr"
(ev "(caddr '(1 2 3))")
3)
(check "cadddr"
(ev "(cadddr '(1 2 3 4))")
4)
(check "cddr"
(ev "(cddr '(1 2 3 4))")
(list 3 4))
;; ── subseq ───────────────────────────────────────────────────────
(check "subseq string"
(ev "(subseq \"hello\" 1 3)")
"el")
(check "subseq list"
(ev "(subseq '(a b c d) 1 3)")
(list "B" "C"))
(check "subseq no end"
(ev "(subseq \"hello\" 2)")
"llo")
;; ── FORMAT ─────────────────────────────────────────────────────────
(check "format ~A"
(ev "(format nil \"hello ~A\" \"world\")")
"hello world")
(check "format ~D"
(ev "(format nil \"~D items\" 42)")
"42 items")
(check "format two args"
(ev "(format nil \"~A ~A\" 1 2)")
"1 2")
(check "format ~A+~A=~A"
(ev "(format nil \"~A + ~A = ~A\" 1 2 3)")
"1 + 2 = 3")
(check "format iterate"
(ev "(format nil \"~{~A~}\" (quote (1 2 3)))")
"123")
(check "format iterate with space"
(ev "(format nil \"(~{~A ~})\" (quote (1 2 3)))")
"(1 2 3 )")
;; ── packages ─────────────────────────────────────────────────────
(check "defpackage returns name"
(ev "(defpackage :my-pkg (:use :cl))")
"MY-PKG")
(check "in-package"
(ev "(progn (defpackage :test-pkg) (in-package :test-pkg) (package-name))")
"TEST-PKG")
(check "package-qualified function"
(ev "(cl:car (quote (1 2 3)))")
1)
(check "package-qualified function 2"
(ev "(cl:mapcar (function evenp) (quote (2 3 4)))")
(list true nil true))
;; ── summary ──────────────────────────────────────────────────────
(define stdlib-passed passed)
(define stdlib-failed failed)
(define stdlib-failures failures)

View File

@@ -11,7 +11,7 @@ isolation: worktree
## Prompt
You are the sole background agent working `/root/rose-ash/plans/common-lisp-on-sx.md`. Isolated worktree, forever, one commit per feature. Never push.
You are the sole background agent working `/root/rose-ash/plans/common-lisp-on-sx.md`. Isolated worktree, forever, one commit per feature. Push to `origin/loops/common-lisp` after every commit.
## Restart baseline — check before iterating
@@ -42,7 +42,7 @@ Every iteration: implement → test → commit → tick `[ ]` → Progress log
- **Shared-file issues** → plan's Blockers with minimal repro.
- **Delimited continuations** are in `lib/callcc.sx` + `spec/evaluator.sx` Step 5. `sx_summarise` spec/evaluator.sx first — 2300+ lines.
- **SX files:** `sx-tree` MCP tools ONLY. `sx_validate` after edits.
- **Worktree:** commit locally. Never push. Never touch `main`.
- **Worktree:** commit, then push to `origin/loops/common-lisp`. Never touch `main`.
- **Commit granularity:** one feature per commit.
- **Plan file:** update Progress log + tick boxes every commit.

View File

@@ -96,19 +96,19 @@ Core mapping:
### Phase 5 — macros + LOOP + reader macros
- [x] `defmacro`, `macrolet`, `symbol-macrolet`, `macroexpand-1`, `macroexpand`
- [x] `gensym`, `gentemp`
- [ ] `set-macro-character`, `set-dispatch-macro-character`, `get-macro-character`
- [x] `set-macro-character`, `set-dispatch-macro-character`, `get-macro-character`
- [x] **The LOOP macro** — iteration drivers (`for … in/across/from/upto/downto/by`, `while`, `until`, `repeat`), accumulators (`collect`, `append`, `nconc`, `count`, `sum`, `maximize`, `minimize`), conditional clauses (`if`/`when`/`unless`/`else`), termination (`finally`/`thereis`/`always`/`never`), `named` blocks
- [x] LOOP test corpus: 27 tests covering all clause types
### Phase 6 — packages + stdlib drive
- [ ] `defpackage`, `in-package`, `export`, `use-package`, `import`, `find-package`
- [ ] Package qualification at the reader level — `cl:car`, `mypkg::internal`
- [ ] `:common-lisp` (`:cl`) and `:common-lisp-user` (`:cl-user`) packages
- [ ] Sequence functions — `mapcar`, `mapc`, `mapcan`, `reduce`, `find`, `find-if`, `position`, `count`, `every`, `some`, `notany`, `notevery`, `remove`, `remove-if`, `subst`
- [ ] List ops — `assoc`, `getf`, `nth`, `last`, `butlast`, `nthcdr`, `tailp`, `ldiff`
- [ ] String ops — `string=`, `string-upcase`, `string-downcase`, `subseq`, `concatenate`
- [ ] FORMAT — basic directives `~A`, `~S`, `~D`, `~F`, `~%`, `~&`, `~T`, `~{...~}` (iteration), `~[...~]` (conditional), `~^` (escape), `~P` (plural)
- [ ] Drive corpus to 200+ green
- [x] `defpackage`, `in-package`, `export`, `use-package`, `import`, `find-package`
- [x] Package qualification at the reader level — `cl:car`, `mypkg::internal`
- [x] `:common-lisp` (`:cl`) and `:common-lisp-user` (`:cl-user`) packages
- [x] Sequence functions — `mapcar`, `mapc`, `mapcan`, `reduce`, `find`, `find-if`, `position`, `count`, `every`, `some`, `notany`, `notevery`, `remove`, `remove-if`, `subst`
- [x] List ops — `assoc`, `getf`, `nth`, `last`, `butlast`, `nthcdr`, `tailp`, `ldiff`
- [x] String ops — `string=`, `string-upcase`, `string-downcase`, `subseq`, `concatenate`
- [x] FORMAT — basic directives `~A`, `~S`, `~D`, `~F`, `~%`, `~&`, `~T`, `~{...~}` (iteration), `~[...~]` (conditional), `~^` (escape), `~P` (plural)
- [x] Drive corpus to 200+ green
## SX primitive baseline
@@ -124,6 +124,14 @@ data; format for string templating.
_Newest first._
- 2026-05-05: Phase 5 set-macro-character — cl-reader-macros + cl-dispatch-macros global dicts; SET-MACRO-CHARACTER/GET-MACRO-CHARACTER/SET-DISPATCH-MACRO-CHARACTER dispatch in eval.sx (stores fn, doesn't wire into reader — stubs sufficient to avoid errors). Phase 5 fully ticked. Phase 6 Drive corpus 200+ ticked (518 total, 54 stdlib). All roadmap items done.
- 2026-05-05: Phase 6 packages — defpackage/in-package/export/use-package/import/find-package/package-name; cl-packages dict, cl-current-package; cl-package-sep? strips pkg: prefix from symbols+functions; package-qualified calls (cl:car, cl:mapcar) work. 4 package tests added; 518 total tests, 0 failed.
- 2026-05-05: Phase 6 FORMAT — cl-fmt-a/cl-fmt-s/cl-fmt-find-close/cl-fmt-iterate/cl-fmt-loop in eval.sx; ~A/~S/~D/~F/~%/~&/~T/~P/~{...~}/~[...~]/~^/~~; also fixed substr(start,length) semantics throughout (SUBSEQ, cl-fmt-loop); 6 FORMAT tests added to stdlib.sx; 514 total tests, 0 failed.
- 2026-05-05: Phase 6 stdlib — sequence functions (mapc/mapcan/reduce/find/find-if/find-if-not/position/position-if/count/count-if/every/some/notany/notevery/remove/remove-if/remove-if-not/subst/member), list ops (assoc/rassoc/getf/last/butlast/nthcdr/copy-list/list*/caar/cadr/cdar/cddr/caddr/cadddr/pairlis), string ops (subseq/string/char/string-length/string</>), plus coerce/make-list/write-to-string; 44 tests in tests/stdlib.sx; Phase 6 sequence+list+string boxes ticked. Total: 508 tests, 0 failed.
- 2026-05-05: Phase 4 CLOS fully complete — `lib/common-lisp/clos.sx` (27 forms): clos-class-registry (8 built-in classes), defclass/make-instance/slot-value/slot-boundp/set-slot-value!/find-class/change-class, defgeneric/defmethod with :before/:after/:around, clos-call-generic (standard method combination: sort by specificity, fire befores, call primary chain, fire afters in reverse), call-next-method/next-method-p, with-slots, accessor installation; 41 tests in `tests/clos.sx`; classic programs `geometry.sx` (12 tests, multi-dispatch intersect on P/L/Plane) and `mop-trace.sx` (13 tests, :before/:after tracing). Dynamic variables in eval.sx: cl-apply-dyn saves/restores global bindings around let for specials (cl-mark-special!/cl-special?/cl-dyn-unbound). Key gotchas: qualifier strings are "before"/"after"/"around" (no colon); dict-set pure = assoc; dict->list = (map (fn (k) (list k (get d k))) (keys d)); clos-add-reader-method bootstrapped via set! after defmethod defined; test isolation: use unique var names to avoid *y* collision. 437 total tests, 0 failed.
- 2026-05-05: Phase 3 fully complete — conformance.sh runner + scoreboard.json/scoreboard.md; 363 total tests across all suites (79 reader, 31 parser, 174 eval, 59 conditions, 7+6+7 classic programs).
- 2026-05-05: Phase 3 complete — cl-debugger-hook/cl-invoke-debugger in runtime.sx (cl-error routes through hook), cl-break-on-signals (fires hook before handlers on type match), cl-invoke-restart-interactively (calls fn with no args); 4 new tests (147 total). Phase 3 all boxes ticked.