scheme: Phase 3 — if/define/set!/begin/lambda/closures + 24 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 28s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 28s
eval.sx grows: five new syntactic operators wired via the table-
driven dispatch from Phase 2. lambda creates closures
{:scm-tag :closure :params :rest :body :env} that capture the
static env; scheme-apply-closure binds formals + rest-arg, evaluates
multi-expression body in (extend static-env), returns last value.
Supports lambda formals shapes:
() → no args
(a b c) → fixed arity
args → bare symbol; binds all call-args as a list
Dotted-pair tail (a b . rest) deferred until parser supports it.
define has both flavours:
(define name expr) — direct binding
(define (name . formals) body...) — lambda sugar
set! walks the env chain via refl-env-find-frame, mutates at the
binding's source frame (no shadowing). Raises on unbound name.
24 new tests in lib/scheme/tests/syntax.sx, including:
- Factorial 5 → 120 and 10 → 3628800 (recursion + closures)
- make-counter via closed-over set! state
- Curried (((curry+ 1) 2) 3) → 6
- (lambda args args) rest-arg binding
- Multi-body lambdas with internal define
109 total Scheme tests (62 parse + 23 eval + 24 syntax).
This commit is contained in:
@@ -78,6 +78,126 @@
|
||||
(error "quote: expects exactly 1 argument"))
|
||||
(:else (first args)))))
|
||||
|
||||
;; if — (if TEST CONSEQUENT) or (if TEST CONSEQUENT ALTERNATE).
|
||||
;; Scheme truthiness: only #f is false; everything else (incl. nil/empty
|
||||
;; list) is truthy. Match SX's `if` semantics where possible.
|
||||
(scheme-define-op! "if"
|
||||
(fn (args env)
|
||||
(cond
|
||||
((< (length args) 2)
|
||||
(error "if: expects (test then [else])"))
|
||||
(:else
|
||||
(let ((test-val (scheme-eval (first args) env)))
|
||||
(cond
|
||||
((not (= test-val false))
|
||||
(scheme-eval (nth args 1) env))
|
||||
((>= (length args) 3)
|
||||
(scheme-eval (nth args 2) env))
|
||||
(:else nil)))))))
|
||||
|
||||
;; set! — mutate an existing binding by walking the env chain.
|
||||
(scheme-define-op! "set!"
|
||||
(fn (args env)
|
||||
(cond
|
||||
((not (= (length args) 2))
|
||||
(error "set!: expects (set! name expr)"))
|
||||
((not (string? (first args)))
|
||||
(error "set!: name must be a symbol"))
|
||||
(:else
|
||||
(let ((name (first args))
|
||||
(val (scheme-eval (nth args 1) env)))
|
||||
(let ((src (refl-env-find-frame env name)))
|
||||
(cond
|
||||
((nil? src)
|
||||
(error (str "set!: unbound variable: " name)))
|
||||
(:else
|
||||
(dict-set! (get src :bindings) name val)
|
||||
val))))))))
|
||||
|
||||
;; define — top-level or internal binding. (define name expr) or
|
||||
;; (define (name . formals) body...) the latter being lambda sugar.
|
||||
(scheme-define-op! "define"
|
||||
(fn (args env)
|
||||
(cond
|
||||
((< (length args) 2)
|
||||
(error "define: expects (define name expr) or (define (name . formals) body)"))
|
||||
((string? (first args))
|
||||
;; (define name expr)
|
||||
(let ((val (scheme-eval (nth args 1) env)))
|
||||
(scheme-env-bind! env (first args) val)
|
||||
val))
|
||||
((list? (first args))
|
||||
;; (define (name . formals) body...) — sugar
|
||||
(let ((header (first args))
|
||||
(body (rest args)))
|
||||
(cond
|
||||
((= (length header) 0)
|
||||
(error "define: malformed function header"))
|
||||
(:else
|
||||
(let ((name (first header))
|
||||
(formals (rest header)))
|
||||
(let ((closure (scheme-make-closure formals nil body env)))
|
||||
(scheme-env-bind! env name closure)
|
||||
closure))))))
|
||||
(:else (error "define: malformed form")))))
|
||||
|
||||
;; begin — evaluate each expression in sequence, return the last.
|
||||
(scheme-define-op! "begin"
|
||||
(fn (args env)
|
||||
(cond
|
||||
((or (nil? args) (= (length args) 0)) nil)
|
||||
(:else (scheme-eval-body args env)))))
|
||||
|
||||
(define scheme-eval-body
|
||||
(fn (forms env)
|
||||
(cond
|
||||
((= (length forms) 1) (scheme-eval (first forms) env))
|
||||
(:else
|
||||
(begin
|
||||
(scheme-eval (first forms) env)
|
||||
(scheme-eval-body (rest forms) env))))))
|
||||
|
||||
;; lambda — (lambda formals body...) where formals is one of:
|
||||
;; () — no args
|
||||
;; (a b c) — fixed-arity
|
||||
;; name — bare symbol; binds all args as a list
|
||||
;; Dotted-pair tail (a b . rest) deferred until parser support lands.
|
||||
(scheme-define-op! "lambda"
|
||||
(fn (args env)
|
||||
(cond
|
||||
((< (length args) 2)
|
||||
(error "lambda: expects (lambda formals body...)"))
|
||||
(:else
|
||||
(let ((formals (first args))
|
||||
(body (rest args)))
|
||||
(cond
|
||||
;; bare symbol: collect-all-args
|
||||
((string? formals)
|
||||
(scheme-make-closure (list) formals body env))
|
||||
;; flat list: each must be a symbol
|
||||
((list? formals)
|
||||
(cond
|
||||
((not (scm-formals-ok? formals))
|
||||
(error "lambda: formals must be symbols"))
|
||||
(:else
|
||||
(scheme-make-closure formals nil body env))))
|
||||
(:else (error "lambda: invalid formals"))))))))
|
||||
|
||||
(define scm-formals-ok?
|
||||
(fn (formals)
|
||||
(cond
|
||||
((or (nil? formals) (= (length formals) 0)) true)
|
||||
((string? (first formals)) (scm-formals-ok? (rest formals)))
|
||||
(:else false))))
|
||||
|
||||
(define scheme-make-closure
|
||||
(fn (params rest-name body env)
|
||||
{:scm-tag :closure
|
||||
:params params
|
||||
:rest rest-name
|
||||
:body body
|
||||
:env env}))
|
||||
|
||||
;; ── eval-args helper ─────────────────────────────────────────────
|
||||
|
||||
(define
|
||||
@@ -131,10 +251,35 @@
|
||||
(scheme-apply-closure proc args))
|
||||
(:else (error (str "scheme-eval: not a procedure: " proc))))))
|
||||
|
||||
;; Stub for Phase 3 — closures land then.
|
||||
(define
|
||||
scheme-apply-closure
|
||||
(fn (proc args) (error "scheme-eval: closures land in Phase 3")))
|
||||
;; Apply a Scheme closure: bind formals + rest, eval body in
|
||||
;; (extend static-env), return value of last form.
|
||||
(define scheme-apply-closure
|
||||
(fn (proc args)
|
||||
(let ((local (scheme-extend-env (get proc :env)))
|
||||
(params (get proc :params))
|
||||
(rest-name (get proc :rest))
|
||||
(body (get proc :body)))
|
||||
(begin
|
||||
(scm-bind-params! local params args rest-name)
|
||||
(scheme-eval-body body local)))))
|
||||
|
||||
(define scm-bind-params!
|
||||
(fn (env params args rest-name)
|
||||
(cond
|
||||
;; No more formals: maybe bind the rest, else check arity.
|
||||
((or (nil? params) (= (length params) 0))
|
||||
(cond
|
||||
((not (nil? rest-name))
|
||||
(scheme-env-bind! env rest-name args))
|
||||
((or (nil? args) (= (length args) 0)) nil)
|
||||
(:else (error "lambda: too many arguments"))))
|
||||
;; Out of args but still have formals → arity error.
|
||||
((or (nil? args) (= (length args) 0))
|
||||
(error "lambda: too few arguments"))
|
||||
(:else
|
||||
(begin
|
||||
(scheme-env-bind! env (first params) (first args))
|
||||
(scm-bind-params! env (rest params) (rest args) rest-name))))))
|
||||
|
||||
;; Evaluate a program (sequence of forms), returning the last value.
|
||||
(define
|
||||
|
||||
Reference in New Issue
Block a user