diff --git a/wasm/app/index.html b/wasm/app/index.html
index 648ccef..c2bc429 100644
--- a/wasm/app/index.html
+++ b/wasm/app/index.html
@@ -71,11 +71,11 @@
- asm tier note: the wat implementation ships a minimal lisp
- subset (special forms, arithmetic, list ops, recursion) — enough for the
- four demos above. symbol lookup is linear; would be moad-0001 at scale,
- documented in asm/lumbda.wat.
- see lumbda.
+ same lisp source, three implementations — verify yourself by
+ eyeballing each tier's output, or by running
+ make wasm-test against the cross-tier suite.
+ asm tier (asm/lumbda.wat) is growing toward feature parity
+ with asm/lumbda.s; see lumbda.
diff --git a/wasm/app/repl.html b/wasm/app/repl.html
new file mode 100644
index 0000000..c0f2935
--- /dev/null
+++ b/wasm/app/repl.html
@@ -0,0 +1,60 @@
+
+
+
+
+
+lumbda repl — three tiers, one prompt
+
+
+
+
+
+
+
+ lumbda.
+
+ three tiers, one prompt
+
+ interactive repl. type a lisp expression at the prompt below; each tier
+ keeps its own persistent state across evals. compare side by side,
+ measure tier-by-tier elapsed, share a link to the
+ playground for static demos.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/wasm/asm/lumbda.wat b/wasm/asm/lumbda.wat
index fb841ea..fa136b3 100644
--- a/wasm/asm/lumbda.wat
+++ b/wasm/asm/lumbda.wat
@@ -61,6 +61,12 @@
(global $sym_and (mut i32) (i32.const 0))
(global $sym_or (mut i32) (i32.const 0))
(global $sym_set (mut i32) (i32.const 0))
+ (global $sym_letstar (mut i32) (i32.const 0))
+ (global $sym_letrec (mut i32) (i32.const 0))
+ (global $sym_when (mut i32) (i32.const 0))
+ (global $sym_unless (mut i32) (i32.const 0))
+ (global $sym_case (mut i32) (i32.const 0))
+ (global $sym_do (mut i32) (i32.const 0))
;; Constants (immediate value addresses)
(global $NIL i32 (i32.const 4))
@@ -689,6 +695,21 @@
(if (i32.eq (local.get $head) (global.get $sym_or))
(then (return (call $eval_or (local.get $rest) (local.get $env)))))
+ (if (i32.eq (local.get $head) (global.get $sym_letstar))
+ (then (return (call $eval_letstar (local.get $rest) (local.get $env)))))
+
+ (if (i32.eq (local.get $head) (global.get $sym_letrec))
+ (then (return (call $eval_letrec (local.get $rest) (local.get $env)))))
+
+ (if (i32.eq (local.get $head) (global.get $sym_when))
+ (then (return (call $eval_when (local.get $rest) (local.get $env)))))
+
+ (if (i32.eq (local.get $head) (global.get $sym_unless))
+ (then (return (call $eval_unless (local.get $rest) (local.get $env)))))
+
+ (if (i32.eq (local.get $head) (global.get $sym_case))
+ (then (return (call $eval_case (local.get $rest) (local.get $env)))))
+
;; Function application
(return (call $apply
(call $eval (local.get $head) (local.get $env))
@@ -752,8 +773,99 @@
(br $loop)))
(global.get $VOID))
- ;; (let ((x e) ...) body) — non-recursive form
+ ;; (let ((x e) ...) body) — non-recursive form (also supports named let)
+ ;; Named let: (let name ((x e) ...) body) -> a self-referential procedure.
(func $eval_let (param $rest i32) (param $env i32) (result i32)
+ (local $first i32)
+ (local $bindings i32)
+ (local $body i32)
+ (local $new_env i32)
+ (local $b i32)
+ (local $sym i32)
+ (local $val i32)
+ (local $name i32)
+ (local $params i32)
+ (local $args i32)
+ (local $closure i32)
+ (local.set $first (call $car (local.get $rest)))
+ ;; Named let if first arg is a symbol.
+ (if (call $is_symbol (local.get $first))
+ (then
+ (local.set $name (local.get $first))
+ (local.set $bindings (call $car (call $cdr (local.get $rest))))
+ (local.set $body (call $cdr (call $cdr (local.get $rest))))
+ ;; Build params list (the binding names) and args list (their inits).
+ (local.set $params (call $extract_let_params (local.get $bindings)))
+ (local.set $args (call $extract_let_inits (local.get $bindings) (local.get $env)))
+ ;; Create closure capturing CURRENT env (so the body can refer to name).
+ (local.set $closure (call $make_closure (local.get $params) (local.get $body) (local.get $env)))
+ ;; Bind the closure to name in a fresh local env and patch its env to include itself.
+ (local.set $new_env (call $env_define (local.get $env) (local.get $name) (local.get $closure)))
+ ;; Patch closure.env to the new env so name resolves to it.
+ (i32.store offset=12 (local.get $closure) (local.get $new_env))
+ (return (call $apply (local.get $closure) (local.get $args)))))
+ (local.set $bindings (local.get $first))
+ (local.set $body (call $cdr (local.get $rest)))
+ (local.set $new_env (local.get $env))
+ (block $done
+ (loop $loop
+ (br_if $done (i32.eq (local.get $bindings) (global.get $NIL)))
+ (local.set $b (call $car (local.get $bindings)))
+ (local.set $sym (call $car (local.get $b)))
+ (local.set $val (call $eval (call $car (call $cdr (local.get $b))) (local.get $env)))
+ (local.set $new_env (call $env_define (local.get $new_env) (local.get $sym) (local.get $val)))
+ (local.set $bindings (call $cdr (local.get $bindings)))
+ (br $loop)))
+ (call $eval_begin (local.get $body) (local.get $new_env)))
+
+ ;; Helper: build params list from let bindings (a list of (sym init)).
+ (func $extract_let_params (param $bindings i32) (result i32)
+ (local $head i32)
+ (local $tail i32)
+ (local $new i32)
+ (local.set $head (global.get $NIL))
+ (local.set $tail (global.get $NIL))
+ (block $done
+ (loop $l
+ (br_if $done (i32.eq (local.get $bindings) (global.get $NIL)))
+ (local.set $new
+ (call $make_pair
+ (call $car (call $car (local.get $bindings)))
+ (global.get $NIL)))
+ (if (i32.eq (local.get $head) (global.get $NIL))
+ (then (local.set $head (local.get $new))
+ (local.set $tail (local.get $new)))
+ (else (call $set_cdr (local.get $tail) (local.get $new))
+ (local.set $tail (local.get $new))))
+ (local.set $bindings (call $cdr (local.get $bindings)))
+ (br $l)))
+ (local.get $head))
+
+ ;; Helper: evaluate each init expression and return the resulting list.
+ (func $extract_let_inits (param $bindings i32) (param $env i32) (result i32)
+ (local $head i32)
+ (local $tail i32)
+ (local $new i32)
+ (local.set $head (global.get $NIL))
+ (local.set $tail (global.get $NIL))
+ (block $done
+ (loop $l
+ (br_if $done (i32.eq (local.get $bindings) (global.get $NIL)))
+ (local.set $new
+ (call $make_pair
+ (call $eval (call $car (call $cdr (call $car (local.get $bindings)))) (local.get $env))
+ (global.get $NIL)))
+ (if (i32.eq (local.get $head) (global.get $NIL))
+ (then (local.set $head (local.get $new))
+ (local.set $tail (local.get $new)))
+ (else (call $set_cdr (local.get $tail) (local.get $new))
+ (local.set $tail (local.get $new))))
+ (local.set $bindings (call $cdr (local.get $bindings)))
+ (br $l)))
+ (local.get $head))
+
+ ;; (let* ((x e1) (y e2)) body) — each binding sees previous bindings.
+ (func $eval_letstar (param $rest i32) (param $env i32) (result i32)
(local $bindings i32)
(local $body i32)
(local $new_env i32)
@@ -768,12 +880,94 @@
(br_if $done (i32.eq (local.get $bindings) (global.get $NIL)))
(local.set $b (call $car (local.get $bindings)))
(local.set $sym (call $car (local.get $b)))
- (local.set $val (call $eval (call $car (call $cdr (local.get $b))) (local.get $env)))
+ (local.set $val (call $eval (call $car (call $cdr (local.get $b))) (local.get $new_env)))
(local.set $new_env (call $env_define (local.get $new_env) (local.get $sym) (local.get $val)))
(local.set $bindings (call $cdr (local.get $bindings)))
(br $loop)))
(call $eval_begin (local.get $body) (local.get $new_env)))
+ ;; (letrec ((f (lambda ...))) body) — each binding visible to all others.
+ ;; First create the env with placeholder bindings, then evaluate inits in
+ ;; the new env (so lambdas capture it), then patch values.
+ (func $eval_letrec (param $rest i32) (param $env i32) (result i32)
+ (local $bindings i32)
+ (local $body i32)
+ (local $new_env i32)
+ (local $b i32)
+ (local $sym i32)
+ (local $val i32)
+ (local $cur i32)
+ (local $bind i32)
+ (local.set $bindings (call $car (local.get $rest)))
+ (local.set $body (call $cdr (local.get $rest)))
+ (local.set $new_env (local.get $env))
+ (local.set $cur (local.get $bindings))
+ ;; Pass 1: bind every name to VOID in the new env.
+ (block $done1
+ (loop $l1
+ (br_if $done1 (i32.eq (local.get $cur) (global.get $NIL)))
+ (local.set $b (call $car (local.get $cur)))
+ (local.set $sym (call $car (local.get $b)))
+ (local.set $new_env (call $env_define (local.get $new_env) (local.get $sym) (global.get $VOID)))
+ (local.set $cur (call $cdr (local.get $cur)))
+ (br $l1)))
+ ;; Pass 2: evaluate each init in new_env (so lambdas see each other),
+ ;; mutate the binding pair's cdr to the real value.
+ (local.set $cur (local.get $bindings))
+ (block $done2
+ (loop $l2
+ (br_if $done2 (i32.eq (local.get $cur) (global.get $NIL)))
+ (local.set $b (call $car (local.get $cur)))
+ (local.set $sym (call $car (local.get $b)))
+ (local.set $val (call $eval (call $car (call $cdr (local.get $b))) (local.get $new_env)))
+ (call $env_set (local.get $new_env) (local.get $sym) (local.get $val))
+ (local.set $cur (call $cdr (local.get $cur)))
+ (br $l2)))
+ (call $eval_begin (local.get $body) (local.get $new_env)))
+
+ ;; (when test body...) -> if test true, eval body, else void
+ (func $eval_when (param $rest i32) (param $env i32) (result i32)
+ (if (i32.ne (call $eval (call $car (local.get $rest)) (local.get $env)) (global.get $FALSE))
+ (then (return (call $eval_begin (call $cdr (local.get $rest)) (local.get $env)))))
+ (global.get $VOID))
+
+ ;; (unless test body...) -> if test false, eval body, else void
+ (func $eval_unless (param $rest i32) (param $env i32) (result i32)
+ (if (i32.eq (call $eval (call $car (local.get $rest)) (local.get $env)) (global.get $FALSE))
+ (then (return (call $eval_begin (call $cdr (local.get $rest)) (local.get $env)))))
+ (global.get $VOID))
+
+ ;; (case key ((1 2) "small") ((3 4) "med") (else "big"))
+ ;; Each clause: (datum-list body) where datum-list is either a list of
+ ;; literals to match via eqv?, or the symbol `else`.
+ (func $eval_case (param $rest i32) (param $env i32) (result i32)
+ (local $key i32)
+ (local $clauses i32)
+ (local $clause i32)
+ (local $data i32)
+ (local $body i32)
+ (local.set $key (call $eval (call $car (local.get $rest)) (local.get $env)))
+ (local.set $clauses (call $cdr (local.get $rest)))
+ (block $done
+ (loop $l
+ (br_if $done (i32.eq (local.get $clauses) (global.get $NIL)))
+ (local.set $clause (call $car (local.get $clauses)))
+ (local.set $data (call $car (local.get $clause)))
+ (local.set $body (call $cdr (local.get $clause)))
+ (if (i32.eq (local.get $data) (global.get $sym_else))
+ (then (return (call $eval_begin (local.get $body) (local.get $env)))))
+ ;; data is a list of literal values; match via eqv?
+ (block $clausedone
+ (loop $datal
+ (br_if $clausedone (i32.eq (local.get $data) (global.get $NIL)))
+ (if (i32.eq (call $car (local.get $data)) (local.get $key))
+ (then (return (call $eval_begin (local.get $body) (local.get $env)))))
+ (local.set $data (call $cdr (local.get $data)))
+ (br $datal)))
+ (local.set $clauses (call $cdr (local.get $clauses)))
+ (br $l)))
+ (global.get $VOID))
+
(func $eval_and (param $rest i32) (param $env i32) (result i32)
(local $val i32)
(local.set $val (global.get $TRUE))
@@ -865,12 +1059,128 @@
(br $loop)))
(local.get $new))
+ ;; ─── Equality helpers ──────────────────────────────────────────
+ ;; Deep structural equality. Returns TRUE/FALSE immediate.
+ (func $equal_p (param $a i32) (param $b i32) (result i32)
+ (local $alen i32)
+ (local $blen i32)
+ (local $i i32)
+ (if (i32.eq (local.get $a) (local.get $b))
+ (then (return (global.get $TRUE))))
+ (if (call $is_pair (local.get $a))
+ (then
+ (if (i32.eqz (call $is_pair (local.get $b)))
+ (then (return (global.get $FALSE))))
+ (if (i32.eq (call $equal_p (call $car (local.get $a)) (call $car (local.get $b))) (global.get $FALSE))
+ (then (return (global.get $FALSE))))
+ (return (call $equal_p (call $cdr (local.get $a)) (call $cdr (local.get $b))))))
+ (if (call $is_string (local.get $a))
+ (then
+ (if (i32.eqz (call $is_string (local.get $b)))
+ (then (return (global.get $FALSE))))
+ (local.set $alen (i32.load offset=4 (local.get $a)))
+ (local.set $blen (i32.load offset=4 (local.get $b)))
+ (if (i32.ne (local.get $alen) (local.get $blen))
+ (then (return (global.get $FALSE))))
+ (local.set $i (i32.const 0))
+ (block $done
+ (loop $l
+ (br_if $done (i32.ge_u (local.get $i) (local.get $alen)))
+ (if (i32.ne
+ (i32.load8_u (i32.add (i32.add (local.get $a) (i32.const 8)) (local.get $i)))
+ (i32.load8_u (i32.add (i32.add (local.get $b) (i32.const 8)) (local.get $i))))
+ (then (return (global.get $FALSE))))
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
+ (br $l)))
+ (return (global.get $TRUE))))
+ ;; Fall through: not equal (different types / not pair)
+ (global.get $FALSE))
+
+ ;; (append a b) returns a new list with b appended after a.
+ (func $append2 (param $a i32) (param $b i32) (result i32)
+ (local $head i32)
+ (local $tail i32)
+ (local $new i32)
+ (local $cur i32)
+ (local.set $head (global.get $NIL))
+ (local.set $tail (global.get $NIL))
+ (local.set $cur (local.get $a))
+ (block $done
+ (loop $l
+ (br_if $done (i32.eqz (call $is_pair (local.get $cur))))
+ (local.set $new (call $make_pair (call $car (local.get $cur)) (global.get $NIL)))
+ (if (i32.eq (local.get $head) (global.get $NIL))
+ (then (local.set $head (local.get $new))
+ (local.set $tail (local.get $new)))
+ (else (call $set_cdr (local.get $tail) (local.get $new))
+ (local.set $tail (local.get $new))))
+ (local.set $cur (call $cdr (local.get $cur)))
+ (br $l)))
+ (if (i32.eq (local.get $head) (global.get $NIL))
+ (then (return (local.get $b))))
+ (call $set_cdr (local.get $tail) (local.get $b))
+ (local.get $head))
+
+ ;; (member key list) — use_equal=1 → equal?; else eq?
+ (func $member_eq (param $key i32) (param $list i32) (param $use_equal i32) (result i32)
+ (local $cur i32)
+ (local $match i32)
+ (local.set $cur (local.get $list))
+ (block $done
+ (loop $l
+ (br_if $done (i32.eqz (call $is_pair (local.get $cur))))
+ (if (local.get $use_equal)
+ (then
+ (local.set $match
+ (i32.eq (call $equal_p (local.get $key) (call $car (local.get $cur))) (global.get $TRUE))))
+ (else
+ (local.set $match
+ (i32.eq (local.get $key) (call $car (local.get $cur))))))
+ (if (local.get $match)
+ (then (return (local.get $cur))))
+ (local.set $cur (call $cdr (local.get $cur)))
+ (br $l)))
+ (global.get $FALSE))
+
+ ;; (assoc key alist) — use_equal=1 → equal? on car; else eq?
+ (func $assoc_eq (param $key i32) (param $alist i32) (param $use_equal i32) (result i32)
+ (local $cur i32)
+ (local $pair i32)
+ (local $match i32)
+ (local.set $cur (local.get $alist))
+ (block $done
+ (loop $l
+ (br_if $done (i32.eqz (call $is_pair (local.get $cur))))
+ (local.set $pair (call $car (local.get $cur)))
+ (if (call $is_pair (local.get $pair))
+ (then
+ (if (local.get $use_equal)
+ (then
+ (local.set $match
+ (i32.eq (call $equal_p (local.get $key) (call $car (local.get $pair))) (global.get $TRUE))))
+ (else
+ (local.set $match
+ (i32.eq (local.get $key) (call $car (local.get $pair))))))
+ (if (local.get $match)
+ (then (return (local.get $pair))))))
+ (local.set $cur (call $cdr (local.get $cur)))
+ (br $l)))
+ (global.get $FALSE))
+
;; ─── Primitives ────────────────────────────────────────────────
(func $apply_primitive (param $id i32) (param $args i32) (result i32)
(local $a i32)
(local $b i32)
(local $sum i32)
(local $cur i32)
+ (local $vmin i32)
+ (local $vmax i32)
+ (local $base i32)
+ (local $exp_n i32)
+ (local $result i32)
+ (local $rev i32)
+ (local $idx i32)
+ (local $idxt i32)
;; Fetch first 2 args (most prims use 1 or 2). Defaults to fixnum 0.
(local.set $a (call $make_fixnum (i32.const 0)))
@@ -1059,6 +1369,255 @@
(then (return (global.get $TRUE)))
(else (return (global.get $FALSE))))))
+ ;; quotient (25): integer truncating division
+ (if (i32.eq (local.get $id) (i32.const 25))
+ (then
+ (return (call $make_fixnum (i32.div_s (call $fixnum_val (local.get $a))
+ (call $fixnum_val (local.get $b)))))))
+
+ ;; remainder (26): integer remainder (sign of dividend)
+ (if (i32.eq (local.get $id) (i32.const 26))
+ (then
+ (return (call $make_fixnum (i32.rem_s (call $fixnum_val (local.get $a))
+ (call $fixnum_val (local.get $b)))))))
+
+ ;; min (27): variadic
+ (if (i32.eq (local.get $id) (i32.const 27))
+ (then
+ (local.set $sum (call $fixnum_val (local.get $a)))
+ (local.set $cur (call $cdr (local.get $args)))
+ (block $done
+ (loop $loop
+ (br_if $done (i32.eq (local.get $cur) (global.get $NIL)))
+ (local.set $vmin (call $fixnum_val (call $car (local.get $cur))))
+ (if (i32.lt_s (local.get $vmin) (local.get $sum))
+ (then (local.set $sum (local.get $vmin))))
+ (local.set $cur (call $cdr (local.get $cur)))
+ (br $loop)))
+ (return (call $make_fixnum (local.get $sum)))))
+
+ ;; max (28): variadic
+ (if (i32.eq (local.get $id) (i32.const 28))
+ (then
+ (local.set $sum (call $fixnum_val (local.get $a)))
+ (local.set $cur (call $cdr (local.get $args)))
+ (block $done
+ (loop $loop
+ (br_if $done (i32.eq (local.get $cur) (global.get $NIL)))
+ (local.set $vmax (call $fixnum_val (call $car (local.get $cur))))
+ (if (i32.gt_s (local.get $vmax) (local.get $sum))
+ (then (local.set $sum (local.get $vmax))))
+ (local.set $cur (call $cdr (local.get $cur)))
+ (br $loop)))
+ (return (call $make_fixnum (local.get $sum)))))
+
+ ;; expt (29): integer exponent (positive)
+ (if (i32.eq (local.get $id) (i32.const 29))
+ (then
+ (local.set $base (call $fixnum_val (local.get $a)))
+ (local.set $exp_n (call $fixnum_val (local.get $b)))
+ (local.set $result (i32.const 1))
+ (block $done
+ (loop $loop
+ (br_if $done (i32.le_s (local.get $exp_n) (i32.const 0)))
+ (local.set $result (i32.mul (local.get $result) (local.get $base)))
+ (local.set $exp_n (i32.sub (local.get $exp_n) (i32.const 1)))
+ (br $loop)))
+ (return (call $make_fixnum (local.get $result)))))
+
+ ;; even? (30)
+ (if (i32.eq (local.get $id) (i32.const 30))
+ (then
+ (if (i32.eqz (i32.and (call $fixnum_val (local.get $a)) (i32.const 1)))
+ (then (return (global.get $TRUE)))
+ (else (return (global.get $FALSE))))))
+
+ ;; odd? (31)
+ (if (i32.eq (local.get $id) (i32.const 31))
+ (then
+ (if (i32.and (call $fixnum_val (local.get $a)) (i32.const 1))
+ (then (return (global.get $TRUE)))
+ (else (return (global.get $FALSE))))))
+
+ ;; positive? (32)
+ (if (i32.eq (local.get $id) (i32.const 32))
+ (then
+ (if (i32.gt_s (call $fixnum_val (local.get $a)) (i32.const 0))
+ (then (return (global.get $TRUE)))
+ (else (return (global.get $FALSE))))))
+
+ ;; negative? (33)
+ (if (i32.eq (local.get $id) (i32.const 33))
+ (then
+ (if (i32.lt_s (call $fixnum_val (local.get $a)) (i32.const 0))
+ (then (return (global.get $TRUE)))
+ (else (return (global.get $FALSE))))))
+
+ ;; set-car! (34)
+ (if (i32.eq (local.get $id) (i32.const 34))
+ (then
+ (call $set_car (local.get $a) (local.get $b))
+ (return (global.get $VOID))))
+
+ ;; set-cdr! (35)
+ (if (i32.eq (local.get $id) (i32.const 35))
+ (then
+ (call $set_cdr (local.get $a) (local.get $b))
+ (return (global.get $VOID))))
+
+ ;; equal? (36): deep structural equality
+ (if (i32.eq (local.get $id) (i32.const 36))
+ (then (return (call $equal_p (local.get $a) (local.get $b)))))
+
+ ;; eqv? (37): identity for our value types
+ (if (i32.eq (local.get $id) (i32.const 37))
+ (then
+ (if (i32.eq (local.get $a) (local.get $b))
+ (then (return (global.get $TRUE)))
+ (else (return (global.get $FALSE))))))
+
+ ;; number? (38) — fixnums only in this tier
+ (if (i32.eq (local.get $id) (i32.const 38))
+ (then
+ (if (call $is_fixnum (local.get $a))
+ (then (return (global.get $TRUE)))
+ (else (return (global.get $FALSE))))))
+
+ ;; integer? (39) — same as number? here
+ (if (i32.eq (local.get $id) (i32.const 39))
+ (then
+ (if (call $is_fixnum (local.get $a))
+ (then (return (global.get $TRUE)))
+ (else (return (global.get $FALSE))))))
+
+ ;; symbol? (40)
+ (if (i32.eq (local.get $id) (i32.const 40))
+ (then
+ (if (call $is_symbol (local.get $a))
+ (then (return (global.get $TRUE)))
+ (else (return (global.get $FALSE))))))
+
+ ;; string? (41)
+ (if (i32.eq (local.get $id) (i32.const 41))
+ (then
+ (if (call $is_string (local.get $a))
+ (then (return (global.get $TRUE)))
+ (else (return (global.get $FALSE))))))
+
+ ;; procedure? (42) — closure or primitive
+ (if (i32.eq (local.get $id) (i32.const 42))
+ (then
+ (if (i32.or (call $is_closure (local.get $a)) (call $is_primitive (local.get $a)))
+ (then (return (global.get $TRUE)))
+ (else (return (global.get $FALSE))))))
+
+ ;; boolean? (43)
+ (if (i32.eq (local.get $id) (i32.const 43))
+ (then
+ (if (i32.or (i32.eq (local.get $a) (global.get $TRUE))
+ (i32.eq (local.get $a) (global.get $FALSE)))
+ (then (return (global.get $TRUE)))
+ (else (return (global.get $FALSE))))))
+
+ ;; caar (44)
+ (if (i32.eq (local.get $id) (i32.const 44))
+ (then (return (call $car (call $car (local.get $a))))))
+
+ ;; cadr (45)
+ (if (i32.eq (local.get $id) (i32.const 45))
+ (then (return (call $car (call $cdr (local.get $a))))))
+
+ ;; cdar (46)
+ (if (i32.eq (local.get $id) (i32.const 46))
+ (then (return (call $cdr (call $car (local.get $a))))))
+
+ ;; cddr (47)
+ (if (i32.eq (local.get $id) (i32.const 47))
+ (then (return (call $cdr (call $cdr (local.get $a))))))
+
+ ;; caddr (48)
+ (if (i32.eq (local.get $id) (i32.const 48))
+ (then (return (call $car (call $cdr (call $cdr (local.get $a)))))))
+
+ ;; cadddr (49)
+ (if (i32.eq (local.get $id) (i32.const 49))
+ (then (return (call $car (call $cdr (call $cdr (call $cdr (local.get $a))))))))
+
+ ;; reverse (50)
+ (if (i32.eq (local.get $id) (i32.const 50))
+ (then
+ (local.set $rev (global.get $NIL))
+ (local.set $cur (local.get $a))
+ (block $done
+ (loop $loop
+ (br_if $done (i32.eqz (call $is_pair (local.get $cur))))
+ (local.set $rev (call $make_pair (call $car (local.get $cur)) (local.get $rev)))
+ (local.set $cur (call $cdr (local.get $cur)))
+ (br $loop)))
+ (return (local.get $rev))))
+
+ ;; append (51) — 2-arg
+ (if (i32.eq (local.get $id) (i32.const 51))
+ (then (return (call $append2 (local.get $a) (local.get $b)))))
+
+ ;; apply (52) — (apply f args)
+ (if (i32.eq (local.get $id) (i32.const 52))
+ (then (return (call $apply (local.get $a) (local.get $b)))))
+
+ ;; error (53) — emit message + raise (we just write to output for now)
+ (if (i32.eq (local.get $id) (i32.const 53))
+ (then
+ (call $out_str (i32.const 0xF030) (i32.const 6)) ;; "
- asm tier note: the wat implementation ships a minimal lisp
- subset (special forms, arithmetic, list ops, recursion) — enough for the
- four demos above. symbol lookup is linear; would be moad-0001 at scale,
- documented in asm/lumbda.wat.
- see lumbda.
+ same lisp source, three implementations — verify yourself by
+ eyeballing each tier's output, or by running
+ make wasm-test against the cross-tier suite.
+ asm tier (asm/lumbda.wat) is growing toward feature parity
+ with asm/lumbda.s; see lumbda.