diff --git a/CHANGES.md b/CHANGES.md index 222f4b9..3128eef 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -906,7 +906,7 @@ Added comprehensive input validation to all Scheme procedures. ## Special Forms ([analyzer.js](./src/core/interpreter/analyzer.js)) - `analyzeIf` - Validates 2-3 arguments -- `analyzeLet` - Validates binding structure +- `analyzeLet` - Validates binding structure - `analyzeLetRec` - Validates binding structure - `analyzeLambda` - Validates param symbols, body not empty - `analyzeSet` - Validates symbol argument @@ -1171,7 +1171,7 @@ Updated [base.sld](/workspaces/scheme-js-4/src/core/scheme/base.sld): char? char=? char? char<=? char>=? char->integer integer->char -;; Strings +;; Strings string? make-string string string-length string-ref string=? string? string<=? string>=? substring string-append string-copy @@ -1379,7 +1379,7 @@ This was the most complex part of the implementation, solving the problem of int - **Problem**: Internal library definitions like `param-dynamic-bind` (used by the `parameterize` macro in `scheme core`) were defined in library-specific environments but `GlobalRef` only looked in the global environment. -- **Solution**: +- **Solution**: - Introduced `libraryScopeEnvMap` in `syntax_object.js` to map library defining scopes to their runtime environments. - Updated `library_loader.js` to register this mapping when loading libraries via `registerLibraryScope(libraryScope, libEnv)`. - Updated `GlobalRef` to carry the defining scope ID. @@ -1399,7 +1399,7 @@ This was the most complex part of the implementation, solving the problem of int ### Hygiene Tests (`tests/functional/macro_tests.js`) All 8 tests pass: - ✅ **Standard Library Capture**: `(syntax-rules () ((_) (list 1 2)))` works even if `list` is shadowed locally -- ✅ **User Global Capture**: `(syntax-rules () ((_) global-var))` works even if `global-var` is shadowed locally +- ✅ **User Global Capture**: `(syntax-rules () ((_) global-var))` works even if `global-var` is shadowed locally - ✅ **Renaming**: Macro-introduced variables don't clash with user variables ### Regression Tests @@ -1500,7 +1500,7 @@ The fix was verified using a targeted reproduction test case and the full system (guard (e (else (if (string? e) e "not-string"))) expr)))) -(test "error" (test-guard-binding (raise "error"))) +(test "error" (test-guard-binding (raise "error"))) ;; Previously failed with "Unbound variable: e" or "global" ;; Now correctly returns "error" @@ -1574,7 +1574,7 @@ Changed macro lookup to use scoped registry: ## Tests Added Created [hygiene_tests.scm](./tests/core/scheme/hygiene_tests.scm) with 10 tests: - Referential transparency (3 tests) -- let-syntax scoping (4 tests) +- let-syntax scoping (4 tests) - letrec-syntax (2 tests) - Hygiene edge cases (1 test) @@ -2151,7 +2151,7 @@ I have implemented a robust Node.js REPL application for the Scheme interpreter - **Interactive REPL**: Run `node repl.js` to start the session. - **Multi-line Input**: The REPL detects incomplete expressions (open parentheses, unclosed strings) and prompts for continuation. -- **File Loading**: Use `(load "filename.scm")` to load Scheme scripts into the current environment. +- **File Loading**: Use `(load "filename.scm")` to load Scheme scripts into the current environment. - **Library Imports**: Use `(import (lib name))` to import R7RS libraries synchronously. - **File Execution**: Run `node repl.js ` to execute a Scheme file. - **Expression Evaluation**: Run `node repl.js -e ""` to evaluate a single expression and exit. @@ -2314,7 +2314,7 @@ TEST SUMMARY: 1166 passed, 0 failed, 3 skipped (js-promise-then p (lambda (x) (display x))) ;; Create promise with executor -(define p2 (make-js-promise +(define p2 (make-js-promise (lambda (resolve reject) (resolve (* 6 7))))) @@ -3379,7 +3379,7 @@ Implemented proper object printing with reader syntax `#{(key val)...}` and circ ### 2. Reader Bridge Fixes (`src/core/primitives/io/reader_bridge.js`) - Added `braceDepth` tracking for `{` and `}` to allow the reader to correctly collect full object literal "chunks" from ports. - Added explicit handling for the `#{` token start. -- **Bug Fixes**: +- **Bug Fixes**: - Fixed an issue where strings ending inside an object literal (e.g., `#{(a \"s\")}`) caused the reader to break early. - Fixed an issue where whitespace following a nested list in an object literal (e.g., `#{(a 1) (b 2)}`) caused premature parsing. @@ -3888,7 +3888,7 @@ Integrated the debugging runtime into the interactive REPLs, providing a profess ## Documentation -- **[debugger_manual.md](./docs/debugger_manual.md)**: Created a detailed user manual for all debugger commands +- **[debugger_manual.md](./docs/debugger_manual.md)**: Created a detailed user manual for all debugger commands and features. ## Verification Results @@ -4024,3 +4024,46 @@ I converted the REPL's evaluation logic to be fully asynchronous in debug mode. ### Node.js REPL - Verified that `:abort` exits the debug loop and returns to the main prompt. + +# Walkthrough: Implementing define-macro (2026-02-08) + +I have implemented the `define-macro` special form, bringing Common Lisp-style unhygienic macros to Scheme-JS. + +## Changes + +### 1. Special Form Handler +- **`src/core/interpreter/analyzers/core_forms.js`**: Added `analyzeDefineMacro`. + - It creates a temporary `Interpreter` with a fresh standard environment. + - It evaluates the transformer body (which is regular Scheme code) into a closure. + - It registers a JS wrapper function in the macro registry that invokes this closure during expansion. +- **`src/core/interpreter/library_registry.js`**: Added `define-macro` to `SYNTAX_KEYWORDS` and `SPECIAL_FORMS`. + +### 2. Library Support +- **`src/extras/scheme/define-macro.sld`**: Created a library exporting `define-macro` under the `scheme-js` namespace. +- **`repl.js` & `web/main.js`**: Updated bootstrap logic to import `(scheme-js define-macro)` by default. +- **`tests/run_scheme_tests_lib.js`**: Updated test runner to include `(scheme-js define-macro)`. + +### 3. Verification +- Created `tests/functional/test_defmacro.scm` demonstrating: + - Standard list destructuring: `(define-macro (name . args) ...)` + - Explicit transformer: `(define-macro name transformer-proc)` + - Usage in expressions. +- Added to `tests/test_manifest.js`. + +## Verification Results + +### Automated Tests +Ran the full test suite. + +``` +TEST SUMMARY: 2021 passed, 0 failed, 7 skipped +``` + +### Manual Verification +Ran `node repl.js tests/functional/test_defmacro.scm`. + +``` +OK +Math OK +Unless OK +``` diff --git a/repl.js b/repl.js index 10a66de..5fe3a3c 100644 --- a/repl.js +++ b/repl.js @@ -117,7 +117,8 @@ async function bootstrapInterpreter() { (scheme file) (scheme process-context) (scheme-js promise) - (scheme-js interop)) + (scheme-js interop) + (scheme-js define-macro)) `; for (const sexp of parse(imports)) { interpreter.run(analyze(sexp), env, [], undefined, { jsAutoConvert: 'raw' }); diff --git a/src/core/interpreter/analyzers/core_forms.js b/src/core/interpreter/analyzers/core_forms.js index c36cb68..1744b1c 100644 --- a/src/core/interpreter/analyzers/core_forms.js +++ b/src/core/interpreter/analyzers/core_forms.js @@ -23,6 +23,8 @@ import { MacroRegistry } from '../macro_registry.js'; import { registerHandler } from './registry.js'; import { compileSyntaxRules } from '../syntax_rules.js'; import { SchemeSyntaxError } from '../errors.js'; +import { Interpreter } from '../interpreter.js'; +import { createGlobalEnvironment } from '../../primitives/index.js'; // These will be set by the analyzer when it initializes // to avoid circular dependencies between analyzer.js and core_forms.js. @@ -464,6 +466,7 @@ export function registerCoreForms() { registerHandler('define', analyzeDefine); registerHandler('begin', analyzeBegin); registerHandler('define-syntax', analyzeDefineSyntax); + registerHandler('define-macro', analyzeDefineMacro); registerHandler('let-syntax', analyzeLetSyntax); registerHandler('letrec-syntax', analyzeLetrecSyntax); registerHandler('quasiquote', (exp, env, ctx) => expandQuasiquote(cadr(exp), env, ctx)); @@ -542,6 +545,110 @@ function analyzeDefineSyntax(exp, syntacticEnv = null, ctx) { return new LiteralNode(null); } +/** + * Analyzes (define-macro (name args...) body...) or (define-macro name transformer). + * Evaluates the transformer immediately in a fresh environment and registers it. + * + * @param {Cons} exp - The expression. + * @param {SyntacticEnv} [syntacticEnv=null] - The environment. + * @param {InterpreterContext} ctx - The context. + * @returns {LiteralNode} + */ +function analyzeDefineMacro(exp, syntacticEnv = null, ctx) { + const head = cadr(exp); + let name; + let transformerLambdaAst; + + // Case 1: (define-macro (name args...) body...) + if (head instanceof Cons) { + const nameObj = car(head); + const args = cdr(head); + const body = cddr(exp); + name = (nameObj instanceof Symbol) ? nameObj.name : syntaxName(nameObj); + + // Construct lambda: (lambda args body...) + const lambdaExp = cons(intern('lambda'), cons(args, body)); + transformerLambdaAst = analyzeLambda(lambdaExp, syntacticEnv, ctx); + } + // Case 2: (define-macro name transformer-proc) + else if (head instanceof Symbol || isSyntaxObject(head)) { + name = (head instanceof Symbol) ? head.name : syntaxName(head); + const transformerExp = caddr(exp); + transformerLambdaAst = analyze(transformerExp, syntacticEnv, ctx); + } else { + throw new SchemeSyntaxError('Invalid define-macro syntax', exp, 'define-macro'); + } + + // 1. Create a temporary interpreter with standard primitives + // We use a separate context for the expansion environment to avoid polluting + // the global state, but we might want to share state in the future. + // For now, "expansion time" is a fresh environment with standard library. + const expansionInterpreter = new Interpreter(ctx); + const expansionEnv = createGlobalEnvironment(expansionInterpreter); + expansionInterpreter.setGlobalEnv(expansionEnv); + + // 2. Evaluate the transformer to get a closure + let transformerClosure; + try { + // Run synchronously - transformers must be available immediately + transformerClosure = expansionInterpreter.run(transformerLambdaAst, expansionEnv); + } catch (e) { + throw new SchemeSyntaxError(`Error evaluating macro transformer for '${name}': ${e.message}`, exp, 'define-macro'); + } + + // 3. Create the JS transformer wrapper + // The wrapper receives the macro call expression (name arg1 arg2 ...) + // It extracts the arguments and calls the Scheme closure. + const jsTransformer = (macroCallExp, useSiteEnv) => { + const argsList = cdr(macroCallExp); // (arg1 arg2 ...) + + // Invoke the closure with the arguments list + // Note: The transformer returns a Scheme expression (AST/Cons) + // which the analyzer will then recursively analyze. + + // We use runWithSentinel to ensure proper stack handling if the macro calls back into JS (unlikely but possible) + // But for simple closure invocation, we can construct an application AST. + + // However, we have a raw closure object and raw arguments (Cons list). + // The simplest way is to use the interpreter's invokeContinuation-like logic or apply primitive logic. + + // Let's manually construct an Apply invocation or similar. + // Or simpler: use expansionInterpreter.run with a TailAppNode if we wrap the closure in a LiteralNode. + + // We need to convert the Cons list of args into an array of AST nodes? + // No, the closure expects Scheme values (Cons list of syntax). + // Wait, (define-macro (f x) ...) expects x to be passed as argument. + // The macro call is (f arg1). + // So the transformer should be called with `arg1`. + + // If the macro is (define-macro (f . args) ...), it expects `args` as a list. + // If the macro is (define-macro (f x) ...), it expects `x`. + + // We need to `apply` the closure to the arguments list. + // The `apply` primitive in Scheme does exactly this. + + try { + // Use the expansion interpreter to run (apply closure argsList) + const applyNode = new TailAppNode( + new VariableNode('apply'), + [ + new LiteralNode(transformerClosure), + new LiteralNode(argsList) + ] + ); + + return expansionInterpreter.run(applyNode, expansionEnv); + } catch (e) { + throw new SchemeSyntaxError(`Error expanding macro '${name}': ${e.message}`, macroCallExp, name); + } + }; + + // 4. Register the transformer + ctx.currentMacroRegistry.define(name, jsTransformer); + + return new LiteralNode(null); +} + /** * Analyzes (let-syntax (...) ). * @param {Cons} exp - The expression. diff --git a/src/core/interpreter/library_registry.js b/src/core/interpreter/library_registry.js index 012905a..2c5e99b 100644 --- a/src/core/interpreter/library_registry.js +++ b/src/core/interpreter/library_registry.js @@ -253,7 +253,8 @@ export const SYNTAX_KEYWORDS = new Set([ 'define-syntax', 'let-syntax', 'letrec-syntax', 'syntax-rules', '...', 'else', '=>', 'import', 'export', 'define-library', 'include', 'include-ci', 'include-library-declarations', - 'cond-expand', 'let', 'letrec', 'call/cc', 'call-with-current-continuation' + 'cond-expand', 'let', 'letrec', 'call/cc', 'call-with-current-continuation', + 'define-macro' ]); /** @@ -266,7 +267,7 @@ export const SPECIAL_FORMS = new Set([ 'if', 'let', 'letrec', 'lambda', 'set!', 'define', 'begin', 'quote', 'quasiquote', 'unquote', 'unquote-splicing', // Macro-related - 'define-syntax', 'let-syntax', 'letrec-syntax', + 'define-syntax', 'let-syntax', 'letrec-syntax', 'define-macro', // Control flow 'call/cc', 'call-with-current-continuation', // Module system diff --git a/src/extras/scheme/define-macro.sld b/src/extras/scheme/define-macro.sld new file mode 100644 index 0000000..dbb3bc6 --- /dev/null +++ b/src/extras/scheme/define-macro.sld @@ -0,0 +1,8 @@ +;; Library for define-macro support +;; This library exports the define-macro special form. +;; It is loaded by default in the REPL environment. + +(define-library (scheme-js define-macro) + (export define-macro) + (import (scheme base)) +) diff --git a/src/packaging/bundled_libraries.js b/src/packaging/bundled_libraries.js index 684669c..3b270e3 100644 --- a/src/packaging/bundled_libraries.js +++ b/src/packaging/bundled_libraries.js @@ -13,6 +13,7 @@ export const BUNDLED_SOURCES = { "core.sld": "(define-library (scheme core)\n (import (scheme primitives))\n \n ;; Include separate Scheme files in dependency order\n (include \"macros.scm\") ; Core macros: and, let, letrec, cond\n (include \"equality.scm\") ; equal?\n (include \"cxr.scm\") ; caar, cadr, etc.\n (include \"numbers.scm\") ; =, <, >, predicates, min/max\n (include \"list.scm\") ; map, for-each, memq, assq, etc.\n (include \"parameter.scm\") ; make-parameter, parameterize\n \n (export\n ;; Macros\n and or let let* letrec cond\n define-record-type define-record-field\n define-class define-class-field define-class-method\n \n ;; Deep equality\n equal?\n \n ;; List operations\n map for-each\n string-map string-for-each\n vector-map vector-for-each\n memq memv member\n assq assv assoc\n length list-ref list-tail reverse list-copy\n make-list list-set!\n \n ;; Compound accessors (cxr)\n caar cadr cdar cddr\n caaar caadr cadar caddr cdaar cdadr cddar cdddr\n caaaar caaadr caadar caaddr cadaar cadadr caddar cadddr\n cdaaar cdaadr cdadar cdaddr cddaar cddadr cdddar cddddr\n \n ;; Comparison operators (variadic)\n = < > <= >=\n \n ;; Numeric predicates\n zero? positive? negative? odd? even?\n \n ;; Min/max\n max min\n \n ;; GCD/LCM\n gcd lcm\n \n ;; Rounding\n round inexact->exact\n \n ;; Parameter objects\n make-parameter parameterize param-dynamic-bind\n \n ;; Misc\n native-report-test-result\n )\n)\n\n", "cxr.scm": ";; CXR Accessors\n;; All 28 compound car/cdr accessors up to 4 levels deep\n\n;; Depth 2\n(define (caar x) (car (car x)))\n(define (cadr x) (car (cdr x)))\n(define (cdar x) (cdr (car x)))\n(define (cddr x) (cdr (cdr x)))\n\n;; Depth 3\n(define (caaar x) (car (car (car x))))\n(define (caadr x) (car (car (cdr x))))\n(define (cadar x) (car (cdr (car x))))\n(define (caddr x) (car (cdr (cdr x))))\n(define (cdaar x) (cdr (car (car x))))\n(define (cdadr x) (cdr (car (cdr x))))\n(define (cddar x) (cdr (cdr (car x))))\n(define (cdddr x) (cdr (cdr (cdr x))))\n\n;; Depth 4\n(define (caaaar x) (car (car (car (car x)))))\n(define (caaadr x) (car (car (car (cdr x)))))\n(define (caadar x) (car (car (cdr (car x)))))\n(define (caaddr x) (car (car (cdr (cdr x)))))\n(define (cadaar x) (car (cdr (car (car x)))))\n(define (cadadr x) (car (cdr (car (cdr x)))))\n(define (caddar x) (car (cdr (cdr (car x)))))\n(define (cadddr x) (car (cdr (cdr (cdr x)))))\n(define (cdaaar x) (cdr (car (car (car x)))))\n(define (cdaadr x) (cdr (car (car (cdr x)))))\n(define (cdadar x) (cdr (car (cdr (car x)))))\n(define (cdaddr x) (cdr (car (cdr (cdr x)))))\n(define (cddaar x) (cdr (cdr (car (car x)))))\n(define (cddadr x) (cdr (cdr (car (cdr x)))))\n(define (cdddar x) (cdr (cdr (cdr (car x)))))\n(define (cddddr x) (cdr (cdr (cdr (cdr x)))))\n", "cxr.sld": ";; R7RS (scheme cxr) library\n;; \n;; Provides compound car/cdr accessors up to 4 levels deep.\n;; Per R7RS Appendix A.\n\n(define-library (scheme cxr)\n (import (scheme core))\n\n (export\n ;; Depth 2\n caar cadr cdar cddr\n \n ;; Depth 3\n caaar caadr cadar caddr\n cdaar cdadr cddar cdddr\n \n ;; Depth 4\n caaaar caaadr caadar caaddr\n cadaar cadadr caddar cadddr\n cdaaar cdaadr cdadar cdaddr\n cddaar cddadr cdddar cddddr\n )\n)\n", + "define-macro.sld": ";; Library for define-macro support\n;; This library exports the define-macro special form.\n;; It is loaded by default in the REPL environment.\n\n(define-library (scheme define-macro)\n (export define-macro)\n (import (scheme base))\n)\n", "equality.scm": ";; Equality Procedures\n;; Structural equality testing\n\n;; /**\n;; * Deep equality check.\n;; * Recursively compares pairs and vectors. Uses eqv? for other types.\n;; *\n;; * @param {*} a - First object.\n;; * @param {*} b - Second object.\n;; * @returns {boolean} #t if objects are structurally equal, #f otherwise.\n;; */\n(define (equal? a b)\n (cond\n ((eqv? a b) #t)\n ((and (pair? a) (pair? b))\n (and (equal? (car a) (car b))\n (equal? (cdr a) (cdr b))))\n ((and (vector? a) (vector? b))\n (let ((len-a (vector-length a))\n (len-b (vector-length b)))\n (if (= len-a len-b)\n (let loop ((i 0))\n (if (= i len-a)\n #t\n (if (equal? (vector-ref a i) (vector-ref b i))\n (loop (+ i 1))\n #f)))\n #f)))\n ((and (bytevector? a) (bytevector? b))\n (let ((len-a (bytevector-length a))\n (len-b (bytevector-length b)))\n (if (= len-a len-b)\n (let loop ((i 0))\n (if (= i len-a)\n #t\n (if (= (bytevector-u8-ref a i) (bytevector-u8-ref b i))\n (loop (+ i 1))\n #f)))\n #f)))\n ((and (string? a) (string? b))\n (eqv? a b)) ; Strings are primitives in JS, so eqv? (Object.is) works. \n (else #f)))\n", "eval.sld": ";; (scheme eval) library\n;;\n;; R7RS evaluation procedures.\n;; Note: environment procedure not fully supported - returns interaction environment.\n\n(define-library (scheme eval)\n (import (scheme base))\n (export eval environment)\n (begin\n ;; eval and interaction-environment are already primitives\n ;; environment returns the interaction-environment for now\n ;; (R7RS allows implementation-defined behavior for environment)\n (define (environment . import-specs)\n (interaction-environment))))\n", "file.sld": ";; R7RS (scheme file) library\n;;\n;; Provides file I/O operations (Node.js only).\n;; Per R7RS §6.13.\n\n(define-library (scheme file)\n (import (scheme primitives))\n \n (export\n open-input-file\n open-output-file\n call-with-input-file\n call-with-output-file\n file-exists?\n delete-file\n )\n \n (begin\n ;; All procedures are implemented as primitives\n ;; Note: These procedures only work in Node.js\n ;; Browser calls will raise errors\n ))\n", diff --git a/tests/functional/test_defmacro.scm b/tests/functional/test_defmacro.scm new file mode 100644 index 0000000..44795d2 --- /dev/null +++ b/tests/functional/test_defmacro.scm @@ -0,0 +1,25 @@ +;; Test 1: Simple macro +(define-macro (my-when test . body) + `(if ,test (begin ,@body))) + +(my-when #t + (display "OK") + (newline)) + +;; Test 2: Macro returning a value +(define-macro (add-one x) + `(+ ,x 1)) + +(if (= (add-one 5) 6) + (display "Math OK") + (display "Math Fail")) +(newline) + +;; Test 3: define-macro name transformer syntax +(define-macro my-unless + (lambda (test . body) + `(if (not ,test) (begin ,@body)))) + +(my-unless #f + (display "Unless OK") + (newline)) diff --git a/tests/run_scheme_tests_lib.js b/tests/run_scheme_tests_lib.js index 7153239..b341292 100644 --- a/tests/run_scheme_tests_lib.js +++ b/tests/run_scheme_tests_lib.js @@ -56,6 +56,7 @@ export async function runSchemeTests(interpreter, logger, testFiles, fileLoader) const promiseExports = await loadLibrary(['scheme-js', 'promise'], analyze, interpreter, interpreter.globalEnv); const jsConversionExports = await loadLibrary(['scheme-js', 'js-conversion'], analyze, interpreter, interpreter.globalEnv); const interopExports = await loadLibrary(['scheme-js', 'interop'], analyze, interpreter, interpreter.globalEnv); + const defineMacroExports = await loadLibrary(['scheme-js', 'define-macro'], analyze, interpreter, interpreter.globalEnv); applyImports(interpreter.globalEnv, baseExports, { libraryName: ['scheme', 'base'] }); applyImports(interpreter.globalEnv, replExports, { libraryName: ['scheme', 'repl'] }); applyImports(interpreter.globalEnv, caseLambdaExports, { libraryName: ['scheme', 'case-lambda'] }); @@ -64,6 +65,7 @@ export async function runSchemeTests(interpreter, logger, testFiles, fileLoader) applyImports(interpreter.globalEnv, promiseExports, { libraryName: ['scheme-js', 'promise'] }); applyImports(interpreter.globalEnv, jsConversionExports, { libraryName: ['scheme-js', 'js-conversion'] }); applyImports(interpreter.globalEnv, interopExports, { libraryName: ['scheme-js', 'interop'] }); + applyImports(interpreter.globalEnv, defineMacroExports, { libraryName: ['scheme-js', 'define-macro'] }); // Note: time and process-context primitives are loaded via primitives/index.js diff --git a/tests/test_manifest.js b/tests/test_manifest.js index 2088669..a95a115 100644 --- a/tests/test_manifest.js +++ b/tests/test_manifest.js @@ -117,6 +117,7 @@ export const schemeTestFiles = [ 'tests/extras/scheme/js_conversion_tests.scm', 'tests/extras/scheme/class_tests.scm', 'tests/extras/scheme/dot_access_tests.scm', + 'tests/functional/test_defmacro.scm', ]; diff --git a/web/main.js b/web/main.js index e692c20..890737e 100644 --- a/web/main.js +++ b/web/main.js @@ -82,7 +82,8 @@ import { ['scheme', 'cxr'], ['scheme', 'char'], ['scheme-js', 'promise'], - ['scheme-js', 'interop'] + ['scheme-js', 'interop'], + ['scheme-js', 'define-macro'] ]; // Pre-load all libraries asynchronously (this populates the cache) @@ -104,7 +105,8 @@ import { (scheme cxr) (scheme char) (scheme-js promise) - (scheme-js interop)) + (scheme-js interop) + (scheme-js define-macro)) `; for (const exp of parse(imports)) { interpreter.run(analyze(exp), env);