diff --git a/lib/scheme/runtime.sx b/lib/scheme/runtime.sx index f2a869ca..d8473171 100644 --- a/lib/scheme/runtime.sx +++ b/lib/scheme/runtime.sx @@ -586,6 +586,40 @@ ((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; diff --git a/lib/scheme/tests/reflection.sx b/lib/scheme/tests/reflection.sx new file mode 100644 index 00000000..2cb3410c --- /dev/null +++ b/lib/scheme/tests/reflection.sx @@ -0,0 +1,100 @@ +;; lib/scheme/tests/reflection.sx — Phase 7 reflective primitives. + +(define scm-ref-pass 0) +(define scm-ref-fail 0) +(define scm-ref-fails (list)) + +(define + scm-ref-test + (fn + (name actual expected) + (if + (= actual expected) + (set! scm-ref-pass (+ scm-ref-pass 1)) + (begin + (set! scm-ref-fail (+ scm-ref-fail 1)) + (append! scm-ref-fails {:name name :actual actual :expected expected}))))) + +(define + scm-ref + (fn (src) (scheme-eval (scheme-parse src) (scheme-standard-env)))) + +(define + scm-ref-all + (fn + (src) + (scheme-eval-program (scheme-parse-all src) (scheme-standard-env)))) + +;; ── eval ───────────────────────────────────────────────────────── + +(scm-ref-test + "eval: arithmetic" + (scm-ref "(eval '(+ 1 2 3) (interaction-environment))") + 6) +(scm-ref-test + "eval: nested" + (scm-ref "(eval '(* (+ 1 2) (- 5 1)) (interaction-environment))") + 12) +(scm-ref-test + "eval: constructed form" + (scm-ref "(eval (list '+ 10 20) (interaction-environment))") + 30) +(scm-ref-test + "eval: variable reference" + (scm-ref-all "(define x 42) (eval 'x (interaction-environment))") + 42) + +;; ── interaction-environment ───────────────────────────────────── + +(scm-ref-test + "interaction-environment: is an env" + (scm-ref "(environment? (interaction-environment))") + true) +(scm-ref-test + "interaction-environment: define persists" + (scm-ref-all + "(define ie (interaction-environment))\n (eval '(define stashed 99) ie)\n (eval 'stashed ie)") + 99) +(scm-ref-test + "interaction-environment: same env across calls" + (scm-ref-all + "(define a (interaction-environment))\n (define b (interaction-environment))\n (eqv? a b)") + true) + +;; ── null-environment ──────────────────────────────────────────── + +(scm-ref-test + "null-environment: is an env" + (scm-ref "(environment? (null-environment 7))") + true) +(scm-ref-test + "null-environment: has no + binding" + (scm-ref-all + "(define ne (null-environment 7))\n (guard (e (else 'unbound)) (eval '+ ne))") + "unbound") + +;; ── scheme-report-environment ─────────────────────────────────── + +(scm-ref-test + "scheme-report-environment: is an env" + (scm-ref "(environment? (scheme-report-environment 7))") + true) +(scm-ref-test + "scheme-report-environment: has +" + (scm-ref "(eval '(+ 1 2) (scheme-report-environment 7))") + 3) +(scm-ref-test + "scheme-report-environment: distinct from interaction" + (scm-ref-all + "(define ie (interaction-environment))\n (define re (scheme-report-environment 7))\n (eval '(define only-in-ie 1) ie)\n (guard (e (else 'unbound)) (eval 'only-in-ie re))") + "unbound") + +;; ── eval with explicit env for sandboxing ────────────────────── + +(scm-ref-test + "eval: sandbox with null-environment" + (scm-ref-all + "(define sandbox (null-environment 7))\n (guard (e (else 'unbound))\n (eval '(+ 1 1) sandbox))") + "unbound") + +(define scm-ref-tests-run! (fn () {:total (+ scm-ref-pass scm-ref-fail) :passed scm-ref-pass :failed scm-ref-fail :fails scm-ref-fails}))