;; lib/scheme/runtime.sx — R7RS-small standard environment. ;; ;; Builds scheme-standard-env from scheme-make-env, populating it with ;; arithmetic, comparison, type predicates, list/pair/vector/string/char ;; primitives, and the higher-order combinators (map/filter/fold). ;; ;; Primitives are bound as SX fns taking a list of evaluated arguments. ;; Combinators that re-enter the evaluator (map, filter, fold, apply, ;; for-each) call `scheme-apply` directly on user-supplied procedures. ;; ;; Public API ;; (scheme-standard-env) — fresh env with the full R7RS-base surface ;; ;; Consumes: lib/scheme/eval.sx (scheme-apply, scheme-make-env, ;; scheme-env-bind!, scheme-string?, scheme-char?, ;; scheme-vector?, scheme-vector-elements, ;; scheme-string-value, scheme-char-value, ;; scheme-string-make, scheme-char-make, ;; scheme-vector-make). ;; ── Arity / fold helpers ───────────────────────────────────────── (define scm-unary (fn (name f) (fn (args) (cond ((not (= (length args) 1)) (error (str name ": expects 1 argument"))) (:else (f (first args))))))) (define scm-binary (fn (name f) (fn (args) (cond ((not (= (length args) 2)) (error (str name ": expects 2 arguments"))) (:else (f (first args) (nth args 1))))))) ;; Variadic left-fold helper. zero-id is the identity (`(+)` → 0). ;; one-fn handles single-arg case (`(- x)` negates). (define scm-fold-step (fn (f acc rest-args) (cond ((or (nil? rest-args) (= (length rest-args) 0)) acc) (:else (scm-fold-step f (f acc (first rest-args)) (rest rest-args)))))) (define scm-fold (fn (name f zero-id one-fn) (fn (args) (cond ((= (length args) 0) zero-id) ((= (length args) 1) (one-fn (first args))) (:else (scm-fold-step f (first args) (rest args))))))) ;; n-ary chained comparison: (< 1 2 3) ≡ (< 1 2) ∧ (< 2 3). (define scm-chain-step (fn (cmp prev rest-args) (cond ((or (nil? rest-args) (= (length rest-args) 0)) true) (:else (let ((next (first rest-args))) (cond ((cmp prev next) (scm-chain-step cmp next (rest rest-args))) (:else false))))))) (define scm-chain (fn (name cmp) (fn (args) (cond ((< (length args) 2) (error (str name ": expects at least 2 arguments"))) (:else (scm-chain-step cmp (first args) (rest args))))))) ;; ── List helpers ───────────────────────────────────────────────── (define scm-list-append (fn (xs ys) (cond ((or (nil? xs) (= (length xs) 0)) ys) (:else (cons (first xs) (scm-list-append (rest xs) ys)))))) (define scm-list-reverse-step (fn (xs acc) (cond ((or (nil? xs) (= (length xs) 0)) acc) (:else (scm-list-reverse-step (rest xs) (cons (first xs) acc)))))) (define scm-all-lists? (fn (xs) (cond ((or (nil? xs) (= (length xs) 0)) true) ((list? (first xs)) (scm-all-lists? (rest xs))) (:else false)))) (define scm-append-all (fn (lists) (cond ((or (nil? lists) (= (length lists) 0)) (list)) ((= (length lists) 1) (first lists)) (:else (scm-list-append (first lists) (scm-append-all (rest lists))))))) ;; ── Map / Filter / Fold ────────────────────────────────────────── ;; These call scheme-apply directly so closures and primitives both work. (define scm-map-step (fn (proc xs) (cond ((or (nil? xs) (= (length xs) 0)) (list)) (:else (cons (scheme-apply proc (list (first xs))) (scm-map-step proc (rest xs))))))) (define scm-filter-step (fn (pred xs) (cond ((or (nil? xs) (= (length xs) 0)) (list)) (:else (let ((keep? (scheme-apply pred (list (first xs))))) (cond ((not (= keep? false)) (cons (first xs) (scm-filter-step pred (rest xs)))) (:else (scm-filter-step pred (rest xs))))))))) (define scm-fold-left-step (fn (proc acc xs) (cond ((or (nil? xs) (= (length xs) 0)) acc) (:else (scm-fold-left-step proc (scheme-apply proc (list acc (first xs))) (rest xs)))))) (define scm-fold-right-step (fn (proc init xs) (cond ((or (nil? xs) (= (length xs) 0)) init) (:else (scheme-apply proc (list (first xs) (scm-fold-right-step proc init (rest xs)))))))) (define scm-for-each-step (fn (proc xs) (cond ((or (nil? xs) (= (length xs) 0)) nil) (:else (begin (scheme-apply proc (list (first xs))) (scm-for-each-step proc (rest xs))))))) ;; ── Vector helpers ────────────────────────────────────────────── (define scm-make-vector-step (fn (n fill acc) (cond ((<= n 0) acc) (:else (scm-make-vector-step (- n 1) fill (cons fill acc)))))) ;; ── Standard env ───────────────────────────────────────────────── (define scheme-standard-env (fn () (let ((env (scheme-make-env))) (scheme-env-bind! env "+" (scm-fold "+" (fn (a b) (+ a b)) 0 (fn (x) x))) (scheme-env-bind! env "-" (scm-fold "-" (fn (a b) (- a b)) 0 (fn (x) (- 0 x)))) (scheme-env-bind! env "*" (scm-fold "*" (fn (a b) (* a b)) 1 (fn (x) x))) (scheme-env-bind! env "/" (scm-fold "/" (fn (a b) (/ a b)) 1 (fn (x) (/ 1 x)))) (scheme-env-bind! env "abs" (scm-unary "abs" (fn (n) (if (< n 0) (- 0 n) n)))) (scheme-env-bind! env "min" (scm-fold "min" (fn (a b) (if (< a b) a b)) nil (fn (x) x))) (scheme-env-bind! env "max" (scm-fold "max" (fn (a b) (if (< a b) b a)) nil (fn (x) x))) (scheme-env-bind! env "modulo" (scm-binary "modulo" (fn (a b) (- a (* b (floor (/ a b))))))) (scheme-env-bind! env "quotient" (scm-binary "quotient" (fn (a b) (floor (/ a b))))) (scheme-env-bind! env "remainder" (scm-binary "remainder" (fn (a b) (- a (* b (floor (/ a b))))))) (scheme-env-bind! env "zero?" (scm-unary "zero?" (fn (n) (= n 0)))) (scheme-env-bind! env "positive?" (scm-unary "positive?" (fn (n) (> n 0)))) (scheme-env-bind! env "negative?" (scm-unary "negative?" (fn (n) (< n 0)))) (scheme-env-bind! env "=" (scm-chain "=" (fn (a b) (= a b)))) (scheme-env-bind! env "<" (scm-chain "<" (fn (a b) (< a b)))) (scheme-env-bind! env ">" (scm-chain ">" (fn (a b) (> a b)))) (scheme-env-bind! env "<=" (scm-chain "<=" (fn (a b) (<= a b)))) (scheme-env-bind! env ">=" (scm-chain ">=" (fn (a b) (>= a b)))) (scheme-env-bind! env "number?" (scm-unary "number?" (fn (v) (number? v)))) (scheme-env-bind! env "boolean?" (scm-unary "boolean?" (fn (v) (boolean? v)))) (scheme-env-bind! env "symbol?" (scm-unary "symbol?" (fn (v) (string? v)))) (scheme-env-bind! env "string?" (scm-unary "string?" (fn (v) (scheme-string? v)))) (scheme-env-bind! env "char?" (scm-unary "char?" (fn (v) (scheme-char? v)))) (scheme-env-bind! env "vector?" (scm-unary "vector?" (fn (v) (scheme-vector? v)))) (scheme-env-bind! env "null?" (scm-unary "null?" (fn (v) (or (nil? v) (and (list? v) (= (length v) 0)))))) (scheme-env-bind! env "pair?" (scm-unary "pair?" (fn (v) (and (list? v) (> (length v) 0))))) (scheme-env-bind! env "procedure?" (scm-unary "procedure?" (fn (v) (or (callable? v) (and (dict? v) (= (get v :scm-tag) :closure)))))) (scheme-env-bind! env "not" (scm-unary "not" (fn (v) (= v false)))) (scheme-env-bind! env "cons" (scm-binary "cons" (fn (a b) (cons a b)))) (scheme-env-bind! env "car" (scm-unary "car" (fn (xs) (cond ((or (nil? xs) (and (list? xs) (= (length xs) 0))) (error "car: empty list")) (:else (first xs)))))) (scheme-env-bind! env "cdr" (scm-unary "cdr" (fn (xs) (cond ((or (nil? xs) (and (list? xs) (= (length xs) 0))) (error "cdr: empty list")) (:else (rest xs)))))) (scheme-env-bind! env "list" (fn (args) args)) (scheme-env-bind! env "length" (scm-unary "length" (fn (xs) (length xs)))) (scheme-env-bind! env "reverse" (scm-unary "reverse" (fn (xs) (scm-list-reverse-step xs (list))))) (scheme-env-bind! env "append" (fn (args) (cond ((scm-all-lists? args) (scm-append-all args)) (:else (error "append: all arguments must be lists"))))) (scheme-env-bind! env "map" (fn (args) (cond ((not (= (length args) 2)) (error "map: expects (proc list)")) (:else (scm-map-step (first args) (nth args 1)))))) (scheme-env-bind! env "filter" (fn (args) (cond ((not (= (length args) 2)) (error "filter: expects (pred list)")) (:else (scm-filter-step (first args) (nth args 1)))))) (scheme-env-bind! env "fold-left" (fn (args) (cond ((not (= (length args) 3)) (error "fold-left: expects (proc init list)")) (:else (scm-fold-left-step (first args) (nth args 1) (nth args 2)))))) (scheme-env-bind! env "fold-right" (fn (args) (cond ((not (= (length args) 3)) (error "fold-right: expects (proc init list)")) (:else (scm-fold-right-step (first args) (nth args 1) (nth args 2)))))) (scheme-env-bind! env "for-each" (fn (args) (cond ((not (= (length args) 2)) (error "for-each: expects (proc list)")) (:else (scm-for-each-step (first args) (nth args 1)))))) (scheme-env-bind! env "apply" (fn (args) (cond ((not (= (length args) 2)) (error "apply: expects (proc args-list)")) (:else (scheme-apply (first args) (nth args 1)))))) (scheme-env-bind! env "string-length" (scm-unary "string-length" (fn (s) (string-length (scheme-string-value s))))) (scheme-env-bind! env "string=?" (scm-binary "string=?" (fn (a b) (= (scheme-string-value a) (scheme-string-value b))))) (scheme-env-bind! env "string-append" (fn (args) (scheme-string-make (scm-fold-step (fn (acc s) (str acc (scheme-string-value s))) "" args)))) (scheme-env-bind! env "substring" (fn (args) (cond ((not (= (length args) 3)) (error "substring: expects (str start end)")) (:else (scheme-string-make (substring (scheme-string-value (first args)) (nth args 1) (nth args 2))))))) (scheme-env-bind! env "char=?" (scm-binary "char=?" (fn (a b) (= (scheme-char-value a) (scheme-char-value b))))) (scheme-env-bind! env "vector" (fn (args) (scheme-vector-make args))) (scheme-env-bind! env "vector-length" (scm-unary "vector-length" (fn (v) (length (scheme-vector-elements v))))) (scheme-env-bind! env "vector-ref" (scm-binary "vector-ref" (fn (v i) (nth (scheme-vector-elements v) i)))) (scheme-env-bind! env "vector->list" (scm-unary "vector->list" (fn (v) (scheme-vector-elements v)))) (scheme-env-bind! env "list->vector" (scm-unary "list->vector" (fn (xs) (scheme-vector-make xs)))) (scheme-env-bind! env "make-vector" (fn (args) (cond ((= (length args) 1) (scheme-vector-make (scm-make-vector-step (first args) nil (list)))) ((= (length args) 2) (scheme-vector-make (scm-make-vector-step (first args) (nth args 1) (list)))) (:else (error "make-vector: expects (n [fill])"))))) (scheme-env-bind! env "eqv?" (scm-binary "eqv?" (fn (a b) (= a b)))) (scheme-env-bind! env "equal?" (scm-binary "equal?" (fn (a b) (= a b)))) (scheme-env-bind! env "eq?" (scm-binary "eq?" (fn (a b) (= a b)))) ;; ── call/cc (R7RS first-class continuations) ──────────── ;; Captures the host SX continuation, wraps it as a Scheme ;; procedure (fn (vargs) ...) and passes it to the user proc. ;; Calling the captured k with one value re-enters the ;; continuation; with multiple values, passes them as a list. (scheme-env-bind! env "call/cc" (fn (args) (cond ((not (= (length args) 1)) (error "call/cc: expects 1 argument")) (:else (call/cc (fn (k) (let ((scheme-k (fn (vargs) (cond ((= (length vargs) 1) (k (first vargs))) (:else (k vargs)))))) (scheme-apply (first args) (list scheme-k))))))))) (scheme-env-bind! env "call-with-current-continuation" (refl-env-lookup env "call/cc")) ;; ── R7RS exception primitives ────────────────────────── ;; raise V — raises V as exception (host SX raise). (scheme-env-bind! env "raise" (fn (args) (cond ((not (= (length args) 1)) (error "raise: expects 1 argument")) (:else (raise (first args)))))) ;; error MSG IRRITANTS... — convention: raise an error-object ;; that's a dict {:scm-error MSG :irritants LIST}. The print ;; surface (error-object-message / error-object-irritants) ;; can pull these apart. (scheme-env-bind! env "error" (fn (args) (cond ((= (length args) 0) (error "error: expects (message [irritant...])")) (:else (raise {:scm-error (cond ((scheme-string? (first args)) (scheme-string-value (first args))) (:else (first args))) :irritants (rest args)}))))) (scheme-env-bind! env "error-object?" (scm-unary "error-object?" (fn (v) (and (dict? v) (string? (get v :scm-error)))))) (scheme-env-bind! env "error-object-message" (scm-unary "error-object-message" (fn (v) (scheme-string-make (get v :scm-error))))) (scheme-env-bind! env "error-object-irritants" (scm-unary "error-object-irritants" (fn (v) (get v :irritants)))) ;; with-exception-handler HANDLER THUNK — runs THUNK; if it ;; raises, calls HANDLER with the raised value (handler can ;; itself raise or return a value). Implemented via host guard. ;; with-exception-handler — catch THUNK's raise; if caught, ;; call HANDLER. If HANDLER itself raises, propagate that to ;; the outer scope (don't re-catch in this same guard, which ;; would loop). The two-step outcome-sentinel pattern mirrors ;; the `guard` special form's escape. (scheme-env-bind! env "with-exception-handler" (fn (args) (cond ((not (= (length args) 2)) (error "with-exception-handler: expects 2 arguments")) (:else (let ((handler (first args)) (thunk (nth args 1))) (let ((outcome (guard (e (true {:scm-weh-raised true :value e})) (scheme-apply thunk (list))))) (cond ((and (dict? outcome) (get outcome :scm-weh-raised)) (scheme-apply handler (list (get outcome :value)))) (:else outcome)))))))) ;; ── R7RS reflection: eval / environment accessors ─────── ;; eval EXPR ENV — apply the evaluator to a user-supplied AST. (scheme-env-bind! env "eval" (fn (args) (cond ((not (= (length args) 2)) (error "eval: expects (eval expr env)")) (:else (scheme-eval (first args) (nth args 1)))))) ;; interaction-environment — the env we're currently building. ;; The closure captures `env`, so each invocation of ;; scheme-standard-env produces a distinct interaction env ;; whose interaction-environment fn returns itself. (scheme-env-bind! env "interaction-environment" (fn (args) (cond ((not (= (length args) 0)) (error "interaction-environment: expects 0 args")) (:else env)))) ;; null-environment — fresh empty env. R7RS ignores version arg. (scheme-env-bind! env "null-environment" (fn (args) (cond ((not (= (length args) 1)) (error "null-environment: expects (version)")) (:else (scheme-make-env))))) ;; scheme-report-environment — fresh full standard env. (scheme-env-bind! env "scheme-report-environment" (fn (args) (cond ((not (= (length args) 1)) (error "scheme-report-environment: expects (version)")) (:else (scheme-standard-env))))) (scheme-env-bind! env "environment?" (scm-unary "environment?" (fn (v) (scheme-env? v)))) ;; dynamic-wind BEFORE THUNK AFTER — runs BEFORE, then THUNK, ;; then AFTER. If THUNK raises, AFTER still runs before the ;; raise propagates. This is the basic-correctness version; ;; proper call/cc-escape interaction would need dynamic-extent ;; tracking, deferred until needed. (scheme-env-bind! env "dynamic-wind" (fn (args) (cond ((not (= (length args) 3)) (error "dynamic-wind: expects (before thunk after)")) (:else (let ((before-thunk (first args)) (mid-thunk (nth args 1)) (after-thunk (nth args 2))) (begin (scheme-apply before-thunk (list)) (let ((outcome (guard (e (true {:scm-dw-raised true :value e})) (scheme-apply mid-thunk (list))))) (begin (scheme-apply after-thunk (list)) (cond ((and (dict? outcome) (get outcome :scm-dw-raised)) (raise (get outcome :value))) (:else outcome)))))))))) env)))