defmacro/macrolet/symbol-macrolet/macroexpand, gensym/gentemp, full
LOOP macro (loop.sx) with all clause types. Phase 2 dynamic variables:
cl-apply-dyn, cl-letstar-bind, cl-mark-special!/cl-special? for
defvar/defparameter specials with let-based dynamic rebinding.
27 macro+LOOP tests; 182 eval tests (8 new dynamic var tests).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
cl-debugger-hook: mutable global (fn (c hook) result); cl-invoke-debugger
calls it with infinite-recursion guard (sets hook nil during call).
cl-error now routes unhandled errors through cl-invoke-debugger instead of
bare host error — allows the hook to invoke a restart and resume.
cl-break-on-signals: when set to a type name, cl-signal fires the debugger
hook before walking handlers if the condition matches.
cl-invoke-restart-interactively: calls the restart fn with no args (no
terminal protocol — equivalent to (invoke-restart name)).
4 new tests in conditions.sx covering all three; Phase 3 fully complete.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
cl-debugger-hook global (nil = default), cl-invoke-debugger walks the hook,
cl-error-with-debugger routes unhandled errors through the hook, and
make-policy-debugger builds a hook from a (fn (condition restarts) name)
policy function. Tests: hook receives condition, policy selects use-zero/abort
restarts, compute-restarts visible inside hook, handler wins before hook fires,
infinite-recursion guard. Wired into test.sh program suite runner.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
restart-demo.sx: safe-divide with division-by-zero condition, use-zero
and retry restarts. Demonstrates handler-bind invoking a restart to
resume computation with a corrected value.
parse-recover.sx: token parser signalling parse-error on non-integer
tokens, skip-token and use-zero restarts. Demonstrates recovery-via-
restart and handler-case abort patterns.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
define-condition with 15-type ANSI hierarchy (condition/error/warning/
simple-error/simple-warning/type-error/arithmetic-error/division-by-zero/
cell-error/unbound-variable/undefined-function/program-error/storage-condition).
cl-condition-of-type? walks the hierarchy; cl-make-condition builds tagged
dicts {:cl-type "cl-condition" :class name :slots {...}}. cl-signal-obj
walks cl-handler-stack for non-unwinding dispatch. cl-handler-case and
cl-restart-case use call/cc escape continuations for unwinding. All stacks
are mutable SX globals (the built-in handler-bind/restart-case only accept
literal AST specs — not computed lists). Key fix: cl-condition-of-type?
captures cl-condition-classes at define-time via let-closure to avoid
free-variable failure through env_merge parent chain.
55 tests in lib/common-lisp/tests/conditions.sx, wired into test.sh.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
lib/js/stdlib.sx (36 forms):
- Bitwise ops (js-bitand/bitor/bitxor/lshift/rshift/urshift/bitnot) use
truncate instead of js-num-to-int (which calls integer /0 and crashes).
- Map class: dict-backed list-of-pairs with linear-scan find, mutable via
dict-set!; js-map-new/get/set!/has/delete!/clear/keys/vals/entries/for-each.
- Set class: backed by SX make-set primitive; set-member?/set-add!/set-remove!
all take (set item) argument order — fixed from (item set) which threw.
- RegExp: callable lambda wrapping js-regex-new (not a dict, so directly callable).
- Wires Map/Set/RegExp into js-global.
lib/js/test.sh: epochs 6000-6032 (25 tests) — all pass.
Result: 492/585 tests pass (was 466/560 before this phase).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
math: abs/ceil/floor/sqrt/sin/cos/tan/asin/acos/atan/exp/log/max/min/pi/huge
string: len/sub/upper/lower/rep/reverse/byte/char/find/match/gmatch/gsub
table: insert/remove/concat/sort
lua-force: force promises (delay thunk protocol)
Fix lua-len: replace has? (unavailable in sx_server) with nil-check.
Fix string.byte: use string->list to get char type, not nth on string.
Fix string.char: truncate float codes before integer->char.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Type predicates, arithmetic, chars (inline α/digit/case),
format, gensym, values, sets, radix, list utilities.
cl-empty? guards all list traversal against () vs nil in sx_server.
Load spec/stdlib.sx in test.sh to expose format.
Fix lib/r7rs.sx number->string to use (= (len r) 0) not (nil? r).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
28 tests, passes on both JS and OCaml.
- spec/stdlib.sx: pure SX format function
- spec/primitives.sx: format primitive declaration
- lib/r7rs.sx: fix number->string to support optional radix arg
- hosts/ocaml: add format-decimal primitive, load stdlib.sx in test runner
- hosts/javascript: load stdlib.sx in test runner
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds 9 regexp primitives to stdlib.regexp. OCaml: SxRegexp(src,flags,Re.re)
using Re.Pcre; $&/$1 capture expansion in replace. JS: native RegExp
with SxRegexp wrapper; regexp-match returns {:match :start :end :groups}.
32 tests in test-regexp.sx, all pass on both hosts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds 13 set primitives to stdlib.sets. OCaml: SxSet as (string,value)
Hashtbl keyed by inspect(val); JS: SxSet wrapping Map keyed by
write-to-string. Structural equality — (make-set '(1 2)) contains 1.
Includes union, intersection, difference, for-each, map.
33 tests in test-sets.sx, all pass on both JS and OCaml.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds read, write, display, newline, write-to-string, display-to-string
and current-*-port primitives to both JS and OCaml hosts.
JS: sxReadNormalize (#t/#f→true/false), sxReadConvert (()→nil),
sxEq array comparison, sxWriteVal symbol/keyword name fix,
readerMacroGet/readerMacroSet registry in parser platform.
OCaml: sx_write_val/sx_display_val helpers, read/write/display/newline
primitives on port types; parser extended for #t/#f and N/D rationals.
42 new tests (test-read-write.sx), all passing on JS and OCaml.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
SxRational type in OCaml (Rational of int * int, stored reduced, denom>0)
and JS (SxRational class with _rational marker). n/d reader syntax in
spec/parser.sx. Arithmetic contagion: int op rational → rational, rational
op float → float. JS keeps int/int → float for CSS backward compatibility.
OCaml as_number + safe_eq extended for cross-type rational equality so
(= 2.5 5/2) → true. 62 tests in test-rationals.sx, all pass.
JS: 2232 passed. OCaml: 4532 passed (+11 vs pre-fix baseline).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eof sentinel and Port{PortInput/PortOutput} in sx_types.ml. All 15 port
primitives in sx_primitives.ml. type_of/inspect updated. 39/39 port tests
pass (4532 total, +39, zero regressions).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>