From: Raphaël Van Dyck Date: Tue, 12 May 2026 15:05:09 +0000 (+0200) Subject: revise user manual, tutorial, reference manual, and implementation notes X-Git-Url: http://evlambda.org/gitweb/gitweb.cgi?a=commitdiff_plain;h=c9392e4e5c81b83b8a1f7670e62260f5faefa405;p=evlambda.git revise user manual, tutorial, reference manual, and implementation notes --- diff --git a/README b/README index 2719fbb..ea2c7fa 100644 --- a/README +++ b/README @@ -54,9 +54,9 @@ How to run the evaluator from the terminal ========================================== Execute the following command from : -node system-files/core.js { --plainrec | --cps | --oocps | --sboocps | --trampoline | --trampoliepp }? { -l | -e
}* +node system-files/core.js { --directstyle | --cps | --oocps | --sboocps | --trampoline | --trampoliepp }? { -l | -e }* ---plainrec: selects the plain recursive evaluator +--directstyle: selects the direct style evaluator --cps: selects the continuation passing style evaluator --oocps: selects the object-oriented CPS evaluator --sboocps: selects the stack-based object-oriented CPS evaluator @@ -67,7 +67,22 @@ node system-files/core.js { --plainrec | --cps | --oocps | --sboocps | --trampol Examples: node system-files/core.js -l system-files/mantle.evl -e '(test-loop 1000000)' -node system-files/core.js --plainrec -l system-files/mantle.evl -e '(test-loop 1000000)' +node system-files/core.js --directstyle -l system-files/mantle.evl -e '(test-loop 1000000)' + +How to run the evaluator from the terminal using Bun +==================================================== + +The Bun runtime is a drop-in replacement for the Node.js runtime. +One important difference between the Bun runtime and the Node.js runtime is that former implements proper tail calls (PTC) and the latter does not. + +Install Bun if it is not already installed. + +Execute the following command from : +bun system-files/core.js { --directstyle | --cps | --oocps | --sboocps | --trampoline | --trampoliepp }? { -l | -e }* + +Examples: +bun system-files/core.js -l system-files/mantle.evl -e '(test-loop 1000000)' +bun system-files/core.js --directstyle -l system-files/mantle.evl -e '(test-loop 1000000)' How to run the EVL to XML converter from the terminal ===================================================== diff --git a/file-systems/router.js b/file-systems/router.js index 885fefd..35d8a16 100644 --- a/file-systems/router.js +++ b/file-systems/router.js @@ -120,7 +120,7 @@ function makeRouter(fileSystem, options) { router.get('/get-file-contents', (req, res) => { res.send(fileSystem.getFileContents(validatePathnameParameter(req.query.pathname), options)); }); - router.put('/put-file-contents', express.raw({limit: '200kb'}), (req, res) => { + router.put('/put-file-contents', express.raw({limit: '250kb'}), (req, res) => { fileSystem.putFileContents(validatePathnameParameter(req.query.pathname), req.body, options); res.end(); }); diff --git a/src/evaluator.js b/src/evaluator.js index 264d251..2f96831 100644 --- a/src/evaluator.js +++ b/src/evaluator.js @@ -17,7 +17,7 @@ const EVALUATE_ALL_FORMS = 2; const CONVERT_EVL_TO_XML = 3; export const evaluatorNames = new Map([ - ['plainrec', 'Plain Recursive'], + ['directstyle', 'Direct Style'], ['cps', 'Continuation Passing Style'], ['oocps', 'Object-Oriented CPS'], ['sboocps', 'Stack-Based Object-Oriented CPS'], @@ -100,7 +100,7 @@ export function convertEVLToHTML(text, xsltString, cssURL, jsURL, windowId, call result = htmlString; break; case ERROR: - result = errorPage(`ERROR: ${response.output}`, cssURL, jsURL, windowId); + result = errorPage(response.output, cssURL, jsURL, windowId); break; case ABORTED: result = errorPage('ABORTED', cssURL, jsURL, windowId); @@ -144,7 +144,7 @@ export function formatForListener(response) { } return text; case ERROR: - return `ERROR: ${response.output}\n`; + return response.output + '\n'; case ABORTED: return 'ABORTED\n'; case TERMINATED: @@ -169,7 +169,7 @@ export function formatForMinibuffer(response) { } return text; case ERROR: - return `ERROR: ${response.output}`; + return response.output; case ABORTED: return 'ABORTED'; case TERMINATED: diff --git a/src/ide.jsx b/src/ide.jsx index 30f874f..0bc1681 100644 --- a/src/ide.jsx +++ b/src/ide.jsx @@ -191,10 +191,10 @@ class AllCapsBuffer extends FileBuffer { return html(); } toggleOnHTMLMode(ide, window) { - const css = findFileBuffer(ide.buffers, '/system/all-caps.css').transaction.state.sliceDoc(); + const css = findFileBuffer(ide.buffers, '/system/common.css').transaction.state.sliceDoc(); const cssBlob = new Blob([css], {type: 'text/css'}); const cssURL = URL.createObjectURL(cssBlob); - const js = findFileBuffer(ide.buffers, '/system/all-caps.js').transaction.state.sliceDoc(); + const js = findFileBuffer(ide.buffers, '/system/common.js').transaction.state.sliceDoc(); const jsBlob = new Blob([js], {type: 'text/javascript'}); const jsURL = URL.createObjectURL(jsBlob); const state = this.transaction.state; @@ -219,10 +219,10 @@ class EVLBuffer extends FileBuffer { } toggleOnHTMLMode(ide, window) { const xslt = findFileBuffer(ide.buffers, '/system/evl2html.xslt').transaction.state.sliceDoc(); - const css = findFileBuffer(ide.buffers, '/system/evl2html.css').transaction.state.sliceDoc(); + const css = findFileBuffer(ide.buffers, '/system/common.css').transaction.state.sliceDoc(); const cssBlob = new Blob([css], {type: 'text/css'}); const cssURL = URL.createObjectURL(cssBlob); - const js = findFileBuffer(ide.buffers, '/system/evl2html.js').transaction.state.sliceDoc(); + const js = findFileBuffer(ide.buffers, '/system/common.js').transaction.state.sliceDoc(); const jsBlob = new Blob([js], {type: 'text/javascript'}); const jsURL = URL.createObjectURL(jsBlob); const state = this.transaction.state; @@ -1234,11 +1234,9 @@ init([ 'IMPLEMENTATION-NOTES', 'BIBLIOGRAPHY', 'LICENSE', - 'all-caps.css', - 'all-caps.js', 'core.js', + 'mantle.evl', 'evl2html.xslt', - 'evl2html.css', - 'evl2html.js', - 'mantle.evl' + 'common.css', + 'common.js' ]); diff --git a/src/lang-evlambda.js b/src/lang-evlambda.js index 488ff1b..3364a00 100644 --- a/src/lang-evlambda.js +++ b/src/lang-evlambda.js @@ -57,10 +57,18 @@ const specialIndentations = new Map([ ['_mlambda', [1, 4, 2]], ['_flambda', [1, 4, 2]], ['_dlambda', [1, 4, 2]], + ['block', [1, 4, 2]], + ['catch', [1, 4, 2]], + ['_handler-bind', [1, 4, 2]], + ['unwind-protect', [1, 4, 2]], + ['when', [1, 4, 2]], + ['loop', [0, 0, 2]], ['vlambda', [1, 4, 2]], ['mlambda', [1, 4, 2]], ['flambda', [1, 4, 2]], ['dlambda', [1, 4, 2]], + ['destructuring-bind', [2, 4, 2]], + ['multiple-value-bind', [2, 4, 2]], ['vdef', [1, 4, 2]], ['fdef', [2, 4, 2]], ['mdef', [2, 4, 2]], @@ -71,7 +79,8 @@ const specialIndentations = new Map([ ['vlet*', [1, 4, 2]], ['flet*', [1, 4, 2]], ['dlet*', [1, 4, 2]], - ['fletrec', [1, 4, 2]] + ['fletrec', [1, 4, 2]], + ['ignore-errors', [0, 0, 2]] ]); const localFunctionDefiners = ['flet', 'mlet', 'flet*', 'fletrec']; diff --git a/src/lezer/evlambda.grammar b/src/lezer/evlambda.grammar index 70a4bee..195ad10 100644 --- a/src/lezer/evlambda.grammar +++ b/src/lezer/evlambda.grammar @@ -56,13 +56,13 @@ XMLPureElement { XMLEmptyElementTag { '<' $[a-z]+ '/>' } XMLComment { '' } Number { $[+-]? $[0-9]+ ('.' $[0-9]+)? } - Symbol { (![\t\n\v\f\r\u0020\u0085\u200E\u200F\u2028\u2029\'`,"()#\\] | '\\' _)+ } + Symbol { (![\t\n\u000B\f\r\u0020\u0085\u200E\u200F\u2028\u2029\'`,"()#\\] | '\\' _)+ } @precedence { XMLMixedElementStartTag, XMLPureElementStartTag, Symbol } @precedence { XMLMixedElementEndTag, XMLPureElementEndTag, Symbol } @precedence { XMLEmptyElementTag, Symbol } @precedence { XMLComment, Symbol } @precedence { Number, Symbol } - whitespace { $[\t\n\v\f\r\u0020\u0085\u200E\u200F\u2028\u2029]+ } + whitespace { $[\t\n\u000B\f\r\u0020\u0085\u200E\u200F\u2028\u2029]+ } XMLCharacterData { ![<]+ } } diff --git a/src/lezer/tokens.js b/src/lezer/tokens.js index 347258a..fccda14 100644 --- a/src/lezer/tokens.js +++ b/src/lezer/tokens.js @@ -9,6 +9,7 @@ const specialOperators = [ 'quote', 'progn', 'if', + '_for-each', '_vlambda', '_mlambda', '_flambda', @@ -19,23 +20,28 @@ const specialOperators = [ 'fset!', 'dref', 'dset!', - '_for-each', - '_catch-errors', + 'block', + 'return-from', + 'catch', + 'throw', + '_handler-bind', + 'unwind-protect', 'apply', 'multiple-value-call', 'multiple-value-apply' ]; const macros = [ - 'quasiquote', - 'unquote', - 'unquote-splicing', + 'when', 'cond', 'econd', + 'loop', 'vlambda', 'mlambda', 'flambda', 'dlambda', + 'destructuring-bind', + 'multiple-value-bind', 'vdef', 'fdef', 'mdef', @@ -46,7 +52,8 @@ const macros = [ 'vlet*', 'flet*', 'dlet*', - 'fletrec' + 'fletrec', + 'ignore-errors' ]; const languageKeywords = new Map(); diff --git a/system-files/IMPLEMENTATION-NOTES b/system-files/IMPLEMENTATION-NOTES index 268faa0..f1ff8e7 100644 --- a/system-files/IMPLEMENTATION-NOTES +++ b/system-files/IMPLEMENTATION-NOTES @@ -17,14 +17,14 @@

The implementation notes document the JavaScript file /system/core.js, which implements the “core” of the EVLambda programming language and the EVL to XML converter (one of the two components of the documentation generator, the other one being the XSLT stylesheet). The implementation notes and the JavaScript file have the same organization (the same sections, in the same order) and are meant to be read side by side.

The JavaScript file implements five interpreter-based evaluators:

    -
  • Plain Recursive (plainrec)
  • +
  • Direct Style (directstyle)
  • Continuation Passing Style (cps)
  • Object-Oriented CPS (oocps)
  • Stack-Based Object-Oriented CPS (sboocps)
  • Trampoline (trampoline)
  • Trampoline++ (trampolinepp)
-

Only the Trampoline and Trampoline++ evaluators allow unbounded iterations through tail-recursive calls. The other evaluators are only useful as stepping stones to understand the Trampoline and Trampoline++ evaluators. The Trampoline++ evaluator is an optimized version of the Trampoline evaluator.

+

Only the trampoline and trampoline++ evaluators allow unbounded iterations through tail-recursive calls. The other evaluators are only useful as stepping stones to understand the trampoline and trampoline++ evaluators. The trampoline++ evaluator is an optimized version of the trampoline evaluator.

The JavaScript file can run inside the IDE (actually inside a web worker started from the IDE) or inside a Node.js runtime environment started from the command line.

Here are quick links to the sections:

    @@ -41,41 +41,47 @@
  1. Lexical and Dynamic Environments
  2. Pairing Parameters with Arguments
  3. Generic Evaluator
  4. -
  5. Plain Recursive Evaluator
  6. +
  7. Direct Style Evaluator
  8. Continuation Passing Style Evaluator
  9. Object-Oriented CPS Evaluator
  10. Stack-Based Object-Oriented CPS Evaluator
  11. Trampoline Evaluator
  12. Trampoline++ Evaluator
  13. -
  14. Primitive Function Definitions (1)
  15. +
  16. Primitive Function Definitions
  17. Bounce
  18. Evaluation Request
  19. +
  20. Outcome
  21. +
  22. Abrupt Completion
  23. +
  24. Abrupt Completion of Type error
  25. +
  26. Abrupt Completion of Type nonlocal-exit
  27. Result
  28. -
  29. EVLObjects
  30. -
  31. EVLObject
  32. -
  33. EVLVoid
  34. -
  35. EVLBoolean
  36. -
  37. EVLNumber
  38. -
  39. EVLCharacter
  40. -
  41. EVLString
  42. -
  43. EVLSymbol
  44. -
  45. EVLKeyword
  46. -
  47. EVLVariable
  48. -
  49. EVLList
  50. -
  51. EVLEmptyList
  52. -
  53. EVLCons
  54. -
  55. EVLVector
  56. -
  57. EVLFunction
  58. -
  59. EVLPrimitiveFunction
  60. -
  61. EVLClosure
  62. +
  63. Multiple Values
  64. +
  65. Primitive Data Type object
  66. +
  67. Primitive Data Type void
  68. +
  69. Primitive Data Type boolean
  70. +
  71. Primitive Data Type number
  72. +
  73. Primitive Data Type character
  74. +
  75. Primitive Data Type string
  76. +
  77. Primitive Data Type symbol
  78. +
  79. Primitive Data Type keyword
  80. +
  81. Primitive Data Type variable
  82. +
  83. Primitive Data Type list
  84. +
  85. Primitive Data Type empty-list
  86. +
  87. Primitive Data Type cons
  88. +
  89. Primitive Data Type vector
  90. +
  91. Primitive Data Type function
  92. +
  93. Primitive Data Type primitive-function
  94. +
  95. Primitive Data Type closure
  96. Miscellaneous Primitive Functions
  97. -
  98. Primitive Function Definitions (2)
  99. +
  100. Primitive Function Definitions (Second Steps)
  101. +
  102. Variables (Special Operators, etc.)
  103. Interface (Command Line)

Global Variables

The constant isRunningInsideNode is true if the JavaScript file is running inside a Node.js runtime environment and false otherwise.

The variable abortSignalArray contains a shared array used by the IDE to abort the current evaluation without terminating the web worker.

The variable selectedEvaluator contains the name of the selected evaluator.

+

The constant optimizeMacroCalls controls whether or not the macro expansions should be burned into the code.

Interface (IDE)

This section implements the interface used by the IDE to control the evaluator and the EVL to XML converter.

The evaluator and the EVL to XML converter are running inside a web worker because running them directly inside the main thread would make the GUI unresponsive when they are running.

@@ -97,7 +103,7 @@
  • id: A unique integer id used to pair responses with requests.
  • status: The value of the constant ERROR.
  • -
  • output: A string describing the error.
  • +
  • output: An error message.

When the processing of the requested action fails because of the abortion of an evaluation, the response message contains the following properties:

    @@ -112,7 +118,7 @@
  • action: The value of the constant INITIALIZE.
  • input: An object containing the following properties:
      -
    • abortSignalBuffer: The shared buffer under the shared array.
    • +
    • abortSignalBuffer: The shared buffer underlying the shared array.
    • selectedEvaluator: The name of the selected evaluator.
    • evlFiles: The contents of some EVLambda source files to load.
    @@ -121,10 +127,10 @@

    Response message when the processing of the requested action succeeds:

    • status: The value of the constant SUCCESS.
    • -
    • output: An array containing the printable representations of the values of the last form of the last EVLambda source file.
    • +
    • output: An array containing either the printable representations of the values of the last form of the last EVLambda source file or the printable representation #v if the EVLambda source files do not contain any forms.

    EVALUATE_FIRST_FORM

    -

    This message is used to request the evaluation of the first form contained in some input string.

    +

    This message is used to request the evaluation of the first top-level form contained inside some input string.

    Request message:

    • action: The value of the constant EVALUATE_FIRST_FORM.
    • @@ -137,10 +143,10 @@

      Response message when the processing of the requested action succeeds:

      • status: The value of the constant SUCCESS.
      • -
      • output: An array containing the printable representations of the values of the first form contained in the input string.
      • +
      • output: An array containing the printable representations of the values of the first top-level form contained inside the input string.

      EVALUATE_ALL_FORMS

      -

      This message is used to request the evaluation of all of the forms contained in some input string.

      +

      This message is used to request the evaluation of the top-level forms contained inside some input string.

      Request message:

      • action: The value of the constant EVALUATE_ALL_FORMS.
      • @@ -149,7 +155,7 @@

        Response message when the processing of the requested action succeeds:

        • status: The value of the constant SUCCESS.
        • -
        • output: An array containing the printable representations of the values of the last form contained in the input string.
        • +
        • output: An array containing either the printable representations of the values of the last form contained inside the input string or the printable representation #v if the input string does not contain any forms.

        CONVERT_EVL_TO_XML

        This message is used to request the conversion of some input string from EVL to XML.

        @@ -165,7 +171,7 @@

      Shared Arrays

      abortSignalArray

      -

      The shared array contained in the variable abortSignalArray is used by the IDE to abort the current evaluation without terminating the web worker. The shared array contains one boolean flag represented by an $8$-bit unsigned integer. The IDE requests the abortion of the current evaluation by setting the flag to true. The Trampoline and Trampoline++ evaluators implement the abort-evaluation functionality by regularly checking the value of the flag. The other evaluators do not implement the abort-evaluation functionality.

      +

      The shared array contained in the variable abortSignalArray is used by the IDE to abort the current evaluation without terminating the web worker. The shared array contains one boolean flag represented by an $8$-bit unsigned integer. The IDE requests the abortion of the current evaluation by setting the flag to true. The trampoline and trampoline++ evaluators implement the abort-evaluation functionality by regularly checking the value of the flag. The other evaluators do not implement the abort-evaluation functionality.

      Errors

      This section defines some custom error types.

      Tokenizer

      @@ -175,7 +181,7 @@
    • whitespace: The run of whitespace preceding the lexeme associated with the next token.
    • lexeme: The lexeme associated with the next token.
    • category: The category of the next token.
    • -
    • value: The value of the next token or null if tokens of this category have no value.
    • +
    • value: The value of the next token or null if the next token has no value.

    Reader

    The reader is a recursive descent parser getting its tokens from the tokenizer.

    @@ -196,8 +202,8 @@
  • If the function readObject returns DOT, CLOSING_PARENTHESIS, or XML_END_TAG, then the function read throws an exception.
  • If the function readObject returns EOI, then the function read returns null to signal the invoker that the end of the input has been reached.
-

In order to evaluates all of the forms contained in an EVLambda source file, the function read is invoked in a loop. If the function read returns an object, then that object is evaluated and the loop continues. If the function read returns null, then the loop terminates.

-

When the source file is a plain EVLambda source file, the function read returns each of the forms in turn and all of the forms get evaluated.

+

In order to evaluate the top-level forms contained inside an EVLambda source file, the function read is invoked in a loop. If the function read returns an object, then that object is evaluated and the loop continues. If the function read returns null, then the loop terminates.

+

When the source file is a plain EVLambda source file, the function read returns each top-level form in turn and all top-level forms get evaluated.

When the source file is a documented EVLambda source file, the first invocation of the function read returns null (because the chapter element is treated as a comment and skipped) and nothing seems to get evaluated. Actually, this is not the case because a callback is attached to the tokenizer that will evaluate each object encountered while skipping the chapter element.

EVL to XML Converter

The job of the EVL to XML converter is to produce an output identical to the input except that (1) the characters less-than, greater-than, and ampersand occurring inside the code have been escaped and (2) some tags have been added to better delimit the code from the surrounding documentation and the comments from the surrounding code.

@@ -234,7 +240,7 @@
  • The grammar recognizes the dot but it does not distinguish between proper lists and dotted lists.
  • The value of a token is the lexeme associated with the token.
  • -

    The tags are always added before or after a run of whitespace. The converter considers each of the runs of whitespace contained in the input and, for each of them, decides what to do depending on the following:

    +

    The tags are always added before or after a run of whitespace. The converter considers each of the runs of whitespace contained inside the input and, for each of them, decides what to do depending on the following:

    • Whether the run of whitespace occurs in top-level context, in XML context, or in EVL context.
    • Whether the token preceding the run of whitespace is beginning-of-input, an XML token, or an EVL token.
    • @@ -269,7 +275,8 @@
      $\metavar{evltok}$$\metavar{ws}$$\metavar{evltok}$

      Form Analyzer

      -

      The form analyzer analyzes a form without analyzing the subforms of the form. The subforms of the form are analyzed if and when they are evaluated. The classification part of the analysis is done by the evaluators themselves by checking whether the form is an empty-list, a cons, a variable, or something else and, if the form is a cons, by checking if the car of the cons is a special operator or something else.

      +

      The analysis of a form does not entail the analysis of the direct subforms of the form or, if the form is a lambda abstraction, the analysis of the body of the form. In all evaluators except the trampoline++ evaluator, the direct subforms and the bodies are analyzed if and when they are evaluated. In the trampoline++ evaluator, a top-level form is fully analyzed during the preprocessing phase.

      +

      The first part of the analysis is done by the evaluators themselves by checking whether the form is the empty list, a cons, a variable, or something else and, if the form is a cons, by checking whether the car of the cons is a special operator or something else.

      Scope-Extent Combinations

      The scope-extent combinations are identified using the members of the following enumeration:

        @@ -281,10 +288,12 @@
        • VAL_NS: value namespace
        • FUN_NS: function namespace
        • +
        • BLK_NS: block namespace
        • +
        • XIT_NS: exit-point namespace

        Global Environment

        -

        Because the global environment is unique, any given variable can have, at any given time, at most one global binding in the value namespace and at most one global binding in the function namespace. An obvious way to implement the global environment is thus to store the global bindings for a variable directly on the JavaScript object representing the variable.

        -

        Variables are represented by instances of the JavaScript class EVLVariable. The property value of the instance representing a variable contains the value of the binding for the variable in the value namespace of the global environment if that binding exists and null otherwise. The property function of the instance representing a variable contains the value of the binding for the variable in the function namespace of the global environment if that binding exists and null otherwise.

        +

        Because the global environment is unique, any given variable can have, at any given time, at most one global binding in the value namespace and one global binding in the function namespace. An obvious way to implement the global environment is thus to store the global bindings for a variable directly on the JavaScript object representing the variable.

        +

        Variables are represented by instances of the class EVLVariable. The property value of the instance representing a variable contains the value of the binding for the variable in the value namespace of the global environment if that binding exists and null otherwise. The property function of the instance representing a variable contains the value of the binding for the variable in the function namespace of the global environment if that binding exists and null otherwise.

        Lexical and Dynamic Environments

        The user manual specifies the lexical and dynamic environments as follows:

          @@ -292,26 +301,22 @@
        • Each lexical or dynamic environment is the result of extending zero or more times in sequence an empty environment.
        • Extending a lexical or dynamic environment is achieved by copying the two sets of bindings and then deleting and adding some bindings from/to the copy of one of the two sets.
        -

        A lexical or dynamic environment is represented not by the result of the sequence of extensions leading to the environment but by a recording of the sequence of extensions leading to the environment. A single extension is recorded into a data structure called a frame and a sequence of extensions is recorded into a chain of frames.

        +

        The reference manual introduces two new namespaces: the block namespace and the exit-point namespace.

        +

        In actuality, a lexical or dynamic environment is represented not by the result of the sequence of extensions leading to the environment but by a recording of the sequence of extensions leading to the environment. A single extension is recorded into a data structure called a frame and a sequence of extensions is recorded into a chain of frames.

        Let $\env$ be a lexical or dynamic environment and$$[(\ns_1,[\var_{1,1},\ldots,\var_{1,m_1}],[\obj_{1,1},\ldots,\obj_{1,m_1}]),\ldots,(\ns_n,[\var_{n,1},\ldots,\var_{n,m_n}],[\obj_{n,1},\ldots,\obj_{n,m_n}])]$$be the sequence of extensions leading to the environment. The extensions are applied from left to right and the first extension is applied to an empty environment. The $i$-th extension$$(\ns_i,[\var_{i,1},\ldots,\var_{i,m_i}],[\obj_{i,1},\ldots,\obj_{i,m_i}])$$extends the previous environment by binding, in the namespace $\ns_i$, the variable $\var_{i,j}$ to the object $\obj_{i,j}$ (for all $i$ from $1$ to $n$ and all $j$ from $1$ to $m_i$). We will denote by $\binding(i,j)$ the binding between $\var_{i,j}$ and $\obj_{i,j}$ created by the $i$-th extension.

        -

        The binding for the variable $\var$ in the namespace $\ns$ of the environment $\env$ can easily be located using the following lookup rule expressed in pseudocode:

        +

        The binding for the variable $\var$ in the namespace $\ns$ of the environment $\env$ can easily be located using the following lookup procedure:

        - Initialize $i$ to $n$.
        - While $i>0$, do the following: -
        - If $\ns_i=\ns$ and there exists $j\in\{1,\ldots,m_i\}$ such that $\var_{i,j}=\var$ (if such $j$ exists, it is necessarily unique because the variables $\var_{i,1},\ldots,\var_{i,m_i}$ are distinct), then $\binding(i,j)$ is the binding for the variable $\var$ in the namespace $\ns$ of the environment $\env$. Otherwise, decrement $i$ by $1$. -
        - The variable $\var$ in unbound in the namespace $\ns$ of the environment $\env$. + Initialize $i$ to $n$. Loop: If $i=0$, then the variable $\var$ in unbound in the namespace $\ns$ of the environment $\env$. Otherwise, if $\ns_i=\ns$ and there exists $j\in\{1,\ldots,m_i\}$ such that $\var_{i,j}=\var$ (if such $j$ exists, it is necessarily unique because the variables $\var_{i,1},\ldots,\var_{i,m_i}$ are distinct), then $\binding(i,j)$ is the binding for the variable $\var$ in the namespace $\ns$ of the environment $\env$. Otherwise, decrement $i$ by $1$ and loop.
        -

        By selecting the greatest $i$ satisfying the conditions, the lookup rule correctly handles the possibility of shadowing of a binding by another binding.

        -

        Lexical and dynamic environments are represented by instances of the classes NullDefiniteEnv and Frame, which are the two concrete subclasses of the abstract class DefiniteEnv.

        +

        By selecting the greatest $i$ satisfying the conditions, the lookup procedure correctly handles the possibility of shadowing of a binding by another binding.

        +

        Lexical and dynamic environments are represented by instances of the classes Frame and NullDefiniteEnv, which are the two concrete subclasses of the abstract class DefiniteEnv.

        For the sake of this document, lexical and dynamic environments are called definite because their scope or their extent is definite. The bindings of a lexical environment have lexical, and thus definite, scope. The bindings of a dynamic environment have dynamic, and thus definite, extent. By contrast, the bindings of the global environment have indefinite scope and indefinite extent.

        Instances of Frame, which represent single extensions, have four properties:

          -
        • namespace: VAL_NS or FUN_NS
        • +
        • namespace: VAL_NS, FUN_NS, BLK_NS, or XIT_NS
        • variables: an array of distinct variables
        • values: an array of objects
        • -
        • next: an instance of NullDefiniteEnv or Frame
        • +
        • next: an instance of Frame or NullDefiniteEnv

        The two arrays variables and values must have the same length $m\ge0$. The array variables contains the variables $\var_1,\ldots,\var_m$ and the array values contains the objects $\obj_1,\ldots,\obj_m$. The property next is used to create chains of instances of Frame. Each chain is terminated by an instance of NullDefiniteEnv, which represents an empty environment.

        Pairing Parameters with Arguments

        @@ -348,79 +353,90 @@
        d is paired with (car (cdr $z$))$\,=\,$4
        e is paired with (cdr (cdr $z$))$\,=\,$() ($0$ new conses and $0$ reused conses)
        (apply (_vlambda (a b c d e . f) (list a b c d e f)) $x=\code{1}$ $y=\code{2}$ $z=\code{'(3 4)}$)
        -
        ERROR: Too few arguments.
        +
        EvaluatorError: too-few-arguments: Too few arguments.

        Generic Evaluator

        The generic evaluator simply dispatches the form to evaluate to the selected evaluator.

        -

        Plain Recursive Evaluator

        +

        Direct Style Evaluator

        Continuation Passing Style Evaluator

        Object-Oriented CPS Evaluator

        Stack-Based Object-Oriented CPS Evaluator

        Trampoline Evaluator

        Trampoline++ Evaluator

        -

        Primitive Function Definitions (1)

        -

        Defining a primitive function is a two-step process. First, the function primitiveFunction is used to add to the Map contained in the variable primitiveFunctions an entry mapping the name of the primitive function to a record containing the minimum number of arguments, the maximum number of arguments, and the JavaScript function implementing the primitive function. Then, a binding between the name of the primitive function and an instance of the JavaScript class EVLPrimitiveFunction is added to the function namespace of the global environment.

        -

        The first step occurs where it makes the most sense. For the primitive functions related to a primitive date type, the first step occurs inside the section defining the JavaScript class associated with the primitive data type.

        -

        The second step is postponed to the section Primitive Function Definitions (2) because it cannot happen before the JavaScript class EVLPrimitiveFunction has been defined.

        +

        Primitive Function Definitions

        +

        Defining a primitive function is a two-step process. First, the function primitiveFunction is used to add to the Map contained in the variable primitiveFunctions an entry mapping the name of the primitive function to a record containing the minimum number of arguments, the maximum number of arguments, and the JavaScript function implementing the primitive function. Then, a binding between the name of the primitive function and an instance of the class EVLPrimitiveFunction is added to the function namespace of the global environment.

        +

        The first step occurs where it makes the most sense. For the primitive functions related to a primitive date type, the first step occurs inside the section defining the class representing the primitive data type.

        +

        The second step is postponed to the section Primitive Function Definitions (Second Steps) because it cannot happen before the class EVLPrimitiveFunction has been defined.

        The actual number of arguments is checked against the minimum and maximum numbers of arguments by the code implementing the invocation of primitive functions.

        The types of the arguments are checked by the JavaScript functions implementing the primitive functions.

        Bounce

        -

        A bounce is what is sent back to the trampoline. A bounce is either an evaluation request or a result.

        +

        A bounce is what is sent back to the trampoline. A bounce is either an evaluation request or an outcome.

        Evaluation Request

        -

        An evaluation request specifies the form to evaluate and the lexical environment with respect to which the form is to be evaluated. The dynamic environment with respect to which the form is to be evaluated is always the current dynamic environment.

        +

        An evaluation request specifies a form to evaluate and the lexical environment with respect to which the form is to be evaluated. The dynamic environment with respect to which the form is to be evaluated is always the current dynamic environment. An evaluation request is represented by an instance of the class EvalReq.

        +

        Outcome

        +

        An outcome is either an abrupt completion or the result of a normal completion.

        +

        Abrupt Completion

        +

        An abrupt completion is either an abrupt completion of type error or an abrupt completion of type nonlocal-exit.

        +

        Abrupt Completion of Type error

        +

        An abrupt completion of type error is represented by an instance of the class EVLError.

        +

        Abrupt Completion of Type nonlocal-exit

        +

        An abrupt completion of type nonlocal-exit is represented by an instance of the class NonlocalExit.

        Result

        -

        A result is either an instance of the class EVLObjects or an instance of a concrete subclass of the abstract class EVLObject.

        -

        EVLObjects

        -

        An instance of the class EVLOjects is a result consisting of a sequence of zero or more objects.

        -

        The method primaryValue returns #v if the result consists of a sequence of zero objects and the first object otherwise.

        -

        The method allValues returns an array containing all of the objects.

        -

        EVLObject

        -

        An instance of a concrete subclass of the abstract class EVLObject is two things at once: an object and a result consisting of a single object.

        +

        The result of a normal completion is represented by an instance of the class EVLObjects (when the result consists of zero or more than one object) or by an instance of a concrete subclass of the abstract class EVLObject (when the result consists of exactly one object).

        +

        Multiple Values

        +

        A result consisting of zero or more than one object is represented by an instance of the class EVLOjects.

        +

        The method primaryValue returns #v if the result consists of zero objects and the first object otherwise.

        +

        The method allValues returns an array containing the objects composing the result.

        +

        Primitive Data Type object

        +

        The abstract class EVLObject represents the primitive data type object.

        +

        An instance of a concrete subclass of the abstract class EVLObject can represent one of two things: an object or a result consisting of a single object.

        The method primaryValue returns the single object.

        The method allValues returns an array containing the single object.

        The method eql implements the default behavior of the equality predicate eql? (that is, pointer equality). The method is overridden in the classes EVLNumber, EVLCharacter, and EVLString.

        -

        The method toString is overridden in some subclasses of the abstract class EVLObject to return the printable representation of this.

        -

        EVLVoid

        -

        An instance of the class EVLVoid is an object of type void.

        -

        EVLBoolean

        -

        An instance of the class EVLBoolean is an object of type boolean.

        -

        EVLNumber

        -

        An instance of the class EVLNumber is an object of type number.

        -

        EVLCharacter

        -

        An instance of the class EVLCharacter is an object of type character.

        -

        EVLString

        -

        An instance of the class EVLString is an object of type string.

        -

        EVLSymbol

        -

        An instance of a concrete subclass of the abstract class EVLSymbol is an object of type symbol.

        -

        EVLKeyword

        -

        An instance of the class EVLKeyword is an object of type keyword.

        -

        EVLVariable

        -

        An instance of the class EVLVariable is an object of type variable.

        -

        EVLList

        -

        An instance of a concrete subclass of the abstract class EVLList is an object of type list.

        -

        EVLEmptyList

        -

        An instance of the class EVLEmptyList is an object of type empty-list.

        -

        EVLCons

        -

        An instance of the class EVLCons is an object of type cons.

        -

        EVLVector

        -

        An instance of the class EVLVector is an object of type vector.

        -

        EVLFunction

        -

        An instance of a concrete subclass of the abstract class EVLFunction is an object of type function.

        -

        EVLPrimitiveFunction

        -

        An instance of the class EVLPrimitiveFunction is an object of type primitive-function.

        -

        EVLClosure

        -

        An instance of the class EVLClosure is an object of type closure.

        +

        The default implementation of the method toString is overridden in some subclasses to return the printable representation of this.

        +

        Primitive Data Type void

        +

        An object of type void is represented by a instance of the class EVLVoid.

        +

        Primitive Data Type boolean

        +

        An object of type boolean is represented by a instance of the class EVLBoolean.

        +

        Primitive Data Type number

        +

        An object of type number is represented by a instance of the class EVLNumber.

        +

        Primitive Data Type character

        +

        An object of type character is represented by a instance of the class EVLCharacter.

        +

        Primitive Data Type string

        +

        An object of type string is represented by a instance of the class EVLString.

        +

        Primitive Data Type symbol

        +

        The abstract class EVLSymbol represents the primitive data type symbol.

        +

        Primitive Data Type keyword

        +

        An object of type keyword is represented by a instance of the class EVLKeyword.

        +

        Primitive Data Type variable

        +

        An object of type variable is represented by a instance of the class EVLVariable.

        +

        Primitive Data Type list

        +

        The abstract class EVLList represents the primitive data type list.

        +

        Primitive Data Type empty-list

        +

        An object of type empty-list is represented by a instance of the class EVLEmptyList.

        +

        Primitive Data Type cons

        +

        An object of type cons is represented by a instance of the class EVLCons.

        +

        Primitive Data Type vector

        +

        An object of type vector is represented by a instance of the class EVLVector.

        +

        Primitive Data Type function

        +

        The abstract class EVLFunction represents the primitive data type function.

        +

        Primitive Data Type primitive-function

        +

        An object of type primitive-function is represented by a instance of the class EVLPrimitiveFunction.

        +

        Primitive Data Type closure

        +

        An object of type closure is represented by a instance of the class EVLClosure.

        Miscellaneous Primitive Functions

        This section implements the primitive functions values, error, and now.

        -

        Primitive Function Definitions (2)

        +

        Primitive Function Definitions (Second Steps)

        The second steps of the primitive function definitions all occur in this section.

        +

        Variables (Special Operators, etc.)

        +

        This section creates and interns the objects of type variable naming the special operators, the boolean operators (which can occur in feature expressions), and the macro mlet (which is handled directly by the trampoline++ evaluator).

        Interface (Command Line)

        This section implements the command-line interface used to control the evaluator and the EVL to XML converter.

        The syntax of the command line to control the evaluator is as follows:

        -
        node core.js { --plainrec | --cps | --oocps | --sboocps | --trampoline | --trampoliepp }? { -l $\metavar{file}$ | -e $\metavar{form}$ }*
        +
        node core.js { --directstyle | --cps | --oocps | --sboocps | --trampoline | --trampoliepp }? { -l $\metavar{file}$ | -e $\metavar{form}$ }*

        The options and arguments have the following meanings:

          -
        • --plainrec: selects the plain recursive evaluator
        • +
        • --directstyle: selects the direct style evaluator
        • --cps: selects the continuation passing style evaluator
        • --oocps: selects the object-oriented CPS evaluator
        • --sboocps: selects the stack-based object-oriented CPS evaluator
        • diff --git a/system-files/REFERENCE-MANUAL b/system-files/REFERENCE-MANUAL index dcaad6d..4472952 100644 --- a/system-files/REFERENCE-MANUAL +++ b/system-files/REFERENCE-MANUAL @@ -18,6 +18,9 @@ $\DeclareMathOperator{\spread}{spread}$ $\DeclareMathOperator{\serialform}{serial-form}$ $\DeclareMathOperator{\iftestform}{if-test-form}$ + $\DeclareMathOperator{\foreachfunctionform}{\_for-each-function-form}$ + $\DeclareMathOperator{\foreachlistform}{\_for-each-list-form}$ + $\DeclareMathOperator{\foreachinvocation}{\_for-each-invocation}$ $\DeclareMathOperator{\setvalueform}{set-value-form}$ $\DeclareMathOperator{\blockserialforms}{block-serial-forms}$ $\DeclareMathOperator{\returnfromvaluesform}{return-from-values-form}$ @@ -27,11 +30,9 @@ $\DeclareMathOperator{\throwvaluesform}{throw-values-form}$ $\DeclareMathOperator{\handlerbindhandlerform}{\_handler-bind-handler-form}$ $\DeclareMathOperator{\handlerbindserialforms}{\_handler-bind-serial-forms}$ + $\DeclareMathOperator{\handlerbindinvocation}{\_handler-bind-invocation}$ $\DeclareMathOperator{\unwindprotectprotectedform}{unwind-protect-protected-form}$ $\DeclareMathOperator{\unwindprotectcleanupforms}{unwind-protect-cleanup-forms}$ - $\DeclareMathOperator{\foreachfunctionform}{\_for-each-function-form}$ - $\DeclareMathOperator{\foreachlistform}{\_for-each-list-form}$ - $\DeclareMathOperator{\foreachelement}{\_for-each-element}$ $\DeclareMathOperator{\functioncalloperatorform}{function-call-operator-form}$ $\DeclareMathOperator{\functioncalloperandform}{function-call-operand-form}$ $\DeclareMathOperator{\macro}{macro}$ @@ -63,7 +64,7 @@

        Note that some whitespace is needed to separate the lexeme + from the lexeme 123 and the lexeme 123 from the lexeme 456 but no whitespace is needed to separate the lexeme ( from the lexeme + or the lexeme 456 from the lexeme ).

        Step 2.2 A component of the reader called the parser converts the sequence of tokens from step 2.1 (minus the tokens of category whitespace, which are ignored by the parser) into a cons whose car is the variable from step 2.1 and whose cdr is a cons whose car is the first number from step 2.1 and whose cdr is a cons whose car is the second number from step 2.1 and whose cdr is the empty list. Together, those three conses represent the list (+ 123 456).

        -

        Step 3 The evaluator evaluates the list (+ 123 456) to the number 579. The evaluation of the top-level form (+ 123 456) entails the evaluation of other non-top-level forms. Each form must be analyzed in order to determine how it should be evaluated. The form (+ 123 456) is analyzed as a plain function call. The variable + is treated as an abbreviation for the form (fref +). The form (fref +) is analyzed as an fref-form. The forms 123 and 456 are analyzed as self-evaluating objects. Because the global function + is a closure, its invocation entails the evaluation (and thus the analysis) of other forms. The component of the evaluator responsible for analyzing forms is called the form analyzer.

        +

        Step 3 The evaluator evaluates the list (+ 123 456) to the number 579. The evaluation of the top-level form (+ 123 456) entails the evaluation of other non-top-level forms. Each form must be analyzed in order to determine how it should be evaluated. The form (+ 123 456) is analyzed as a plain function call. The variable + is treated as an abbreviation for the form (fref +). The form (fref +) is analyzed as an fref form. The forms 123 and 456 are analyzed as self-evaluating objects. Because the global function + is a closure, its invocation entails the evaluation (and thus the analysis) of other forms. The component of the evaluator responsible for analyzing forms is called the form analyzer.

        Step 4 The printer converts the number 579 into the sequence of characters 579. (The sequence of characters is the printable representation of the number.)

        Step 5 The sequence of characters 579 is written into the listener buffer.

        EVLambda has three levels of syntax:

        @@ -75,19 +76,19 @@

        Each level of syntax is described later in its own section.

        EVLambda Source Files

        The reader is used not only to convert the characters typed into a listener buffer into an object but also to convert the characters contained inside an EVLambda source file into a sequence of objects.

        -

        EVLambda source files come in two varieties: the plain EVLambda source files, which contain only EVLambda source code, and the documented EVLambda source files, which contain a mix of EVLambda source code and documentation in XML format.

        +

        EVLambda source files come in two varieties: the plain EVLambda source files, which only contain EVLambda source code, and the documented EVLambda source files, which contain a mix of EVLambda source code and documentation in XML format.

        Here is an example of a plain EVLambda source file:

        (fdef fact (n)
        (if (= n 0)
        1
        (* n (fact (- n 1)))))

        (test 1 (fact 0))
        (test 120 (fact 5))
        (test 3628800 (fact 10))

        (fdef fib (n)
        (if (= n 0)
        0
        (if (= n 1)
        1
        (+ (fib (- n 1)) (fib (- n 2))))))

        (test 0 (fib 0))
        (test 5 (fib 5))
        (test 55 (fib 10))

        Here is an example of a documented EVLambda source file:

        -
        <chapter>
        <title>Recursive Functions</title>
        <para>...para...</para>
        <para>...para...</para>
        <section>
        <title>Factorial Function</title>
        <para>...para...</para>
        <para>...para...</para>
        (fdef fact (n)
        <para>...block...</para>
        <para>...block...</para>
        (if (= n 0)
        1 <comment>...eol...</comment>
        (* n (fact (- n 1))))) <comment>...eoll...</comment>

        (test 1 (fact 0))
        (test 120 (fact 5))
        (test 3628800 (fact 10))
        </section>
        <section>
        <title>Fibonacci Sequence</title>
        <para>...para...</para>
        <para>...para...</para>
        (fdef fib (n)
        <para>...block...</para>
        <para>...block...</para>
        (if (= n 0)
        0 <comment>...eol...</comment>
        (if (= n 1)
        1 <comment>...eol...</comment>
        (+ (fib (- n 1)) (fib (- n 2)))))) <comment>...eoll...</comment>

        (test 0 (fib 0))
        (test 5 (fib 5))
        (test 55 (fib 10))
        </section>
        </chapter>
        +
        <chapter>
        <title>Recursive Functions</title>
        <p>...para...</p>
        <p>...para...</p>
        <section>
        <title>Factorial Function</title>
        <p>...para...</p>
        <p>...para...</p>
        (fdef fact (n)
        <p>...block...</p>
        <p>...block...</p>
        (if (= n 0)
        1 <comment>...eol...</comment>
        (* n (fact (- n 1))))) <comment>...eoll...</comment>

        (test 1 (fact 0))
        (test 120 (fact 5))
        (test 3628800 (fact 10))
        </section>
        <section>
        <title>Fibonacci Sequence</title>
        <p>...para...</p>
        <p>...para...</p>
        (fdef fib (n)
        <p>...block...</p>
        <p>...block...</p>
        (if (= n 0)
        0 <comment>...eol...</comment>
        (if (= n 1)
        1 <comment>...eol...</comment>
        (+ (fib (- n 1)) (fib (- n 2)))))) <comment>...eoll...</comment>

        (test 0 (fib 0))
        (test 5 (fib 5))
        (test 55 (fib 10))
        </section>
        </chapter>

        Documented EVLambda source files can be converted to HTML by a component of the programming language called the documentation generator.

        Extensible Markup Language (XML)

        An extensible markup language (XML) document is a annotated text document. An XML document is divided into two intermingled parts: the character data (the content) and the markup (the annotations). Markup can take many forms. Documented EVLambda source files use the following forms of markup:

        start tags (without attributes)
        -
        <chapter>, <section>, <title>, <para>, <comment>, …
        +
        <chapter>, <section>, <title>, <p>, <comment>, …
        end tags
        -
        </chapter>, </section>, </title>, </para>, </comment>, …
        +
        </chapter>, </section>, </title>, </p>, </comment>, …
        empty-element tags (without attributes)
        <br/>, …
        comments
        @@ -453,7 +454,7 @@
        Value: The value is an object of type number representing the mathematical number of which the value matching the pattern is a decimal representation.
        keyword
        Pattern: ':' [^:]+
        -
        Value: The value is the object of type keyword whose name is the value matching the pattern.
        +
        Value: The value is the object of type keyword whose name is the value matching the pattern minus the initial colon.
        variable
        Pattern: [^:]+
        Value: The value is the object of type variable whose name is the value matching the pattern.
        @@ -582,18 +583,18 @@
        $\metavar{input}\Coloneq$ $\metavar{object}$
        $\mathcal{M}(\metavar{input})=[\mathcal{M}(\metavar{object})]$
        -
        $\metavar{input}\Coloneq$ $\metavarn{object}{1}\ldots\metavarn{object}{n\geq0}$
        -
        $\mathcal{M}(\metavar{input})=[\mathcal{M}(\metavarn{object}{1}),\ldots,\mathcal{M}(\metavarn{object}{n})]$
        -
        $\metavar{input}\Coloneq$ $\metavar{xml-comment}$* $\metavar{xml-element}$ $\metavar{xml-comment}$*
        -
        $\mathcal{M}(\metavar{input})=\mathcal{M}(\metavar{xml-element})$
        +
        $\metavar{input}\Coloneq$ $\metavar{object}_1\ldots\metavar{object}_{n\ge0}$
        +
        $\mathcal{M}(\metavar{input})=[\mathcal{M}(\metavar{object}_1),\ldots,\mathcal{M}(\metavar{object}_n)]$
        +
        $\metavar{input}\Coloneq$ $\metavar{cmt}_1\ldots\metavar{cmt}_{n\ge0}$ $\metavar{xml-element}$ $\metavar{cmt}'_1\ldots\metavar{cmt}'_{n'\ge0}$
        +
        $\mathcal{M}(\metavar{input})=\mathcal{M}(\metavar{cmt}_1)\|\cdots\|\mathcal{M}(\metavar{cmt}_n)\|\mathcal{M}(\metavar{xml-element})\|\mathcal{M}(\metavar{cmt}'_1)\|\cdots\|\mathcal{M}(\metavar{cmt}'_{n'})$
        $\metavar{xml-markup}\Coloneq$ $\metavar{xml-element}$
        $\mathcal{M}(\metavar{xml-markup})=\mathcal{M}(\metavar{xml-element})$
        $\metavar{xml-markup}\Coloneq$ $\metavar{xml-comment}$
        $\mathcal{M}(\metavar{xml-markup})=\mathcal{M}(\metavar{xml-comment})$
        -
        $\metavar{xml-element}\Coloneq$ xml-start-tag $\metavarn{xml-element-content}{1}\ldots\metavarn{xml-element-content}{n\geq0}$ xml-end-tag
        -
        $\mathcal{M}(\metavar{xml-element})=\mathcal{M}(\metavarn{xml-element-content}{1})\|\cdots\|\mathcal{M}(\metavarn{xml-element-content}{n})$
        +
        $\metavar{xml-element}\Coloneq$ xml-start-tag $\metavar{xml-element-content}_1\ldots\metavar{xml-element-content}_{n\ge0}$ xml-end-tag
        +
        $\mathcal{M}(\metavar{xml-element})=\mathcal{M}(\metavar{xml-element-content}_1)\|\cdots\|\mathcal{M}(\metavar{xml-element-content}_n)$
        $\metavar{xml-element}\Coloneq$ xml-empty-element-tag
        $\mathcal{M}(\metavar{xml-element})=[]$
        @@ -654,14 +655,14 @@
        $\metavar{proper-list}\Coloneq$ left-parenthesis right-parenthesis
        $\mathcal{M}(\metavar{proper-list})=$ the empty list
        -
        $\metavar{proper-list}\Coloneq$ left-parenthesis $\metavarn{object}{1}\ldots\metavarn{object}{n\geq1}$ right-parenthesis
        -
        $\mathcal{M}(\metavar{proper-list})=$ the first cons of a chain of $n$ conses $\cons_1,\ldots,\cons_n$ such that the car of $\cons_i$ is $\mathcal{M}(\metavarn{object}{i})$, the cdr of $\cons_{i\lt n}$ is $\cons_{i+1}$, and the cdr of $\cons_n$ is the empty list
        +
        $\metavar{proper-list}\Coloneq$ left-parenthesis $\metavar{object}_1\ldots\metavar{object}_{n\ge1}$ right-parenthesis
        +
        $\mathcal{M}(\metavar{proper-list})=$ the first cons of a chain of $n$ conses $\cons_1,\ldots,\cons_n$ such that the car of $\cons_i$ is $\mathcal{M}(\metavar{object}_i)$, the cdr of $\cons_{i\lt n}$ is $\cons_{i+1}$, and the cdr of $\cons_n$ is the empty list
        -
        $\metavar{dotted-list}\Coloneq$ left-parenthesis $\metavarn{object}{1}\ldots\metavarn{object}{n\geq1}$ dot $\metavarn{object}{n+1}$ right-parenthesis
        -
        $\mathcal{M}(\metavar{dotted-list})=$ the first cons of a chain of $n$ conses $\cons_1,\ldots,\cons_n$ such that the car of $\cons_i$ is $\mathcal{M}(\metavarn{object}{i})$, the cdr of $\cons_{i\lt n}$ is $\cons_{i+1}$, and the cdr of $\cons_n$ is $\mathcal{M}(\metavarn{object}{n+1})$
        +
        $\metavar{dotted-list}\Coloneq$ left-parenthesis $\metavar{object}_1\ldots\metavar{object}_{n\ge1}$ dot $\metavar{object}_{n+1}$ right-parenthesis
        +
        $\mathcal{M}(\metavar{dotted-list})=$ the first cons of a chain of $n$ conses $\cons_1,\ldots,\cons_n$ such that the car of $\cons_i$ is $\mathcal{M}(\metavar{object}_i)$, the cdr of $\cons_{i\lt n}$ is $\cons_{i+1}$, and the cdr of $\cons_n$ is $\mathcal{M}(\metavar{object}_{n+1})$
        -
        $\metavar{vector}\Coloneq$ hash-left-parenthesis $\metavarn{object}{1}\ldots\metavarn{object}{n\geq0}$ right-parenthesis
        -
        $\mathcal{M}(\metavar{vector})=$ a vector of $n$ elements $\mlvar{elem}_1,\ldots,\mlvar{elem}_n$ such that $\mlvar{elem}_i$ is $\mathcal{M}(\metavarn{object}{i})$
        +
        $\metavar{vector}\Coloneq$ hash-left-parenthesis $\metavar{object}_1\ldots\metavar{object}_{n\ge0}$ right-parenthesis
        +
        $\mathcal{M}(\metavar{vector})=$ a vector of $n$ elements $\mlvar{elem}_1,\ldots,\mlvar{elem}_n$ such that $\mlvar{elem}_i$ is $\mathcal{M}(\metavar{object}_i)$

        Note that the XML markup inside abbreviations, lists, and vectors cannot produce objects and has therefore been omitted from the definition of the function $\mathcal{M}$.

        Pattern Language and Template Languages

        @@ -745,14 +746,14 @@ #($\metavar{pattern}$*) -

        The rules specifying if an object matches a pattern are the following:

        +

        The rules specifying whether an object matches a pattern are the following:

        • An object matches the name of a type if and only if the object is a member of the type.
        • -
        • An object matches a readable representation if and only if the object is (or is eql to) the object denoted by the readable representation.
        • +
        • An object matches a readable representation if and only if the object is eql? to the object denoted by the readable representation.
        • An object matches the pattern () if and only if the object is the empty list.
        • -
        • An object matches the pattern ($\metavarn{pattern}{1}\ldots\metavarn{pattern}{n\geq0}$) if and only if the object is the first cons of a chain of $n$ conses $\cons_1,\ldots,\cons_n$ such that the car of $\cons_i$ matches $\metavarn{pattern}{i}$, the cdr of $\cons_{i\lt n}$ is $\cons_{i+1}$, and the cdr of $\cons_n$ is the empty list.
        • -
        • An object matches the pattern ($\metavarn{pattern}{1}\ldots\metavarn{pattern}{n\geq1}$ . $\metavarn{pattern}{n+1}$) if and only if the object is the first cons of a chain of $n$ conses $\cons_1,\ldots,\cons_n$ such that the car of $\cons_i$ matches $\metavarn{pattern}{i}$, the cdr of $\cons_{i\lt n}$ is $\cons_{i+1}$, and the cdr of $\cons_n$ matches $\metavarn{pattern}{n+1}$.
        • -
        • An object matches the pattern #($\metavarn{pattern}{1}\ldots\metavarn{pattern}{n\geq0}$) if and only if the object is a vector of $n$ elements $\mlvar{elem}_1,\ldots,\mlvar{elem}_n$ such that $\mlvar{elem}_i$ matches $\metavarn{pattern}{i}$.
        • +
        • An object matches the pattern ($\metavar{pattern}_1\ldots\metavar{pattern}_{n\ge0}$) if and only if the object is the first cons of a chain of $n$ conses $\cons_1,\ldots,\cons_n$ such that the car of $\cons_i$ matches $\metavar{pattern}_i$, the cdr of $\cons_{i\lt n}$ is $\cons_{i+1}$, and the cdr of $\cons_n$ is the empty list.
        • +
        • An object matches the pattern ($\metavar{pattern}_1\ldots\metavar{pattern}_{n\ge1}$ . $\metavar{pattern}_{n+1}$) if and only if the object is the first cons of a chain of $n$ conses $\cons_1,\ldots,\cons_n$ such that the car of $\cons_i$ matches $\metavar{pattern}_i$, the cdr of $\cons_{i\lt n}$ is $\cons_{i+1}$, and the cdr of $\cons_n$ matches $\metavar{pattern}_{n+1}$.
        • +
        • An object matches the pattern #($\metavar{pattern}_1\ldots\metavar{pattern}_{n\ge0}$) if and only if the object is a vector of $n$ elements $\mlvar{elem}_1,\ldots,\mlvar{elem}_n$ such that $\mlvar{elem}_i$ matches $\metavar{pattern}_i$.

        By way of example, let us consider the template language specified by the following context-free grammar:

        @@ -782,7 +783,18 @@

        The objects specified by the example template language (= the objects matching at least one sentence of the example template language) are the proper lists of conses of arbitrary objects. As suggested by the names of the nonterminal symbols, the first object of each cons operates as a key, the second object of each cons operates as a value, and the proper list as a whole is called an association list.

        Read-Time Conditionalization Facility

        -

        The purpose of the read-time conditionalization facility is to alter the flow of tokens according to some conditions. Because the facility is conceptually located between the tokenizer and the parser, its associated syntax does not belong to the context-free grammar used by the parser. It is however convenient to specify the syntax of a read-time conditional by the following production rule:

        +

        A feature is a symbol associated with a property of the implementation. The feature list is the proper list of features that is the value of the global variable *features*. For each feature, the meanings of the presence of the feature on the feature list and the absence of the feature from the feature list must be specified.

        +

        Each evaluator has an associated feature:

        +
        + + + + + + + +
        FeatureMeaning
        directstyleThe feature is present on the feature list if and only if the evaluator is an instance of the direct style evaluator.
        cpsThe feature is present on the feature list if and only if the evaluator is an instance of the continuation passing style evaluator.
        oocpsThe feature is present on the feature list if and only if the evaluator is an instance of the object-oriented CPS evaluator.
        sboocpsThe feature is present on the feature list if and only if the evaluator is an instance of the stack-based object-oriented CPS evaluator.
        trampolineThe feature is present on the feature list if and only if the evaluator is an instance of the trampoline evaluator.
        trampolineppThe feature is present on the feature list if and only if the evaluator is an instance of the trampoline++ evaluator.
        +

        The purpose of the read-time conditionalization facility is to alter the flow of tokens according to whether or not some features belong or do not belong to the feature list. Because the facility is conceptually located between the tokenizer and the parser, its associated syntax does not belong to the context-free grammar used by the parser. It is however convenient to specify the syntax of a read-time conditional by the following production rule:

        @@ -791,13 +803,18 @@
        $\metavar{read-time-conditional}$

        The first object is called the feature expression and the second object is called the conditionalized object.

        -

        When the facility encounters a hash-plus or a hash-minus token, it instructs the parser to parse, hand over, and discard the feature expression. The feature expression is then evaluated by the facility according to the rules stated below. If the feature expression evaluates to true, then the facility ends its processing of the read-time conditional by instructing the parser to parse and retain the conditionalized object. (The net effect is the same as if only the tokens of the conditionalized object had existed.) If the feature expression evaluates to false, then the facility ends its processing of the read-time conditional by instructing the parser to parse and discard the conditionalized object. (The net effect is the same as if the tokens of the read-time conditional had not existed.)

        -

        Feature expressions must belong to the set objects specified by the template language specified by the following context-free grammar:

        +

        When the facility encounters a hash-plus or a hash-minus token, it instructs the parser to parse, hand over, and discard the feature expression. The feature expression is then evaluated by the facility according to the rules stated below. If the read-time conditional starts with a hash-plus token and the feature expression evaluates to true or the read-time conditional starts with a hash-minus token and the feature expression evaluates to false, then the facility ends its processing of the read-time conditional by instructing the parser to parse and retain the conditionalized object. (The net effect is the same as if only the tokens of the conditionalized object had existed.) If the read-time conditional starts with a hash-plus token and the feature expression evaluates to false or the read-time conditional starts with a hash-minus token and the feature expression evaluates to true, then the facility ends its processing of the read-time conditional by instructing the parser to parse and discard the conditionalized object. (The net effect is the same as if the tokens of the read-time conditional had not existed.)

        +

        Feature expressions must belong to the set of objects specified by the template language specified by the following context-free grammar:

        - + + + + + + @@ -807,20 +824,21 @@
        $\metavar{feature-expression}$ $\Coloneq$$\symbol$ | (not $\metavar{operand}$) | (and $\metavar{operand}$*) | (or $\metavar{operand}$*)$\metavar{feature}$ | (not $\metavar{operand}$) | (and $\metavar{operand}$*) | (or $\metavar{operand}$*)
        $\metavar{feature}$$\Coloneq$$\symbol$
        $\metavar{operand}$

        Feature expressions are evaluated as follows:

        -
        $\metavar{feature-expression}\Coloneq$ $\symbol$
        -
        The feature expression evaluates to true if the symbol belongs to the value of the global variable *features* and to false otherwise.
        +
        $\metavar{feature-expression}\Coloneq$ $\metavar{feature}$
        +
        The feature expression evaluates to true if the feature belongs to the feature list and to false otherwise.
        $\metavar{feature-expression}\Coloneq$ (not $\metavar{operand}$)
        The feature expression evaluates to true if its operand evaluates to false and to false otherwise.
        -
        $\metavar{feature-expression}\Coloneq$ (and $\metavar{operand}$*)
        +
        $\metavar{feature-expression}\Coloneq$ (and $\metavar{operand}_1\ldots\metavar{operand}_{n\ge0}$)
        The feature expression evaluates to true if all its operands evaluate to true and to false otherwise.
        -
        $\metavar{feature-expression}\Coloneq$ (or $\metavar{operand}$*)
        +
        $\metavar{feature-expression}\Coloneq$ (or $\metavar{operand}_1\ldots\metavar{operand}_{n\ge0}$)
        The feature expression evaluates to false if all its operands evaluate to false and to true otherwise.

        By way of example, let us consider the following sequence of characters:

        (1 . #+foo 2 #-foo 3)
        -

        If the symbol foo belongs to the value of the global variable *features*, then the sequence of characters is parsed as the dotted list (1 . 2). Otherwise, the sequence of characters is parsed as the dotted list (1 . 3).

        +

        If the feature foo belongs to the feature list, then the sequence of characters is parsed as the dotted list (1 . 2). Otherwise, the sequence of characters is parsed as the dotted list (1 . 3).

        Form Analyzer

        -

        The forms recognized by the form analyzer are specified by the template language specified by the following context-free grammar:

        +

        A form is any object submitted to the evaluator. A top-level form is a form submitted to the evaluator by an entity other than the evaluator, the IDE for instance. A non-top-level form is a form submitted to the evaluator by the evaluator in the course of the evaluation of a top-level form.

        +

        Analyzing a form is the first step in evaluating a form. If a form is not recognized by the form analyzer, then the evaluation of the form completes abruptly for a reason of type error. The forms recognized by the form analyzer are specified by the template language specified by the following context-free grammar ($\metavar{form}$ is a misnomer as any object submitted to the evaluator is a form):

        @@ -830,7 +848,7 @@ - + @@ -872,6 +890,21 @@ + + + + + + + + + + + + + + + @@ -1012,21 +1045,6 @@ - - - - - - - - - - - - - - - @@ -1084,7 +1102,7 @@ - + @@ -1105,29 +1123,27 @@
        $\metavar{form}$
        $\metavar{special-form}$ $\Coloneq$$\metavar{quote-form}$ | $\metavar{progn-form}$ | $\metavar{if-form}$ | $\metavar{lambda-abstraction}$ | $\metavar{variable-reference}$ | $\metavar{variable-assignment}$ | $\metavar{block-form}$ | $\metavar{return-from-form}$ | $\metavar{catch-form}$ | $\metavar{throw-form}$ | $\metavar{\_handler-bind-form}$ | $\metavar{unwind-protect-form}$ | $\metavar{\_for-each-form}$ | $\metavar{apply-form}$ | $\metavar{multiple-value-call-form}$ | $\metavar{multiple-value-apply-form}$$\metavar{quote-form}$ | $\metavar{progn-form}$ | $\metavar{if-form}$ | $\metavar{\_for-each-form}$ | $\metavar{lambda-abstraction}$ | $\metavar{variable-reference}$ | $\metavar{variable-assignment}$ | $\metavar{block-form}$ | $\metavar{return-from-form}$ | $\metavar{catch-form}$ | $\metavar{throw-form}$ | $\metavar{\_handler-bind-form}$ | $\metavar{unwind-protect-form}$ | $\metavar{apply-form}$ | $\metavar{multiple-value-call-form}$ | $\metavar{multiple-value-apply-form}$
        $\metavar{quote-form}$$\Coloneq$ $\metavar{form}$
        $\metavar{\_for-each-form}$$\Coloneq$(_for-each $\metavar{function-form}$ $\metavar{list-form}$)
        $\metavar{function-form}$$\Coloneq$$\metavar{form}$
        $\metavar{list-form}$$\Coloneq$$\metavar{form}$
        $\metavar{lambda-abstraction}$ $\Coloneq$$\Coloneq$ $\metavar{form}$
        $\metavar{\_for-each-form}$$\Coloneq$(_for-each $\metavar{function-form}$ $\metavar{list-form}$)
        $\metavar{function-form}$$\Coloneq$$\metavar{form}$
        $\metavar{list-form}$$\Coloneq$$\metavar{form}$
        $\metavar{apply-form}$ $\Coloneq$
        $\metavar{special-operator}$ $\Coloneq$quote | progn | if | _vlambda | _mlambda | _flambda | _dlambda | vref | fref | dref | vset! | fset! | dset! | block | return-from | catch | throw | _handler-bind | unwind-protect | _for-each | apply | multiple-value-call | multiple-value-applyquote | progn | if | _for-each | _vlambda | _mlambda | _flambda | _dlambda | vref | fref | dref | vset! | fset! | dset! | block | return-from | catch | throw | _handler-bind | unwind-protect | apply | multiple-value-call | multiple-value-apply
        $\metavar{call}$
        ($\var_1\ldots\var_{n\ge1}$ . $\var_{n+1}$)$\var_1,\ldots,\var_n$$\var_{n+1}$

        The variables quasiquote, unquote, and unquote-splicing are not special operators. The variable quasiquote is the name of a global macro and the variables unquote and unquote-splicing are variables having special meanings in the context of a call to the global macro quasiquote.

        -

        A form consisting of a variable $\var$ is treated as an abbreviation. If it appears in operator position, then it is treated as an abbreviation for the special form (fref $\var$). Otherwise, it is treated as an abbreviation for the special form (vref $\var$).

        -

        Analyzing a form is the first step in evaluating a form. If the analysis fails, then the evaluation fails.

        -

        Some form analyzers analyze a form without analyzing the subforms of the form. With such form analyzers, the subforms of the form are analyzed if and when they are evaluated. Form analyzers associated with interpreters usually function this way.

        +

        A form consisting of a variable $\variable$ is treated as an abbreviation. If it appears in operator position, then it is treated as an abbreviation for the special form (fref $\variable$). Otherwise, it is treated as an abbreviation for the special form (vref $\variable$).

        According to the template language, some objects can simultaneously be a special form, a macro call, and a plain function call. Additional rules are needed to disambiguate the situation.

        The following rules used by the form analyzer are not expressed by the template language:

          -
        • A list whose first element is a special operator is never analyzed as a call. Either the list is successfully analyzed as a special form or the analysis fails.
        • -
        • A list whose first element is a variable (other than a special operator) naming a macro according to the lookup rule used by fref is never analyzed as a plain function call. Either the list is successfully analyzed as a macro call or the analysis fails.
        • +
        • A list whose first element is a special operator is never analyzed as a call.
        • +
        • A list whose first element is a variable (other than a special operator) naming a macro according to the lookup rule used by fref is never analyzed as a plain function call.
        • The variables of a parameter list must be distinct.
        -

        The subforms of a form are the forms that are directly or indirectly contained inside the form but are not contained inside a lambda abstraction. The serial-forms of the body of a lambda abstraction are not considered to be subforms of the lambda abstraction because the evaluation of a lambda abstraction does not entail the evaluation of the serial-forms of its body.

        -

        A form is said to be in tail position with respect to a lambda abstraction if and only if one of the following mutually exclusive conditions is satisfied:

        +

        The direct subforms of a form are the direct components of the form that are identified as forms by the form's production rule. Note that the body of a lambda abstraction is not considered to be a direct subform of the lambda abstraction as the evaluation of a lambda abstraction does not entail the evaluation of its body. The subforms of a form are the direct subforms of the form, the direct subforms of the direct subforms of the form, …

        +

        A form is said to be in tail position with respect to a lambda abstraction other than a _dlambda form if and only if one of the following conditions is satisfied:

          -
        • The form is the last serial-form of the body of the lambda abstraction.
        • -
        • The form is the last serial-form of a progn-form in tail position with respect to the lambda abstraction.
        • -
        • The form is the then-form or the else-form of an if-form in tail position with respect to the lambda abstraction.
        • +
        • The form is the last serial form of the body of the lambda abstraction.
        • +
        • The form is the last serial form of a progn form in tail position with respect to the lambda abstraction.
        • +
        • The form is the then form or the else form of an if form in tail position with respect to the lambda abstraction.

        Documentation Generator

        In this section, the character ⇰ marks the places where line breaks have been added to wrap long lines.

        Let us consider the following documented EVLambda source file:

        -
        <chapter>
        <title>Recursive Functions</title>
        <para>...para...</para>
        <para>...para...</para>
        <section>
        <title>Factorial Function</title>
        <para>...para...</para>
        <para>...para...</para>
        (fdef fact (n)
        <para>...block...</para>
        <para>...block...</para>
        (if (= n 0)
        1 <comment>...eol...</comment>
        (* n (fact (- n 1))))) <comment>...eoll...</comment>

        (test 1 (fact 0))
        (test 120 (fact 5))
        (test 3628800 (fact 10))
        </section>
        <section>
        <title>Fibonacci Sequence</title>
        <para>...para...</para>
        <para>...para...</para>
        (fdef fib (n)
        <para>...block...</para>
        <para>...block...</para>
        (if (= n 0)
        0 <comment>...eol...</comment>
        (if (= n 1)
        1 <comment>...eol...</comment>
        (+ (fib (- n 1)) (fib (- n 2)))))) <comment>...eoll...</comment>

        (test 0 (fib 0))
        (test 5 (fib 5))
        (test 55 (fib 10))
        </section>
        </chapter>
        +
        <chapter>
        <title>Recursive Functions</title>
        <p>...para...</p>
        <p>...para...</p>
        <section>
        <title>Factorial Function</title>
        <p>...para...</p>
        <p>...para...</p>
        (fdef fact (n)
        <p>...block...</p>
        <p>...block...</p>
        (if (= n 0)
        1 <comment>...eol...</comment>
        (* n (fact (- n 1))))) <comment>...eoll...</comment>

        (test 1 (fact 0))
        (test 120 (fact 5))
        (test 3628800 (fact 10))
        </section>
        <section>
        <title>Fibonacci Sequence</title>
        <p>...para...</p>
        <p>...para...</p>
        (fdef fib (n)
        <p>...block...</p>
        <p>...block...</p>
        (if (= n 0)
        0 <comment>...eol...</comment>
        (if (= n 1)
        1 <comment>...eol...</comment>
        (+ (fib (- n 1)) (fib (- n 2)))))) <comment>...eoll...</comment>

        (test 0 (fib 0))
        (test 5 (fib 5))
        (test 55 (fib 10))
        </section>
        </chapter>

        The first step of the documentation generation process converts the documented EVLambda source file into the following XML document:

        -
        <chapter>
        <title>Recursive Functions</title>
        <para>...para...</para>
        <para>...para...</para>
        <section>
        <title>Factorial Function</title>
        <para>...para...</para>
        <para>...para...</para>
        <toplevelcode><blockcode>(fdef fact (n)⇰
        </blockcode><indentation style="margin-left: 2ch;"><blockcomment>
        <para>...block...</para>
        <para>...block...</para></blockcomment></indentation><blockcode>
        (if (= n 0)
        1 <comment>...eol...</comment>
        (* n (fact (- n 1))))) ⇰
        <comment>...eoll...</comment></blockcode></toplevelcode>

        <toplevelcode><blockcode>(test 1 (fact 0))
        (test 120 (fact 5))
        (test 3628800 (fact 10))</blockcode></toplevelcode>
        </section>
        <section>
        <title>Fibonacci Sequence</title>
        <para>...para...</para>
        <para>...para...</para>
        <toplevelcode><blockcode>(fdef fib (n)⇰
        </blockcode><indentation style="margin-left: 2ch;"><blockcomment>
        <para>...block...</para>
        <para>...block...</para></blockcomment></indentation><blockcode>
        (if (= n 0)
        0 <comment>...eol...</comment>
        (if (= n 1)
        1 <comment>...eol...</comment>
        (+ (fib (- n 1)) (fib (- n 2)))))) ⇰
        <comment>...eoll...</comment></blockcode></toplevelcode>

        <toplevelcode><blockcode>(test 0 (fib 0))
        (test 5 (fib 5))
        (test 55 (fib 10))</blockcode></toplevelcode>
        </section>
        </chapter>
        +
        <chapter>
        <title>Recursive Functions</title>
        <p>...para...</p>
        <p>...para...</p>
        <section>
        <title>Factorial Function</title>
        <p>...para...</p>
        <p>...para...</p>
        <toplevelcode><blockcode>(fdef fact (n)⇰
        </blockcode><indentation style="margin-left: 2ch;"><blockcomment>
        <p>...block...</p>
        <p>...block...</p></blockcomment></indentation><blockcode>
        (if (= n 0)
        1 <comment>...eol...</comment>
        (* n (fact (- n 1))))) ⇰
        <comment>...eoll...</comment></blockcode></toplevelcode>

        <toplevelcode><blockcode>(test 1 (fact 0))
        (test 120 (fact 5))
        (test 3628800 (fact 10))</blockcode></toplevelcode>
        </section>
        <section>
        <title>Fibonacci Sequence</title>
        <p>...para...</p>
        <p>...para...</p>
        <toplevelcode><blockcode>(fdef fib (n)⇰
        </blockcode><indentation style="margin-left: 2ch;"><blockcomment>
        <p>...block...</p>
        <p>...block...</p></blockcomment></indentation><blockcode>
        (if (= n 0)
        0 <comment>...eol...</comment>
        (if (= n 1)
        1 <comment>...eol...</comment>
        (+ (fib (- n 1)) (fib (- n 2)))))) ⇰
        <comment>...eoll...</comment></blockcode></toplevelcode>

        <toplevelcode><blockcode>(test 0 (fib 0))
        (test 5 (fib 5))
        (test 55 (fib 10))</blockcode></toplevelcode>
        </section>
        </chapter>

        Here are a few remarks about the XML document:

        • The XML document is well-formed. (In this case, the documented EVLambda source file was already well-formed because the code does not contain any < or & character.)
        • @@ -1141,235 +1157,251 @@

          Semantics

          Primitive Data Types and Primitive Functions

          This section inventories all the primitive data types and all the primitive functions.

          -

          For each primitive function, a template function call, an indication of the outcome of the corresponding invocation, and a description of the primitive function's behavior are provided.

          -

          In template function calls, argument names imply the following type restrictions:

          -
            -
          • Arguments named $\object$, with or without a subscript, can be of any type.
          • -
          • Arguments named $\number$, with or without a subscript, must be of type number.
          • -
          • Arguments named $\string$, with or without a subscript, must be of type string.
          • -
          • Arguments named $\variable$, with or without a subscript, must be of type variable.
          • -
          • Arguments named $\cons$, with or without a subscript, must be of type cons.
          • -
          -

          If any argument is not of its required type, then the invocation of the primitive function completes abruptly for a reason of type error.

          Primitive Data Types

          Here is a tree-view representation of the hierarchy of primitive data types:

          object
          |-void
          |-boolean
          |-number
          |-character
          |-string
          |-symbol
          | |-keyword
          | |-variable
          |-list
          | |-empty-list
          | |-cons
          |-vector
          |-function
          | |-primitive-function
          | |-closure

          Primitive Data Type object and Related Primitive Functions

          (object? $\object$) ⇒ $\boolean$
          -
          The primitive function returns #t if $\object$ is of type object (which is always the case) and #f otherwise.
          +
          The function returns #t if $\object$ is of type object (which is always the case) and #f otherwise.
          (eq? $\object_1$ $\object_2$) ⇒ $\boolean$
          -
          The primitive function is an equality predicate. It returns #t if and only if the two objects are one and the same. In other words, it returns #t if and only if the two objects have the same address in the heap.
          +
          The function returns #t if and only if the two objects are one and the same. In other words, the function returns #t if and only if the two objects have the same address in the heap.
          (eql? $\object_1$ $\object_2$) ⇒ $\boolean$
          -
          The primitive function is an equality predicate. If both objects are of type number, then it returns #t if and only if the two objects represent the same mathematical number. Otherwise, if both objects are of type character, then it returns #t if and only if the two objects represent the same UTF-$16$ code unit. Otherwise, if both objects are of type string, then it returns #t if and only if the two objects represent the same indexed sequence of UTF-$16$ code units. Otherwise, it returns #t if and only if the two objects are eq?.
          +
          If both objects are of type number, then the function returns #t if and only if the two objects represent the same mathematical number. Otherwise, if both objects are of type character, then the function returns #t if and only if the two objects represent the same UTF-$16$ code unit. Otherwise, if both objects are of type string, then the function returns #t if and only if the two objects represent the same indexed sequence of UTF-$16$ code units. Otherwise, the function returns #t if and only if the two objects are eq?.

          Primitive Data Type void and Related Primitive Functions

          (void? $\object$) ⇒ $\boolean$
          -
          The primitive function returns #t if $\object$ is of type void and #f otherwise.
          +
          The function returns #t if $\object$ is of type void and #f otherwise.

          Primitive Data Type boolean and Related Primitive Functions

          (boolean? $\object$) ⇒ $\boolean$
          -
          The primitive function returns #t if $\object$ is of type boolean and #f otherwise.
          +
          The function returns #t if $\object$ is of type boolean and #f otherwise.

          Primitive Data Type number and Related Primitive Functions

          Objects of type number represent mathematical numbers using the floating-point format IEEE 754 binary 64. Some (but not all) of the integers that can be represented exactly by an object of type number are the integers between $-2^{53}=−9,007,199,254,740,992$ and $2^{53}=9,007,199,254,740,992$ (bounds included).

          (number? $\object$) ⇒ $\boolean$
          -
          The primitive function returns #t if $\object$ is of type number and #f otherwise.
          +
          The function returns #t if $\object$ is of type number and #f otherwise.
          (_+ $\number_1$ $\number_2$) ⇒ $\number$
          -
          The primitive function returns the sum $\number_1+\number_2$.
          +
          The function returns the sum $\number_1+\number_2$.
          (_- $\number_1$ $\number_2$) ⇒ $\number$
          -
          The primitive function returns the difference $\number_1-\number_2$.
          +
          The function returns the difference $\number_1-\number_2$.
          (_* $\number_1$ $\number_2$) ⇒ $\number$
          -
          The primitive function returns the product $\number_1\times\number_2$.
          +
          The function returns the product $\number_1\times\number_2$.
          (_/ $\number_1$ $\number_2$) ⇒ $\number$
          -
          The primitive function returns the quotient $\number_1\div\number_2$.
          +
          The function returns the quotient $\number_1\div\number_2$.
          (% $\number_1$ $\number_2$) ⇒ $\number$
          -
          The primitive function returns the remainder of the division of $\number_1$ by $\number_2$ when the quotient is forced to be an integer.
          +
          The function returns the remainder of the division of $\number_1$ by $\number_2$ when the quotient is forced to be an integer.
          (= $\number_1$ $\number_2$) ⇒ $\boolean$
          -
          The primitive function returns #t if $\number_1$ and $\number_2$ are numerically equal and #f otherwise.
          +
          The function returns #t if $\number_1$ and $\number_2$ are numerically equal and #f otherwise.
          (/= $\number_1$ $\number_2$) ⇒ $\boolean$
          -
          The primitive function returns #t if $\number_1$ and $\number_2$ are numerically different and #f otherwise.
          +
          The function returns #t if $\number_1$ and $\number_2$ are numerically different and #f otherwise.
          (< $\number_1$ $\number_2$) ⇒ $\boolean$
          -
          The primitive function returns #t if $\number_1$ is numerically less than $\number_2$ and #f otherwise.
          +
          The function returns #t if $\number_1$ is numerically less than $\number_2$ and #f otherwise.
          (<= $\number_1$ $\number_2$) ⇒ $\boolean$
          -
          The primitive function returns #t if $\number_1$ is numerically less than or equal to $\number_2$ and #f otherwise.
          +
          The function returns #t if $\number_1$ is numerically less than or equal to $\number_2$ and #f otherwise.
          (> $\number_1$ $\number_2$) ⇒ $\boolean$
          -
          The primitive function returns #t if $\number_1$ is numerically greater than $\number_2$ and #f otherwise.
          +
          The function returns #t if $\number_1$ is numerically greater than $\number_2$ and #f otherwise.
          (>= $\number_1$ $\number_2$) ⇒ $\boolean$
          -
          The primitive function returns #t if $\number_1$ is numerically greater than or equal to $\number_2$ and #f otherwise.
          +
          The function returns #t if $\number_1$ is numerically greater than or equal to $\number_2$ and #f otherwise.
          -

          The primitive functions _+, _-, _*, and _/ have an underscore at the beginning of their names to distinguish them from the similarly named nonprimitive functions +, -, *, and /, which all accept a variable number of arguments.

          +

          The functions _+, _-, _*, and _/ have an underscore at the beginning of their names to distinguish them from the similarly named nonprimitive functions +, -, *, and /, which all accept a variable number of arguments.

          Primitive Data Type character and Related Primitive Functions

          Contrary to what was said in the user manual, an object of type character represents a UTF-$16$ code unit (instead of a Unicode character).

          (character? $\object$) ⇒ $\boolean$
          -
          The primitive function returns #t if $\object$ is of type character and #f otherwise.
          +
          The function returns #t if $\object$ is of type character and #f otherwise.

          Primitive Data Type string and Related Primitive Functions

          Contrary to what was said in the user manual, an object of type string represents an indexed sequence of UTF-$16$ code units (instead of an indexed sequence of Unicode characters).

          (string? $\object$) ⇒ $\boolean$
          -
          The primitive function returns #t if $\object$ is of type string and #f otherwise.
          +
          The function returns #t if $\object$ is of type string and #f otherwise.

          Primitive Data Type symbol and Related Primitive Functions

          (symbol? $\object$) ⇒ $\boolean$
          -
          The primitive function returns #t if $\object$ is of type symbol and #f otherwise.
          +
          The function returns #t if $\object$ is of type symbol and #f otherwise.

          Primitive Data Type keyword and Related Primitive Functions

          (keyword? $\object$) ⇒ $\boolean$
          -
          The primitive function returns #t if $\object$ is of type keyword and #f otherwise.
          +
          The function returns #t if $\object$ is of type keyword and #f otherwise.
          (make-keyword? $\string$) ⇒ $\keyword$
          -
          The primitive function returns a new keyword whose name is $\string$. The keyword is not interned.
          +
          The function returns a new uninterned keyword whose name is $\string$.

          Primitive Data Type variable and Related Primitive Functions

          (variable? $\object$) ⇒ $\boolean$
          -
          The primitive function returns #t if $\object$ is of type variable and #f otherwise.
          +
          The function returns #t if $\object$ is of type variable and #f otherwise.
          (make-variable $\string$) ⇒ $\variable$
          -
          The primitive function returns a new variable whose name is $\string$. The variable is not interned.
          +
          The function returns a new uninterned variable whose name is $\string$.
          (variable-value $\variable$) ⇒ $\object$
          -
          If there exists a binding for $\variable$ in the value namespace of the global environment, then the primitive function returns the value of that binding. Otherwise, the primitive function returns #v.
          +
          If there exists a binding for $\variable$ in the value namespace of the global environment, then the function returns the value of that binding. Otherwise, the function returns #v.
          (variable-set-value! $\variable$ $\object$) ⇒ $\object$
          -
          If there exists a binding for $\variable$ in the value namespace of the global environment, then the primitive function replaces the value of that binding by $\object$. Otherwise, the primitive function adds to the value namespace of the global environment a new binding between $\variable$ and $\object$. In both cases, the primitive function returns $\object$.
          +
          If there exists a binding for $\variable$ in the value namespace of the global environment, then the value of that binding is replaced by $\object$ and the functions returns $\object$. Otherwise, a new binding between $\variable$ and $\object$ is added to the value namespace of the global environment and the function returns $\object$.
          (variable-value-bound? $\variable$) ⇒ $\boolean$
          -
          If there exists a binding for $\variable$ in the value namespace of the global environment, then the primitive function returns #t. Otherwise, the primitive function returns #f.
          +
          If there exists a binding for $\variable$ in the value namespace of the global environment, then the function returns #t. Otherwise, the function returns #f.
          (variable-unbind-value! $\variable$)#v
          -
          If there exists a binding for $\variable$ in the value namespace of the global environment, then that binding is deleted and the primitive function returns #v. Otherwise, the primitive function simply returns #v.
          +
          If there exists a binding for $\variable$ in the value namespace of the global environment, then that binding is deleted and the function returns #v. Otherwise, the function simply returns #v.
          (variable-function $\variable$) ⇒ $\object$
          -
          If there exists a binding for $\variable$ in the function namespace of the global environment, then the primitive function returns the value of that binding. Otherwise, the primitive function returns #v.
          +
          If there exists a binding for $\variable$ in the function namespace of the global environment, then the function returns the value of that binding. Otherwise, the function returns #v.
          (variable-set-function! $\variable$ $\object$) ⇒ $\object$
          -
          If there exists a binding for $\variable$ in the function namespace of the global environment, then the primitive function replaces the value of that binding by $\object$. Otherwise, the primitive function adds to the function namespace of the global environment a new binding between $\variable$ and $\object$. In both cases, the primitive function returns $\object$.
          +
          If there exists a binding for $\variable$ in the function namespace of the global environment, then the value of that binding is replaced by $\object$ and the function returns $\object$. Otherwise, a new binding between $\variable$ and $\object$ is added to the function namespace of the global environment and the function returns $\object$.
          (variable-function-bound? $\variable$) ⇒ $\boolean$
          -
          If there exists a binding for $\variable$ in the function namespace of the global environment, then the primitive function returns #t. Otherwise, the primitive function returns #f.
          +
          If there exists a binding for $\variable$ in the function namespace of the global environment, then the function returns #t. Otherwise, the function returns #f.
          (variable-unbind-function! $\variable$)#v
          -
          If there exists a binding for $\variable$ in the function namespace of the global environment, then that binding is deleted and the primitive function returns #v. Otherwise, the primitive function simply returns #v.
          +
          If there exists a binding for $\variable$ in the function namespace of the global environment, then that binding is deleted and the function returns #v. Otherwise, the function simply returns #v.
          +
          +

          A property list is an association between keys (usually of type keyword) and values (of any type). Each variable has an associated property list. The keys must be of type keyword and the values are accessed through the following primitive functions:

          +
          +
          (variable-plist-ref $\variable$ $\keyword$) ⇒ $\object$
          +
          If there exists an association between $\keyword$ and a value, then the function returns that value. Otherwise, the function returns #v.
          +
          (variable-plist-set! $\variable$ $\keyword$ $\object$) ⇒ $\object$
          +
          If there exists an association between $\keyword$ and a value, then that value by replaced by $\object$ and the function returns $\object$. Otherwise, an association between $\keyword$ and $\object$ is created and the function returns $\object$.
          +
          (variable-plist-bound? $\variable$ $\keyword$) ⇒ $\boolean$
          +
          If there exists an association between $\keyword$ and a value, then the function returns #t. Otherwise, the function returns #f.
          +
          (variable-plist-unbind! $\variable$ $\keyword$)#v
          +
          If there exists an association between $\keyword$ and a value, then that association is deleted and the function returns #v. Otherwise, the function simply returns #v.

          Primitive Data Type list and Related Primitive Functions

          (list? $\object$) ⇒ $\boolean$
          -
          The primitive function returns #t if $\object$ is of type list and #f otherwise.
          +
          The function returns #t if $\object$ is of type list and #f otherwise.
          +
          (_make-list $\number$) ⇒ $\list$
          +
          If $\number$ is not a nonnegative integer, then the function completes abruptly for a reason of type error. Otherwise, the function returns a new list of length $\number$ whose elements are all #v. The function exists mainly for testing purposes.

          Primitive Data Type empty-list and Related Primitive Functions

          (empty-list? $\object$) ⇒ $\boolean$
          -
          The primitive function returns #t if $\object$ is of type empty-list and #f otherwise.
          +
          The function returns #t if $\object$ is of type empty-list and #f otherwise.

          Primitive Data Type cons and Related Primitive Functions

          (cons? $\object$) ⇒ $\boolean$
          -
          The primitive function returns #t if $\object$ is of type cons and #f otherwise.
          +
          The function returns #t if $\object$ is of type cons and #f otherwise.
          (cons $\object_1$ $\object_2$) ⇒ $\cons$
          -
          The primitive function returns a new cons whose first element is $\object_1$ and whose second element is $\object_2$.
          +
          The function returns a new cons whose first element is $\object_1$ and whose second element is $\object_2$.
          (car $\cons$) ⇒ $\object$
          -
          The primitive function returns the first element of $\cons$.
          +
          The function returns the first element of $\cons$.
          (set-car! $\cons$ $\object$) ⇒ $\object$
          -
          The primitive function replaces the first element of $\cons$ by $\object$ and returns $\object$.
          +
          The function replaces the first element of $\cons$ by $\object$ and returns $\object$.
          (cdr $\cons$) ⇒ $\object$
          -
          The primitive function returns the second element of $\cons$
          +
          The function returns the second element of $\cons$
          (set-cdr! $\cons$ $\object$) ⇒ $\object$
          -
          The primitive function replaces the second element of $\cons$ by $\object$ and returns $\object$.
          +
          The function replaces the second element of $\cons$ by $\object$ and returns $\object$.

          Primitive Data Type vector and Related Primitive Functions

          +

          A vector is an association between indexes (nonnegative integers between zero and the length of the vector minus one) and values of arbitrary types. Not all indexes need to have an associated value. Vectors with missing values have no readable representations. In printable representations, indexes with no associated value appear to be associated with the value #v.

          (vector? $\object$) ⇒ $\boolean$
          -
          The primitive function returns #t if $\object$ is of type vector and #f otherwise.
          +
          The function returns #t if $\object$ is of type vector and #f otherwise.
          +
          (make-vector $\number$ $\object$) ⇒ $\vector$
          +
          If $\number$ is not a nonnegative integer, then the function completes abruptly for a reason of type error. Otherwise, the function returns a new vector of length $\number$. The argument $\object$ is optional. If $\object$ is not provided, then the indexes are all with no associated value. If $\object$ is provided, then the indexes are all associated with the value $\object$.
          +
          (vector-length $\vector$) ⇒ $\number$
          +
          The function returns the length of $\vector$.
          +
          (vector-ref $\vector$ $\number$) ⇒ $\object$
          +
          If $\number$ is not a nonnegative integer between zero and the length of $\vector$ minus one, then the function completes abruptly for a reason of type error. Otherwise, if there exists an association between $\number$ and a value, then the function returns that value. Otherwise, the function returns #v.
          +
          (vector-set! $\vector$ $\number$ $\object$) ⇒ $\object$
          +
          If $\number$ is not a nonnegative integer between zero and the length of $\vector$ minus one, then the function completes abruptly for a reason of type error. Otherwise, if there exists an association between $\number$ and a value, then that value is replaced by $\object$ and the function returns $\object$. Otherwise, an association between $\number$ and $\object$ is created and the function returns $\object$.
          +
          (vector-bound? $\vector$ $\number$) ⇒ $\boolean$
          +
          If $\number$ is not a nonnegative integer between zero and the length of $\vector$ minus one, then the function completes abruptly for a reason of type error. Otherwise, if there exists an association between $\number$ and a value, then the function returns #t. Otherwise, the function returns #f.
          +
          (vector-unbind! $\vector$ $\number$)#v
          +
          If $\number$ is not a nonnegative integer between zero and the length of $\vector$ minus one, then the function completes abruptly for a reason of type error. Otherwise, if there exists an association between $\number$ and a value, then that association is deleted and the function returns #v. Otherwise, the function simply returns #v.

          Primitive Data Type function and Related Primitive Functions

          (function? $\object$) ⇒ $\boolean$
          -
          The primitive function returns #t if $\object$ is of type function and #f otherwise.
          +
          The function returns #t if $\object$ is of type function and #f otherwise.

          Primitive Data Type primitive-function and Related Primitive Functions

          (primitive-function? $\object$) ⇒ $\boolean$
          -
          The primitive function returns #t if $\object$ is of type primitive-function and #f otherwise.
          +
          The function returns #t if $\object$ is of type primitive-function and #f otherwise.

          Primitive Data Type closure and Related Primitive Functions

          (closure? $\object$) ⇒ $\boolean$
          -
          The primitive function returns #t if $\object$ is of type closure and #f otherwise.
          +
          The function returns #t if $\object$ is of type closure and #f otherwise.

          Miscellaneous Primitive Functions

          (values $\object_1\ldots\object_n$) ⇒ $\object_1,\ldots,\object_n$
          -
          The primitive function converts its arguments into values: when invoked on the arguments $\object_1,\ldots,\object_n$, the primitive function returns the values $\object_1,\ldots,\object_n$.
          +
          The function converts its arguments into values: when invoked on the arguments $\object_1,\ldots,\object_n$, the function returns the values $\object_1,\ldots,\object_n$.
          (error $\string$) ⇒ completes abruptly for a reason of type error
          -
          The invocation of the primitive function completes abruptly for a reason of type error whose payload is $\string$.
          +
          The invocation of the function completes abruptly for a reason of type error carrying the category "Error" and the description $\string$.
          (now) ⇒ $\number$
          -
          The primitive function returns the number of milliseconds elapsed since 1970-01-01 00:00:00.000 UTC.
          +
          The function returns the number of milliseconds elapsed since 1970-01-01 00:00:00.000 UTC.

          Forms

          This section supplements and amends the evaluation rules stated in the user manual.

          +

          Special Form _for-each

          +

          The special form _for-each is evaluated as follows:

          +
          +
          (_for-each $\metavar{function-form}$ $\metavar{list-form}$)
          +
          The function form is evaluated. Let $\mlvar{function}$ be the primary value of the function form. If $\mlvar{function}$ is not a function, then the evaluation of the _for-each form completes abruptly for a reason of type error. Otherwise, the list form is evaluated. Let $\mlvar{list}$ be the primary value of the list form. If $\mlvar{list}$ is not a proper list, then the evaluation of the _for-each form completes abruptly for a reason of type error. Otherwise, the function $\mlvar{function}$ is invoked in sequence on each element of the list $\mlvar{list}$, from the first element to the last element. If any invocation completes abruptly for any reason or does not complete, then the following invocations do not happen and the evaluation of the _for-each form also completes abruptly for the same reason or does not complete either. Otherwise, the _for-each form evaluates to #v.
          +

          Advanced Control Structures

          -

          The two pairs of special forms block/return-from and catch/throw use two additional namespaces: the block-name namespace and the exit-point namespace.

          +

          The two pairs of special forms block/return-from and catch/throw use two additional namespaces: the block namespace, which is only found in lexical environments, and the exit-point namespace, which is only found in dynamic environments.

          Special Forms block/return-from

          The special forms block and return-from are evaluated as follows:

          (block $\metavar{block-name}$ $\metavar{serial-form}$*)
          -
          Let $\mlvar{exit-tag}$ be a new uninterned variable. The serial-forms are evaluated in sequence from left to right with respect to (1) the environment extending the current lexical environment to bind, in the block namespace, the variable $\mlvar{block-name}$ to $\mlvar{exit-tag}$ and (2) the environment extending the current dynamic environment to bind, in the exit-point namespace, the variable $\mlvar{exit-tag}$ to #v (the value is unimportant). If the evaluation of any serial-form completes abruptly for any reason, then the following serial-forms are not evaluated and the evaluation of the block-form proceeds as follows: -
          Let $\mlvar{reason}$ be the reason for the abrupt completion of the serial-form. If $\mlvar{reason}$ is of type nonlocal-exit and carries an exit tag eq? to $\mlvar{exit-tag}$, then the evaluation of the block-form completes normally and produces the values carried by $\mlvar{reason}$. Otherwise, the evaluation of the block-form completes abruptly for the reason $\mlvar{reason}$.
          - Otherwise, the evaluations of the serial-forms all complete normally and the evaluation of the block-form completes normally and produces the values of the last serial-form or #v if there are no serial-forms.
          +
          Let $\mlvar{exit-tag}$ be a new uninterned variable. The serial forms are evaluated in sequence from left to right with respect to (1) the environment extending the current lexical environment to bind, in the block namespace, the variable $\metavar{block-name}$ to $\mlvar{exit-tag}$ and (2) the environment extending the current dynamic environment to bind, in the exit-point namespace, the variable $\mlvar{exit-tag}$ to #v (the value is unimportant). If the evaluation of any serial form completes abruptly for any reason, then the following serial forms are not evaluated and the evaluation of the block form proceeds as follows: +
          Let $\mlvar{reason}$ be the reason for the abrupt completion of the serial form. If $\mlvar{reason}$ is of type nonlocal-exit and carries an exit tag eq? to $\mlvar{exit-tag}$, then the evaluation of the block form completes normally and produces the values carried by $\mlvar{reason}$. Otherwise, the evaluation of the block form completes abruptly for the reason $\mlvar{reason}$.
          + Otherwise, the evaluations of the serial forms all complete normally and the evaluation of the block form completes normally and produces the values of the last serial form or #v if there are no serial forms.
          (return-from $\metavar{block-name}$ $\metavar{values-form}$)
          -
          If there exists no binding for the variable $\metavar{block-name}$ in the block namespace of the current lexical environment, then the evaluation of the return-from-form completes abruptly for a reason of type error. Otherwise, let $\mlvar{exit-tag}$ be the value of the binding for the variable $\mlvar{block-name}$ in the block namespace of the current lexical environment. If there exists no binding for the variable $\mlvar{exit-tag}$ in the exit-point namespace of the current dynamic environment, then the evaluation of the return-from-form completes abruptly for a reason of type error. Otherwise, the values-form is evaluated. If the evaluation of the values-form completes abruptly for any reason, then the evaluation of the return-from-form completes abruptly for the same reason. Otherwise, the evaluation of the return-from-form completes abruptly for a reason of type nonlocal-exit carrying $\mlvar{exit-tag}$ and the values of the values-form.
          +
          If there exists no binding for the variable $\metavar{block-name}$ in the block namespace of the current lexical environment, then the evaluation of the return-from form completes abruptly for a reason of type error. Otherwise, let $\mlvar{exit-tag}$ be the value of the binding for the variable $\metavar{block-name}$ in the block namespace of the current lexical environment. If there exists no binding for the variable $\mlvar{exit-tag}$ in the exit-point namespace of the current dynamic environment, then the evaluation of the return-from form completes abruptly for a reason of type error. Otherwise, the values form is evaluated. If the evaluation of the values form completes abruptly for any reason, then the evaluation of the return-from form completes abruptly for the same reason. Otherwise, the evaluation of the return-from form completes abruptly for a reason of type nonlocal-exit carrying $\mlvar{exit-tag}$ and the values of the values form.
          Special Forms catch/throw

          The special forms catch and throw are evaluated as follows:

          (catch $\metavar{exit-tag-form}$ $\metavar{serial-form}$*)
          -
          The exit-tag-form is evaluated. If the evaluation of the exit-tag-form completes abruptly for any reason, then the evaluation of the catch-form completes abruptly for the same reason. Otherwise, let $\mlvar{exit-tag}$ be the primary value of the exit-tag-form. If $\mlvar{exit-tag}$ is not a variable, then the evaluation of the catch-form completes abruptly for a reason of type error. Otherwise, the serial-forms are evaluated in sequence from left to right with respect to (1) the current lexical environment and (2) the environment extending the current dynamic environment to bind, in the exit-point namespace, the variable $\mlvar{exit-tag}$ to #v (the value is unimportant). If the evaluation of any serial-form completes abruptly for any reason, then the following serial-forms are not evaluated and the evaluation of the catch-form proceeds as follows: -
          Let $\mlvar{reason}$ be the reason for the abrupt completion of the serial-form. If $\mlvar{reason}$ is of type nonlocal-exit and carries an exit tag eq? to $\mlvar{exit-tag}$, then the evaluation of the catch-form completes normally and produces the values carried by $\mlvar{reason}$. Otherwise, the evaluation of the catch-form completes abruptly for the reason $\mlvar{reason}$.
          - Otherwise, the evaluations of the serial-forms all complete normally and the evaluation of the catch-form completes normally and produces the values of the last serial-form or #v if there are no serial-forms.
          +
          The exit-tag form is evaluated. If the evaluation of the exit-tag form completes abruptly for any reason, then the evaluation of the catch form completes abruptly for the same reason. Otherwise, let $\mlvar{exit-tag}$ be the primary value of the exit-tag form. If $\mlvar{exit-tag}$ is not a variable, then the evaluation of the catch form completes abruptly for a reason of type error. Otherwise, the serial forms are evaluated in sequence from left to right with respect to (1) the current lexical environment and (2) the environment extending the current dynamic environment to bind, in the exit-point namespace, the variable $\mlvar{exit-tag}$ to #v (the value is unimportant). If the evaluation of any serial form completes abruptly for any reason, then the following serial forms are not evaluated and the evaluation of the catch form proceeds as follows: +
          Let $\mlvar{reason}$ be the reason for the abrupt completion of the serial form. If $\mlvar{reason}$ is of type nonlocal-exit and carries an exit tag eq? to $\mlvar{exit-tag}$, then the evaluation of the catch form completes normally and produces the values carried by $\mlvar{reason}$. Otherwise, the evaluation of the catch form completes abruptly for the reason $\mlvar{reason}$.
          + Otherwise, the evaluations of the serial forms all complete normally and the evaluation of the catch form completes normally and produces the values of the last serial form or #v if there are no serial forms.
          (throw $\metavar{exit-tag-form}$ $\metavar{values-form}$)
          -
          The exit-tag-form is evaluated. If the evaluation of the exit-tag-form completes abruptly for any reason, then the evaluation of the throw-form completes abruptly for the same reason. Otherwise, let $\mlvar{exit-tag}$ be the primary value of the exit-tag-form. If $\mlvar{exit-tag}$ is not a variable, then the evaluation of the throw-form completes abruptly for a reason of type error. Otherwise, if there exists no binding for the variable $\mlvar{exit-tag}$ in the exit-point namespace of the current dynamic environment, then the evaluation of the throw-form completes abruptly for a reason of type error. Otherwise, the values-form is evaluated. If the evaluation of the values-form completes abruptly for any reason, then the evaluation of the throw-form completes abruptly for the same reason. Otherwise, the evaluation of the throw-form completes abruptly for a reason of type nonlocal-exit carrying $\mlvar{exit-tag}$ and the values of the values-form.
          +
          The exit-tag form is evaluated. If the evaluation of the exit-tag form completes abruptly for any reason, then the evaluation of the throw form completes abruptly for the same reason. Otherwise, let $\mlvar{exit-tag}$ be the primary value of the exit-tag form. If $\mlvar{exit-tag}$ is not a variable, then the evaluation of the throw form completes abruptly for a reason of type error. Otherwise, if there exists no binding for the variable $\mlvar{exit-tag}$ in the exit-point namespace of the current dynamic environment, then the evaluation of the throw form completes abruptly for a reason of type error. Otherwise, the values form is evaluated. If the evaluation of the values form completes abruptly for any reason, then the evaluation of the throw form completes abruptly for the same reason. Otherwise, the evaluation of the throw form completes abruptly for a reason of type nonlocal-exit carrying $\mlvar{exit-tag}$ and the values of the values form.
          Special Form _handler-bind

          The special form _handler-bind is evaluated as follows:

          (_handler-bind $\metavar{handler-form}$ $\metavar{serial-form}$*)
          -
          The handler-form is evaluated. If the evaluation of the handler-form completes abruptly for any reason, then the evaluation of the _handler-bind-form completes abruptly for the same reason. Otherwise, let $\mlvar{handler}$ be the primary value of the handler-form. If $\mlvar{handler}$ is not a function, then the evaluation of the _handler-bind-form completes abruptly for a reason of type error. Otherwise, the serial-forms are evaluated in sequence from left to right. If the evaluation of any serial-form completes abruptly for any reason, then the following serial-forms are not evaluated and the evaluation of the _handler-bind-form proceeds as follows: -
          Let $\mlvar{reason}$ be the reason for the abrupt completion of the serial-form. If $\mlvar{reason}$ is of type nonlocal-exit, then the evaluation of the _handler-bind-form completes abruptly for the reason $\mlvar{reason}$. Otherwise, $\mlvar{handler}$ is invoked on the payload of $\mlvar{reason}$. If the invocation completes abruptly for any reason, then the evaluation of the _handler-bind-form also completes abruptly for the same reason. Otherwise, if the invocation does not complete, then the evaluation of the _handler-bind-form does not complete either. Otherwise, the _handler-bind-form evaluates to the values of the invocation.
          - Otherwise, the evaluations of the serial-forms all complete normally and the evaluation of the _handler-bind-form completes normally and produces the values of the last serial-form or #v if there are no serial-forms.
          +
          The handler form is evaluated. If the evaluation of the handler form completes abruptly for any reason, then the evaluation of the _handler-bind form completes abruptly for the same reason. Otherwise, let $\mlvar{handler}$ be the primary value of the handler form. If $\mlvar{handler}$ is not a function, then the evaluation of the _handler-bind form completes abruptly for a reason of type error. Otherwise, the serial forms are evaluated in sequence from left to right. If the evaluation of any serial form completes abruptly for any reason, then the following serial forms are not evaluated and the evaluation of the _handler-bind form proceeds as follows: +
          Let $\mlvar{reason}$ be the reason for the abrupt completion of the serial form. If $\mlvar{reason}$ is of type nonlocal-exit, then the evaluation of the _handler-bind form completes abruptly for the reason $\mlvar{reason}$. Otherwise, $\mlvar{reason}$ is necessarily of type error and $\mlvar{handler}$ is invoked on the category and description carried by $\mlvar{reason}$. If the invocation completes abruptly for any reason, then the evaluation of the _handler-bind form also completes abruptly for the same reason. Otherwise, if the invocation does not complete, then the evaluation of the _handler-bind form does not complete either. Otherwise, the evaluation of the _handler-bind form completes abruptly for the reason $\mlvar{reason}$.
          + Otherwise, the evaluations of the serial forms all complete normally and the evaluation of the _handler-bind form completes normally and produces the values of the last serial form or #v if there are no serial forms.
          Special Form unwind-protect

          The special form unwind-protect is evaluated as follows:

          (unwind-protect $\metavar{protected-form}$ $\metavar{cleanup-form}$*)
          -
          The protected-form is evaluated. If the evaluation of the protected-form completes abruptly for any reason, then the evaluation of the unwind-protect-form proceeds as follows: -
          Let $\mlvar{reason}$ be the reason for the abrupt completion of the protected-form. The cleanup-forms are evaluated in sequence from left to right. If the evaluation of any cleanup-form completes abruptly for any reason, then the following serial-forms are not evaluated and the evaluation of the unwind-protect-form completes abruptly for the same reason. Otherwise, the evaluations of the cleanup-forms all complete normally and the evaluation of the unwind-protect-form completes abruptly for the reason $\mlvar{reason}$.
          - Otherwise, the evaluation of the protected-form completes normally and the evaluation of the unwind-protect-form proceeds as follows: -
          The cleanup-forms are evaluated in sequence from left to right. If the evaluation of any cleanup-form completes abruptly for any reason, then the following serial-forms are not evaluated and the evaluation of the unwind-protect-form completes abruptly for the same reason. Otherwise, the evaluations of the cleanup-forms all complete normally and the evaluation of the unwind-protect-form completes normally and produces the values of the protected-form.
          +
          The protected form is evaluated. If the evaluation of the protected form completes abruptly for any reason, then the evaluation of the unwind-protect form proceeds as follows: +
          Let $\mlvar{reason}$ be the reason for the abrupt completion of the protected form. The cleanup forms are evaluated in sequence from left to right. If the evaluation of any cleanup form completes abruptly for any reason, then the following cleanup forms are not evaluated and the evaluation of the unwind-protect form completes abruptly for the same reason. Otherwise, the evaluations of the cleanup forms all complete normally and the evaluation of the unwind-protect form completes abruptly for the reason $\mlvar{reason}$.
          + Otherwise, the evaluation of the protected form completes normally and the evaluation of the unwind-protect form proceeds as follows: +
          The cleanup forms are evaluated in sequence from left to right. If the evaluation of any cleanup form completes abruptly for any reason, then the following cleanup forms are not evaluated and the evaluation of the unwind-protect form completes abruptly for the same reason. Otherwise, the evaluations of the cleanup forms all complete normally and the evaluation of the unwind-protect form completes normally and produces the values of the protected form.
          -

          Special Form _for-each

          -

          The special form _for-each is evaluated as follows:

          -
          -
          (_for-each $\metavar{function-form}$ $\metavar{list-form}$)
          -
          The function-form is evaluated. Let $\mlvar{function}$ be the primary value of the function-form. If $\mlvar{function}$ is not a function, then the evaluation of the _for-each-form completes abruptly for a reason of type error. Otherwise, the list-form is evaluated. Let $\mlvar{list}$ be the primary value of the list-form. If $\mlvar{list}$ is not a proper list, then the evaluation of the _for-each-form completes abruptly for a reason of type error. Otherwise, the function $\mlvar{function}$ is invoked in sequence on each element of the list $\mlvar{list}$, from the first element to the last element. If any invocation completes abruptly for any reason or does not complete, then the evaluation of the _for-each-form also completes abruptly for the same reason or does not complete either. Otherwise, the _for-each-form evaluates to #v.
          -

          Function Calls

          A spreadable sequence of objects is a nonempty sequence of objects such that the last element of the sequence is a proper list of objects. Let $\mlvar{seq}=[\obj_1,\ldots,\obj_n,\code{(}\obj'_1\ldots\obj'_m\code{)}]$, where $n$ and $m$ are nonnegative integers, be a spreadable sequence of objects. We will denote by $\spread(\mlvar{seq})$ the sequence of objects $[\obj_1,\ldots,\obj_n,\obj'_1,\ldots,\obj'_m]$.

          The function calls are evaluated as follows (the differences in behavior between the different types of function calls are highlighted with a gray background):

          ($\metavar{operator-form}$ $\metavar{operand-forms}$)
          -
          The operator-form is evaluated. Let $\mlvar{operator}$ be the primary value of the operator-form. If $\mlvar{operator}$ is not a function, then the evaluation of the plain function call completes abruptly for a reason of type error. Otherwise, the operand-forms are evaluated in sequence from left to right, the primary values of the operand-forms are collected into a sequence $\mlvar{seq}$, and $\mlvar{operator}$ is invoked on $\mlvar{seq}$. If the invocation completes abruptly for any reason, then the evaluation of the plain function call also completes abruptly for the same reason. Otherwise, if the invocation does not complete, then the evaluation of the plain function call does not complete either. Otherwise, the plain function call evaluates to the values of the invocation. (The behavior described here is identical to the behavior described in the user manual.)
          +
          The operator form is evaluated. Let $\mlvar{operator}$ be the primary value of the operator form. If $\mlvar{operator}$ is not a function, then the evaluation of the plain function call completes abruptly for a reason of type error. Otherwise, the operand forms are evaluated in sequence from left to right, the primary values of the operand forms are collected into a sequence $\mlvar{seq}$, and $\mlvar{operator}$ is invoked on $\mlvar{seq}$. If the invocation completes abruptly for any reason, then the evaluation of the plain function call also completes abruptly for the same reason. Otherwise, if the invocation does not complete, then the evaluation of the plain function call does not complete either. Otherwise, the plain function call evaluates to the values of the invocation. (The behavior described here is identical to the behavior described in the user manual.)
          (apply $\metavar{operator-form}$ $\metavar{operand-forms}$)
          -
          The operator-form is evaluated. Let $\mlvar{operator}$ be the primary value of the operator-form. If $\mlvar{operator}$ is not a function, then the evaluation of the apply-form completes abruptly for a reason of type error. Otherwise, the operand-forms are evaluated in sequence from left to right, the primary values of the operand-forms are collected into a sequence $\mlvar{seq}$, and $\mlvar{operator}$ is invoked on $\spread(\mlvar{seq})$. (The evaluation of the apply-form completes abruptly for a reason of type error if $\mlvar{seq}$ is not a spreadable sequence of objects.) If the invocation completes abruptly for any reason, then the evaluation of the apply-form also completes abruptly for the same reason. Otherwise, if the invocation does not complete, then the evaluation of the apply-form does not complete either. Otherwise, the apply-form evaluates to the values of the invocation.
          +
          The operator form is evaluated. Let $\mlvar{operator}$ be the primary value of the operator form. If $\mlvar{operator}$ is not a function, then the evaluation of the apply form completes abruptly for a reason of type error. Otherwise, the operand forms are evaluated in sequence from left to right, the primary values of the operand forms are collected into a sequence $\mlvar{seq}$, and $\mlvar{operator}$ is invoked on $\spread(\mlvar{seq})$. (The evaluation of the apply form completes abruptly for a reason of type error if $\mlvar{seq}$ is not a spreadable sequence of objects.) If the invocation completes abruptly for any reason, then the evaluation of the apply form also completes abruptly for the same reason. Otherwise, if the invocation does not complete, then the evaluation of the apply form does not complete either. Otherwise, the apply form evaluates to the values of the invocation.
          (multiple-value-call $\metavar{operator-form}$ $\metavar{operand-forms}$)
          -
          The operator-form is evaluated. Let $\mlvar{operator}$ be the primary value of the operator-form. If $\mlvar{operator}$ is not a function, then the evaluation of the multiple-value-call-form completes abruptly for a reason of type error. Otherwise, the operand-forms are evaluated in sequence from left to right, all the values of the operand-forms are collected into a sequence $\mlvar{seq}$, and $\mlvar{operator}$ is invoked on the sequence $\mlvar{seq}$. If the invocation completes abruptly for any reason, then the evaluation of the multiple-value-call-form also completes abruptly for the same reason. Otherwise, if the invocation does not complete, then the evaluation of the multiple-value-call-form does not complete either. Otherwise, the multiple-value-call-form evaluates to the values of the invocation.
          +
          The operator form is evaluated. Let $\mlvar{operator}$ be the primary value of the operator form. If $\mlvar{operator}$ is not a function, then the evaluation of the multiple-value-call form completes abruptly for a reason of type error. Otherwise, the operand forms are evaluated in sequence from left to right, all the values of the operand forms are collected into a sequence $\mlvar{seq}$, and $\mlvar{operator}$ is invoked on the sequence $\mlvar{seq}$. If the invocation completes abruptly for any reason, then the evaluation of the multiple-value-call form also completes abruptly for the same reason. Otherwise, if the invocation does not complete, then the evaluation of the multiple-value-call form does not complete either. Otherwise, the multiple-value-call form evaluates to the values of the invocation.
          (multiple-value-apply $\metavar{operator-form}$ $\metavar{operand-forms}$)
          -
          The operator-form is evaluated. Let $\mlvar{operator}$ be the primary value of the operator-form. If $\mlvar{operator}$ is not a function, then the evaluation of the multiple-value-apply-form completes abruptly for a reason of type error. Otherwise, the operand-forms are evaluated in sequence from left to right, all the values of the operand-forms are collected into a sequence $\mlvar{seq}$, and $\mlvar{operator}$ is invoked on $\spread(\mlvar{seq})$. (The evaluation of the multiple-value-apply-form completes abruptly for a reason of type error if $\mlvar{seq}$ is not a spreadable sequence of objects.) If the invocation completes abruptly for any reason, then the evaluation of the multiple-value-apply-form also completes abruptly for the same reason. Otherwise, if the invocation does not complete, then the evaluation of the multiple-value-apply-form does not complete either. Otherwise, the multiple-value-apply-form evaluates to the values of the invocation.
          +
          The operator form is evaluated. Let $\mlvar{operator}$ be the primary value of the operator form. If $\mlvar{operator}$ is not a function, then the evaluation of the multiple-value-apply form completes abruptly for a reason of type error. Otherwise, the operand forms are evaluated in sequence from left to right, all the values of the operand forms are collected into a sequence $\mlvar{seq}$, and $\mlvar{operator}$ is invoked on $\spread(\mlvar{seq})$. (The evaluation of the multiple-value-apply form completes abruptly for a reason of type error if $\mlvar{seq}$ is not a spreadable sequence of objects.) If the invocation completes abruptly for any reason, then the evaluation of the multiple-value-apply form also completes abruptly for the same reason. Otherwise, if the invocation does not complete, then the evaluation of the multiple-value-apply form does not complete either. Otherwise, the multiple-value-apply form evaluates to the values of the invocation.
          -

          A primitive function is invoked as described in the user manual. A closure is invoked as described in the user manual except for the way the parameters are paired with the arguments in order to extend the value namespace of the lexical environment captured by the closure (when the corresponding lambda abstraction is a _vlambda-form or an _mlambda-form), the function namespace of the lexical environment captured by the closure (when the corresponding lambda abstraction is an _flambda-form), or the value namespace of the current dynamic environment (when the corresponding lambda abstraction is a _dlambda-form). Let $\arg_1,\ldots,\arg_n$ be the arguments and $var_1,\ldots,\var_m$ be the required parameters. The way the parameters are paired with the arguments depends on the absence or presence of a rest parameter:

          +

          A primitive function is invoked as described in the user manual. A closure is invoked as described in the user manual except for the way the parameters are paired with the arguments in order to extend the value namespace of the lexical environment captured by the closure (when the corresponding lambda abstraction is a _vlambda form or an _mlambda form), the function namespace of the lexical environment captured by the closure (when the corresponding lambda abstraction is an _flambda form), or the value namespace of the current dynamic environment (when the corresponding lambda abstraction is a _dlambda form). Let $var_1,\ldots,\var_n$ be the required parameters and $\arg_1,\ldots,\arg_m$ be the arguments. The way the parameters are paired with the arguments depends on the absence or presence of a rest parameter:

          Case 1, there is no rest parameter:
          -
          If $n\neq m$, then the invocation completes abruptly for a reason of type error. Otherwise, the appropriate namespace of the appropriate environment is extended to bind $\var_i$ to $\arg_i$ for all $i$ from $1$ to $m$. (This is the behavior described in the user manual.)
          -
          Case 2, there is a rest parameter $\var_{m+1}$:
          -
          If $n\lt m$, then the invocation completes abruptly for a reason of type error. Otherwise, the appropriate namespace of the appropriate environment is extended to bind $\var_i$ to $\arg_i$ for all $i$ from $1$ to $m$ and $\var_{m+1}$ to a proper list whose elements are $\arg_{m+1},\ldots,arg_{n}$. The conses used to build the proper list are not necessarily new conses. When the function call is an apply-form or a multiple-value-apply-form, the proper list and the last element of the spreadable sequence of objects can share conses.
          +
          If $n\ne m$, then the invocation completes abruptly for a reason of type error. Otherwise, the appropriate namespace of the appropriate environment is extended to bind $\var_i$ to $\arg_i$ for all $i$ from $1$ to $n$. (This is the behavior described in the user manual.)
          +
          Case 2, there is a rest parameter $\var_{n+1}$:
          +
          If $m\lt n$, then the invocation completes abruptly for a reason of type error. Otherwise, the appropriate namespace of the appropriate environment is extended to bind $\var_i$ to $\arg_i$ for all $i$ from $1$ to $n$ and $\var_{n+1}$ to a proper list whose elements are $\arg_{n+1},\ldots,arg_{m}$. The conses used to build the proper list are not necessarily new conses. When the function call is an apply form or a multiple-value-apply form, the proper list and the last element of the spreadable sequence of objects can share conses.

          Here are some examples, where the bindings are assumed to belong to the value namespace:

          @@ -1383,13 +1415,16 @@
          (a b c . d)$[\code{1},\code{2},\code{3}]$$[\vbinding{a}{1},\vbinding{b}{2},\vbinding{c}{3},\vbinding{d}{()}]$
          (a b c d . e)$[\code{1},\code{2},\code{3}]$N/A (error: too few arguments)
          -

          Here is one way to reproduce the preceding examples using plain function calls and _vlambda-forms:

          -
          > ((_vlambda (a b) (list a b)) 1 2 3)
          ERROR: Too many arguments.

          > ((_vlambda (a b c) (list a b c)) 1 2 3)
          (1 2 3)

          > ((_vlambda (a b c d) (list a b c d)) 1 2 3)
          ERROR: Too few arguments.

          > ((_vlambda a (list a)) 1 2 3)
          ((1 2 3))

          > ((_vlambda (a . b) (list a b)) 1 2 3)
          (1 (2 3))

          > ((_vlambda (a b . c) (list a b c)) 1 2 3)
          (1 2 (3))

          > ((_vlambda (a b c . d) (list a b c d)) 1 2 3)
          (1 2 3 ())

          > ((_vlambda (a b c d . e) (list a b c d e)) 1 2 3)
          ERROR: Too few arguments.
          +

          Here is one way to reproduce the preceding examples using plain function calls and _vlambda forms:

          +
          > ((_vlambda (a b) (list a b)) 1 2 3)
          EvaluatorError: too-many-arguments: Too many arguments.

          > ((_vlambda (a b c) (list a b c)) 1 2 3)
          (1 2 3)

          > ((_vlambda (a b c d) (list a b c d)) 1 2 3)
          EvaluatorError: too-few-arguments: Too few arguments.

          > ((_vlambda a (list a)) 1 2 3)
          ((1 2 3))

          > ((_vlambda (a . b) (list a b)) 1 2 3)
          (1 (2 3))

          > ((_vlambda (a b . c) (list a b c)) 1 2 3)
          (1 2 (3))

          > ((_vlambda (a b c . d) (list a b c d)) 1 2 3)
          (1 2 3 ())

          > ((_vlambda (a b c d . e) (list a b c d e)) 1 2 3)
          EvaluatorError: too-few-arguments: Too few arguments.

          Evaluation Rules in Continuation-Passing Style

          Elementary continuations:

          • $\serialform$
          • $\iftestform$
          • +
          • $\foreachfunctionform$
          • +
          • $\foreachlistform$
          • +
          • $\foreachinvocation$
          • $\setvalueform$
          • $\blockserialforms$
          • $\returnfromvaluesform$
          • @@ -1399,11 +1434,9 @@
          • $\throwvaluesform$
          • $\handlerbindhandlerform$
          • $\handlerbindserialforms$
          • +
          • $\handlerbindinvocation$
          • $\unwindprotectprotectedform$
          • $\unwindprotectcleanupforms$
          • -
          • $\foreachfunctionform$
          • -
          • $\foreachlistform$
          • -
          • $\foreachelement$
          • $\functioncalloperatorform$
          • $\functioncalloperandform$
          • $\macro$
          • @@ -1426,14 +1459,25 @@
            To invoke the auxiliary function $\evalserialforms$ on $\mlvar{serial-forms}$, $\lexenv$, $\dynenv$, and $k$, do the following:
            If $\mlvar{serial-forms}$ is empty, then invoke $k$ on #v. Otherwise, invoke the auxiliary function $\evalserialformforms$ on $\mlvar{serial-forms}$, $\lexenv$, $\dynenv$, and $k$.
            To invoke the auxiliary function $\evalserialformforms$ on $\mlvar{serial-forms}$, $\lexenv$, $\dynenv$, and $k$, do the following:
            -
            If $\mlvar{serial-forms}$ contains exactly one element, then evaluate the single element of $\mlvar{serial-forms}$ with respect to $\lexenv$, $\dynenv$, and $k$. Otherwise, evaluate the first element of $\mlvar{serial-forms}$ with respect to $\lexenv$, $\dynenv$, and a $\serialform$ continuation capturing $\mlvar{serial-forms}$ minus its first element, $\lexenv$, $\dynenv$, and $k$.
            +
            If $\mlvar{serial-forms}$ contains exactly one element, then evaluate the single element of $\mlvar{serial-forms}$ with respect to $\lexenv$, $\dynenv$, and $k$. Otherwise, evaluate the first element of $\mlvar{serial-forms}$ with respect to $\lexenv$, $\dynenv$, and a $\serialform$ continuation capturing $\mlvar{serial-forms}$, $\lexenv$, $\dynenv$, and $k$.
            To invoke the continuation $\serialform(\mlvar{serial-forms},\lexenv,\dynenv,k)$ on $\outcome$, do the following:
            -
            If $\outcome$ is an abrupt completion reason, then invoke $k$ on $\outcome$. Otherwise, invoke the auxiliary function $\evalserialformforms$ on $\mlvar{serial-forms}$, $\lexenv$, $\dynenv$, and $k$.
            +
            If $\outcome$ is an abrupt completion reason, then invoke $k$ on $\outcome$. Otherwise, invoke the auxiliary function $\evalserialformforms$ on $\mlvar{serial-forms}$ minus its first element, $\lexenv$, $\dynenv$, and $k$.
            To evaluate (if $\mlvar{test-form}$ $\mlvar{then-form}$ $\mlvar{else-form}$) with respect to $\lexenv$, $\dynenv$, and $k$, do the following:
            -
            Evaluate $\mlvar{test-form}$ with respect to an $\iftestform$ continuation capturing $\mlvar{then-form}$, $\mlvar{else-form}$, $\lexenv$, $\dynenv$, and $k$.
            +
            Evaluate $\mlvar{test-form}$ with respect to $\lexenv$, $\dynenv$, and an $\iftestform$ continuation capturing $\mlvar{then-form}$, $\mlvar{else-form}$, $\lexenv$, $\dynenv$, and $k$.
            To invoke the continuation $\iftestform(\mlvar{then-form},\mlvar{else-form},\lexenv,\dynenv,k)$ on $\outcome$, do the following:
            If $\outcome$ is an abrupt completion reason, then invoke $k$ on $\outcome$. Otherwise, let $\mlvar{test}$ be the primary value of $\outcome$. If $\mlvar{test}$ is not a boolean, then invoke $k$ on an abrupt completion reason of type error. If $\mlvar{test}$ is the boolean #t, then evaluate $\mlvar{then-form}$ with respect to $\lexenv$, $\dynenv$, and $k$. If $\mlvar{test}$ is the boolean #f, then evaluate $\mlvar{else-form}$ with respect to $\lexenv$, $\dynenv$, and $k$.
            + +
            To evaluate (_for-each $\mlvar{function-form}$ $\mlvar{list-form}$) with respect to $\lexenv$, $\dynenv$, and $k$, do the following:
            +
            Evaluate $\mlvar{function-form}$ with respect to $\lexenv$, $\dynenv$, and a $\foreachfunctionform$ continuation capturing $\mlvar{list-form}$, $\lexenv$, $\dynenv$, and $k$.
            +
            To invoke the continuation $\foreachfunctionform(\mlvar{list-form},\lexenv,\dynenv,k)$ on $\outcome$, do the following:
            +
            If $\outcome$ is an abrupt completion reason, then invoke $k$ on $\outcome$. Otherwise, let $\mlvar{function}$ be the primary value of $\outcome$. If $\mlvar{function}$ is not a function, then invoke $k$ on an abrupt completion reason of type error. Otherwise, evaluate $\mlvar{list-form}$ with respect to $\lexenv$, $\dynenv$, and a $\foreachlistform$ continuation capturing $\mlvar{function}$, $\dynenv$, and $k$.
            +
            To invoke the continuation $\foreachlistform(\mlvar{function},\dynenv,k)$ on $\outcome$, do the following:
            +
            If $\outcome$ is an abrupt completion reason, then invoke $k$ on $\outcome$. Otherwise, let $\mlvar{list}$ be the primary value of $\outcome$. If $\mlvar{list}$ is not a proper list, then invoke $k$ on an abrupt completion reason of type error. Otherwise, invoke the auxiliary function $\foreach$ on $\mlvar{function}$, $\mlvar{list}$, $\dynenv$, and $k$.
            +
            To invoke the auxiliary function $\foreach$ on $\mlvar{function}$, $\mlvar{list}$, $\dynenv$, and $k$, do the following:
            +
            If $\mlvar{list}$ is empty, then invoke $k$ on #v. Otherwise, invoke the auxiliary function $\invoke$ on $\mlvar{function}$, the car of $\mlvar{list}$, $\dynenv$, and a $\foreachinvocation$ continuation capturing $\mlvar{function}$, $\mlvar{list}$, $\dynenv$, and $k$.
            +
            To invoke the continuation $\foreachinvocation(\mlvar{function},\mlvar{list},\dynenv,k)$ on $\outcome$, do the following:
            +
            If $\outcome$ is an abrupt completion reason, then invoke $k$ on $\outcome$. Otherwise, invoke the auxiliary function $\foreach$ on $\mlvar{function}$, the cdr of $\mlvar{list}$, $\dynenv$, and $k$.
            To evaluate (_vlambda $\mlvar{parameter-list}$ $\mlvar{body}$) with respect to $\lexenv$, $\dynenv$, and $k$, do the following:
            To evaluate (_mlambda $\mlvar{parameter-list}$ $\mlvar{body}$) with respect to $\lexenv$, $\dynenv$, and $k$, do the following:
            @@ -1444,75 +1488,66 @@
            To evaluate (vref $\mlvar{variable}$) with respect to $\lexenv$, $\dynenv$, and $k$, do the following:
            To evaluate (fref $\mlvar{variable}$) with respect to $\lexenv$, $\dynenv$, and $k$, do the following:
            To evaluate (dref $\mlvar{variable}$) with respect to $\lexenv$, $\dynenv$, and $k$, do the following:
            -
            If a binding exists for $\mlvar{variable}$, then invoke $k$ on its value. Otherwise, invoke $k$ on an abrupt completion reason of type error.
            +
            If there exists a binding for $\mlvar{variable}$, then invoke $k$ on the value of that binding. Otherwise, invoke $k$ on an abrupt completion reason of type error.
            To evaluate (vset! $\mlvar{variable}$ $\mlvar{value-form}$) with respect to $\lexenv$, $\dynenv$, and $k$, do the following:
            To evaluate (fset! $\mlvar{variable}$ $\mlvar{value-form}$) with respect to $\lexenv$, $\dynenv$, and $k$, do the following:
            To evaluate (dset! $\mlvar{variable}$ $\mlvar{value-form}$) with respect to $\lexenv$, $\dynenv$, and $k$, do the following:
            -
            Evaluate $\mlvar{value-form}$ with respect to a $\setvalueform$ continuation capturing $\mlvar{variable}$, $\lexenv$, $\dynenv$, and $k$.
            +
            Evaluate $\mlvar{value-form}$ with respect to $\lexenv$, $\dynenv$, and a $\setvalueform$ continuation capturing $\mlvar{variable}$, $\lexenv$, $\dynenv$, and $k$.
            To invoke the continuation $\setvalueform(\mlvar{variable},\lexenv,\dynenv,k)$ on $\outcome$, do the following:
            -
            If $\outcome$ is an abrupt completion reason, then invoke $k$ on $\outcome$. Otherwise, let $\mlvar{value}$ be the primary value of $\outcome$. If a binding exists for $\mlvar{variable}$, then set its value to $\mlvar{value}$ and invoke $k$ on $\mlvar{value}$. Otherwise, create a binding between $\mlvar{variable}$ and $\mlvar{value}$ and invoke $k$ on $\mlvar{value}$.
            +
            If $\outcome$ is an abrupt completion reason, then invoke $k$ on $\outcome$. Otherwise, let $\mlvar{value}$ be the primary value of $\outcome$. If there exists a binding for $\mlvar{variable}$, then replace the value of that binding by $\mlvar{value}$ and invoke $k$ on $\mlvar{value}$. Otherwise, create a new binding between $\mlvar{variable}$ and $\mlvar{value}$ and invoke $k$ on $\mlvar{value}$.
            To evaluate (block $\mlvar{block-name}$ $\mlvar{serial-forms}$) with respect to $\lexenv$, $\dynenv$, and $k$, do the following:
            -
            Let $\mlvar{exit-tag}$ be a new uninterned variable. Invoke the auxiliary function $\evalserialforms$ on $\mlvar{serial-forms}$ and a $\blockserialforms$ continuation capturing $\mlvar{exit-tag}$, the environment extending $\lexenv$ to bind, in the block namespace, the variable $\mlvar{block-name}$ to $\mlvar{exit-tag}$, the environment extending $\dynenv$ to bind, in the exit-point namespace, the variable $\mlvar{exit-tag}$ to #v, and $k$.
            -
            To invoke the continuation $\blockserialforms(\mlvar{exit-tag},\lexenv,\dynenv,k)$ on $\outcome$, do the following:
            +
            Let $\mlvar{exit-tag}$ be a new uninterned variable. Invoke the auxiliary function $\evalserialforms$ on $\mlvar{serial-forms}$, the environment extending $\lexenv$ to bind, in the block namespace, the variable $\mlvar{block-name}$ to $\mlvar{exit-tag}$, the environment extending $\dynenv$ to bind, in the exit-point namespace, the variable $\mlvar{exit-tag}$ to #v, and a $\blockserialforms$ continuation capturing $\mlvar{exit-tag}$ and $k$.
            +
            To invoke the continuation $\blockserialforms(\mlvar{exit-tag},k)$ on $\outcome$, do the following:
            If $result$ is an abrupt completion reason of type nonlocal-exit carrying an exit tag eq? to $\mlvar{exit-tag}$, then invoke $k$ on the values carried by $\outcome$. Otherwise, invoke $k$ on $\outcome$.
            To evaluate (return-from $\mlvar{block-name}$ $\mlvar{values-form}$) with respect to $\lexenv$, $\dynenv$, and $k$, do the following:
            -
            If there exists no binding for the variable $\mlvar{block-name}$ in the block namespace of $\lexenv$, then invoke $k$ on an abrupt completion reason of type error. Otherwise, let $\mlvar{exit-tag}$ be the value of the binding for the variable $\mlvar{block-name}$ in the block namespace of $\lexenv$. If there exists no binding for the variable $\mlvar{exit-tag}$ in the exit-point namespace of $\dynenv$, then invoke $k$ on an abrupt completion reason of type error. Otherwise, evaluate $\mlvar{values-form}$ with respect to $\lexenv$, $\dynenv$, and a $\returnfromvaluesform$ continuation capturing $\mlvar{exit-tag}$, $\lexenv$, $\dynenv$, and $k$.
            -
            To invoke the continuation $\returnfromvaluesform(\mlvar{exit-tag},\lexenv,\dynenv,k)$ on $\outcome$, do the following:
            +
            If there exists no binding for the variable $\mlvar{block-name}$ in the block namespace of $\lexenv$, then invoke $k$ on an abrupt completion reason of type error. Otherwise, let $\mlvar{exit-tag}$ be the value of the binding for the variable $\mlvar{block-name}$ in the block namespace of $\lexenv$. If there exists no binding for the variable $\mlvar{exit-tag}$ in the exit-point namespace of $\dynenv$, then invoke $k$ on an abrupt completion reason of type error. Otherwise, evaluate $\mlvar{values-form}$ with respect to $\lexenv$, $\dynenv$, and a $\returnfromvaluesform$ continuation capturing $\mlvar{exit-tag}$ and $k$.
            +
            To invoke the continuation $\returnfromvaluesform(\mlvar{exit-tag},k)$ on $\outcome$, do the following:
            If $\outcome$ is an abrupt completion reason, then invoke $k$ on $\outcome$. Otherwise, invoke $k$ on an abrupt completion reason of type nonlocal-exit carrying $\mlvar{exit-tag}$ and $\outcome$.
            To evaluate (catch $\mlvar{exit-tag-form}$ $\mlvar{serial-forms}$) with respect to $\lexenv$, $\dynenv$, and $k$, do the following:
            -
            Evaluate $\mlvar{exit-tag-form}$ with respect to a $\catchexittagform$ continuation capturing $\mlvar{serial-forms}$, $\lexenv$, $\dynenv$, and $k$.
            +
            Evaluate $\mlvar{exit-tag-form}$ with respect to $\lexenv$, $\dynenv$, and a $\catchexittagform$ continuation capturing $\mlvar{serial-forms}$, $\lexenv$, $\dynenv$, and $k$.
            To invoke the continuation $\catchexittagform(\mlvar{serial-forms},\lexenv,\dynenv,k)$ on $\outcome$, do the following:
            -
            If $\outcome$ is an abrupt completion reason, then invoke $k$ on $\outcome$. Otherwise, let $\mlvar{exit-tag}$ be the primary value of $\outcome$. If $\mlvar{exit-tag}$ is not a variable, then invoke $k$ on an abrupt completion reason of type error. Otherwise, invoke the auxiliary function $\evalserialforms$ on $\mlvar{serial-forms}$ and a $\catchserialforms$ continuation capturing $\mlvar{exit-tag}$, $\lexenv$, the environment extending $\dynenv$ to bind, in the exit-point namespace, the variable $\mlvar{exit-tag}$ to #v, and $k$.
            -
            To invoke the continuation $\catchserialforms(\mlvar{exit-tag},\lexenv,\dynenv,k)$ on $\outcome$, do the following:
            +
            If $\outcome$ is an abrupt completion reason, then invoke $k$ on $\outcome$. Otherwise, let $\mlvar{exit-tag}$ be the primary value of $\outcome$. If $\mlvar{exit-tag}$ is not a variable, then invoke $k$ on an abrupt completion reason of type error. Otherwise, invoke the auxiliary function $\evalserialforms$ on $\mlvar{serial-forms}$, $\lexenv$, the environment extending $\dynenv$ to bind, in the exit-point namespace, the variable $\mlvar{exit-tag}$ to #v, and a $\catchserialforms$ continuation capturing $\mlvar{exit-tag}$ and $k$.
            +
            To invoke the continuation $\catchserialforms(\mlvar{exit-tag},k)$ on $\outcome$, do the following:
            If $result$ is an abrupt completion reason of type nonlocal-exit carrying an exit tag eq? to $\mlvar{exit-tag}$, then invoke $k$ on the values carried by $\outcome$. Otherwise, invoke $k$ on $\outcome$.
            To evaluate (throw $\mlvar{exit-tag-form}$ $\mlvar{values-form}$) with respect to $\lexenv$, $\dynenv$, and $k$, do the following:
            -
            Evaluate $\mlvar{exit-tag-form}$ with respect to a $\throwexittagform$ continuation capturing $\mlvar{values-form}$, $\lexenv$, $\dynenv$, and $k$.
            +
            Evaluate $\mlvar{exit-tag-form}$ with respect to $\lexenv$, $\dynenv$, and a $\throwexittagform$ continuation capturing $\mlvar{values-form}$, $\lexenv$, $\dynenv$, and $k$.
            To invoke the continuation $\throwexittagform(\mlvar{values-form},\lexenv,\dynenv,k)$ on $\outcome$, do the following:
            -
            If $\outcome$ is an abrupt completion reason, then invoke $k$ $\outcome$. Otherwise, let $\mlvar{exit-tag}$ be the primary value of $\outcome$. If $\mlvar{exit-tag}$ is not a variable, then invoke $k$ on an abrupt completion reason of type error. Otherwise, if there exists no binding for the variable $\mlvar{exit-tag}$ in the exit-point namespace of $\dynenv$, then invoke $k$ on an abrupt completion reason of type error. Otherwise, evaluate $\mlvar{values-form}$ with respect to a $\throwvaluesform$ continuation capturing $\mlvar{exit-tag}$, $\lexenv$, $\dynenv$, and $k$.
            -
            To invoke the continuation $\throwvaluesform(\mlvar{exit-tag},\lexenv,\dynenv,k)$ on $\outcome$, do the following:
            +
            If $\outcome$ is an abrupt completion reason, then invoke $k$ $\outcome$. Otherwise, let $\mlvar{exit-tag}$ be the primary value of $\outcome$. If $\mlvar{exit-tag}$ is not a variable, then invoke $k$ on an abrupt completion reason of type error. Otherwise, if there exists no binding for the variable $\mlvar{exit-tag}$ in the exit-point namespace of $\dynenv$, then invoke $k$ on an abrupt completion reason of type error. Otherwise, evaluate $\mlvar{values-form}$ with respect to $\lexenv$, $\dynenv$, and a $\throwvaluesform$ continuation capturing $\mlvar{exit-tag}$ and $k$.
            +
            To invoke the continuation $\throwvaluesform(\mlvar{exit-tag},k)$ on $\outcome$, do the following:
            If $\outcome$ is an abrupt completion reason, then invoke $k$ on $\outcome$. Otherwise, invoke $k$ on an abrupt completion reason of type nonlocal-exit carrying $\mlvar{exit-tag}$ and $\outcome$.
            To evaluate (_handler-bind $\mlvar{handler-form}$ $\mlvar{serial-forms}$) with respect to $\lexenv$, $\dynenv$, and $k$, do the following:
            -
            Evaluate $\mlvar{handler-form}$ with respect to a $\handlerbindhandlerform$ continuation capturing $\mlvar{serial-forms}$, $\lexenv$, $\dynenv$, and $k$.
            +
            Evaluate $\mlvar{handler-form}$ with respect to $\lexenv$, $\dynenv$, and a $\handlerbindhandlerform$ continuation capturing $\mlvar{serial-forms}$, $\lexenv$, $\dynenv$, and $k$.
            To invoke the continuation $\handlerbindhandlerform(\mlvar{serial-forms},\lexenv,\dynenv,k)$ on $\outcome$, do the following:
            -
            If $\outcome$ is an abrupt completion reason, then invoke $k$ on $\outcome$. Otherwise, let $\mlvar{handler}$ be the primary value of $\outcome$. If $\mlvar{handler}$ is not a function, then invoke $k$ on an abrupt completion reason of type error. Otherwise, invoke the auxiliary function $\evalserialforms$ on $\mlvar{serial-forms}$ and a $\handlerbindserialforms$ continuation capturing $\mlvar{handler}$, $\lexenv$, $\dynenv$, and $k$.
            -
            To invoke the continuation $\handlerbindserialforms(\mlvar{handler},\lexenv,\dynenv,k)$ on $\outcome$, do the following:
            -
            If $\outcome$ is an abrupt completion reason of type error, then invoke the auxiliary function $\invoke$ on $\mlvar{handler}$, the string carried by $\outcome$, $\dynenv$, and $k$. Otherwise, invoke $k$ on $\outcome$.
            +
            If $\outcome$ is an abrupt completion reason, then invoke $k$ on $\outcome$. Otherwise, let $\mlvar{handler}$ be the primary value of $\outcome$. If $\mlvar{handler}$ is not a function, then invoke $k$ on an abrupt completion reason of type error. Otherwise, invoke the auxiliary function $\evalserialforms$ on $\mlvar{serial-forms}$, $\lexenv$, $\dynenv$, and a $\handlerbindserialforms$ continuation capturing $\mlvar{handler}$, $\dynenv$, and $k$.
            +
            To invoke the continuation $\handlerbindserialforms(\mlvar{handler},\dynenv,k)$ on $\outcome$, do the following:
            +
            If $\outcome$ is an abrupt completion reason of type error, then invoke the auxiliary function $\invoke$ on $\mlvar{handler}$, the category and description carried by $\outcome$, $\dynenv$, and a $\handlerbindinvocation$ continuation capturing $\mlvar{outcome}$ and $k$. Otherwise, invoke $k$ on $\outcome$.
            +
            To invoke the continuation $\handlerbindinvocation(\mlvar{serial-forms-outcome},k)$ on $\outcome$, do the following:
            +
            If $\outcome$ is an abrupt completion reason, then invoke $k$ on $\mlvar{outcome}$. Otherwise, invoke $k$ on $\mlvar{serial-forms-outcome}$.
            To evaluate (unwind-protect $\mlvar{protected-form}$ $\mlvar{cleanup-forms}$) with respect to $\lexenv$, $\dynenv$, and $k$, do the following:
            -
            Evaluate $\mlvar{protected-form}$ with respect to an $\unwindprotectprotectedform$ continuation capturing $\mlvar{cleanup-forms}$, $\lexenv$, $\dynenv$, and $k$.
            +
            Evaluate $\mlvar{protected-form}$ with respect to $\lexenv$, $\dynenv$, and an $\unwindprotectprotectedform$ continuation capturing $\mlvar{cleanup-forms}$, $\lexenv$, $\dynenv$, and $k$.
            To invoke the continuation $\unwindprotectprotectedform(\mlvar{cleanup-forms},\lexenv,\dynenv,k)$ on $\outcome$, do the following:
            -
            Invoke the auxiliary function $\evalserialforms$ on $\mlvar{cleanup-forms}$ and an $\unwindprotectcleanupforms$ continuation capturing $\outcome$, $\lexenv$, $\dynenv$, and $k$.
            -
            To invoke the continuation $\unwindprotectcleanupforms(\mlvar{protected-form-outcome},\lexenv,\dynenv,k)$ on $\outcome$, do the following:
            +
            Invoke the auxiliary function $\evalserialforms$ on $\mlvar{cleanup-forms}$, $\lexenv$, $\dynenv$, and an $\unwindprotectcleanupforms$ continuation capturing $\outcome$ and $k$.
            +
            To invoke the continuation $\unwindprotectcleanupforms(\mlvar{protected-form-outcome},k)$ on $\outcome$, do the following:
            If $\outcome$ is an abrupt completion reason, then invoke $k$ on $\outcome$. Otherwise, invoke $k$ on $\mlvar{protected-form-outcome}$.
            - -
            To evaluate (_for-each $\mlvar{function-form}$ $\mlvar{list-form}$) with respect to $\lexenv$, $\dynenv$, and $k$, do the following:
            -
            Evaluate $\mlvar{function-form}$ with respect to a $\foreachfunctionform$ continuation capturing $\mlvar{list-form}$, $\lexenv$, $\dynenv$, and $k$.
            -
            To invoke the continuation $\foreachfunctionform(\mlvar{list-form},\lexenv,\dynenv,k)$ on $\outcome$, do the following:
            -
            If $\outcome$ is an abrupt completion reason, then invoke $k$ on $\outcome$. Otherwise, let $\mlvar{function}$ be the primary value of $\outcome$. If $\mlvar{function}$ is not a function, then invoke $k$ on an abrupt completion reason of type error. Otherwise, evaluate $\mlvar{list-form}$ with respect to a $\foreachlistform$ continuation capturing $\mlvar{function}$, $\lexenv$, $\dynenv$, and $k$.
            -
            To invoke the continuation $\foreachlistform(\mlvar{function},\lexenv,\dynenv,k)$ on $\outcome$, do the following:
            -
            If $\outcome$ is an abrupt completion reason, then invoke $k$ on $\outcome$. Otherwise, let $\mlvar{list}$ be the primary value of $\outcome$. If $\mlvar{list}$ is not a proper list, then invoke $k$ on an abrupt completion reason of type error. Otherwise, invoke the auxiliary function $\foreach$ on $\mlvar{function}$, $\mlvar{list}$, $\lexenv$, $\dynenv$, and $k$.
            -
            To invoke the auxiliary function $\foreach$ on $\mlvar{function}$, $\mlvar{list}$, $\lexenv$, $\dynenv$, and $k$, do the following:
            -
            If $\mlvar{list}$ is empty, then invoke $k$ on #v. Otherwise, invoke the auxiliary function $\invoke$ on $\mlvar{function}$, the car of $\mlvar{list}$, $\dynenv$, and a $\foreachelement$ continuation capturing $\mlvar{function}$, the cdr of $\mlvar{list}$, $\lexenv$, $\dynenv$, and $k$.
            -
            To invoke the continuation $\foreachelement(\mlvar{function},\mlvar{list},\lexenv,\dynenv,k)$ on $\outcome$, do the following:
            -
            If $\outcome$ is an abrupt completion reason, then invoke $k$ on $\outcome$. Otherwise, invoke the auxiliary function $\foreach$ on $\mlvar{function}$, $\mlvar{list}$, $\lexenv$, $\dynenv$, and $k$.
            To evaluate ($\mlvar{operator-form}$ $\mlvar{operand-forms}$) with respect to $\lexenv$, $\dynenv$, and $k$, do the following:
            To evaluate (apply $\mlvar{operator-form}$ $\mlvar{operand-forms}$) with respect to $\lexenv$, $\dynenv$, and $k$, do the following:
            To evaluate (multiple-value-call $\mlvar{operator-form}$ $\mlvar{operand-forms}$) with respect to $\lexenv$, $\dynenv$, and $k$, do the following:
            To evaluate (multiple-value-apply $\mlvar{operator-form}$ $\mlvar{operand-forms}$) with respect to $\lexenv$, $\dynenv$, and $k$, do the following:
            -
            Evaluate $\mlvar{operator-form}$ with respect to a $\functioncalloperatorform$ continuation capturing $\mlvar{operand-forms}$, $\lexenv$, $\dynenv$, and $k$.
            +
            Evaluate $\mlvar{operator-form}$ with respect to $\lexenv$, $\dynenv$, and a $\functioncalloperatorform$ continuation capturing $\mlvar{operand-forms}$, $\lexenv$, $\dynenv$, and $k$.
            To invoke the continuation $\functioncalloperatorform(\mlvar{operand-forms},\lexenv,\dynenv,k)$ on $\outcome$, do the following:
            -
            If $\outcome$ is an abrupt completion reason, then invoke $k$ on $\outcome$. Otherwise, let $\mlvar{operator}$ be the primary value of $\outcome$. If $\mlvar{operator}$ is not a function, then invoke $k$ on an abrupt completion reason of type error. Otherwise, invoke the auxiliary function $\evaloperandforms$ on $\mlvar{operator}$, $\mlvar{operand-forms}$, $[]$, $\lexenv$, $\dynenv$, and $k$.
            -
            To invoke the auxiliary function $\evaloperandforms$ on $\mlvar{operator}$, $\mlvar{operand-forms}$, $\mlvar{arguments}$, $\lexenv$, $\dynenv$, and $k$, do the following:
            -
            If $\mlvar{operand-forms}$ is not empty, then evaluate the first element of $\mlvar{operand-forms}$ with respect to a $\functioncalloperandform$ continuation capturing $\mlvar{operator}$, $\mlvar{operand-forms}$ minus its first element, $\mlvar{arguments}$, $\lexenv$, $\dynenv$, and $k$. Otherwise, invoke the auxiliary function $\invoke$ on $\mlvar{operator}$, $\mlvar{arguments}$, $\dynenv$, and $k$.
            -
            To invoke the continuation $\functioncalloperandform(\mlvar{operator},\mlvar{operand-forms},\mlvar{arguments},\lexenv,\dynenv,k)$ on $\outcome$, do the following:
            -
            If $\outcome$ is an abrupt completion reason, then invoke $k$ on $\outcome$. Otherwise, let $\mlvar{augmented-arguments}$ be the result of appending the primary value of $\outcome$ or all the values of $\outcome$ to $\mlvar{arguments}$. Invoke the auxiliary function $\evaloperandforms$ on $\mlvar{operator}$, $\mlvar{operand-forms}$, $\mlvar{augmented-arguments}$, $\lexenv$, $\dynenv$, and $k$.
            +
            If $\outcome$ is an abrupt completion reason, then invoke $k$ on $\outcome$. Otherwise, let $\mlvar{function}$ be the primary value of $\outcome$. If $\mlvar{function}$ is not a function, then invoke $k$ on an abrupt completion reason of type error. Otherwise, invoke the auxiliary function $\evaloperandforms$ on $\mlvar{function}$, $\mlvar{operand-forms}$, $[]$, $\lexenv$, $\dynenv$, and $k$.
            +
            To invoke the auxiliary function $\evaloperandforms$ on $\mlvar{function}$, $\mlvar{operand-forms}$, $\mlvar{arguments}$, $\lexenv$, $\dynenv$, and $k$, do the following:
            +
            If $\mlvar{operand-forms}$ is empty, then invoke the auxiliary function $\invoke$ on $\mlvar{function}$, $\mlvar{arguments}$, $\dynenv$, and $k$. Otherwise, evaluate the first element of $\mlvar{operand-forms}$ with respect to $\lexenv$, $\dynenv$, and a $\functioncalloperandform$ continuation capturing $\mlvar{function}$, $\mlvar{operand-forms}$, $\mlvar{arguments}$, $\lexenv$, $\dynenv$, and $k$.
            +
            To invoke the continuation $\functioncalloperandform(\mlvar{function},\mlvar{operand-forms},\mlvar{arguments},\lexenv,\dynenv,k)$ on $\outcome$, do the following:
            +
            If $\outcome$ is an abrupt completion reason, then invoke $k$ on $\outcome$. Otherwise, let $\mlvar{augmented-arguments}$ be the result of appending the primary value of $\outcome$ or all the values of $\outcome$ to $\mlvar{arguments}$. Invoke the auxiliary function $\evaloperandforms$ on $\mlvar{function}$, $\mlvar{operand-forms}$ minus its first element, $\mlvar{augmented-arguments}$, $\lexenv$, $\dynenv$, and $k$.
            To invoke the auxiliary function $\invoke$ on $\mlvar{function}$, $\mlvar{arguments}$, $\dynenv$, and $k$, do the following:
            If $\mlvar{function}$ is a primitive function, then invoke $k$ on the outcome of the invocation of the JavaScript function implementing $\mlvar{function}$ on $\mlvar{arguments}$. If $\mlvar{function}$ is a closure, then invoke the auxiliary function $\evalserialforms$ on the body of the lambda abstraction recorded by the closure, the lexical environment recorded by the closure or an extension thereof, $\dynenv$ or an extension thereof, and $k$.
            diff --git a/system-files/TUTORIAL b/system-files/TUTORIAL index 324a0e6..b6c348b 100644 --- a/system-files/TUTORIAL +++ b/system-files/TUTORIAL @@ -14,128 +14,113 @@

            The tutorial provides an introduction to writing programs. It builds on the contents of the user manual, particularly the sections “Programming Language” and “Listener Buffers”.

            Building Blocks

            This section introduces the global functions and macros that will be used in the tutorial. Some global functions and macros are included for the sake of completeness and will not actually be used in the tutorial.

            -

            In template function calls, argument names imply the following type restrictions:

            -
              -
            • Arguments named $\object$, with or without a subscript, can be of any type.
            • -
            • Arguments named $\boolean$, with or without a subscript, must be of type boolean.
            • -
            • Arguments named $\number$, with or without a subscript, must be of type number.
            • -
            • Arguments named $\cons$, with or without a subscript, must be of type cons.
            • -
            -

            In template macro calls, names enclosed in angle brackets have the following meanings:

            -
              -
            • $\metavar{test-forms}$ matches any sequence of zero or more objects
            • -
            • $\metavar{variable}$ matches any variable
            • -
            • $\metavar{value-form}$ matches any object
            • -
            • $\metavar{parameter-list}$ matches any list of distinct variables
            • -
            • $\metavar{body}$ matches any sequence of zero or more objects
            • -

            It is customary for a function or macro that tests a condition and returns a boolean to have a name ending with a question mark. This rule has some notable exceptions, though.

            Data Type boolean

            -
            (not $\boolean$)
            +
            (not $\boolean$) ⇒ $\boolean$
            The function returns #t if $\boolean$ is #f and #f if $\boolean$ is #t.
            > (not #t)
            #f

            > (not #f)
            #t
            +

            In the following two template macro calls, $\metavar{test-forms}$ matches any sequence of zero or more objects.

            (and $\metavar{test-forms}$)
            -
            The macro returns #t if all its arguments are #t and #f otherwise. The exact behavior of the macro is as follows: The macro evaluates the test-forms in sequence from left to right. If a test-form evaluates to an object that is not a boolean, then the following test-forms are not evaluated and the evaluation of the macro call completes abruptly for a reason of type error. If a test-form evaluates to #f, then the following test-forms are not evaluated and the macro call evaluates to #f. If all test-forms evaluate to #t, then the macro call evaluates to #t. Note that the previous condition is automatically satisfied if there are no test-forms.
            +
            The macro returns #t if all its arguments are #t and #f otherwise. The exact behavior of the macro is as follows: The test forms are evaluated in sequence from left to right. If a test form evaluates to an object that is not a boolean, then the following test forms are not evaluated and the evaluation of the macro call completes abruptly for a reason of type error. If a test form evaluates to #f, then the following test forms are not evaluated and the macro call evaluates to #f. If all test forms evaluate to #t, then the macro call evaluates to #t. Note that the previous condition is automatically satisfied if there are no test forms.
            > (and)
            #t

            > (and #t)
            #t

            > (and #f)
            #f

            > (and #t #t)
            #t

            > (and #t #f)
            #f

            > (and #f #t)
            #f

            > (and #f #f)
            #f
            (or $\metavar{test-forms}$)
            -
            The macro returns #f if all its arguments are #f and #t otherwise. The exact behavior of the macro is as follows: The macro evaluates the test-forms in sequence from left to right. If a test-form evaluates to an object that is not a boolean, then the following test-forms are not evaluated and the evaluation of the macro call completes abruptly for a reason of type error. If a test-form evaluates to #t, then the following test-forms are not evaluated and the macro call evaluates to #t. If all test-forms evaluate to #f, then the macro call evaluates to #f. Note that the previous condition is automatically satisfied if there are no test-forms.
            +
            The macro returns #f if all its arguments are #f and #t otherwise. The exact behavior of the macro is as follows: The test forms are evaluated in sequence from left to right. If a test form evaluates to an object that is not a boolean, then the following test forms are not evaluated and the evaluation of the macro call completes abruptly for a reason of type error. If a test form evaluates to #t, then the following test forms are not evaluated and the macro call evaluates to #t. If all test forms evaluate to #f, then the macro call evaluates to #f. Note that the previous condition is automatically satisfied if there are no test forms.
            > (or)
            #f

            > (or #t)
            #t

            > (or #f)
            #f

            > (or #t #t)
            #t

            > (or #t #f)
            #t

            > (or #f #t)
            #t

            > (or #f #f)
            #f
            -

            When we say that a macro does this and that, what we really mean is that the expansion of the macro does this and that. In the case of the macros and and or, it is their expansions that evaluate the test-forms in sequence from left to right, etc.

            -

            The macros and and or stop evaluating their operands as soon as they can determine that the result of the macro call is definitively true or definitively false. This short-circuiting behavior is possible only because and and or are macros. When a function call is evaluated, all the operand-forms are always evaluated (unless the evaluation of the operator-form completes abruptly or does not complete, the primary value of the operator-form is not a function, or the evaluation of one of the operand-forms other than the last one completes abruptly or does not complete).

            +

            The macros and and or stop evaluating the test forms as soon as they can determine that the result of the macro call is definitively true or definitively false. This short-circuiting behavior is possible only because and and or are macros. When a function call is evaluated, all the operand forms are always evaluated (unless the evaluation of the operator form completes abruptly or does not complete, the primary value of the operator form is not a function, or the evaluation of one of the operand forms other than the last one completes abruptly or does not complete).

            Here are two expansions illustrating the implementations of the macros and and or:

              -
            • (and $\metavarn{test-form}{1}$ $\metavarn{test-form}{2}$ $\metavarn{test-form}{3}$) expands into
              (if $\metavarn{test-form}{1}$ (if $\metavarn{test-form}{2}$ (if $\metavarn{test-form}{3}$ #t #f) #f) #f)
            • -
            • (or $\metavarn{test-form}{1}$ $\metavarn{test-form}{2}$ $\metavarn{test-form}{3}$) expands into
              (if $\metavarn{test-form}{1}$ #t (if $\metavarn{test-form}{2}$ #t (if $\metavarn{test-form}{3}$ #t #f)))
            • +
            • (and $\metavar{test-form}_1$ $\metavar{test-form}_2$ $\metavar{test-form}_3$) expands into
              (if $\metavar{test-form}_1$ (if $\metavar{test-form}_2$ (if $\metavar{test-form}_3$ #t #f) #f) #f)
            • +
            • (or $\metavar{test-form}_1$ $\metavar{test-form}_2$ $\metavar{test-form}_3$) expands into
              (if $\metavar{test-form}_1$ #t (if $\metavar{test-form}_2$ #t (if $\metavar{test-form}_3$ #t #f)))

            The name of the function not and the names of the macros and and or do not end with a question mark because the function and the macros do not really test a condition. Instead, they combine booleans (they are boolean operators).

            Data Type number

            Arithmetic Operators

            -
            (+ $\number_1\ldots\number_n$)
            -
            If the function is invoked on zero numbers, then the number 0 is returned. If the function is invoked on one number, then that number is returned. If the function is invoked on more than one number, then the result of adding those numbers from left to right is returned: the second number is added to the first number, then the third number is added to the partial result just computed, … The function is a closure built on top of the primitive function _+, which must be invoked on exactly two numbers.
            +
            (+ $\number_1\ldots\number_n$) ⇒ $\number$
            +
            If the function is invoked on zero numbers, then the number 0 is returned. If the function is invoked on one number, then that number is returned. If the function is invoked on more than one number, then the result of adding those numbers from left to right is returned: the second number is added to the first number, then the third number is added to the partial result just computed, … The function + is a nonprimitive function built on top of the primitive function _+, which must be invoked on exactly two numbers.
            > (+)
            0

            > (+ 1)
            1

            > (+ 1 2)
            3

            > (+ 1 2 3)
            6
            -
            (- $\number_1\ldots\number_n$)
            -
            If the function is invoked on zero numbers, then the invocation completes abruptly for a reason of type error. If the function is invoked on one number, then the opposite of that number is returned. If the function is invoked on more than one number, then the result of subtracting those numbers from left to right is returned: the second number is subtracted from the first number, then the third number is subtracted from the partial result just computed, … The function is a closure built on top of the primitive function _-, which must be invoked on exactly two numbers.
            +
            (- $\number_1\ldots\number_n$) ⇒ $\number$
            +
            If the function is invoked on zero numbers, then the invocation completes abruptly for a reason of type error. If the function is invoked on one number, then the opposite of that number is returned. If the function is invoked on more than one number, then the result of subtracting those numbers from left to right is returned: the second number is subtracted from the first number, then the third number is subtracted from the partial result just computed, … The function - is a nonprimitive function built on top of the primitive function _-, which must be invoked on exactly two numbers.
            -
            > (-)
            ERROR: Expecting at least one number.

            > (- 1)
            -1

            > (- 0 1)
            -1

            > (- 0 1 2)
            -3

            > (- 0 1 2 3)
            -6
            +
            > (-)
            EvaluatorError: program-error: Expecting at least one number.

            > (- 1)
            -1

            > (- 0 1)
            -1

            > (- 0 1 2)
            -3

            > (- 0 1 2 3)
            -6
            -
            (* $\number_1\ldots\number_n$)
            -
            If the function is invoked on zero numbers, then the number 1 is returned. If the function is invoked on one number, then that number is returned. If the function is invoked on more than one number, then the result of multiplying those numbers from left to right is returned: the first number is multiplied by the second number, then the partial result just computed is multiplied by the third number, … The function is a closure built on top of the primitive function _*, which must be invoked on exactly two numbers.
            +
            (* $\number_1\ldots\number_n$) ⇒ $\number$
            +
            If the function is invoked on zero numbers, then the number 1 is returned. If the function is invoked on one number, then that number is returned. If the function is invoked on more than one number, then the result of multiplying those numbers from left to right is returned: the first number is multiplied by the second number, then the partial result just computed is multiplied by the third number, … The function * is a nonprimitive function built on top of the primitive function _*, which must be invoked on exactly two numbers.
            > (*)
            1

            > (* 2)
            2

            > (* 2 4)
            8

            > (* 2 4 8)
            64
            -
            (/ $\number_1\ldots\number_n$)
            -
            If the function is invoked on zero numbers, then the invocation completes abruptly for a reason of type error. If the function is invoked on one number, then the inverse of that number is returned. If the function is invoked on more than one number, then the result of dividing those numbers from left to right is returned: the first number is divided by the second number, then the partial result just computed is divided by the third number, … The function is a closure built on top of the primitive function _/, which must be invoked on exactly two numbers.
            +
            (/ $\number_1\ldots\number_n$) ⇒ $\number$
            +
            If the function is invoked on zero numbers, then the invocation completes abruptly for a reason of type error. If the function is invoked on one number, then the inverse of that number is returned. If the function is invoked on more than one number, then the result of dividing those numbers from left to right is returned: the first number is divided by the second number, then the partial result just computed is divided by the third number, … The function / is a nonprimitive function built on top of the primitive function _/, which must be invoked on exactly two numbers.
            -
            > (/)
            ERROR: Expecting at least one number.

            > (/ 2)
            0.5

            > (/ 1 2)
            0.5

            > (/ 1 2 4)
            0.125

            > (/ 1 2 4 8)
            0.015625
            +
            > (/)
            EvaluatorError: program-error: Expecting at least one number.

            > (/ 2)
            0.5

            > (/ 1 2)
            0.5

            > (/ 1 2 4)
            0.125

            > (/ 1 2 4 8)
            0.015625

            Comparison Operators

            -
            (= $\number_1$ $\number_2$)
            +
            (= $\number_1$ $\number_2$) ⇒ $\boolean$
            The function returns #t if $\number_1$ and $\number_2$ are numerically equal and #f otherwise.
            -
            (/= $\number_1$ $\number_2$)
            +
            (/= $\number_1$ $\number_2$) ⇒ $\boolean$
            The function returns #t if $\number_1$ and $\number_2$ are numerically different and #f otherwise.
            -
            (< $\number_1$ $\number_2$)
            +
            (< $\number_1$ $\number_2$) ⇒ $\boolean$
            The function returns #t if $\number_1$ is numerically less than $\number_2$ and #f otherwise.
            -
            (<= $\number_1$ $\number_2$)
            +
            (<= $\number_1$ $\number_2$) ⇒ $\boolean$
            The function returns #t if $\number_1$ is numerically less than or equal to $\number_2$ and #f otherwise.
            -
            (> $\number_1$ $\number_2$)
            +
            (> $\number_1$ $\number_2$) ⇒ $\boolean$
            The function returns #t if $\number_1$ is numerically greater than $\number_2$ and #f otherwise.
            -
            (>= $\number_1$ $\number_2$)
            +
            (>= $\number_1$ $\number_2$) ⇒ $\boolean$
            The function returns #t if $\number_1$ is numerically greater than or equal to $\number_2$ and #f otherwise.
            > (list (= -1 0) (= 0 0) (= 1 0))
            (#f #t #f)

            > (list (/= -1 0) (/= 0 0) (/= 1 0))
            (#t #f #t)

            > (list (< -1 0) (< 0 0) (< 1 0))
            (#t #f #f)

            > (list (<= -1 0) (<= 0 0) (<= 1 0))
            (#t #t #f)

            > (list (> -1 0) (> 0 0) (> 1 0))
            (#f #f #t)

            > (list (>= -1 0) (>= 0 0) (>= 1 0))
            (#f #t #t)

            The names of the comparison operators do not end with a question mark because they traditionally do not (in mathematics and other programming languages).

            Data Type list

            -
            (list? $\object$)
            +
            (list? $\object$) ⇒ $\boolean$
            The function returns #t if $\object$ is of type list and #f otherwise.
            > (list? '())
            #t

            > (list? '(1 2 3))
            #t
            -
            (list $\object_1\ldots\object_n$)
            +
            (list $\object_1\ldots\object_n$) ⇒ $\list$
            The function collects its arguments into a list: when invoked on the arguments $\object_1,\ldots,\object_n$, the function returns a list whose elements are $\object_1,\ldots,\object_n$.
            > (list)
            ()

            > (list 1 2 3)
            (1 2 3)

            Data Type empty-list

            -
            (empty-list? $\object$)
            +
            (empty-list? $\object$) ⇒ $\boolean$
            The function returns #t if $\object$ is of type empty-list and #f otherwise.
            > (empty-list? '())
            #t

            > (empty-list? '(1 2 3))
            #f

            Data Type cons

            -
            (cons? $\object$)
            +
            (cons? $\object$) ⇒ $\boolean$
            The function returns #t if $\object$ is of type cons and #f otherwise.
            > (cons? '())
            #f

            > (cons? '(1 2 3))
            #t
            -
            (cons $\object_1$ $\object_2$)
            +
            (cons $\object_1$ $\object_2$) ⇒ $\cons$
            The function returns a new cons whose first element is $\object_1$ and whose second element is $\object_2$.
            > (cons 3 '())
            (3)

            > (cons 2 (cons 3 '()))
            (2 3)

            > (cons 1 (cons 2 (cons 3 '())))
            (1 2 3)
            -
            (car $\cons$)
            +
            (car $\cons$) ⇒ $\object$
            The function returns the first element of $\cons$.
            -
            (cdr $\cons$)
            +
            (cdr $\cons$) ⇒ $\object$
            The function returns the second element of $\cons$.
            > (car '(1 2 3))
            1

            > (cdr '(1 2 3))
            (2 3)

            > (car (cdr '(1 2 3)))
            2

            > (cdr (cdr '(1 2 3)))
            (3)

            > (car (cdr (cdr '(1 2 3))))
            3

            > (cdr (cdr (cdr '(1 2 3))))
            ()

            Equality Predicates

            The purpose of an equality predicate is to test the sameness of two objects. An equality predicate returns #t if the two objects are the same and #f otherwise. Because there are multiple notions of sameness, there are multiple equality predicates. The three main equality predicates are eq?, eql?, and equal?.

            -
            (eq? $\object_1$ $\object_2$)
            +
            (eq? $\object_1$ $\object_2$) ⇒ $\boolean$
            The function returns #t if and only if the two objects are one and the same. In other words, the function returns #t if and only if the two objects have the same address in the heap.
            -
            (eql? $\object_1$ $\object_2$)
            +
            (eql? $\object_1$ $\object_2$) ⇒ $\boolean$
            If both objects are of type number, then the function returns #t if and only if the two objects represent the same mathematical number. Otherwise, if both objects are of type character, then the function returns #t if and only if the two objects represent the same Unicode character. Otherwise, if both objects are of type string, then the function returns #t if and only if the two objects represent the same indexed sequence of Unicode characters. Otherwise, the function returns #t if and only if the two objects are eq?.
            -
            (equal? $\object_1$ $\object_2$)
            +
            (equal? $\object_1$ $\object_2$) ⇒ $\boolean$
            If both objects are of type cons, then the function returns #t if and only if the cars of the two objects are equal? and the cdrs of the two objects are equal?. Otherwise, if both objects are of type vector, then the function returns #t if and only if the two objects have the same length and their corresponding elements are equal?. Otherwise, the function returns #t if and only if the two objects are eql?.

            The three main equality predicates are related as follows:

            @@ -152,21 +137,21 @@

            When testing the sameness of two objects of type number, we want to test if the two objects represent the same mathematical number. Because there can exist in the heap more than one object of type number representing the same mathematical number, representing the same mathematical number is not equivalent to being one and the same. Therefore, eq? is not an appropriate equality predicate to test the sameness of two objects of type number. The appropriate equality predicates are eql? and the equivalent equal?.

            When testing the sameness of two objects of type character, we want to test if the two objects represent the same Unicode character. Because there can exist in the heap more than one object of type character representing the same Unicode character, representing the same Unicode character is not equivalent to being one and the same. Therefore, eq? is not an appropriate equality predicate to test the sameness of two objects of type character. The appropriate equality predicates are eql? and the equivalent equal?.

            When testing the sameness of two objects of type string, we want to test if the two objects represent the same indexed sequence of Unicode characters. Because there can exist in the heap more than one object of type string representing the same indexed sequence of Unicode characters, representing the same indexed sequence of Unicode characters is not equivalent to being one and the same. Therefore, eq? is not an appropriate equality predicate to test the sameness of two objects of type string. The appropriate equality predicates are eql? and the equivalent equal?.

            -

            Using eq? to test the sameness of two objects of type number, two objects of type character, or two objects of type string is never safe because the evaluator is free to make copies of objects of those types at any time. Let us illustrate the point by considering the form ((_vlambda (x) (eq? x x)) 0). It is not guaranteed that the object of type number created by the reader, the value of the variable x, the first argument passed to the function eq?, and the second argument passed to the function eq? are one and the same. If the two objects passed to the function eq? happen to be one and the same, then the test evaluates to #t. If the two objects passed to the function eq? happen not to be one and the same, then the test evaluates to #f. Even in this seemingly straightforward case, the result of the test is unpredictable. The same goes for objects of type character and string.

            +

            Using eq? to test the sameness of two objects of type number, two objects of type character, or two objects of type string is never safe because the evaluator is free to make copies of objects of those types at any time. Let us illustrate the point by considering the form ((_vlambda (x) (eq? x x)) 0). It is not guaranteed that the object of type number created by the reader, the value of the variable x, the first argument passed to the function eq?, and the second argument passed to the function eq? are one and the same. If the two objects passed to the function eq? happen to be one and the same, then the test evaluates to #t. If the two objects passed to the function eq? happen not to be one and the same, then the test evaluates to #f. Even in this seemingly straightforward case, the result of the test is unpredictable. The same goes for objects of type character and string.

            By design, any two objects of type keyword/variable sharing the same name are considered to be the same object. Because there cannot exist in the heap more than one object of type keyword/variable with the same name, having the same name is equivalent to being one and the same. Therefore, the appropriate equality predicates to test the sameness of two objects of type keyword/variable are eq? and the equivalent eql? and equal?.

            By design, any two objects of type empty-list are considered to be the same object. Because there exists in the heap exactly one object of type empty-list, two objects of type empty-list are necessarily one and the same. Therefore, the appropriate equality predicates to test the sameness of two objects of type empty-list are eq? and the equivalent eql? and equal?.

            -

            When testing the sameness of two objects of type cons or two objects of type vector, we can use eq? or the equivalent eql? to test if the two objects are one and the same or we can use equal? to test if the two objects have the same elements. Whether to use (1) eq? or the equivalent eql? or (2) equal? is usually obvious from the purpose of the test.

            +

            When testing the sameness of two objects of type cons or two objects of type vector, we can use eq? or the equivalent eql? to test if the two objects are one and the same or we can use equal? to test if the two objects have the same elements. Whether to use eq?/eql? or equal? is usually obvious from the purpose of the test.

            When testing the sameness of two objects of type primitive-function or two objects of type closure, we would like to test if the two objects have the same behavior (same input/output mapping and same side effects). In practice, we can only test if the two objects are one and the same by using eq? or the equivalent eql? and equal?. For objects of type primitive-function, being one and the same is equivalent to having the same behavior. For objects of type closure, being one and the same implies having the same behavior but the converse is not true.

            -

            When testing the sameness of two objects that can each be of type $type_1$, $type_2$, …, an equality predicate that is appropriate to test the sameness of two objects of type $type_1$, two objects of type $type_2$, … must be used.

            +

            When testing the sameness of two objects that can each be of leaf type $type_1$, $type_2$, …, an equality predicate that is appropriate to test the sameness of two objects of type $type_1$, two objects of type $type_2$, … must be used.

            As a matter of style, when more than one equality predicate is appropriate, the most discriminating one should be used. As a matter of style again, when testing the sameness of two objects that can only be of type number, the comparison operator = should be used instead of the equality predicate eql?.

            Global Definitions

            (vdef $\metavar{variable}$ $\metavar{value-form}$)
            -
            The purpose of the macro is to define a global variable by ensuring that the variable is bound in the value namespace of the global environment to the primary value of the value-form. The macro call evaluates to the variable.
            +
            The macro defines a global variable by ensuring that the variable is bound in the value namespace of the global environment to the primary value of the value form. The macro call evaluates to the variable.
            (fdef $\metavar{variable}$ $\metavar{parameter-list}$ $\metavar{body}$)
            -
            The purpose of the macro is to define a global function by ensuring that the variable is bound in the function namespace of the global environment to the closure resulting from the evaluation of the _vlambda-form (_vlambda $\metavar{parameter-list}$ $\metavar{body}$). The macro call evaluates to the variable.
            +
            The macro defines a global function by ensuring that the variable is bound in the function namespace of the global environment to the closure resulting from the evaluation of the _vlambda form (_vlambda $\metavar{parameter-list}$ $\metavar{body}$). The macro call evaluates to the variable.
            (mdef $\metavar{variable}$ $\metavar{parameter-list}$ $\metavar{body}$)
            -
            The purpose of the macro is to define a global macro by ensuring that the variable is bound in the function namespace of the global environment to the closure resulting from the evaluation of the _mlambda-form (_mlambda $\metavar{parameter-list}$ $\metavar{body}$). The macro call evaluates to the variable.
            +
            The macro defines a global macro by ensuring that the variable is bound in the function namespace of the global environment to the closure resulting from the evaluation of the _mlambda form (_mlambda $\metavar{parameter-list}$ $\metavar{body}$). The macro call evaluates to the variable.

            Evaluation and Invocation Traces

            An evaluation trace is a structured recording of some or all of the evaluations, invocations, and various steps performed by the evaluator to evaluate a form. Evaluation traces have the following format:

            @@ -175,14 +160,14 @@
          • Times flows from top to bottom.
          • Each recorded evaluation contributes two lines to the trace: an eval-in line of the form “The form $\mlvar{form}$ is evaluated with respect to $\lexenv$ and $\dynenv$.” recording the start of the evaluation and a matching eval-out line recording the completion of the evaluation. The eval-out line can take the following forms:
              -
            • “The form $\mlvar{form}$ evaluates to $\obj_1,\ldots,\obj_n$.”, which records the normal completion of the evaluation
            • -
            • “The evaluation of the form $\mlvar{form}$ completes abruptly.”, which records the abrupt completion of the evaluation
            • +
            • “The form $\mlvar{form}$ evaluates to $\obj_1,\ldots,\obj_n$.”, which records the normal completion of the evaluation.
            • +
            • “The evaluation of the form $\mlvar{form}$ completes abruptly.”, which records the abrupt completion of the evaluation.
          • Each recorded invocation contributes two lines to the trace: an invoke-in line of the form “The function $\mlvar{function}$ is invoked on $\obj_1,\ldots,\obj_n$.” recording the start of the invocation and a matching invoke-out line recording the completion of the invocation. The invoke-out line can take the following forms:
              -
            • “The function $\mlvar{function}$ returns $\obj_1,\ldots,\obj_m$.”, which records the normal completion of the invocation
            • -
            • “The invocation of the function $\mlvar{function}$ completes abruptly.”, which records the abrupt completion of the invocation
            • +
            • “The function $\mlvar{function}$ returns $\obj_1,\ldots,\obj_m$.”, which records the normal completion of the invocation.
            • +
            • “The invocation of the function $\mlvar{function}$ completes abruptly.”, which records the abrupt completion of the invocation.
          • A step line is a free-form line recording a step other than an evaluation or invocation.
          • @@ -212,7 +197,7 @@
          • The form (fref +) is evaluated with respect to $[]$ and $[]$.
            • -
            • The form (fref +) is analyzed as an fref-form.
            • +
            • The form (fref +) is analyzed as an fref form.
            • The variable + is looked up.
          • @@ -234,7 +219,7 @@
          • The global function + is invoked on the numbers 1 and 2.
            • -
            • The global function + is a closure recording the following two pieces of information: the _vlambda-form (_vlambda (x y) (_+ x y)) and the lexical environment $[]$.
            • +
            • The global function + is a closure recording the following two pieces of information: the _vlambda form (_vlambda (x y) (_+ x y)) and the lexical environment $[]$.
            • A new lexical environment is created that extends the lexical environment $[]$ to bind, in the value namespace, the variable x to the number 1 and the variable y to the number 2.
            • The form (_+ x y) is evaluated with respect to $[\vbinding{x}{1},\vbinding{y}{2}]$ and $[]$.
            • @@ -244,7 +229,7 @@
            • The form (fref _+) is evaluated with respect to $[\vbinding{x}{1},\vbinding{y}{2}]$ and $[]$.
              • -
              • The form (fref _+) is analyzed as an fref-form.
              • +
              • The form (fref _+) is analyzed as an fref form.
              • The variable _+ is looked up.
            • @@ -253,7 +238,7 @@
            • The form (vref x) is evaluated with respect to $[\vbinding{x}{1},\vbinding{y}{2}]$ and $[]$.
              • -
              • The form (vref x) is analyzed as a vref-form.
              • +
              • The form (vref x) is analyzed as a vref form.
              • The variable x is looked up.
            • @@ -262,7 +247,7 @@
            • The form (vref y) is evaluated with respect to $[\vbinding{x}{1},\vbinding{y}{2}]$ and $[]$.
              • -
              • The form (vref y) is analyzed as a vref-form.
              • +
              • The form (vref y) is analyzed as a vref form.
              • The variable y is looked up.
            • @@ -292,7 +277,7 @@
            • The form (+ 1 2) evaluates to the number 3.
            -

            Let us consider the following global macro, whose purpose is to evaluate the form with respect to a lexical environment extending the current lexical environment to bind, in the value namespace, the variable to the primary value of the value-form:

            +

            Let us consider the following global macro, whose purpose is to evaluate the form with respect to the lexical environment extending the current lexical environment to bind, in the value namespace, the variable to the primary value of the value form:

            > (mdef simple-vlet (variable value-form form)
            (list (list '_vlambda (list variable) form) value-form))
            simple-vlet

            > (simple-vlet x 1 (+ x 2))
            3

            > (simple-vlet x 1 (simple-vlet y 2 (+ x y)))
            3

            Here is an evaluation trace of the evaluation of the top-level form (simple-vlet x 1 (+ x 2)):

            @@ -308,7 +293,7 @@
            • The form ((_vlambda (x) (+ x 2)) 1) is analyzed as a plain function call.
            • The form (_vlambda (x) (+ x 2)) is evaluated with respect to $[]$ and $[]$.
            • -
            • The form (_vlambda (x) (+ x 2)) evaluates to a closure recording the following two pieces of information: the _vlambda-form (_vlambda (x) (+ x 2)) and the lexical environment $[]$.
            • +
            • The form (_vlambda (x) (+ x 2)) evaluates to a closure recording the following two pieces of information: the _vlambda form (_vlambda (x) (+ x 2)) and the lexical environment $[]$.
            • The form 1 is evaluated with respect to $[]$ and $[]$.
            • The form 1 evaluates to the number 1.
            • The closure is invoked on the number 1.
            • @@ -342,7 +327,7 @@
              • The form ((_vlambda (x) (simple-vlet y 2 (+ x y))) 1) is analyzed as a plain function call.
              • The form (_vlambda (x) (simple-vlet y 2 (+ x y))) is evaluated with respect to $[]$ and $[]$.
              • -
              • The form (_vlambda (x) (simple-vlet y 2 (+ x y))) evaluates to a closure recording the following two pieces of information: the _vlambda-form (_vlambda (x) (simple-vlet y 2 (+ x y))) and the lexical environment $[]$.
              • +
              • The form (_vlambda (x) (simple-vlet y 2 (+ x y))) evaluates to a closure recording the following two pieces of information: the _vlambda form (_vlambda (x) (simple-vlet y 2 (+ x y))) and the lexical environment $[]$.
              • The form 1 is evaluated with respect to $[]$ and $[]$.
              • The form 1 evaluates to the number 1.
              • The closure is invoked on the number 1.
              • @@ -360,7 +345,7 @@
                • The form ((_vlambda (y) (+ x y)) 2) is analyzed as a plain function call.
                • The form (_vlambda (y) (+ x y)) is evaluated with respect to $[\vbinding{x}{1}]$ and $[]$.
                • -
                • The form (_vlambda (y) (+ x y)) evaluates to a closure recording the following two pieces of information: the _vlambda-form (_vlambda (y) (+ x y)) and the lexical environment $[\vbinding{x}{1}]$.
                • +
                • The form (_vlambda (y) (+ x y)) evaluates to a closure recording the following two pieces of information: the _vlambda form (_vlambda (y) (+ x y)) and the lexical environment $[\vbinding{x}{1}]$.
                • The form 2 is evaluated with respect to $[\vbinding{x}{1}]$ and $[]$.
                • The form 2 evaluates to the number 2.
                • The closure is invoked on the number 2.
                • @@ -389,7 +374,7 @@
                • The form (simple-vlet x 1 (simple-vlet y 2 (+ x y))) evaluates to the number 3.
            -

            When debugging a macro, it is often useful to examine some expansions generated by the macro. The expansion of the macro call ($\metavar{macro-operator}$ $\metavarn{macro-operand}{1}\ldots\metavarn{macro-operand}{n}$) can easily be obtained by evaluating the plain function call ((fref $\metavar{macro-operator}$) $\code{'}\metavarn{macro-operand}{1}\ldots\code{'}\metavarn{macro-operand}{n}$):

            +

            When debugging a macro, it is often useful to examine some expansions generated by the macro. The expansion of the macro call ($\metavar{macro-operator}$ $\metavar{macro-operand}_1\ldots\metavar{macro-operand}_n$) can easily be obtained by evaluating the plain function call ((fref $\metavar{macro-operator}$) $\code{'}\metavar{macro-operand}_1\ldots\code{'}\metavar{macro-operand}_n$):

            > ((fref simple-vlet) 'x '1 '(+ x 2))
            ((_vlambda (x) (+ x 2)) 1)

            > ((fref simple-vlet) 'x '1 '(simple-vlet y 2 (+ x y)))
            ((_vlambda (x) (simple-vlet y 2 (+ x y))) 1)

            > ((fref simple-vlet) 'y '2 '(+ x y))
            ((_vlambda (y) (+ x y)) 2)

            > ((fref simple-vlet) 'x '1 ((fref simple-vlet) 'y '2 '(+ x y)))
            ((_vlambda (x) ((_vlambda (y) (+ x y)) 2)) 1)

            Let us consider the following global function, which returns the absolute value of its argument:

            > (fdef abs (x)
            (if (>= x 0) x (- x)))
            abs

            > (abs 1)
            1

            > (abs -1)
            1
            @@ -405,7 +390,7 @@
          • The form (if (>= x 0) x (- x))) is evaluated with respect to $[\vbinding{x}{1}]$ and $[]$.
            • -
            • The form (if (>= x 0) x (- x))) is analyzed as an if-form.
            • +
            • The form (if (>= x 0) x (- x))) is analyzed as an if form.
            • The form (>= x 0) is evaluated with respect to $[\vbinding{x}{1}]$ and $[]$.
              • @@ -414,7 +399,7 @@
            • The form (>= x 0) evaluates to the boolean #t.
            • -
            • Because the test-form evaluates to the boolean #t, the then-form is selected for evaluation.
            • +
            • Because the test form evaluates to the boolean #t, the then form is selected for evaluation.
            • The form x is evaluated with respect to $[\vbinding{x}{1}]$ and $[]$.
            • The form x evaluates to the number 1.
            @@ -440,7 +425,7 @@
          • The form (if (>= x 0) x (- x))) is evaluated with respect to $[\vbinding{x}{-1}]$ and $[]$.
            • -
            • The form (if (>= x 0) x (- x))) is analyzed as an if-form.
            • +
            • The form (if (>= x 0) x (- x))) is analyzed as an if form.
            • The form (>= x 0) is evaluated with respect to $[\vbinding{x}{-1}]$ and $[]$.
              • @@ -449,7 +434,7 @@
            • The form (>= x 0) evaluates to the boolean #f.
            • -
            • Because the test-form evaluates to the boolean #f, the else-form is selected for evaluation.
            • +
            • Because the test form evaluates to the boolean #f, the else form is selected for evaluation.
            • The form (- x) is evaluated with respect to $[\vbinding{x}{-1}]$ and $[]$.
              • @@ -503,13 +488,13 @@

                Recursive Functions

                A recursive function is a function that invokes itself directly ($f\rightarrow f$) or indirectly ($f\rightarrow g\rightarrow\cdots\rightarrow f$).

                A recursive function call is a function call through which a recursive function invokes itself directly or indirectly.

                -

                A form is said to be in tail position with respect to a lambda abstraction if and only if one of the following mutually exclusive conditions is satisfied:

                +

                A form is said to be in tail position with respect to a lambda abstraction other than a _dlambda form if and only if one of the following conditions is satisfied:

                  -
                • The form is the last serial-form of the body of the lambda abstraction.
                • -
                • The form is the last serial-form of a progn-form in tail position with respect to the lambda abstraction.
                • -
                • The form is the then-form or the else-form of an if-form in tail position with respect to the lambda abstraction.
                • +
                • The form is the last serial form of the body of the lambda abstraction.
                • +
                • The form is the last serial form of a progn form in tail position with respect to the lambda abstraction.
                • +
                • The form is the then form or the else form of an if form in tail position with respect to the lambda abstraction.
                -

                A form in tail position with respect to a lambda abstraction has the following property: If (1) a closure resulting from the evaluation of the lambda abstraction is invoked and (2) the form happens to be evaluated during the invocation of the closure, then the result of the evaluation of the form becomes the result of the invocation of the closure without any further processing. (The values of the form are returned as is and not used otherwise.)

                +

                A form $\mlvar{form}$ in tail position with respect to a lambda abstraction has the following property: If (1) a closure resulting from the evaluation of the lambda abstraction is invoked and (2) the form $\mlvar{form}$ happens to be evaluated during the invocation of the closure, then the result of the evaluation of the form $\mlvar{form}$ becomes the result of the invocation of the closure without any further processing. (The values of the form are returned as is and not used otherwise.)

                Factorial Function

                The factorial function is defined by the following recurrence relation, where $n$ is a nonnegative integer:

                  @@ -582,67 +567,67 @@

                  The factorial is computed during the contraction phase by adding factors to a running product. The running product is initialized to the number 1 by the innermost invocation and the whole product is equal to $6\times(5\times(4\times(3\times(2\times(1\times1)))))$.

                  The values of the factorial function can also be computed by the global function fact-iter:

                  -
                  > (fdef fact-iter (n)
                  (fact-iter-internal n 1))
                  fact-iter

                  > (fdef fact-iter-internal (n acc)
                  (if (= n 0) acc (fact-iter-internal (- n 1) (* n acc))))
                  fact-iter-internal

                  > (fact-iter 10)
                  3628800
                  -

                  The bulk of the work is done by the auxiliary global function fact-iter-internal, which contains one recursive function call. The call to fact-iter-internal in fact-iter and the recursive function call in fact-iter-internal are both in tail position.

                  +
                  > (fdef fact-iter (n)
                  (fact-iter/internal n 1))
                  fact-iter

                  > (fdef fact-iter/internal (n acc)
                  (if (= n 0) acc (fact-iter/internal (- n 1) (* n acc))))
                  fact-iter/internal

                  > (fact-iter 10)
                  3628800
                  +

                  The bulk of the work is done by the auxiliary global function fact-iter/internal, which contains one recursive function call. The call to fact-iter/internal in fact-iter and the recursive function call in fact-iter/internal are both in tail position.

                  Here is an invocation trace of the evaluation of the form (fact-iter 6):

                  • The global function fact-iter is invoked on the number 6.
                    • -
                    • The global function fact-iter-internal is invoked on the numbers 6 and 1.
                    • +
                    • The global function fact-iter/internal is invoked on the numbers 6 and 1.
                      • -
                      • The global function fact-iter-internal is invoked on the numbers 5 and 6.
                      • +
                      • The global function fact-iter/internal is invoked on the numbers 5 and 6.
                        • -
                        • The global function fact-iter-internal is invoked on the numbers 4 and 30.
                        • +
                        • The global function fact-iter/internal is invoked on the numbers 4 and 30.
                          • -
                          • The global function fact-iter-internal is invoked on the numbers 3 and 120.
                          • +
                          • The global function fact-iter/internal is invoked on the numbers 3 and 120.
                            • -
                            • The global function fact-iter-internal is invoked on the numbers 2 and 360.
                            • +
                            • The global function fact-iter/internal is invoked on the numbers 2 and 360.
                              • -
                              • The global function fact-iter-internal is invoked on the numbers 1 and 720.
                              • +
                              • The global function fact-iter/internal is invoked on the numbers 1 and 720.
                                • -
                                • The global function fact-iter-internal is invoked on the numbers 0 and 720.
                                • -
                                • The global function fact-iter-internal returns the number 720.
                                • +
                                • The global function fact-iter/internal is invoked on the numbers 0 and 720.
                                • +
                                • The global function fact-iter/internal returns the number 720.
                              • -
                              • The global function fact-iter-internal returns the number 720.
                              • +
                              • The global function fact-iter/internal returns the number 720.
                            • -
                            • The global function fact-iter-internal returns the number 720.
                            • +
                            • The global function fact-iter/internal returns the number 720.
                          • -
                          • The global function fact-iter-internal returns the number 720.
                          • +
                          • The global function fact-iter/internal returns the number 720.
                        • -
                        • The global function fact-iter-internal returns the number 720.
                        • +
                        • The global function fact-iter/internal returns the number 720.
                      • -
                      • The global function fact-iter-internal returns the number 720.
                      • +
                      • The global function fact-iter/internal returns the number 720.
                    • -
                    • The global function fact-iter-internal returns the number 720.
                    • +
                    • The global function fact-iter/internal returns the number 720.
                  • The global function fact-iter returns the number 720.
                  -

                  There are two phases in the evaluation of the form (fact-iter 6): (1) an expansion phase during which the number of active invocations increases and (2) a contraction phase during which the number of active invocations decreases. When the evaluator is processing the invocation of fact-iter-internal on the numbers 0 and 720, there are $8$ active invocations ($1$ of fact-iter and $7$ of fact-iter-internal):

                  +

                  There are two phases in the evaluation of the form (fact-iter 6): (1) an expansion phase during which the number of active invocations increases and (2) a contraction phase during which the number of active invocations decreases. When the evaluator is processing the invocation of fact-iter/internal on the numbers 0 and 720, there are $8$ active invocations ($1$ of fact-iter and $7$ of fact-iter/internal):

                    -
                  1. One invocation evaluating the body of fact-iter with respect to a lexical environment binding, in the value namespace, the variable n to the number 6. That invocation is waiting for the value of the invocation of fact-iter-internal on the numbers n = 6 and 1. When that value is eventually available, it will be returned as the value of the invocation without any further processing.
                  2. -
                  3. One invocation evaluating the body of fact-iter-internal with respect to a lexical environment binding, in the value namespace, the variable n to the number 6 and the variable acc to the number 1. That invocation is waiting for the value of the recursive invocation of fact-iter-internal on the numbers (- n 1) = 5 and (* n acc) = 6. When that value is eventually available, it will be returned as the value of the invocation without any further processing.
                  4. -
                  5. One invocation evaluating the body of fact-iter-internal with respect to a lexical environment binding, in the value namespace, the variable n to the number 5 and the variable acc to the number 6. That invocation is waiting…
                  6. -
                  7. One invocation evaluating the body of fact-iter-internal with respect to a lexical environment binding, in the value namespace, the variable n to the number 4 and the variable acc to the number 30. That invocation is waiting…
                  8. -
                  9. One invocation evaluating the body of fact-iter-internal with respect to a lexical environment binding, in the value namespace, the variable n to the number 3 and the variable acc to the number 120. That invocation is waiting…
                  10. -
                  11. One invocation evaluating the body of fact-iter-internal with respect to a lexical environment binding, in the value namespace, the variable n to the number 2 and the variable acc to the number 360. That invocation is waiting…
                  12. -
                  13. One invocation evaluating the body of fact-iter-internal with respect to a lexical environment binding, in the value namespace, the variable n to the number 1 and the variable acc to the number 720. That invocation is waiting for the value of the recursive invocation of fact-iter-internal on the numbers (- n 1) = 0 and (* n acc) = 720. When that value is eventually available, it will be returned as the value of the invocation without any further processing.
                  14. -
                  15. One invocation evaluating the body of fact-iter-internal with respect to a lexical environment binding, in the value namespace, the variable n to the number 0 and the variable acc to the number 720. That invocation directly returns the number acc = 720 as its value without any further recursive invocation of fact-iter-internal.
                  16. +
                  17. One invocation evaluating the body of fact-iter with respect to a lexical environment binding, in the value namespace, the variable n to the number 6. That invocation is waiting for the value of the invocation of fact-iter/internal on the numbers n = 6 and 1. When that value is eventually available, it will be returned as the value of the invocation without any further processing.
                  18. +
                  19. One invocation evaluating the body of fact-iter/internal with respect to a lexical environment binding, in the value namespace, the variable n to the number 6 and the variable acc to the number 1. That invocation is waiting for the value of the recursive invocation of fact-iter/internal on the numbers (- n 1) = 5 and (* n acc) = 6. When that value is eventually available, it will be returned as the value of the invocation without any further processing.
                  20. +
                  21. One invocation evaluating the body of fact-iter/internal with respect to a lexical environment binding, in the value namespace, the variable n to the number 5 and the variable acc to the number 6. That invocation is waiting…
                  22. +
                  23. One invocation evaluating the body of fact-iter/internal with respect to a lexical environment binding, in the value namespace, the variable n to the number 4 and the variable acc to the number 30. That invocation is waiting…
                  24. +
                  25. One invocation evaluating the body of fact-iter/internal with respect to a lexical environment binding, in the value namespace, the variable n to the number 3 and the variable acc to the number 120. That invocation is waiting…
                  26. +
                  27. One invocation evaluating the body of fact-iter/internal with respect to a lexical environment binding, in the value namespace, the variable n to the number 2 and the variable acc to the number 360. That invocation is waiting…
                  28. +
                  29. One invocation evaluating the body of fact-iter/internal with respect to a lexical environment binding, in the value namespace, the variable n to the number 1 and the variable acc to the number 720. That invocation is waiting for the value of the recursive invocation of fact-iter/internal on the numbers (- n 1) = 0 and (* n acc) = 720. When that value is eventually available, it will be returned as the value of the invocation without any further processing.
                  30. +
                  31. One invocation evaluating the body of fact-iter/internal with respect to a lexical environment binding, in the value namespace, the variable n to the number 0 and the variable acc to the number 720. That invocation directly returns the number acc = 720 as its value without any further recursive invocation of fact-iter/internal.

                  The factorial is computed during the expansion phase by adding factors to a running product. The running product, which is initialized to the number 1 by fact-iter, is propagated up the invocation chain through the variable acc. The whole product, which is equal to $1\times(2\times(3\times(4\times(5\times(6\times1)))))$, is propagated down the invocation chain without any further processing.

                  Fibonacci Sequence

                  @@ -814,75 +799,75 @@

                  During the evaluation of the form (fib 6), the global function fib is invoked $1$ time on the number 6, $1$ time on the number 5, $2$ times on the number 4, $3$ times on the number 3, $5$ times on the number 2, $8$ times on the number 1, and $5$ times on the number 0.

                  When the evaluator is processing the first invocation of fib on the number 0 (that invocation has a gray background in the invocation trace), there are $6$ active invocations of fib:

                    -
                  1. One invocation evaluating the body of fib with respect to a lexical environment binding, in the value namespace, the variable n to the number 6. That invocation is waiting for the value of the recursive invocation of fib on the number (- n 1) = 5. When that value is eventually available, it will be stored in a temporary location and fib will be invoked recursively on the number (- n 2) = 4. When the value of the second recursive invocation of fib is eventually available, it will be added to the stored value of the first recursive invocation of fib and the result of the addition will be returned as the value of the invocation.
                  2. +
                  3. One invocation evaluating the body of fib with respect to a lexical environment binding, in the value namespace, the variable n to the number 6. That invocation is waiting for the value of the recursive invocation of fib on the number (- n 1) = 5. When that value is eventually available, it will be saved in a temporary location and fib will be invoked recursively on the number (- n 2) = 4. When the value of the second recursive invocation of fib is eventually available, it will be added to the saved value of the first recursive invocation of fib and the result of the addition will be returned as the value of the invocation.
                  4. One invocation evaluating the body of fib with respect to a lexical environment binding, in the value namespace, the variable n to the number 5. That invocation is waiting…
                  5. One invocation evaluating the body of fib with respect to a lexical environment binding, in the value namespace, the variable n to the number 4. That invocation is waiting…
                  6. -
                  7. One invocation evaluating the body of fib with respect to a lexical environment binding, in the value namespace, the variable n to the number 3. That invocation is waiting for the value of the recursive invocation of fib on the number (- n 1) = 2. When that value is eventually available, it will be stored in a temporary location and fib will be invoked recursively on the number (- n 2) = 1. When the value of the second recursive invocation of fib is eventually available, it will be added to the stored value of the first recursive invocation of fib and the result of the addition will be returned as the value of the invocation.
                  8. -
                  9. One invocation evaluating the body of fib with respect to a lexical environment binding, in the value namespace, the variable n to the number 2. That invocation has already stored in a temporary location the value of the recursive invocation of fib on the number (- n 1) = 1 and is waiting for the value of the recursive invocation of fib on the number (- n 2) = 0. When the value of the second recursive invocation of fib is eventually available, it will be added to the stored value of the first recursive invocation of fib and the result of the addition will be returned as the value of the invocation.
                  10. +
                  11. One invocation evaluating the body of fib with respect to a lexical environment binding, in the value namespace, the variable n to the number 3. That invocation is waiting for the value of the recursive invocation of fib on the number (- n 1) = 2. When that value is eventually available, it will be saved in a temporary location and fib will be invoked recursively on the number (- n 2) = 1. When the value of the second recursive invocation of fib is eventually available, it will be added to the saved value of the first recursive invocation of fib and the result of the addition will be returned as the value of the invocation.
                  12. +
                  13. One invocation evaluating the body of fib with respect to a lexical environment binding, in the value namespace, the variable n to the number 2. That invocation has already saved in a temporary location the value of the recursive invocation of fib on the number (- n 1) = 1 and is waiting for the value of the recursive invocation of fib on the number (- n 2) = 0. When the value of the second recursive invocation of fib is eventually available, it will be added to the saved value of the first recursive invocation of fib and the result of the addition will be returned as the value of the invocation.
                  14. One invocation evaluating the body of fib with respect to a lexical environment binding, in the value namespace, the variable n to the number 0. That invocation directly returns the number 0 as its value without any further recursive invocation of fib.

                  The values of the Fibonacci sequence can also be computed by the global function fib-iter:

                  -
                  > (fdef fib-iter (n)
                  (fib-iter-internal n 0 1))
                  fib-iter

                  > (fdef fib-iter-internal (n a b)
                  (if (= n 0)
                  a
                  (if (= n 1)
                  b
                  (fib-iter-internal (- n 1) b (+ a b)))))
                  fib-iter-internal

                  > (fib-iter 10)
                  55
                  -

                  The bulk of the work is done by the auxiliary global function fib-iter-internal, which contains one recursive function call. The call to fib-iter-internal in fib-iter and the recursive function call in fib-iter-internal are both in tail position.

                  -

                  Whereas the global function fib computes the Fibonacci sequence top-down in a branching process that repeats the computation of some Fibonacci numbers, the global function fib-iter computes the Fibonacci sequence bottom-up in a linear process that do not repeat the computation of any Fibonacci numbers:

                  +
                  > (fdef fib-iter (n)
                  (fib-iter/internal n 0 1))
                  fib-iter

                  > (fdef fib-iter/internal (n a b)
                  (if (= n 0)
                  a
                  (if (= n 1)
                  b
                  (fib-iter/internal (- n 1) b (+ a b)))))
                  fib-iter/internal

                  > (fib-iter 10)
                  55
                  +

                  The bulk of the work is done by the auxiliary global function fib-iter/internal, which contains one recursive function call. The call to fib-iter/internal in fib-iter and the recursive function call in fib-iter/internal are both in tail position.

                  +

                  Whereas the global function fib computes the Fibonacci sequence top-down in a branching process that repeats the computation of some Fibonacci numbers, the global function fib-iter computes the Fibonacci sequence bottom-up in a linear process that does not repeat the computation of any Fibonacci numbers:

                  • $F_2$ is computed from $F_1$ and $F_0$
                  • $F_3$ is computed from $F_2$ and $F_1$
                  -

                  Let $n_\textrm{init}$ be the the argument of fib-iter and $n$, $a$, and $b$ be the arguments of the invocation of fib-iter-internal under consideration. When processing the invocation of fib-iter-internal from fib-iter, $n=n_\textrm{init}$, $a=F_0$, and $b=F_1$. If $n_\textrm{init}=0$, then fib-iter-internal returns $a=F_0$. Otherwise, if $n_\textrm{init}=1$, then fib-iter-internal returns $b=F_1$. Otherwise, fib-iter-internal invokes itself recursively. On each recursive invocation of fib-iter-internal, $n$ is decremented by $1$, $a$ is replaced by the next Fibonacci number in the sequence $F_0,F_1,F_2,\ldots$, and $b$ is replaced by the next Fibonacci number in the sequence $F_1,F_2,F_3,\ldots$ When processing the $(n_\textrm{init}-1)$-th recursive invocation of fib-iter-internal, $n=n_\textrm{init}-(n_\textrm{init}-1)=1$, $a=F_{0+n_\textrm{init}-1}=F_{n_\textrm{init}-1}$, and $b=F_{1+n_\textrm{init}-1}=F_{n_\textrm{init}}$ and fib-iter-internal returns $b=F_{n_\textrm{init}}$.

                  +

                  Let $n_\textrm{init}$ be the the argument of fib-iter and $n$, $a$, and $b$ be the arguments of the invocation of fib-iter/internal under consideration. When processing the invocation of fib-iter/internal from fib-iter, $n=n_\textrm{init}$, $a=F_0$, and $b=F_1$. If $n_\textrm{init}=0$, then fib-iter/internal returns $a=F_0$. Otherwise, if $n_\textrm{init}=1$, then fib-iter/internal returns $b=F_1$. Otherwise, fib-iter/internal invokes itself recursively. On each recursive invocation of fib-iter/internal, $n$ is decremented by $1$, $a$ is replaced by the next Fibonacci number in the sequence $F_0,F_1,F_2,\ldots$, and $b$ is replaced by the next Fibonacci number in the sequence $F_1,F_2,F_3,\ldots$ When processing the $(n_\textrm{init}-1)$-th recursive invocation of fib-iter/internal, $n=n_\textrm{init}-(n_\textrm{init}-1)=1$, $a=F_{0+n_\textrm{init}-1}=F_{n_\textrm{init}-1}$, and $b=F_{1+n_\textrm{init}-1}=F_{n_\textrm{init}}$ and fib-iter/internal returns $b=F_{n_\textrm{init}}$.

                  Here is an invocation trace of the evaluation of the form (fib-iter 6):

                  • The global function fib-iter is invoked on the number 6.
                    • -
                    • The global function fib-iter-internal is invoked on the numbers 6, 0, and 1.
                    • +
                    • The global function fib-iter/internal is invoked on the numbers 6, 0, and 1.
                      • -
                      • The global function fib-iter-internal is invoked on the numbers 5, 1, and 1.
                      • +
                      • The global function fib-iter/internal is invoked on the numbers 5, 1, and 1.
                        • -
                        • The global function fib-iter-internal is invoked on the numbers 4, 1, and 2.
                        • +
                        • The global function fib-iter/internal is invoked on the numbers 4, 1, and 2.
                          • -
                          • The global function fib-iter-internal is invoked on the numbers 3, 2, and 3.
                          • +
                          • The global function fib-iter/internal is invoked on the numbers 3, 2, and 3.
                            • -
                            • The global function fib-iter-internal is invoked on the numbers 2, 3, and 5.
                            • +
                            • The global function fib-iter/internal is invoked on the numbers 2, 3, and 5.
                              • -
                              • The global function fib-iter-internal is invoked on the numbers 1, 5, and 8.
                              • -
                              • The global function fib-iter-internal returns the number 8.
                              • +
                              • The global function fib-iter/internal is invoked on the numbers 1, 5, and 8.
                              • +
                              • The global function fib-iter/internal returns the number 8.
                            • -
                            • The global function fib-iter-internal returns the number 8.
                            • +
                            • The global function fib-iter/internal returns the number 8.
                          • -
                          • The global function fib-iter-internal returns the number 8.
                          • +
                          • The global function fib-iter/internal returns the number 8.
                        • -
                        • The global function fib-iter-internal returns the number 8.
                        • +
                        • The global function fib-iter/internal returns the number 8.
                      • -
                      • The global function fib-iter-internal returns the number 8.
                      • +
                      • The global function fib-iter/internal returns the number 8.
                    • -
                    • The global function fib-iter-internal returns the number 8.
                    • +
                    • The global function fib-iter/internal returns the number 8.
                  • The global function fib-iter returns the number 8.
                  -

                  When the evaluator is processing the invocation of fib-iter-internal on the numbers 1, 5, and 8, there are $7$ active invocations ($1$ of fib-iter and $6$ of fib-iter-internal):

                  +

                  When the evaluator is processing the invocation of fib-iter/internal on the numbers 1, 5, and 8, there are $7$ active invocations ($1$ of fib-iter and $6$ of fib-iter/internal):

                    -
                  1. One invocation evaluating the body of fib-iter with respect to a lexical environment binding, in the value namespace, the variable n to the number 6. That invocation is waiting for the value of the invocation of fib-iter-internal on the numbers n = 6, 0 and 1. When that value is eventually available, it will be returned as the value of the invocation without any further processing.
                  2. -
                  3. One invocation evaluating the body of fib-iter-internal with respect to a lexical environment binding, in the value namespace, the variable n to the number 6, the variable a to the number 0, and the variable b to the number 1. That invocation is waiting for the value of the recursive invocation of fib-iter-internal on the numbers (- n 1) = 5, b = 1, and (+ a b) = 1. When that value is eventually available, it will be returned as the value of the invocation without any further processing.
                  4. -
                  5. One invocation evaluating the body of fib-iter-internal with respect to a lexical environment binding, in the value namespace, the variable n to the number 5, the variable a to the number 1, and the variable b to the number 1. That invocation is waiting…
                  6. -
                  7. One invocation evaluating the body of fib-iter-internal with respect to a lexical environment binding, in the value namespace, the variable n to the number 4, the variable a to the number 1, and the variable b to the number 2. That invocation is waiting…
                  8. -
                  9. One invocation evaluating the body of fib-iter-internal with respect to a lexical environment binding, in the value namespace, the variable n to the number 3, the variable a to the number 2, and the variable b to the number 3. That invocation is waiting…
                  10. -
                  11. One invocation evaluating the body of fib-iter-internal with respect to a lexical environment binding, in the value namespace, the variable n to the number 2, the variable a to the number 3, and the variable b to the number 5. That invocation is waiting for the value of the recursive invocation of fib-iter-internal on the numbers (- n 1) = 1, b = 5, and (+ a b) = 8. When that value is eventually available, it will be returned as the value of the invocation without any further processing.
                  12. -
                  13. One invocation evaluating the body of fib-iter-internal with respect to a lexical environment binding, in the value namespace, the variable n to the number 1, the variable a to the number 5, and the variable 8 to the number 5. That invocation directly returns the number b = 8 as its value without any further recursive invocation of fib-iter-internal.
                  14. +
                  15. One invocation evaluating the body of fib-iter with respect to a lexical environment binding, in the value namespace, the variable n to the number 6. That invocation is waiting for the value of the invocation of fib-iter/internal on the numbers n = 6, 0 and 1. When that value is eventually available, it will be returned as the value of the invocation without any further processing.
                  16. +
                  17. One invocation evaluating the body of fib-iter/internal with respect to a lexical environment binding, in the value namespace, the variable n to the number 6, the variable a to the number 0, and the variable b to the number 1. That invocation is waiting for the value of the recursive invocation of fib-iter/internal on the numbers (- n 1) = 5, b = 1, and (+ a b) = 1. When that value is eventually available, it will be returned as the value of the invocation without any further processing.
                  18. +
                  19. One invocation evaluating the body of fib-iter/internal with respect to a lexical environment binding, in the value namespace, the variable n to the number 5, the variable a to the number 1, and the variable b to the number 1. That invocation is waiting…
                  20. +
                  21. One invocation evaluating the body of fib-iter/internal with respect to a lexical environment binding, in the value namespace, the variable n to the number 4, the variable a to the number 1, and the variable b to the number 2. That invocation is waiting…
                  22. +
                  23. One invocation evaluating the body of fib-iter/internal with respect to a lexical environment binding, in the value namespace, the variable n to the number 3, the variable a to the number 2, and the variable b to the number 3. That invocation is waiting…
                  24. +
                  25. One invocation evaluating the body of fib-iter/internal with respect to a lexical environment binding, in the value namespace, the variable n to the number 2, the variable a to the number 3, and the variable b to the number 5. That invocation is waiting for the value of the recursive invocation of fib-iter/internal on the numbers (- n 1) = 1, b = 5, and (+ a b) = 8. When that value is eventually available, it will be returned as the value of the invocation without any further processing.
                  26. +
                  27. One invocation evaluating the body of fib-iter/internal with respect to a lexical environment binding, in the value namespace, the variable n to the number 1, the variable a to the number 5, and the variable 8 to the number 5. That invocation directly returns the number b = 8 as its value without any further recursive invocation of fib-iter/internal.
                  diff --git a/system-files/USER-MANUAL b/system-files/USER-MANUAL index 76fb6b9..4e97e97 100644 --- a/system-files/USER-MANUAL +++ b/system-files/USER-MANUAL @@ -29,31 +29,31 @@
                  void
                  There is exactly one object of type void. Its readable representation is #v and its purpose is to represent missing or undefined objects.
                  boolean
                  -
                  Booleans represent truth values. There are exactly two objects of type boolean: #t (representing true) and #f (representing false).
                  +
                  Objects of type boolean represent truth values. There are exactly two objects of type boolean: #t (representing true) and #f (representing false). An object of type boolean is often simply called a boolean when there is no risk of confusion.
                  number
                  -
                  Numbers represent mathematical numbers: 123, -123, 123.456, -123.456, … The representation of a mathematical number by an object of type number can be exact or approximate.
                  +
                  Objects of type number represent mathematical numbers: 123, -123, 123.456, -123.456, … The representation of a mathematical number by an object of type number can be exact or approximate. An object of type number is often simply called a number when there is no risk of confusion.
                  character
                  -
                  Characters represent Unicode characters: #"a", #"b", #"c", #"ä½ ", #"好", … Most of the characters used in the world have a corresponding Unicode character.
                  +
                  Objects of type character represent Unicode characters: #"a", #"b", #"c", #"ä½ ", #"好", … Most of the characters used in the world have a corresponding Unicode character. An object of type character is often simply called a character when there is no risk of confusion.
                  string
                  -
                  Strings represent indexed sequences of Unicode characters: "abc", "你好", …
                  +
                  Objects of type string represents indexed sequences of Unicode characters: "abc", "你好", … An object of type string is often simply called a string when there is no risk of confusion.
                  keyword
                  -
                  Keywords can, among other uses, represent named values: :red, :green, :blue, …
                  +
                  Objects of type keyword can, among other uses, represent named values: :red, :green, :blue, … An object of type keyword is often simply called a keyword when there is no risk of confusion.
                  variable
                  -
                  Variables are primarily used to name objects and various other entities. For example, the names of the types are variables: object, void, …
                  +
                  Objects of type variable are commonly used to name objects and other kinds of entities. For example, the names of the types are objects of type variable: object, void, … An object of type variable is often simply called a variable when there is no risk of confusion.
                  empty-list
                  -
                  There is exactly one object of type empty-list. Its readable representation is () and its purpose is to represent empty lists of objects.
                  +
                  There is exactly one object of type empty-list. Its readable representation is () and its purpose is to represent empty lists of objects. The single object of type empty-list is often simply called the empty list when there is no risk of confusion.
                  cons
                  -
                  A cons is an ordered pair of objects. The first element is called the car of the cons and the second element is called the cdr of the cons. Conses are the building blocks of many data structures. In particular, conses can be chained together to represent nonempty lists of objects. A nonempty list of objects is represented by a cons whose car is the first element of the list and whose cdr is the sublist of the list obtained by omitting its first element. For example, the list (1 2 3) is represented by a chain of three conses: a first cons whose car is the number 1 and whose cdr is the second cons, a second cons whose car is the number 2 and whose cdr is the third cons, and a third cons whose car is the number 3 and whose cdr is the empty list.
                  +
                  Objects of type cons are ordered pairs of objects. The first/second element of an object of type cons is called its car/cdr. An object of type cons is often simply called a cons when there is no risk of confusion. Conses are the building blocks of many data structures. In particular, conses can be chained together to represent nonempty lists of objects. A nonempty list of objects is represented by a cons whose car is the first element of the list and whose cdr is the sublist of the list obtained by omitting its first element. For example, the list (1 2 3) is represented by a chain of three conses: a first cons whose car is the number 1 and whose cdr is the second cons, a second cons whose car is the number 2 and whose cdr is the third cons, and a third cons whose car is the number 3 and whose cdr is the empty list.
                  vector
                  -
                  Vectors represent indexed sequences of objects: #(), #(1 2 3), …
                  +
                  Objects of type vector represent indexed sequences of objects: #(), #(1 2 3), … An object of type vector is often simply called a vector when there is no risk of confusion.
                  primitive-function
                  -
                  Primitive functions are input/output mappings implemented in a programming language other than EVLambda. Primitive functions have no readable representations and their printable representation is #<primitive-function>.
                  +
                  Objects of type primitive-function are input/output mappings implemented in a programming language other than EVLambda. Objects of type primitive-function have no readable representations and their printable representation is #<primitive-function>. An object of type primitive-function is often simply called a primitive function when there is no risk of confusion.
                  closure
                  -
                  Closures are input/output mappings implemented in EVLambda. Some closures are tagged as being a macro. Macros are code to code mappings used to create new language constructs. Closures have no readable representations and their printable representation is #<closure>.
                  +
                  Objects of type closure are input/output mappings implemented in EVLambda. Some objects of type closure are tagged as being macros. Macros are code to code mappings used to create new language constructs. Objects of type closure have no readable representations and their printable representation is #<closure>. An object of type closure is often simply called a closure when there is no risk of confusion.
                  -

                  Each symbol has an associated sequence of characters called the name of the symbol. The readable representation of a keyword is its name preceded by a semicolon. The readable representation of a variable is its name. The reader always converts the same readable representation of a symbol into the same symbol. To that end, the reader maintains two mappings from names to symbols called packages: one package mapping the names of the previously encountered variables to the corresponding variables and one package mapping the names of the previously encountered keywords to the corresponding keywords. Adding a symbol to its package is called interning the symbol.

                  -

                  The printable representation of a list consists of a left parenthesis followed by the printable representations of the elements of the list separated by a single space followed by a right parenthesis. The printable representation of a list is just one of its infinitely many readable representations. In particular, other readable representations can be obtained by using sequences of at least one whitespace character instead of single spaces to separate the elements of the list.

                  -

                  Although a macro is technically a function (because a macro is an object of type closure and type closure is a subtype of type function), the word “function” is often used to denote specifically a function other than a macro (that is, a primitive function or a closure not tagged as being a macro).

                  +

                  Each symbol has an associated sequence of (Unicode) characters called the name of the symbol. The readable representation of a keyword is its name preceded by a colon. The readable representation of a variable is its name. The reader always converts the same readable representation of a symbol into the same symbol. To that end, the reader maintains two mappings from names to symbols called packages: one package mapping the names of the previously encountered keywords to the corresponding keywords and one package mapping the names of the previously encountered variables to the corresponding variables. Adding a symbol to its package is called interning the symbol.

                  +

                  The printable representation of a list consists of a left parenthesis followed by the printable representations of the elements of the list separated by a single space followed by a right parenthesis. The printable representation of a list is just one of its infinitely many readable representations. In particular, other readable representations can be obtained by using arbitrary sequences of at least one whitespace character instead of single spaces to separate the elements of the list.

                  +

                  Although a macro is technically a function (because a macro is an object of type closure and the type closure is a subtype of the type function), the word “function” is often used to denote specifically a function other than a macro (that is, a primitive function or a closure not tagged as being a macro).

                  Variables name objects through the use of namespaces, bindings, environments, and lookup rules:

                  • Namespaces are a general mechanism allowing the same name to have different meanings in different contexts. EVLambda uses namespaces to allow the same variable to name two objects: one object that is used by default in contexts requiring an object of type function and another object that is used by default in all other contexts.
                  • @@ -73,14 +73,14 @@
                  • An environment references its bindings.

                  The references embedded into the representation of an object, binding, or environment can be thought of as occupying memory locations denoted by the object, binding, or environment. For example, a cons can be thought of as denoting two memory locations: one containing a reference to the car of the cons and one containing a reference to the cdr of the cons. By abuse of language, we often say that a memory location contains an object, binding, or environment when in reality the memory location contains a reference to the object, binding, or environment.

                  -

                  Multiple objects, bindings, and environments can reference a common object, binding, or environment, leading to the sharing of the common object, binding, or environment. An object, binding, or environment can reference itself directly ($X\rightarrow X$) or indirectly ($X\rightarrow Y\rightarrow\cdots\rightarrow X$), leading to the existence of a cycle.

                  +

                  Multiple objects, bindings, and/or environments can reference a common object, binding, or environment, leading to the sharing of the common object, binding, or environment. An object, binding, or environment can reference itself directly ($X\rightarrow X$) or indirectly ($X\rightarrow Y\rightarrow\cdots\rightarrow X$), leading to the existence of a cycle.

                  Objects of type void, boolean, keyword, symbol, and empty-list have the following uniqueness properties:

                  • There exists in the heap exactly one object of type void.
                  • There exists in the heap exactly one object of type boolean representing true.
                  • There exists in the heap exactly one object of type boolean representing false.
                  • -
                  • There cannot exist in the heap more than one object of type keyword with the same name. (This property is enforced by the reader.)
                  • -
                  • There cannot exist in the heap more than one object of type variable with the same name. (This property is enforced by the reader.)
                  • +
                  • There cannot exist in the heap more than one interned object of type keyword with the same name. (This property is enforced by the reader.)
                  • +
                  • There cannot exist in the heap more than one interned object of type variable with the same name. (This property is enforced by the reader.)
                  • There exists in the heap exactly one object of type empty-list.

                  Objects of type integer, character, and string do not have similar uniqueness properties:

                  @@ -91,12 +91,12 @@

                Objects of type void, boolean, number, character, string, keyword, symbol, empty-list, primitive-function, and closure are immutable and cannot be altered. Objects of type cons and vector, bindings, and environments are mutable and can be altered in the following ways:

                  -
                • The car and the cdr of a cons can be replaced by another object.
                • +
                • The car or the cdr of a cons can be replaced by another object.
                • Any element of a vector can be replaced by another object.
                • The value of a binding can be replaced by another object.
                • A binding can be added to or deleted from an environment.
                -

                (As explained above, what replacing an object by another object really means is replacing a reference to an object by a reference to another object.)

                +

                As explained above, what replacing an object by another object really means is replacing a reference to an object by a reference to another object.

                The life cycle of an object, binding, or environment consists of the following events: a creation (which consists of an allocation followed by an initialization) followed by any number of alterations followed by a destruction (which consists of a deallocation). Alterations are possible only if the object, binding, or environment is mutable.

                The destruction of an object, binding, or environment occurs automatically if and when the object, binding, or environment becomes unreachable. The rules used to determine if an object, binding, or environment is reachable are as follows (the concepts of global environment and control stack will be introduced later in this section):

                  @@ -112,8 +112,8 @@
                • The evaluation can complete abruptly because of an error or because of a nonlocal exit. An evaluation that completes abruptly does not produce any result.
                • The evaluation can get caught in an infinite loop and never complete.
                -

                The primary value of a form whose evaluation completed normally is defined as follows: If the result consists of one or more objects, then the primary value of the form is the first object. Otherwise, the primary value of the form is #v.

                -

                The abrupt completion of an evaluation has an associated reason, which has a type and carries a payload. An abrupt completion caused by an error has an associated reason of type error, which carries a payload consisting of a string describing the error. An abrupt completion caused by a nonlocal exit has an associated reason of type nonlocal-exit, which carries a payload consisting of a variable (the exit tag identifying the exit point) and a sequence of zero or more objects (the values to propagate to the exit point).

                +

                The primary value of a form whose evaluation has completed normally is defined as follows: If the result consists of one or more objects, then the primary value of the form is the first object. Otherwise, the primary value of the form is #v.

                +

                The abrupt completion of an evaluation has an associated reason, which has a type and carries a payload. An abrupt completion caused by an error has an associated reason of type error, which carries a payload consisting of two strings: the category of the error and a human-readable description of the error. An abrupt completion caused by a nonlocal exit has an associated reason of type nonlocal-exit, which carries a payload consisting of a variable (the exit tag identifying the exit point of the nonlocal exit) and a sequence of zero or more objects (the values to propagate to the exit point of the nonlocal exit).

                Forms submitted to the evaluator through a listener buffer, through the Evaluate Form command, or through the Load Buffer command are called top-level forms. A consequence of the evaluation rules stated later in this section is that the evaluation of a top-level form usually entails the evaluation of other non-top-level forms.

                Execution of EVLambda code is achieved through interpretation or compilation.

                An interpreter for a language $X$ is a program capable of directly executing code written in language $X$. Language $X$ is called the source language of the interpreter. A compiler for a language $X$ is a program capable of translating code written in language $X$ into code written in a language $Y$. Language $X$ is called the source language of the compiler and language $Y$ is called the target language of the compiler. Code handed to an interpreter or compiler is called source code. A file containing source code is called a source file. Code produced by a compiler is called compiled code. A file containing compiled code is called a compiled file.

                @@ -173,11 +173,12 @@

                A consequence of the lookup rules is that a binding in the value/function namespace of the current lexical/dynamic environment will effectively shadow a binding for the same variable in the value/function namespace of the global environment.

                A global variable is a binding between a variable and an object (of any type) in the value namespace of the global environment, or the name or value of such binding. A global function is a binding between a variable and a function other than a macro in the function namespace of the global environment, or the name or value of such binding. A global macro is a binding between a variable and a macro in the function namespace of the global environment, or the name or value of such binding. Global variables, global functions, and global macros are created and altered using language constructs called global definitions.

                A local variable is a binding between a variable and an object (of any type) in the value namespace of a lexical environment, or the name or value of such binding. A local function is a binding between a variable and a function other than a macro in the function namespace of a lexical environment, or the name or value of such binding. A local macro is a binding between a variable and a macro in the function namespace of a lexical environment, or the name or value of such binding. A dynamic variable is a binding between a variable and an object (of any type) in the value namespace of a dynamic environment, or the name or value of such binding. Local variables, local functions, local macros, and dynamic variables are created (but not altered) using language constructs called binding constructs.

                -

                When a form is submitted to the evaluator, the evaluator analyzes the form to determine how to evaluate it. If the form is the empty list, then the evaluation completes abruptly for a reason of type error. Otherwise, if the form is neither a variable nor a cons, then the result of the evaluation is the form itself. (Objects that are neither the empty list, nor a variable, nor a cons are said to be self-evaluating.) Otherwise, if the form is a variable $\var$, then the variable is treated as an abbreviation for either (vref $\var$) or (fref $\var$), depending on the context of its occurrence. Otherwise, the form is necessarily a cons and the evaluation completes abruptly for a reason of type error unless the form matches one of the following patterns:

                +

                When a form is submitted to the evaluator, the evaluator analyzes the form to determine how to evaluate it. If the form is the empty list, then the evaluation completes abruptly for a reason of type error. Otherwise, if the form is neither a variable nor a cons, then the result of the evaluation is the form itself. (Objects that are neither the empty list, nor a variable, nor a cons are said to be self-evaluating.) Otherwise, if the form is a variable $\variable$, then the variable is treated as an abbreviation for either (vref $\variable$) or (fref $\variable$), depending on the context of its occurrence. Otherwise, the form is necessarily a cons and the evaluation completes abruptly for a reason of type error unless the form matches one of the following patterns:

                1. (quote $\metavar{literal}$)
                2. (progn $\metavar{serial-forms}$)
                3. (if $\metavar{test-form}$ $\metavar{then-form}$ $\metavar{else-form}$)
                4. +
                5. (_for-each $\metavar{function-form}$ $\metavar{list-form}$)
                6. (_vlambda $\metavar{parameter-list}$ $\metavar{body}$)
                7. (_mlambda $\metavar{parameter-list}$ $\metavar{body}$)
                8. (_flambda $\metavar{parameter-list}$ $\metavar{body}$)
                9. @@ -194,7 +195,6 @@
                10. (throw $\metavar{exit-tag-form}$ $\metavar{values-form}$)
                11. (_handler-bind $\metavar{handler-form}$ $\metavar{serial-forms}$)
                12. (unwind-protect $\metavar{protected-form}$ $\metavar{cleanup-forms}$)
                13. -
                14. (_for-each $\metavar{function-form}$ $\metavar{list-form}$)
                15. (apply $\metavar{operator-form}$ $\metavar{operand-forms}$)
                16. (multiple-value-call $\metavar{operator-form}$ $\metavar{operand-forms}$)
                17. (multiple-value-apply $\metavar{operator-form}$ $\metavar{operand-forms}$)
                18. @@ -208,6 +208,8 @@
                19. $\metavar{test-form}$ matches any object
                20. $\metavar{then-form}$ matches any object
                21. $\metavar{else-form}$ matches any object
                22. +
                23. $\metavar{function-form}$ matches any object
                24. +
                25. $\metavar{list-form}$ matches any object
                26. $\metavar{parameter-list}$ matches any list of distinct variables (as we will see in the reference manual, a parameter list can actually take two other forms)
                27. $\metavar{body}$ matches any sequence of zero or more objects
                28. $\metavar{variable}$ matches any variable
                29. @@ -218,8 +220,6 @@
                30. $\metavar{handler-form}$ matches any object
                31. $\metavar{protected-form}$ matches any object
                32. $\metavar{cleanup-forms}$ matches any sequence of zero or more objects
                33. -
                34. $\metavar{function-form}$ matches any object
                35. -
                36. $\metavar{list-form}$ matches any object
                37. $\metavar{macro-operator}$ matches any variable naming a macro according to the lookup rule used by fref
                38. $\metavar{macro-operands}$ matches any sequence of zero or more objects
                39. $\metavar{operator-form}$ matches any object
                40. @@ -227,10 +227,10 @@

              By way of example, a form matches the first pattern if and only if it is a list of two elements whose first element is the variable quote.

              The patterns are tried in sequence and the first matching pattern wins.

              -

              A form matching one of the first twenty-three patterns is called a special form and the variables quote, progn, if, _vlambda, _mlambda, _flambda, _dlambda, vref, vset!, fref, fset!, dref, dset!, block, return-from, catch, throw, _handler-bind, unwind-protect, _for-each, apply, multiple-value-call, and multiple-value-apply are called special operators.

              -

              Special forms matching patterns 4 (_vlambda), 5 (_mlambda), 6 (_flambda), and 7 (_dlambda) are called lambda abstractions. Lambda abstractions are the fundamental binding constructs from which all other binding constructs are built. Forms consisting of a variable and special forms matching patterns 8 (vref), 10 (fref), and 12 (dref) are called variable references. Special forms matching patterns 9 (vset!), 11 (fset!), and 13 (dset!) are called variable assignments. Forms matching pattern 24 are called macro calls. Forms matching pattern 25 are called plain function calls.

              -

              The rule regarding forms consisting of a variable is the following: If a variable $\var$ occurs in operator position, then the variable is treated as an abbreviation for (fref $\var$) and the function namespace is used. Otherwise, the variable is treated as an abbreviation for (vref $\var$) and the value namespace is used. For example, the plain function call (f x) would be treated as an abbreviation for ((fref f) (vref x)). The full forms (fref $\var$) and (vref $\var$) can always be used to force the use of a specific namespace.

              -

              As we will see below, the evaluation of a form entails the evaluation of some or all of the forms directly contained inside the form. When a direct subform is evaluated, the following rules apply:

              +

              A form matching one of the first twenty-three patterns is called a special form and the variables quote, progn, if, _for-each, _vlambda, _mlambda, _flambda, _dlambda, vref, vset!, fref, fset!, dref, dset!, block, return-from, catch, throw, _handler-bind, unwind-protect, apply, multiple-value-call, and multiple-value-apply are called special operators.

              +

              Special forms matching patterns 5 (_vlambda), 6 (_mlambda), 7 (_flambda), and 8 (_dlambda) are called lambda abstractions. Lambda abstractions are the fundamental binding constructs from which all other binding constructs are built. Forms consisting of a variable and special forms matching patterns 9 (vref), 11 (fref), and 13 (dref) are called variable references. Special forms matching patterns 10 (vset!), 12 (fset!), and 14 (dset!) are called variable assignments. Forms matching pattern 24 are called macro calls. Forms matching pattern 25 are called plain function calls.

              +

              The rule regarding forms consisting of a variable is the following: If a variable $\variable$ occurs in operator position, then the variable is treated as an abbreviation for (fref $\variable$) and the function namespace is used. Otherwise, the variable is treated as an abbreviation for (vref $\variable$) and the value namespace is used. For example, the plain function call (f x) would be treated as an abbreviation for ((fref f) (vref x)). The full forms (fref $\variable$) and (vref $\variable$) can always be used to force the use of a specific namespace.

              +

              As we will see below, the evaluation of a form entails the evaluation of some or all of the direct components of the form that are identified as forms by the form's pattern. When a direct subform is evaluated, the following rules apply:

              • The direct subform is evaluated with respect to the same lexical and dynamic environments as the containing form. (But see below how closures are invoked.)
              • Generally, if the evaluation of the direct subform completes abruptly for any reason, then the evaluation of the containing form immediately completes abruptly for the same reason. The only forms that can behave differently are the special forms block, catch, _handler-bind, and unwind-protect, which are described in the reference manual.
              • @@ -239,28 +239,30 @@

                Special forms, macro calls, and plain function calls are evaluated as follows:

                (quote $\metavar{literal}$)
                -
                The quote-form evaluates to the unevaluated literal. Using a quote-form, any object can be treated as data. For any object $\obj$, (quote $\obj$) can be abbreviated to '$\obj$.
                +
                The quote form evaluates to the unevaluated literal. Using a quote form, any object can be treated as data. For any object $\object$, (quote $\object$) can be abbreviated to '$\object$.
                (progn $\metavar{serial-forms}$)
                -
                The serial-forms are evaluated in sequence from left to right. If there is at least one serial-form, then the progn-form evaluates to the values of the last serial-form. Otherwise, the progn-form evaluates to #v.
                +
                The serial forms are evaluated in sequence from left to right. If there is at least one serial form, then the progn form evaluates to the values of the last serial form. Otherwise, the progn form evaluates to #v.
                (if $\metavar{test-form}$ $\metavar{then-form}$ $\metavar{else-form}$)
                -
                The test-form is evaluated. Let $\mlvar{test}$ be the primary value of the test-form. If $\mlvar{test}$ is not a boolean, then the evaluation of the if-form completes abruptly for a reason of type error. If $\mlvar{test}$ is the boolean #t, then the then-form is evaluated and the if-form evaluates to the values of the then-form. If $\mlvar{test}$ is the boolean #f, then the else-form is evaluated and the if-form evaluates to the values of the else-form.
                +
                The test form is evaluated. Let $\mlvar{test}$ be the primary value of the test form. If $\mlvar{test}$ is not a boolean, then the evaluation of the if form completes abruptly for a reason of type error. If $\mlvar{test}$ is the boolean #t, then the then form is evaluated (but not the else form) and the if form evaluates to the values of the then form. If $\mlvar{test}$ is the boolean #f, then the else form is evaluated (but not the then form) and the if form evaluates to the values of the else form.
                +
                (_for-each $\metavar{function-form}$ $\metavar{list-form}$)
                +
                This special form, which is a hack only needed and implemented by some of the interpreters, is described in the reference manual.
                (_vlambda $\metavar{parameter-list}$ $\metavar{body}$)
                (_mlambda $\metavar{parameter-list}$ $\metavar{body}$)
                (_flambda $\metavar{parameter-list}$ $\metavar{body}$)
                (_dlambda $\metavar{parameter-list}$ $\metavar{body}$)
                -
                A lambda abstraction evaluates to a closure recording the following two pieces of information: the lambda abstraction and the current lexical environment (which is said to be captured by the closure). A closure resulting from the evaluation of an _mlambda-form is tagged as being a macro.
                +
                A lambda abstraction evaluates to a closure recording the following two pieces of information: the lambda abstraction and the current lexical environment (which is said to be captured by the closure). A closure resulting from the evaluation of an _mlambda form is tagged as being a macro. Note that the body of a lambda abstraction is not considered to be a direct subform of the lambda abstraction as the evaluation of a lambda abstraction does not entail the evaluation of its body.
                (vref $\metavar{variable}$)
                -
                If there exists a binding for the variable in the value namespace of the current lexical environment, then the vref-form evaluates to the value of that binding. Otherwise, if there exists a binding for the variable in the value namespace of the global environment, then the vref-form evaluates to the value of that binding. Otherwise, the evaluation of the vref-form completes abruptly for a reason of type error.
                +
                If there exists a binding for the variable in the value namespace of the current lexical environment, then the vref form evaluates to the value of that binding. Otherwise, if there exists a binding for the variable in the value namespace of the global environment, then the vref form evaluates to the value of that binding. Otherwise, the evaluation of the vref form completes abruptly for a reason of type error.
                (vset! $\metavar{variable}$ $\metavar{value-form}$)
                -
                The value-form is evaluated. Let $\mlvar{value}$ be the primary value of the value-form. If there exists a binding for the variable in the value namespace of the current lexical environment, then the value of that binding is replaced by $\mlvar{value}$. Otherwise, if there exists a binding for the variable in the value namespace of the global environment, then the value of that binding is replaced by $\mlvar{value}$. Otherwise, a new binding between the variable and $\mlvar{value}$ is added to the value namespace of the global environment. In all three cases, the vset-form evaluates to $\mlvar{value}$.
                +
                The value form is evaluated. Let $\mlvar{value}$ be the primary value of the value form. If there exists a binding for the variable in the value namespace of the current lexical environment, then the value of that binding is replaced by $\mlvar{value}$. Otherwise, if there exists a binding for the variable in the value namespace of the global environment, then the value of that binding is replaced by $\mlvar{value}$. Otherwise, a new binding between the variable and $\mlvar{value}$ is added to the value namespace of the global environment. In all three cases, the vset form evaluates to $\mlvar{value}$.
                (fref $\metavar{variable}$)
                -
                If there exists a binding for the variable in the function namespace of the current lexical environment, then the fref-form evaluates to the value of that binding. Otherwise, if there exists a binding for the variable in the function namespace of the global environment, then the fref-form evaluates to the value of that binding. Otherwise, the evaluation of the fref-form completes abruptly for a reason of type error.
                +
                If there exists a binding for the variable in the function namespace of the current lexical environment, then the fref form evaluates to the value of that binding. Otherwise, if there exists a binding for the variable in the function namespace of the global environment, then the fref form evaluates to the value of that binding. Otherwise, the evaluation of the fref form completes abruptly for a reason of type error.
                (fset! $\metavar{variable}$ $\metavar{value-form}$)
                -
                The value-form is evaluated. Let $\mlvar{value}$ be the primary value of the value-form. If there exists a binding for the variable in the function namespace of the current lexical environment, then the value of that binding is replaced by $\mlvar{value}$. Otherwise, if there exists a binding for the variable in the function namespace of the global environment, then the value of that binding is replaced by $\mlvar{value}$. Otherwise, a new binding between the variable and $\mlvar{value}$ is added to the function namespace of the global environment. In all three cases, the fset-form evaluates to $\mlvar{value}$.
                +
                The value form is evaluated. Let $\mlvar{value}$ be the primary value of the value form. If there exists a binding for the variable in the function namespace of the current lexical environment, then the value of that binding is replaced by $\mlvar{value}$. Otherwise, if there exists a binding for the variable in the function namespace of the global environment, then the value of that binding is replaced by $\mlvar{value}$. Otherwise, a new binding between the variable and $\mlvar{value}$ is added to the function namespace of the global environment. In all three cases, the fset form evaluates to $\mlvar{value}$.
                (dref $\metavar{variable}$)
                -
                If there exists a binding for the variable in the value namespace of the current dynamic environment, then the dref-form evaluates to the value of that binding. Otherwise, if there exists a binding for the variable in the value namespace of the global environment, then the dref-form evaluates to the value of that binding. Otherwise, the evaluation of the dref-form completes abruptly for a reason of type error.
                +
                If there exists a binding for the variable in the value namespace of the current dynamic environment, then the dref form evaluates to the value of that binding. Otherwise, if there exists a binding for the variable in the value namespace of the global environment, then the dref form evaluates to the value of that binding. Otherwise, the evaluation of the dref form completes abruptly for a reason of type error.
                (dset! $\metavar{variable}$ $\metavar{value-form}$)
                -
                The value-form is evaluated. Let $\mlvar{value}$ be the primary value of the value-form. If there exists a binding for the variable in the value namespace of the current dynamic environment, then the value of that binding is replaced by $\mlvar{value}$. Otherwise, if there exists a binding for the variable in the value namespace of the global environment, then the value of that binding is replaced by $\mlvar{value}$. Otherwise, a new binding between the variable and $\mlvar{value}$ is added to the value namespace of the global environment. In all three cases, the dset-form evaluates to $\mlvar{value}$.
                +
                The value form is evaluated. Let $\mlvar{value}$ be the primary value of the value form. If there exists a binding for the variable in the value namespace of the current dynamic environment, then the value of that binding is replaced by $\mlvar{value}$. Otherwise, if there exists a binding for the variable in the value namespace of the global environment, then the value of that binding is replaced by $\mlvar{value}$. Otherwise, a new binding between the variable and $\mlvar{value}$ is added to the value namespace of the global environment. In all three cases, the dset form evaluates to $\mlvar{value}$.
                (block $\metavar{block-name}$ $\metavar{serial-forms}$)
                (return-from $\metavar{block-name}$ $\metavar{values-form}$)
                (catch $\metavar{exit-tag-form}$ $\metavar{serial-forms}$)
                @@ -268,16 +270,14 @@
                (_handler-bind $\metavar{handler-form}$ $\metavar{serial-forms}$)
                (unwind-protect $\metavar{protected-form}$ $\metavar{cleanup-forms}$)
                These special forms, which implement advanced control structures, are described in the reference manual.
                -
                (_for-each $\metavar{function-form}$ $\metavar{list-form}$)
                -
                This special form, which is a hack only needed and implemented by some of the interpreters, is described in the reference manual.
                (apply $\metavar{operator-form}$ $\metavar{operand-forms}$)
                (multiple-value-call $\metavar{operator-form}$ $\metavar{operand-forms}$)
                (multiple-value-apply $\metavar{operator-form}$ $\metavar{operand-forms}$)
                These special forms, which are variants of the plain function call, are described in the reference manual.
                ($\metavar{macro-operator}$ $\metavar{macro-operands}$)
                -
                Following the definition of a macro call, the macro-operator matches a variable naming a macro according to the lookup rule used by fref. That macro is invoked on the unevaluated macro-operands. If the invocation completes abruptly for any reason, then the evaluation of the macro call also completes abruptly for the same reason. Otherwise, if the invocation does not complete, then the evaluation of the macro call does not complete either. Otherwise, the primary value of the invocation, which is called the expansion of the macro call, is evaluated with respect to the current lexical and dynamic environments. If the evaluation of the expansion completes abruptly for any reason, then the evaluation of the macro call also completes abruptly for the same reason. Otherwise, if the evaluation of the expansion does not complete, then the evaluation of the macro call does not complete either. Otherwise, the macro call evaluates to the values of the expansion.
                +
                Following the definition of a macro call, the macro operator matches a variable naming a macro according to the lookup rule used by fref. That macro is invoked on the unevaluated macro operands. If the invocation completes abruptly for any reason, then the evaluation of the macro call also completes abruptly for the same reason. Otherwise, if the invocation does not complete, then the evaluation of the macro call does not complete either. Otherwise, the primary value of the invocation, which is called the expansion of the macro call, is evaluated with respect to the current lexical and dynamic environments. If the evaluation of the expansion completes abruptly for any reason, then the evaluation of the macro call also completes abruptly for the same reason. Otherwise, if the evaluation of the expansion does not complete, then the evaluation of the macro call does not complete either. Otherwise, the macro call evaluates to the values of the expansion.
                ($\metavar{operator-form}$ $\metavar{operand-forms}$)
                -
                The operator-form is evaluated. Let $\mlvar{operator}$ be the primary value of the operator-form. If $\mlvar{operator}$ is not a function, then the evaluation of the plain function call completes abruptly for a reason of type error. Otherwise, the operand-forms are evaluated in sequence from left to right and $\mlvar{operator}$ is invoked on the primary values of the operand-forms. If the invocation completes abruptly for any reason, then the evaluation of the plain function call also completes abruptly for the same reason. Otherwise, if the invocation does not complete, then the evaluation of the plain function call does not complete either. Otherwise, the plain function call evaluates to the values of the invocation.
                +
                The operator form is evaluated. Let $\mlvar{operator}$ be the primary value of the operator form. If $\mlvar{operator}$ is not a function, then the evaluation of the plain function call completes abruptly for a reason of type error. Otherwise, the operand forms are evaluated in sequence from left to right and $\mlvar{operator}$ is invoked on the primary values of the operand forms. If the invocation completes abruptly for any reason, then the evaluation of the plain function call also completes abruptly for the same reason. Otherwise, if the invocation does not complete, then the evaluation of the plain function call does not complete either. Otherwise, the plain function call evaluates to the values of the invocation.

                Invoking a function is what causes the function to compute the output corresponding to an input. The input of an invocation consists of zero or more objects—object references actually—called the arguments of the invocation. (The function is said to be invoked on the arguments and the arguments are said to be passed to the function.) The invocation of a function has three possible outcomes:

                  @@ -285,20 +285,20 @@
                • The invocation can complete abruptly because of an error or because of a nonlocal exit. An invocation that completes abruptly does not produce any output.
                • The invocation can get caught in an infinite loop and never complete.
                -

                The primary value of an invocation that completed normally is defined as follows: If the output consists of one or more objects, then the primary value of the invocation is the first object. Otherwise, the primary value of the invocation is #v.

                +

                The primary value of an invocation that has completed normally is defined as follows: If the output consists of one or more objects, then the primary value of the invocation is the first object. Otherwise, the primary value of the invocation is #v.

                The abrupt completion of an invocation has an associated reason, which has a type and carries a payload. The types and payloads for invocations are the same as for evaluations.

                -

                The verbs “accept” and “return” are often used to describe the input/output mapping implemented by a function. For example, we could describe a function by saying that the function accepts two numbers and returns the sum of their squares.

                +

                The verbs “accept” and “return” are often used to describe the input/output mapping implemented by a function. For example, one could describe a function by saying that the function accepts two numbers and returns the sum of their squares.

                A primitive function is invoked as follows:

                -
                Let us assume that, as is the case with all the evaluators currently available, the primitive function is implemented by a JavaScript function accepting (an encoding of) the arguments of the invocation of the primitive function and returning (an encoding of) the values of the invocation of the primitive function. The JavaScript function is invoked on the arguments. If the invocation of the JavaScript function completes abruptly for any reason, then the invocation of the primitive function also completes abruptly for the same reason. Otherwise, if the invocation of the JavaScript function does not complete, then the invocation of the primitive function does not complete either. Otherwise, the primitive function returns the values returned by the JavaScript function.
                +
                Let us assume that, as is the case with all the evaluators currently available, the primitive function is implemented by a JavaScript function accepting (an encoding of) the arguments of an invocation of the primitive function and returning (an encoding of) the values of the invocation of the primitive function. The JavaScript function is invoked on the arguments. If the invocation of the JavaScript function completes abruptly for any reason, then the invocation of the primitive function also completes abruptly for the same reason. Otherwise, if the invocation of the JavaScript function does not complete, then the invocation of the primitive function does not complete either. Otherwise, the primitive function returns the values returned by the JavaScript function.

                A closure is invoked as follows:

                If the number of variables in the parameter list of the lambda abstraction recorded by the closure and the number of arguments are different, then the invocation completes abruptly for a reason of type error. (As we will see in the reference manual, it is actually possible to create closures accepting a variable number of arguments.) Otherwise, let $\var_1,\ldots,\var_n$ be the variables composing the parameter list of the lambda abstraction recorded by the closure, $\arg_1,\ldots,\arg_n$ be the arguments, and $\lexenv$ and $\dynenv$ be the following environments:

                  -
                • If the closure results from the evaluation of a _vlambda-form or an _mlambda-form, then $\lexenv$ is the environment extending the lexical environment recorded by the closure to bind, in the value namespace, the variable $\var_i$ to the argument $\arg_i$ (for all $i$ from $1$ to $n$) and $\dynenv$ is the current dynamic environment.
                • -
                • If the closure results from the evaluation of an _flambda-form, then $\lexenv$ is the environment extending the lexical environment recorded by the closure to bind, in the function namespace, the variable $\var_i$ to the argument $\arg_i$ (for all $i$ from $1$ to $n$) and $\dynenv$ is the current dynamic environment.
                • -
                • If the closure results from the evaluation of a _dlambda-form, then $\lexenv$ is the lexical environment recorded by the closure and $\dynenv$ is the environment extending the current dynamic environment to bind, in the value namespace, the variable $\var_i$ to the argument $\arg_i$ (for all $i$ from $1$ to $n$).
                • +
                • If the closure results from the evaluation of a _vlambda form or an _mlambda form, then $\lexenv$ is the environment extending the lexical environment recorded by the closure to bind, in the value namespace, the variable $\var_i$ to the argument $\arg_i$ (for all $i$ from $1$ to $n$) and $\dynenv$ is the current dynamic environment.
                • +
                • If the closure results from the evaluation of an _flambda form, then $\lexenv$ is the environment extending the lexical environment recorded by the closure to bind, in the function namespace, the variable $\var_i$ to the argument $\arg_i$ (for all $i$ from $1$ to $n$) and $\dynenv$ is the current dynamic environment.
                • +
                • If the closure results from the evaluation of a _dlambda form, then $\lexenv$ is the lexical environment recorded by the closure and $\dynenv$ is the environment extending the current dynamic environment to bind, in the value namespace, the variable $\var_i$ to the argument $\arg_i$ (for all $i$ from $1$ to $n$).
                -

                The objects composing the body of the lambda abstraction recorded by the closure are evaluated with respect to $\lexenv$ and $\dynenv$ as if they were the serial-forms of a progn-form. (The serial-forms are said to belong to an implicit progn-form.) If the evaluation of the progn-form completes abruptly for any reason, then the invocation also completes abruptly for the same reason. Otherwise, if the evaluation of the progn-form does not complete, then the invocation does not complete either. Otherwise, the closure returns the values of the progn-form.

                +

                The objects composing the body of the lambda abstraction recorded by the closure are evaluated with respect to $\lexenv$ and $\dynenv$ as if they were the serial forms of a progn form. (The objects composing the body are said to belong to an implicit progn form.) If the evaluation of the progn form completes abruptly for any reason, then the invocation also completes abruptly for the same reason. Otherwise, if the evaluation of the progn form does not complete, then the invocation does not complete either. Otherwise, the closure returns the values of the progn form.

                As we will see in the reference manual, the evaluation of a top-level form cannot complete abruptly for a reason of type nonlocal-exit. The three possible outcomes of the evaluation of a top-level form are thus the following:

                  @@ -306,8 +306,8 @@
                • The evaluation of the top-level form completes abruptly for a reason of type error.
                • The evaluation of the top-level form does not complete.
                -

                The special operators _vlambda, _mlambda, _flambda, and _dlambda have an underscore at the beginning of their names to distinguish them from the similarly named macros vlambda, mlambda, flambda, and dlambda. The purpose of those macros is to facilitate the creation of closures accepting a variable number of arguments. The special operator _handler-bind has an underscore at the beginning of its name to distinguish it from the similarly named macro handler-bind. The special operator _for-each has an underscore at the beginning of its name because it is a hack only needed and implemented by some of the interpreters.

                -

                The evaluator uses a data structure called the control stack to coordinate its activities. Each time a top-level form is submitted to the evaluator, a new control stack is created that will be used throughout the evaluation of the form. The same control stack is used to evaluate the top-level form and all non-top-level forms whose evaluations are entailed by the evaluation of the top-level form.

                +

                The special operator _for-each has an underscore at the beginning of its name because it is a hack only needed and implemented by some of the interpreters. The special operators _vlambda, _mlambda, _flambda, and _dlambda have an underscore at the beginning of their names to distinguish them from the similarly named macros vlambda, mlambda, flambda, and dlambda. The purpose of those macros is to facilitate the creation of closures accepting a variable number of arguments. The special operator _handler-bind has an underscore at the beginning of its name to distinguish it from the similarly named macro handler-bind.

                +

                The evaluator uses a data structure called a control stack to coordinate its activities. Each time a top-level form is submitted to the evaluator, a new control stack is created that will be used throughout the evaluation of the top-level form. The same control stack is used to evaluate the top-level form and all the non-top-level forms whose evaluations are entailed by the evaluation of the top-level form.

                An evaluation/invocation that completes normally produces a result/output consisting of zero or more objects. The production of that result/output, which only occurs if the evaluation/invocation completes normally, is the primary effect of the evaluation/invocation. In addition to or in place of its primary effect, an evaluation/invocation can also have secondary effects called side effects. Examples of side effects are:

                • The abrupt completion of the evaluation/invocation.
                • @@ -321,52 +321,53 @@
                • The transfer of information from the form/function to the outside world (an output operation).

                A consequence of the existence of side effects is that the repeated evaluations of the same form or the repeated invocations of the same function on the same arguments do not necessarily have the same outcome and, if they complete normally, do not necessarily produce the same result/output.

                -

                It is customary for a special operator, function, or macro that can alter an object, binding, or environment to have a name ending with an exclamation mark. This explains the exclamation mark in the names of the special operators vset!, fset!, and dset!.

                +

                It is customary for a special operator, function, or macro that can alter objects, bindings, and/or environments to have a name ending with an exclamation mark. This explains the exclamation mark in the names of the special operators vset!, fset!, and dset!.

                Integrated Development Environment

                -

                The integrated development environment (IDE) is a web application that can run either from the EVLambda web server (online mode) or from a web server running on your machine (offline mode). The code running in the web browser is exactly the same in both modes but the behavior of the IDE is slightly different because the backends have different capabilities.

                +

                The integrated development environment (IDE) is a web application that can run either from the EVLambda web server (online mode) or from a web server running on the user's machine (offline mode). The code running in the web browser is exactly the same in both modes but the behavior of the IDE is slightly different because the backends have different capabilities.

                The IDE's graphical user interface consists of a menu bar at the top left, an info bar at the top right, a minibuffer at the bottom, and a set of windows in the main area. Each window consists of a contents area and a status bar. At any given time, a window displays the contents of a buffer, of which there are two types: the file buffers and the listener buffers. A given buffer can be displayed in any number of windows (including zero) and any buffer can be displayed in any window. This organization around buffers and windows is borrowed from the Emacs text editor.

                A file buffer is a buffer whose contents reflects the contents of a file. The contents of the buffer is read from the file through open and revert operations and written into the file through save operations. When a window displays the contents of a file buffer, its status bar displays the name of the file, followed by a star when the current contents of the buffer differs from the contents that was last read from or written into the file.

                -

                When the IDE starts, it automatically opens a predefined set of files. In online mode, the files are located in a directory on the machine hosting the EVLambda web server. In offline mode, the files are located in the directory <EVLAMBDA_HOME>/system-files on your machine.

                -

                A listener buffer is a buffer that allows you to evaluate forms interactively. When a window displays the contents of a listener buffer, its status bar displays the name of the buffer. Currently, the IDE has exactly one listener buffer whose name is “Listener 1”.

                +

                When the IDE starts, it automatically opens a predefined set of files. In online mode, the files are located in a directory on the machine hosting the EVLambda web server. In offline mode, the files are located in the directory <EVLAMBDA_HOME>/system-files on the user's machine.

                +

                A listener buffer is a buffer that allows the user to evaluate forms interactively. When a window displays the contents of a listener buffer, its status bar displays the name of the buffer. Currently, the IDE has exactly one listener buffer whose name is “Listener 1”.

                At any given time, there is exactly one selected window and, by extension, exactly one selected buffer. The status bar of the selected window is darker than the status bar of the nonselected windows. A window becomes the selected window when it receives the focus, which happens for instance when it receives a click event.

                Currently, the only function of the minibuffer is to display messages and evaluation results.

                File Buffers

                A window can display the contents of a file buffer in one of two modes: raw mode or HTML mode. In raw mode, the contents of the buffer is displayed in the CodeMirror text editor. In HTML mode, the contents of the buffer, or some HTML contents derived from the contents of the buffer, is displayed in an HTML viewer.

                The all-caps files (USER-MANUAL, …), which are actually HTML files, can be displayed in raw mode or HTML mode. The EVLambda source files (extension .evl), which contain a mix of EVLambda source code and documentation in XML format, can be displayed in raw mode or HTML mode. The other types of files are always displayed in raw mode.

                Listener Buffers

                -

                A listener buffer allows you to evaluate forms interactively. To evaluate a form in a listener buffer, you type in a readable representation of the form after the prompt and press the Return or Enter key when the cursor is at the very end of the buffer. In response, the form is evaluated, the printable representations of the resulting values are printed separated by a newline, and a new prompt is printed, allowing you to evaluate another form. This sequence of operations is called a read-eval-print loop (REPL).

                +

                A listener buffer allows the user to evaluate forms interactively. To evaluate a form in a listener buffer, the user types in a readable representation of the form after the prompt and presses the Return or Enter key when the cursor is at the very end of the buffer. In response, the form is evaluated, the printable representations of the resulting values are printed separated by a newline, and a new prompt is printed, allowing the user to evaluate another form. This sequence of operations is called a read-eval-print loop (REPL).

                Notes:

                  -
                • The prompt is the greater-than sign printed at the beginning of a line to inform you that the listener buffer is waiting for a form to be typed in.
                • -
                • If you press the Return or Enter key when (1) the cursor is not at the very end of the buffer or (2) the form is missing or incomplete, then a newline is simply inserted into the buffer and no evaluation takes place.
                • -
                • If the evaluation completes abruptly, then the reason of the abrupt completion is necessarily of type error and the payload of the reason (a string describing the error) is printed in place of the printable representations of the (nonexisting) resulting values.
                • -
                • If the evaluation does not complete, then you must abort the evaluation or restart the evaluator in order to get a new prompt.
                • +
                • The prompt is the greater-than sign printed at the beginning of a line to inform the user that the listener buffer is waiting for a form to be typed in.
                • +
                • If the user presses the Return or Enter key when (1) the cursor is not at the very end of the buffer or (2) the form is missing or incomplete, then a newline is simply inserted into the buffer and no evaluation takes place.
                • +
                • If the evaluation completes abruptly, then the reason for the abrupt completion is necessarily of type error and a message combining the category and the description carried by the reason is printed in place of the printable representations of the (nonexisting) resulting values.
                • +
                • If the evaluation does not complete, then the user must abort the evaluation or restart the evaluator in order to get a new prompt.
                -

                We will now illustrate some of the concepts introduced in the section Programming Language by providing a commented transcript of a sequence of evaluations conducted in a listener buffer. If you want to reproduce the evaluations, be sure to start with a fresh Trampoline++ evaluator. To get a fresh Trampoline++ evaluator, restart the evaluator (using the Restart Evaluator… command from the Eval menu) with Trampoline++ selected as the evaluator name.

                -

                The global functions used in the evaluations are listed below. For each function, a template function call and a description of the function's behavior are provided. The variable in operator position is the name of the function (i.e., the variable bound to the function in the function namespace of the global environment).

                +

                Some of the concepts introduced in the section Programming Language will now be illustrated by providing a commented transcript of a sequence of evaluations conducted in a listener buffer. If you want to reproduce the evaluations, be sure to start with a fresh trampoline++ evaluator. To get a fresh trampoline++ evaluator, restart the evaluator (using the Restart Evaluator… command from the Eval menu) with Trampoline++ selected.

                +

                The global functions used in the evaluations are listed below. For each function, a template function call, an indication of the outcome of the invocation, and a description of the function's behavior are provided. The variable in operator position is the name of the function (i.e., the variable bound to the function in the function namespace of the global environment). The arguments named after a type must be of that type, otherwise the invocation completes abruptly for a reason of type error.

                -
                (car $\cons$)
                -
                The function car returns the first element of $\cons$, which must be of type cons.
                -
                (cdr $\cons$)
                -
                The function cdr returns the second element of $\cons$, which must be of type cons.
                -
                (list $\object_1\ldots\object_n$)
                -
                The function list collects its arguments, which can be of any types, into a list: when invoked on the arguments $\object_1,\ldots,\object_n$, the function returns a list whose elements are $\object_1,\ldots,\object_n$.
                -
                (+ $\number_1\ldots\number_n$)
                -
                The function + returns the sum of its arguments, which must be of type number.
                -
                (* $\number_1\ldots\number_n$)
                -
                The function * returns the product of its arguments, which must be of type number.
                -
                (values $\object_1\ldots\object_n$)
                -
                The function values converts its arguments, which can be of any types, into values: when invoked on the arguments $\object_1,\ldots,\object_n$, the function returns the values $\object_1,\ldots,\object_n$.
                +
                (car $\cons$) ⇒ $\object$
                +
                The function returns the first element of $\cons$.
                +
                (cdr $\cons$) ⇒ $\object$
                +
                The function returns the second element of $\cons$.
                +
                (list $\object_1\ldots\object_n$) ⇒ $\list$
                +
                The function collects its arguments into a list: when invoked on the arguments $\object_1,\ldots,\object_n$, the function returns a list whose elements are $\object_1,\ldots,\object_n$.
                +
                (+ $\number_1\ldots\number_n$) ⇒ $\number$
                +
                The function returns the sum of its arguments.
                +
                (* $\number_1\ldots\number_n$) ⇒ $\number$
                +
                The function returns the product of its arguments.
                +
                (values $\object_1\ldots\object_n$) ⇒ $\object_1,\ldots,\object_n$
                +
                The function converts its arguments into values: when invoked on the arguments $\object_1,\ldots,\object_n$, the function returns the values $\object_1,\ldots,\object_n$.

                The global macros used in the evaluations are listed below. For each macro, a template macro call and a description of the macro's behavior are provided. The variable in operator position is the name of the macro (i.e., the variable bound to the macro in the function namespace of the global environment).

                (vdef $\metavar{variable}$ $\metavar{value-form}$)
                -
                The purpose of the macro vdef is to define a global variable by ensuring that the variable is bound in the value namespace of the global environment to the primary value of the value-form. The macro call evaluates to the variable.
                +
                The macro defines a global variable by ensuring that the variable is bound in the value namespace of the global environment to the primary value of the value form. The macro call evaluates to the variable.
                (fdef $\metavar{variable}$ $\metavar{parameter-list}$ $\metavar{body}$)
                -
                The purpose of the macro fdef is to define a global function by ensuring that the variable is bound in the function namespace of the global environment to the closure resulting from the evaluation of the _vlambda-form (_vlambda $\metavar{parameter-list}$ $\metavar{body}$). The macro call evaluates to the variable.
                +
                The macro defines a global function by ensuring that the variable is bound in the function namespace of the global environment to the closure resulting from the evaluation of the _vlambda form (_vlambda $\metavar{parameter-list}$ $\metavar{body}$). The macro call evaluates to the variable.
                (loop $\metavar{serial-forms}$)
                -
                The purpose of the macro loop is to create an infinite loop repeatedly evaluating the serial-forms in sequence from left to right. For example, the macro call (loop) endlessly does nothing. The evaluation of the macro call normally does not complete but there are ways to exit an infinite loop.
                +
                The macro creates an infinite loop repeatedly evaluating the serial forms in sequence from left to right. For example, the macro call (loop) endlessly does nothing. The evaluation of the macro call normally does not complete but there are ways to exit an infinite loop.
                +

                For any object $\object$, '$\object$ is an abbreviation for (quote $\object$).

                Here is the commented transcript, where each gray box contains a form and its values. The character ⏎ marks the places where the Return or Enter key should be pressed.

                > (+ 1 2)⏎
                3

                The evaluation produces a result consisting of the sum of the two numbers 1 and 2. Because numbers are self-evaluating, quoting the numbers is not necessary.

                @@ -374,8 +375,8 @@

                The evaluation produces the same result if the numbers are quoted. Quoting self-evaluating objects is unidiomatic, though.

                > (car '(1 2 3))⏎
                1

                The evaluation produces a result consisting of the first element of the list (1 2 3). Because lists are not self-evaluating, quoting the list is necessary.

                -
                > (car (1 2 3))⏎
                ERROR: The operator-form does not evaluate to a function.
                -

                The evaluation completes abruptly for a reason of type error if the list is not quoted. The explanation for this behavior is as follows: The evaluator treats the list (1 2 3) as a plain function call and the operator-form, the number 1, does not evaluate to a function.

                +
                > (car (1 2 3))⏎
                EvaluatorError: form-type-error:
                The operator form does not evaluate to a function.
                +

                The evaluation completes abruptly for a reason of type error if the list is not quoted. The explanation for this behavior is as follows: The evaluator treats the list (1 2 3) as a plain function call and the operator form, the number 1, does not evaluate to a function.

                > (cdr '(1 2 3))⏎
                (2 3)

                The evaluation produces a result consisting of the sublist of the list (1 2 3) obtained by omitting its first element.

                > (car (cdr '(1 2 3)))⏎
                2
                @@ -386,9 +387,9 @@

                The evaluation produces a result consisting of the third element of the list (1 2 3).

                > (cdr (cdr (cdr '(1 2 3))))⏎
                ()

                The evaluation produces a result consisting of the sublist of the list (1 2 3) obtained by omitting its first three elements.

                -
                > (car (cdr (cdr (cdr '(1 2 3)))))⏎
                ERROR: The 1st argument is not of type EVLCons.
                +
                > (car (cdr (cdr (cdr '(1 2 3)))))⏎
                EvaluatorError: argument-type-error:
                The 1st argument is not of type EVLCons.

                The evaluation completes abruptly for a reason of type error because the empty list is not a cons.

                -
                > (disk-area 2)⏎
                ERROR: The variable 'disk-area' is unbound in the FUNCTION namespace.
                +
                > (disk-area 2)⏎
                EvaluatorError: unbound-variable:
                The variable 'disk-area' is unbound in the function namespace.

                The evaluation completes abruptly for a reason of type error because the global function disk-area is undefined.

                > (fdef disk-area (r) (* 3.14 r r))⏎
                disk-area

                The evaluation produces a result consisting of the variable disk-area. More importantly, the evaluation has the side effect of defining the global function disk-area. In its intended usage, the function accepts the radius of a disk and returns the area of the disk computed using 3.14 as the value of pi. When the function is invoked, its body is evaluated with respect to a lexical environment binding, in the value namespace, the variable r to the argument of the invocation (that is, the radius of the disk).

                @@ -400,7 +401,7 @@

                The evaluation produces the expected result.

                > (fdef disk-area (r) (* *pi* r r))⏎
                disk-area

                The evaluation has the side effect of redefining the global function disk-area to compute the area of the disk using the value of the global variable *pi* as the value of pi. It is customary for a global or dynamic variable to have a name starting and ending with an asterisk.

                -
                > (disk-area 2)⏎
                ERROR: The variable '*pi*' is unbound in the VALUE namespace.
                +
                > (disk-area 2)⏎
                EvaluatorError: unbound-variable:
                The variable '*pi*' is unbound in the value namespace.

                The evaluation completes abruptly for a reason of type error because the global variable *pi* is undefined.

                > (vdef *pi* 3.141593)⏎
                *pi*

                The evaluation produces a result consisting of the variable *pi*. More importantly, the evaluation has the side effect of defining the global variable *pi*.

                @@ -425,31 +426,31 @@
                > (list (values) (values 1) 1 (values 1 2))⏎
                (#v 1 1 1)

                The primary values of the forms (values), (values 1), 1, and (values 1 2) are #v, 1, 1, and 1, respectively.

                > (loop)⏎
                ABORTED
                -

                The evaluation is caught in an infinite loop. You can use the Abort Evaluation command from the Eval menu to stop the evaluation and get a new prompt.

                +

                The evaluation is caught in an infinite loop. The Abort Evaluation command from the Eval menu is one way to stop the evaluation and get a new prompt.

                > (disk-area 2)⏎
                12.5663706

                Aborting an evaluation has no effect on the global definitions.

                > (loop)⏎
                TERMINATED
                -

                The evaluation is caught in an infinite loop. You can use the Restart Evaluator… command from the Eval menu to stop the evaluation and get a new prompt.

                -
                > (disk-area 2)⏎
                ERROR: The variable 'disk-area' is unbound in the FUNCTION namespace.
                +

                The evaluation is caught in an infinite loop. The Restart Evaluator… command from the Eval menu is another way to stop the evaluation and get a new prompt.

                +
                > (disk-area 2)⏎
                EvaluatorError: unbound-variable:
                The variable 'disk-area' is unbound in the function namespace.

                Restarting the evaluator erases all global definitions.

                Menu Bar

                File Menu

                Save Buffer

                -

                Writes the contents of the selected file buffer into its associated file.

                -

                This command is not available in online mode.

                +

                The command writes the contents of the selected file buffer into its associated file.

                +

                The command is not available in online mode.

                Revert Buffer…

                -

                Reverts the contents of the selected file buffer to the contents of its associated file.

                +

                The command reverts the contents of the selected file buffer to the contents of its associated file.

                Edit Menu

                Toggle HTML Mode

                -

                Toggles the selected window between raw and HTML modes.

                -

                This command is only available when the selected window displays the contents of an all-caps file or an EVLambda source file.

                +

                The command toggles the selected window between raw and HTML modes.

                +

                The command is only available when the selected window displays the contents of an all-caps file or an EVLambda source file.

                Clear Listener…

                -

                Clears the selected listener buffer.

                +

                The command clears the selected listener buffer.

                All contents before the last prompt is deleted.

                Eval Menu

                Evaluate Form

                -

                Evaluates a top-level form contained inside the selected file buffer.

                -

                This command is only available when the selected window displays the contents of an EVLambda source file.

                +

                The command evaluates a top-level form contained inside the selected file buffer.

                +

                The command is only available when the selected window displays the contents of an EVLambda source file.

                A top-level form is a form that is not contained inside another form.

                The top-level form to evaluate is selected as follows:

                  @@ -458,35 +459,35 @@
                • Otherwise, if the first nonblank character before the cursor position is the last character of a top-level form, then that form is selected for evaluation.
                • Otherwise, no form is selected for evaluation and no evaluation takes place.
                -

                If the evaluation of the top-level form completes normally, then the printable representations of the resulting values are printed in the minibuffer, separated by a comma. If the evaluation of the top-level form completes abruptly, then the reason of the abrupt completion is necessarily of type error and the payload of the reason is printed in the minibuffer. If the evaluation of the top-level form does not complete, then no new evaluation is possible until the evaluation is aborted or the evaluator is restarted.

                +

                If the evaluation of the top-level form completes normally, then the printable representations of the resulting values are printed in the minibuffer, separated by a comma. If the evaluation of the top-level form completes abruptly, then the reason for the abrupt completion is necessarily of type error and a message combining the category and the description carried by the reason is printed in the minibuffer. If the evaluation of the top-level form does not complete, then no new evaluation is possible until the evaluation is aborted or the evaluator is restarted.

                Load Buffer

                -

                Evaluates the top-level forms contained inside the selected file buffer.

                -

                This command is only available when the selected window displays the contents of an EVLambda source file.

                -

                The top-level forms contained inside the selected file buffer are evaluated as if they were part of a progn-form.

                -

                If the evaluation of the progn-form completes normally, then the printable representations of the resulting values are printed in the minibuffer, separated by a comma. If the evaluation of the progn-form completes abruptly, then the reason of the abrupt completion is necessarily of type error and the payload of the reason is printed in the minibuffer. If the evaluation of the progn-form does not complete, then no new evaluation is possible until the evaluation is aborted or the evaluator is restarted.

                +

                The command evaluates the top-level forms contained inside the selected file buffer.

                +

                The command is only available when the selected window displays the contents of an EVLambda source file.

                +

                The top-level forms contained inside the selected file buffer are evaluated as if they were part of a progn form.

                +

                If the evaluation of the progn form completes normally, then the printable representations of the resulting values are printed in the minibuffer, separated by a comma. If the evaluation of the progn form completes abruptly, then the reason for the abrupt completion is necessarily of type error and a message combining the category and the description carried by the reason is printed in the minibuffer. If the evaluation of the progn form does not complete, then no new evaluation is possible until the evaluation is aborted or the evaluator is restarted.

                Abort Evaluation

                -

                Aborts the current evaluation.

                +

                The command aborts the current evaluation.

                Restart Evaluator…

                -

                Terminates the current evaluator and starts a new one.

                +

                The command terminates the current evaluator and starts a new one.

                Warning: All global definitions are lost.

                -

                The following evaluators are available:

                +

                The following interpreter-based evaluators are available:

                  -
                • Plain Recursive (plainrec)
                • +
                • Direct Style (directstyle)
                • Continuation Passing Style (cps)
                • Object-Oriented CPS (oocps)
                • Stack-Based Object-Oriented CPS (sboocps)
                • Trampoline (trampoline)
                • Trampoline++ (trampolinepp)
                -

                Only the Trampoline and Trampoline++ evaluators allow unbounded iterations through tail-recursive calls. The other evaluators are only useful as stepping stones to understand the Trampoline and Trampoline++ evaluators. The Trampoline++ evaluator is an optimized version of the Trampoline evaluator.

                +

                Only the trampoline and trampoline++ evaluators allow unbounded iterations through tail-recursive calls. The other evaluators are only useful as stepping stones to understand the trampoline and trampoline++ evaluators. The trampoline++ evaluator is an optimized version of the trampoline evaluator.

                View Menu

                Select Other Window

                -

                Selects one of the nonselected windows.

                -

                This command is not available when the selected window is maximized.

                +

                The command selects one of the nonselected windows.

                +

                The command is not available when the selected window is maximized.

                Toggle Maximized State

                -

                Toggles the selected window between unmaximized and maximized states.

                +

                The command toggles the selected window between unmaximized and maximized states.

                Buffer Menu

                -

                The buffer menu allows you to select the buffer displayed in the selected window.

                +

                The buffer menu allows the user to select the buffer displayed in the selected window.

                The buffer menu contains the following entries:

                • /system/USER-MANUAL: the user manual (this file)
                • @@ -495,17 +496,15 @@
                • /system/IMPLEMENTATION-NOTES: the implementation notes
                • /system/BIBLIOGRAPHY: the bibliography
                • /system/LICENSE: the license
                • -
                • /system/all-caps.css: the CSS file styling the all-caps files
                • -
                • /system/all-caps.js: the JavaScript file loaded by the all-caps files
                • -
                • /system/core.js: the JavaScript file implementing the evaluators, the primitive data types, the primitive functions, etc., constituting the “core” of the EVLambda programming language
                • -
                • /system/evl2html.xslt: the XSLT file used to convert the EVLambda source files to HTML
                • -
                • /system/evl2html.css: the CSS file styling the EVLambda source files converted to HTML
                • -
                • /system/evl2html.js: the JavaScript file loaded by the EVLambda source files converted to HTML
                • +
                • /system/core.js: the JavaScript file implementing the interpreters, the primitive data types, the primitive functions, etc., constituting the “core” of the EVLambda programming language
                • /system/mantle.evl: the EVLambda source file implementing the nonprimitive data types, the nonprimitive functions, the macros, etc., constituting the “mantle” of the EVLambda programming language
                • +
                • /system/evl2html.xslt: the XSLT file used to convert the EVLambda source files to HTML
                • +
                • /system/common.css: the CSS file styling the all-caps files and the EVLambda source files converted to HTML
                • +
                • /system/common.js: the JavaScript file loaded by the all-caps files and the EVLambda source files converted to HTML
                • Listener 1: the initial listener

                Help Menu

                -

                The help menu allows you to quickly navigate to various parts of the EVLambda website.

                +

                The help menu allows the user to quickly navigate to various parts of the EVLambda website.

                The help menu contains the following entries:

                • Home: link to the home page
                • @@ -522,7 +521,7 @@
                • Bill of Materials: link to a list of the libraries used by the IDE

                Info Bar

                -

                The info bar displays the type of the current evaluator.

                +

                The info bar displays the name of the current evaluator.

                Keyboard Shortcuts

                diff --git a/system-files/all-caps.css b/system-files/all-caps.css deleted file mode 100644 index 818800b..0000000 --- a/system-files/all-caps.css +++ /dev/null @@ -1,146 +0,0 @@ -/* SPDX-FileCopyrightText: Copyright (c) 2024-2025 Raphaël Van Dyck */ -/* SPDX-License-Identifier: BSD-3-Clause */ - -html { - font-family: Arial, sans-serif; - font-size: 14pt; - line-height: 1.4; -} - -@media print { - html { - font-size: 12pt; - } -} - -div.preamble { - display: none; -} - -h1 { - counter-reset: h2counter h3counter h4counter h5counter h6counter; -} - -h2 { - counter-increment: h2counter; - counter-set: h3counter h4counter h5counter h6counter; -} - -h2:before { - content: counter(h2counter) ". "; -} - -h3 { - counter-increment: h3counter; - counter-set: h4counter h5counter h6counter; -} - -h3:before { - content: counter(h2counter) "." counter(h3counter) ". "; -} - -h4 { - counter-increment: h4counter; - counter-set: h5counter h6counter; -} - -h4:before { - content: counter(h2counter) "." counter(h3counter) "." counter(h4counter) ". "; -} - -h5 { - counter-increment: h5counter; - counter-set: h6counter; -} - -h5:before { - content: counter(h2counter) "." counter(h3counter) "." counter(h4counter) "." counter(h5counter) ". "; -} - -h6 { - counter-increment: h6counter; -} - -h6:before { - content: counter(h2counter) "." counter(h3counter) "." counter(h4counter) "." counter(h5counter) "." counter(h6counter) ". "; -} - -.bg { - background-color: lightgray; -} - -.strike { - text-decoration: line-through; -} - -pre.repl { - margin-left: 2em; - border-radius: 10px; - padding: 10px; - width: 800px; - background-color: lightgray; -} - -div.trace ul { - border-left: 1px solid; -} - -div.trace > ul { - border-left: none; -} - -div.trace li { - list-style-type: none; -} - -span.charseq { - font-family: monospace; - line-height: 2em; -} - -span.char { - margin: 1px; - border: 1px solid; - padding: 1px; -} - -table.plain { - border-collapse: collapse; - border: 1px solid; -} - -table.plain th, table.plain td { - border: 1px solid; - padding: 5px; - text-align: left; -} - -table.ks { - border-collapse: collapse; - border: 1px solid; -} - -table.ks th, table.ks td { - border: 1px solid; - padding: 5px; -} - -table.ebnf { - margin-left: 1em; -} - -td.lhs, td.def, td.rhs { - padding: 5px; -} - -td.lhs { - text-align: right; -} - -td.def { - text-align: center; -} - -td.rhs { - text-align: left; -} diff --git a/system-files/all-caps.js b/system-files/all-caps.js deleted file mode 100644 index a8a3fec..0000000 --- a/system-files/all-caps.js +++ /dev/null @@ -1,88 +0,0 @@ -// SPDX-FileCopyrightText: Copyright (c) 2024-2025 Raphaël Van Dyck -// SPDX-License-Identifier: BSD-3-Clause - -/******************/ -/* Event Handlers */ -/******************/ - -window.addEventListener('focus', event => { - window.parent.dispatchEvent(new CustomEvent('iframeFocus', {detail: windowId})); -}); - -window.addEventListener('keydown', event => { - if (event.ctrlKey && (event.altKey || event.metaKey)) { - window.parent.dispatchEvent(new CustomEvent('iframeKeyDown', {detail: event})); - event.preventDefault(); - } -}); - -/***********/ -/* MathJax */ -/***********/ - -const origin = document.location.origin; - -window.MathJax = { - options: { - skipHtmlTags: {'[-]': ['code']} - }, - loader: { - load: ['[tex]/texhtml', '[tex]/mathtools'] - }, - tex: { - packages: {'[+]': ['texhtml', 'mathtools']}, - allowTexHTML: true, - inlineMath: {'[+]': [['$', '$']]}, - macros: { - code: ['#1', 1], - mlvar: ['\\textit{#1}', 1], - metavar: ['\\langle\\textrm{#1}\\rangle', 1], - metavarn: ['\\langle\\textrm{#1}_{#2}\\rangle', 2], - var: '\\mlvar{var}', - obj: '\\mlvar{obj}', - arg: '\\mlvar{arg}', - env: '\\mlvar{env}', - lexenv: '\\mlvar{lexenv}', - dynenv: '\\mlvar{dynenv}', - ns: '\\mlvar{ns}', - vbind: '\\rightarrow_\\textrm{v}', - vbinding: ['\\code{#1}\\vbind\\code{#2}', 2], - fbind: '\\rightarrow_\\textrm{f}', - fbinding: ['\\code{#1}\\fbind\\code{#2}', 2], - object: '\\mlvar{object}', - void: '\\mlvar{void}', - boolean: '\\mlvar{boolean}', - number: '\\mlvar{number}', - character: '\\mlvar{character}', - string: '\\mlvar{string}', - symbol: '\\mlvar{symbol}', - keyword: '\\mlvar{keyword}', - variable: '\\mlvar{variable}', - list: '\\mlvar{list}', - emptylist: '\\mlvar{empty-list}', - cons: '\\mlvar{cons}', - vector: '\\mlvar{vector}', - function: '\\mlvar{function}', - primitivefunction: '\\mlvar{primitive-function}', - closure: '\\mlvar{closure}' - } - }, - output: { - //font: 'mathjax-newcm', - font: 'mathjax-stix2', - fontPath: origin + '/ide/%%FONT%%-font', - displayAlign: 'left', - displayIndent: '2em', - displayOverflow: 'linebreak', - linebreaks: { - inline: true - } - } -}; - -(function () { - var script = document.createElement('script'); - script.src = origin + '/ide/mathjax/tex-chtml-nofont.js'; - script.defer = true; - document.head.appendChild(script); -})(); diff --git a/system-files/common.css b/system-files/common.css new file mode 100644 index 0000000..c857b32 --- /dev/null +++ b/system-files/common.css @@ -0,0 +1,159 @@ +/* SPDX-FileCopyrightText: Copyright (c) 2024-2025 Raphaël Van Dyck */ +/* SPDX-License-Identifier: BSD-3-Clause */ + +html { + font-family: Arial, sans-serif; + font-size: 14pt; + line-height: 1.4; +} + +@media print { + html { + font-size: 12pt; + } +} + +div.preamble { + display: none; +} + +h1 { + counter-reset: h2counter h3counter h4counter h5counter h6counter; +} + +h2 { + counter-increment: h2counter; + counter-set: h3counter h4counter h5counter h6counter; +} + +h2:before { + content: counter(h2counter) ". "; +} + +h3 { + counter-increment: h3counter; + counter-set: h4counter h5counter h6counter; +} + +h3:before { + content: counter(h2counter) "." counter(h3counter) ". "; +} + +h4 { + counter-increment: h4counter; + counter-set: h5counter h6counter; +} + +h4:before { + content: counter(h2counter) "." counter(h3counter) "." counter(h4counter) ". "; +} + +h5 { + counter-increment: h5counter; + counter-set: h6counter; +} + +h5:before { + content: counter(h2counter) "." counter(h3counter) "." counter(h4counter) "." counter(h5counter) ". "; +} + +h6 { + counter-increment: h6counter; +} + +h6:before { + content: counter(h2counter) "." counter(h3counter) "." counter(h4counter) "." counter(h5counter) "." counter(h6counter) ". "; +} + +.bg { + background-color: lightgray; +} + +.strike { + text-decoration: line-through; +} + +pre.repl { + margin-left: 2em; + border-radius: 10px; + padding: 10px; + width: 800px; + background-color: lightgray; +} + +div.trace ul { + border-left: 1px solid; +} + +div.trace > ul { + border-left: none; +} + +div.trace li { + list-style-type: none; +} + +span.charseq { + font-family: monospace; + line-height: 2em; +} + +span.char { + margin: 1px; + border: 1px solid; + padding: 1px; +} + +table.plain { + border-collapse: collapse; + border: 1px solid; +} + +table.plain th, table.plain td { + border: 1px solid; + padding: 5px; + text-align: left; +} + +table.ks { + border-collapse: collapse; + border: 1px solid; +} + +table.ks th, table.ks td { + border: 1px solid; + padding: 5px; + text-align: left; +} + +table.ebnf { + margin-left: 1em; +} + +td.lhs, td.def, td.rhs { + padding: 5px; +} + +td.lhs { + text-align: right; +} + +td.def { + text-align: center; +} + +td.rhs { + text-align: left; +} + +pre.blockcode, div.indentation { + font-family: monospace; +} + +div.blockcomment, span.eolcomment { + font-family: Arial, sans-serif; +} + +span.eolcomment::before { + content: '\2014 '; /* em dash followed by a single space */ +} diff --git a/system-files/common.js b/system-files/common.js new file mode 100644 index 0000000..e82f6e6 --- /dev/null +++ b/system-files/common.js @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: Copyright (c) 2024-2025 Raphaël Van Dyck +// SPDX-License-Identifier: BSD-3-Clause + +/******************/ +/* Event Handlers */ +/******************/ + +window.addEventListener('focus', event => { + window.parent.dispatchEvent(new CustomEvent('iframeFocus', {detail: windowId})); +}); + +window.addEventListener('keydown', event => { + if (event.ctrlKey && (event.altKey || event.metaKey)) { + window.parent.dispatchEvent(new CustomEvent('iframeKeyDown', {detail: event})); + event.preventDefault(); + } +}); + +/***********/ +/* MathJax */ +/***********/ + +const origin = document.location.origin; + +window.MathJax = { + options: { + skipHtmlTags: {'[-]': ['code']} + }, + loader: { + load: ['[tex]/texhtml', '[tex]/mathtools'] + }, + tex: { + packages: {'[+]': ['texhtml', 'mathtools']}, + allowTexHTML: true, + inlineMath: {'[+]': [['$', '$']]}, + macros: { + code: ['#1', 1], + mlvar: ['\\textit{#1}', 1], + metavar: ['\\langle\\textrm{#1}\\rangle', 1], + var: '\\mlvar{var}', + obj: '\\mlvar{obj}', + arg: '\\mlvar{arg}', + env: '\\mlvar{env}', + lexenv: '\\mlvar{lexenv}', + dynenv: '\\mlvar{dynenv}', + ns: '\\mlvar{ns}', + vbind: '\\rightarrow_\\textrm{v}', + vbinding: ['\\code{#1}\\vbind\\code{#2}', 2], + fbind: '\\rightarrow_\\textrm{f}', + fbinding: ['\\code{#1}\\fbind\\code{#2}', 2], + object: '\\mlvar{object}', + void: '\\mlvar{void}', + boolean: '\\mlvar{boolean}', + number: '\\mlvar{number}', + character: '\\mlvar{character}', + string: '\\mlvar{string}', + symbol: '\\mlvar{symbol}', + keyword: '\\mlvar{keyword}', + variable: '\\mlvar{variable}', + list: '\\mlvar{list}', + emptylist: '\\mlvar{empty-list}', + cons: '\\mlvar{cons}', + vector: '\\mlvar{vector}', + function: '\\mlvar{function}', + primitivefunction: '\\mlvar{primitive-function}', + closure: '\\mlvar{closure}' + } + }, + output: { + //font: 'mathjax-newcm', + font: 'mathjax-stix2', + fontPath: origin + '/ide/%%FONT%%-font', + displayAlign: 'left', + displayIndent: '2em', + displayOverflow: 'linebreak', + linebreaks: { + inline: true + } + } +}; + +(function () { + var script = document.createElement('script'); + script.src = origin + '/ide/mathjax/tex-chtml-nofont.js'; + script.defer = true; + document.head.appendChild(script); +})(); diff --git a/system-files/core.js b/system-files/core.js index bc654d4..423aa98 100644 --- a/system-files/core.js +++ b/system-files/core.js @@ -10,6 +10,8 @@ const isRunningInsideNode = (typeof process !== 'undefined') && (process.release let abortSignalArray = null; let selectedEvaluator = null; +const optimizeMacroCalls = true; + /*******************/ /* Interface (IDE) */ /*******************/ @@ -63,7 +65,7 @@ function abortedOrError(exception) { if (exception instanceof Aborted) { return {status: ABORTED}; } else { - return {status: ERROR, output: exception.message}; + return {status: ERROR, output: exception.name + ': ' + exception.message}; } } @@ -204,13 +206,6 @@ class EVLToXMLConverterError extends Error { } } -class FormAnalyzerError extends Error { - constructor(message) { - super(message); - this.name = 'FormAnalyzerError'; - } -} - class EvaluatorError extends Error { constructor(message) { super(message); @@ -1440,15 +1435,11 @@ function countSpacesAfterFirstNewline(string) { /* Form Analyzer */ /*****************/ -function formAnalyzerError(formName) { - throw new FormAnalyzerError(`Malformed ${formName} form.`); -} - function checkVariable(object, formName) { if (object instanceof EVLVariable) { return object; } else { - formAnalyzerError(formName); + return new MalformedForm(formName); } } @@ -1456,7 +1447,7 @@ function checkEmptyList(object, formName) { if (object instanceof EVLEmptyList) { return object; } else { - formAnalyzerError(formName); + return new MalformedForm(formName); } } @@ -1464,7 +1455,7 @@ function checkCons(object, formName) { if (object instanceof EVLCons) { return object; } else { - formAnalyzerError(formName); + return new MalformedForm(formName); } } @@ -1474,12 +1465,24 @@ function checkProperList(object, formName) { if (list instanceof EVLCons) { list = list.cdr; } else { - formAnalyzerError(formName); + return new MalformedForm(formName); } } return object; } +function isProperList(object) { + let list = object; + while (list !== EVLEmptyList.NIL) { + if (list instanceof EVLCons) { + list = list.cdr; + } else { + return false; + } + } + return true; +} + function checkParameterList(object, formName) { if (object instanceof EVLVariable) { return [[object], true]; @@ -1492,7 +1495,7 @@ function checkParameterList(object, formName) { if (list.car instanceof EVLVariable) { parameters.push(list.car); } else { - formAnalyzerError(formName); + return new MalformedForm(formName); } if (list.cdr instanceof EVLVariable) { parameters.push(list.cdr); @@ -1502,95 +1505,235 @@ function checkParameterList(object, formName) { list = list.cdr; } } else { - formAnalyzerError(formName); + return new MalformedForm(formName); } } if (new Set(parameters).size !== parameters.length) { - formAnalyzerError(formName); + return new MalformedForm(formName); } return [parameters, rest]; } } function analyzeQuote(form) { + const formName = 'quote form'; let cons = form; - cons = checkCons(cons.cdr, 'quote'); + cons = checkCons(cons.cdr, formName); + if (isError(cons)) return cons; const literal = cons.car; - checkEmptyList(cons.cdr, 'quote'); + const emptyList = checkEmptyList(cons.cdr, formName); + if (isError(emptyList)) return emptyList; return [literal]; } function analyzeProgn(form) { + const formName = 'progn form'; let cons = form; - const forms = checkProperList(cons.cdr, 'progn'); - return [forms]; + const serialForms = checkProperList(cons.cdr, formName); + if (isError(serialForms)) return serialForms; + return [serialForms]; } function analyzeIf(form) { + const formName = 'if form'; let cons = form; - cons = checkCons(cons.cdr, 'if'); + cons = checkCons(cons.cdr, formName); + if (isError(cons)) return cons; const testForm = cons.car; - cons = checkCons(cons.cdr, 'if'); + cons = checkCons(cons.cdr, formName); + if (isError(cons)) return cons; const thenForm = cons.car; - cons = checkCons(cons.cdr, 'if'); + cons = checkCons(cons.cdr, formName); + if (isError(cons)) return cons; const elseForm = cons.car; - checkEmptyList(cons.cdr, 'if'); + const emptyList = checkEmptyList(cons.cdr, formName); + if (isError(emptyList)) return emptyList; return [testForm, thenForm, elseForm]; } +function analyzeForEach(form) { + const formName = '_for-each form'; + let cons = form; + cons = checkCons(cons.cdr, formName); + if (isError(cons)) return cons; + const functionForm = cons.car; + cons = checkCons(cons.cdr, formName); + if (isError(cons)) return cons; + const listForm = cons.car; + const emptyList = checkEmptyList(cons.cdr, formName); + if (isError(emptyList)) return emptyList; + return [functionForm, listForm]; +} + function analyzeLambda(form) { + const formName = 'lambda abstraction'; let cons = form; - cons = checkCons(cons.cdr, '_lambda'); - const [parameters, rest] = checkParameterList(cons.car, '_lambda'); - const forms = checkProperList(cons.cdr, '_lambda'); - return [parameters, rest, forms]; + cons = checkCons(cons.cdr, formName); + if (isError(cons)) return cons; + const parameterList = checkParameterList(cons.car, formName); + if (isError(parameterList)) return parameterList; + const serialForms = checkProperList(cons.cdr, formName); + if (isError(serialForms)) return serialForms; + return [parameterList[0], parameterList[1], serialForms]; } function analyzeRef(form) { + const formName = 'variable reference'; let cons = form; - cons = checkCons(cons.cdr, 'ref'); - const variable = checkVariable(cons.car, 'ref'); - checkEmptyList(cons.cdr, 'ref'); + cons = checkCons(cons.cdr, formName); + if (isError(cons)) return cons; + const variable = checkVariable(cons.car, formName); + if (isError(variable)) return variable; + const emptyList = checkEmptyList(cons.cdr, formName); + if (isError(emptyList)) return emptyList; return [variable]; } function analyzeSet(form) { + const formName = 'variable assignment'; let cons = form; - cons = checkCons(cons.cdr, 'set'); - const variable = checkVariable(cons.car, 'set'); - cons = checkCons(cons.cdr, 'set'); + cons = checkCons(cons.cdr, formName); + if (isError(cons)) return cons; + const variable = checkVariable(cons.car, formName); + if (isError(variable)) return variable; + cons = checkCons(cons.cdr, formName); + if (isError(cons)) return cons; const valueForm = cons.car; - checkEmptyList(cons.cdr, 'set'); + const emptyList = checkEmptyList(cons.cdr, formName); + if (isError(emptyList)) return emptyList; return [variable, valueForm]; } -function analyzeForEach(form) { +function analyzeBlock(form) { + const formName = 'block form'; let cons = form; - cons = checkCons(cons.cdr, '_for-each'); - const functionForm = cons.car; - cons = checkCons(cons.cdr, '_for-each'); - const listForm = cons.car; - checkEmptyList(cons.cdr, '_for-each'); - return [functionForm, listForm]; + cons = checkCons(cons.cdr, formName); + if (isError(cons)) return cons; + const blockName = checkVariable(cons.car, formName); + if (isError(blockName)) return blockName; + const serialForms = checkProperList(cons.cdr, formName); + if (isError(serialForms)) return serialForms; + return [blockName, serialForms]; +} + +function analyzeReturnFrom(form) { + const formName = 'return-from form'; + let cons = form; + cons = checkCons(cons.cdr, formName); + if (isError(cons)) return cons; + const blockName = checkVariable(cons.car, formName); + if (isError(blockName)) return blockName; + cons = checkCons(cons.cdr, formName); + if (isError(cons)) return cons; + const valuesForm = cons.car; + const emptyList = checkEmptyList(cons.cdr, formName); + if (isError(emptyList)) return emptyList; + return [blockName, valuesForm]; +} + +function analyzeCatch(form) { + const formName = 'catch form'; + let cons = form; + cons = checkCons(cons.cdr, formName); + if (isError(cons)) return cons; + const exitTagForm = cons.car; + const serialForms = checkProperList(cons.cdr, formName); + if (isError(serialForms)) return serialForms; + return [exitTagForm, serialForms]; +} + +function analyzeThrow(form) { + const formName = 'throw form'; + let cons = form; + cons = checkCons(cons.cdr, formName); + if (isError(cons)) return cons; + const exitTagForm = cons.car; + cons = checkCons(cons.cdr, formName); + if (isError(cons)) return cons; + const valuesForm = cons.car; + const emptyList = checkEmptyList(cons.cdr, formName); + if (isError(emptyList)) return emptyList; + return [exitTagForm, valuesForm]; +} + +function analyzeHandlerBind(form) { + const formName = '_handler-bind form'; + let cons = form; + cons = checkCons(cons.cdr, formName); + if (isError(cons)) return cons; + const handlerForm = cons.car; + const serialForms = checkProperList(cons.cdr, formName); + if (isError(serialForms)) return serialForms; + return [handlerForm, serialForms]; } +function analyzeUnwindProtect(form) { + const formName = 'unwind-protect form'; + let cons = form; + cons = checkCons(cons.cdr, formName); + if (isError(cons)) return cons; + const protectedForm = cons.car; + const cleanupForms = checkProperList(cons.cdr, formName); + if (isError(cleanupForms)) return cleanupForms; + return [protectedForm, cleanupForms]; +} -function analyzeCatchErrors(form) { +function analyzeMlet(form) { + // (mlet ((variable parameter-list . serial-forms)*) . serial-forms) + const formName = 'mlet form'; let cons = form; - cons = checkCons(cons.cdr, '_catch-errors'); - const tryForm = cons.car; - checkEmptyList(cons.cdr, '_catch-errors'); - return [tryForm]; + cons = checkCons(cons.cdr, formName); + if (isError(cons)) return cons; + const mletBindings = checkMletBindings(cons.car, formName); + if (isError(mletBindings)) return mletBindings; + const serialForms = checkProperList(cons.cdr, formName); + if (isError(serialForms)) return serialForms; + return [mletBindings, serialForms]; +} + +function checkMletBindings(list, formName) { + const mletBindings = []; + while (list !== EVLEmptyList.NIL) { + if (list instanceof EVLCons) { + let cons = checkCons(list.car, formName); + if (isError(cons)) return cons; + const variable = checkVariable(cons.car, formName); + if (isError(variable)) return variable; + cons = checkCons(cons.cdr, formName); + if (isError(cons)) return cons; + const parameterList = checkProperList(cons.car, formName); + if (isError(parameterList)) return parameterList; + const serialForms = checkProperList(cons.cdr, formName); + if (isError(serialForms)) return serialForms; + mletBindings.push([variable, parameterList, serialForms]); + list = list.cdr; + } else { + return new MalformedForm(formName); + } + } + return mletBindings; } -function analyzeCall(mv, apply, form) { +function analyzeCall(mv, apply, form, lenv) { + const formName = 'call'; let cons = form; if (mv || apply) { - cons = checkCons(cons.cdr, 'call'); + cons = checkCons(cons.cdr, formName); + if (isError(cons)) return cons; } const operator = cons.car; - const operands = checkProperList(cons.cdr, 'call'); - return [operator, operands]; + const operands = checkProperList(cons.cdr, formName); + if (isError(operands)) return operands; + if (mv || apply || !(operator instanceof EVLVariable)) { + return [false, operator, operands]; + } else { + const fn = lenv.ref(FUN_NS, operator); + if (fn instanceof EVLClosure && fn.macro) { + return [true, fn, operands]; + } else { + return [false, operator, operands]; + } + } } /*****************************/ @@ -1606,18 +1749,13 @@ const DYN_SCOPE = 1; // indefinite scope and dynamic extent const VAL_NS = 0; // value namespace const FUN_NS = 1; // function namespace +const BLK_NS = 2; // block namespace +const XIT_NS = 3; // exit-point namespace /**********************/ /* Global Environment */ /**********************/ -class UnboundVariable extends EvaluatorError { - constructor(variable, namespace) { - super(`The variable '${variable.name}' is unbound in the ${namespace} namespace.`); - this.name = 'UnboundVariable'; - } -} - class GlobalEnv { static ref(namespace, variable) { switch (namespace) { @@ -1626,7 +1764,7 @@ class GlobalEnv { if (value !== null) { return value; } else { - throw new UnboundVariable(variable, 'value'); + return new UnboundVariable(variable, 'value'); } } case FUN_NS: { @@ -1634,9 +1772,12 @@ class GlobalEnv { if (value !== null) { return value; } else { - throw new UnboundVariable(variable, 'function'); + return new UnboundVariable(variable, 'function'); } } + case BLK_NS: + case XIT_NS: + return null; default: throw new CannotHappen('GlobalEnv.ref'); } @@ -1651,21 +1792,6 @@ class GlobalEnv { throw new CannotHappen('GlobalEnv.set'); } } - // ref variant used by the preprocessor - static preprocessorRef(namespace, variable) { - switch (namespace) { - case VAL_NS: { - const value = variable.value; - return [null, null, value]; - } - case FUN_NS: { - const value = variable.function; - return [null, null, value]; - } - default: - throw new CannotHappen('GlobalEnv.preprocessorRef'); - } - } } /************************************/ @@ -1685,9 +1811,8 @@ class NullDefiniteEnv extends DefiniteEnv { set(namespace, variable, value) { return GlobalEnv.set(namespace, variable, value); } - // ref variant used by the preprocessor - preprocessorRef(namespace, variable, i) { - return GlobalEnv.preprocessorRef(namespace, variable); + lookup(namespace, variable, i) { + return [true, null, null]; } } @@ -1709,7 +1834,7 @@ class Frame extends DefiniteEnv { } } } - return this.next?.ref(namespace, variable); + return this.next.ref(namespace, variable); } set(namespace, variable, value) { if (this.namespace === namespace) { @@ -1719,18 +1844,17 @@ class Frame extends DefiniteEnv { } } } - return this.next?.set(namespace, variable, value); + return this.next.set(namespace, variable, value); } - // ref variant used by the preprocessor - preprocessorRef(namespace, variable, i) { + lookup(namespace, variable, i) { if (this.namespace === namespace) { for (let j = 0; j < this.variables.length; j++) { if (this.variables[j] === variable) { - return [i, j, this.values[j]]; + return [false, i, j]; } } } - return this.next.preprocessorRef(namespace, variable, i + 1); + return this.next.lookup(namespace, variable, i + 1); } } @@ -1738,27 +1862,6 @@ class Frame extends DefiniteEnv { /* Pairing Parameters with Arguments */ /*************************************/ -class TooFewArguments extends EvaluatorError { - constructor() { - super('Too few arguments.'); - this.name = 'TooFewArguments'; - } -} - -class TooManyArguments extends EvaluatorError { - constructor() { - super('Too many arguments.'); - this.name = 'TooManyArguments'; - } -} - -class MalformedSpreadableSequenceOfObjects extends EvaluatorError { - constructor() { - super('Malformed spreadable sequence of objects.'); - this.name = 'MalformedSpreadableSequenceOfObjects'; - } -} - function pairPrimFunParameters(apply, args, arityMin, arityMax) { if (!apply) { return pairPrimFunParametersNoApply(args, arityMin, arityMax); @@ -1770,10 +1873,10 @@ function pairPrimFunParameters(apply, args, arityMin, arityMax) { function pairPrimFunParametersNoApply(args, arityMin, arityMax) { const nargs = args.length; if (nargs < arityMin) { - throw new TooFewArguments(); + return new TooFewArguments(); } if (arityMax !== null && nargs > arityMax) { - throw new TooManyArguments(); + return new TooManyArguments(); } return args; } @@ -1787,11 +1890,11 @@ function pairPrimFunParametersApply(args, arityMin, arityMax) { spreadArgs.push(args[i]); i++; } else { - throw new TooManyArguments(); + return new TooManyArguments(); } } if (nargs === 0 || !(args[nargs - 1] instanceof EVLList)) { - throw new MalformedSpreadableSequenceOfObjects(); + return new SpreadError(); } let argList = args[nargs - 1]; while (argList !== EVLEmptyList.NIL) { @@ -1800,15 +1903,15 @@ function pairPrimFunParametersApply(args, arityMin, arityMax) { spreadArgs.push(argList.car); i++; } else { - throw new TooManyArguments(); + return new TooManyArguments(); } argList = argList.cdr; } else { - throw new MalformedSpreadableSequenceOfObjects(); + return new SpreadError(); } } if (i < arityMin) { - throw new TooFewArguments(); + return new TooFewArguments(); } return spreadArgs; } @@ -1833,10 +1936,10 @@ function pairClosureParametersNoApplyNoRest(args, parameters) { const nargs = args.length; const nparameters = parameters.length; if (nargs < nparameters) { - throw new TooFewArguments(); + return new TooFewArguments(); } if (nargs > nparameters) { - throw new TooManyArguments(); + return new TooManyArguments(); } return args; } @@ -1864,7 +1967,7 @@ function pairClosureParametersNoApplyRest(args, parameters) { } } if (i < nparameters - 1) { - throw new TooFewArguments(); + return new TooFewArguments(); } values[nparameters - 1] = list; return values; @@ -1880,11 +1983,11 @@ function pairClosureParametersApplyNoRest(args, parameters) { values[i] = args[i]; i++; } else { - throw new TooManyArguments(); + return new TooManyArguments(); } } if (nargs === 0 || !(args[nargs - 1] instanceof EVLList)) { - throw new MalformedSpreadableSequenceOfObjects(); + return new SpreadError(); } let argList = args[nargs - 1]; while (argList !== EVLEmptyList.NIL) { @@ -1893,15 +1996,15 @@ function pairClosureParametersApplyNoRest(args, parameters) { values[i] = argList.car; i++; } else { - throw new TooManyArguments(); + return new TooManyArguments(); } argList = argList.cdr; } else { - throw new MalformedSpreadableSequenceOfObjects(); + return new SpreadError(); } } if (i < nparameters) { - throw new TooFewArguments(); + return new TooFewArguments(); } return values; } @@ -1929,7 +2032,7 @@ function pairClosureParametersApplyRest(args, parameters) { } } if (nargs === 0 || !(args[nargs - 1] instanceof EVLList)) { - throw new MalformedSpreadableSequenceOfObjects(); + return new SpreadError(); } let argList = args[nargs - 1]; while (argList !== EVLEmptyList.NIL) { @@ -1947,11 +2050,11 @@ function pairClosureParametersApplyRest(args, parameters) { } argList = argList.cdr; } else { - throw new MalformedSpreadableSequenceOfObjects(); + return new SpreadError(); } } if (i < nparameters - 1) { - throw new TooFewArguments(); + return new TooFewArguments(); } values[nparameters - 1] = list; return values; @@ -1971,99 +2074,112 @@ function listToArray(list) { /*********************/ function genericEval(form) { + let outcome = null; switch(selectedEvaluator) { - case 'plainrec': - return plainrecEval(form); + case 'directstyle': + outcome = directstyleEval(form); + break; case 'cps': - return cpsEval(form); + outcome = cpsEval(form); + break; case 'oocps': - return oocpsEval(form); + outcome = oocpsEval(form); + break; case 'sboocps': - return sboocpsEval(form); + outcome = sboocpsEval(form); + break; case 'trampoline': - return trampolineEval(form); + outcome = trampolineEval(form); + break; case 'trampolinepp': - return trampolineppEval(form); + outcome = trampolineppEval(form); + break; default: throw new CannotHappen('genericEval'); } + if (isNonlocalExit(outcome)) { + outcome = new RunawayNonlocalExit(); + } + if (isNormalCompletion(outcome)) { + return outcome; + } else if (isError(outcome)) { + throw new EvaluatorError(outcome.category.jsValue + ': ' + outcome.description.jsValue); + } else { + throw new CannotHappen('genericEval'); + } } -function emptyListError() { - throw new EvaluatorError('The empty list does not evaluate.'); -} - -function ifTestFormError() { - throw new EvaluatorError('The test-form does not evaluate to a boolean.'); -} - -function forEachNotImplemented() { - throw new EvaluatorError('The _for-each-form is not implemented.'); -} - -function forEachFunctionFormError() { - throw new EvaluatorError('The function-form does not evaluate to a function.'); -} - -function forEachListFormError() { - throw new EvaluatorError('The list-form does not evaluate to a proper list.'); -} - -function callOperatorFormError() { - throw new EvaluatorError('The operator-form does not evaluate to a function.'); +function alterForm(form, newForm) { + if (newForm instanceof EVLCons) { + form.car = newForm.car; + form.cdr = newForm.cdr; + } else { + form.car = prognVariable; + form.cdr = new EVLCons(newForm, EVLEmptyList.NIL); + } } -/*****************************/ -/* Plain Recursive Evaluator */ -/*****************************/ +/**************************/ +/* Direct Style Evaluator */ +/**************************/ -function plainrecEval(form) { - return plainrecEvalForm(form, nullDefiniteEnv, nullDefiniteEnv); +function directstyleEval(form) { + return directstyleEvalForm(form, nullDefiniteEnv, nullDefiniteEnv); } -function plainrecEvalForm(form, lenv, denv) { +function directstyleEvalForm(form, lenv, denv) { if (form instanceof EVLEmptyList) { - emptyListError(); + return new EmptyListError(); } else if (form instanceof EVLCons) { switch (form.car) { case quoteVariable: - return plainrecEvalQuote(form, lenv, denv); + return directstyleEvalQuote(form, lenv, denv); case prognVariable: - return plainrecEvalProgn(form, lenv, denv); + return directstyleEvalProgn(form, lenv, denv); case ifVariable: - return plainrecEvalIf(form, lenv, denv); + return directstyleEvalIf(form, lenv, denv); + case _forEachVariable: + return directstyleEvalForEach(form, lenv, denv); case _vlambdaVariable: - return plainrecEvalLambda(LEX_SCOPE, VAL_NS, false, form, lenv, denv); + return directstyleEvalLambda(LEX_SCOPE, VAL_NS, false, form, lenv, denv); case _mlambdaVariable: - return plainrecEvalLambda(LEX_SCOPE, VAL_NS, true, form, lenv, denv); + return directstyleEvalLambda(LEX_SCOPE, VAL_NS, true, form, lenv, denv); case _flambdaVariable: - return plainrecEvalLambda(LEX_SCOPE, FUN_NS, false, form, lenv, denv); + return directstyleEvalLambda(LEX_SCOPE, FUN_NS, false, form, lenv, denv); case _dlambdaVariable: - return plainrecEvalLambda(DYN_SCOPE, VAL_NS, false, form, lenv, denv); + return directstyleEvalLambda(DYN_SCOPE, VAL_NS, false, form, lenv, denv); case vrefVariable: - return plainrecEvalRef(LEX_SCOPE, VAL_NS, form, lenv, denv); + return directstyleEvalRef(LEX_SCOPE, VAL_NS, form, lenv, denv); case vsetVariable: - return plainrecEvalSet(LEX_SCOPE, VAL_NS, form, lenv, denv); + return directstyleEvalSet(LEX_SCOPE, VAL_NS, form, lenv, denv); case frefVariable: - return plainrecEvalRef(LEX_SCOPE, FUN_NS, form, lenv, denv); + return directstyleEvalRef(LEX_SCOPE, FUN_NS, form, lenv, denv); case fsetVariable: - return plainrecEvalSet(LEX_SCOPE, FUN_NS, form, lenv, denv); + return directstyleEvalSet(LEX_SCOPE, FUN_NS, form, lenv, denv); case drefVariable: - return plainrecEvalRef(DYN_SCOPE, VAL_NS, form, lenv, denv); + return directstyleEvalRef(DYN_SCOPE, VAL_NS, form, lenv, denv); case dsetVariable: - return plainrecEvalSet(DYN_SCOPE, VAL_NS, form, lenv, denv); - case _forEachVariable: - forEachNotImplemented(); - case _catchErrorsVariable: - return plainrecEvalCatchErrors(form, lenv, denv); + return directstyleEvalSet(DYN_SCOPE, VAL_NS, form, lenv, denv); + case blockVariable: + return directstyleEvalBlock(form, lenv, denv); + case returnFromVariable: + return directstyleEvalReturnFrom(form, lenv, denv); + case catchVariable: + return directstyleEvalCatch(form, lenv, denv); + case throwVariable: + return directstyleEvalThrow(form, lenv, denv); + case _handlerBindVariable: + return directstyleEvalHandlerBind(form, lenv, denv); + case unwindProtectVariable: + return directstyleEvalUnwindProtect(form, lenv, denv); case applyVariable: - return plainrecEvalCall(false, true, form, lenv, denv); + return directstyleEvalCall(false, true, form, lenv, denv); case multipleValueCallVariable: - return plainrecEvalCall(true, false, form, lenv, denv); + return directstyleEvalCall(true, false, form, lenv, denv); case multipleValueApplyVariable: - return plainrecEvalCall(true, true, form, lenv, denv); + return directstyleEvalCall(true, true, form, lenv, denv); default: - return plainrecEvalCall(false, false, form, lenv, denv); + return directstyleEvalCall(false, false, form, lenv, denv); } } else if (form instanceof EVLVariable) { return lenv.ref(VAL_NS, form); @@ -2072,138 +2188,306 @@ function plainrecEvalForm(form, lenv, denv) { } } -function plainrecEvalQuote(form, lenv, denv) { - const [literal] = analyzeQuote(form); +function directstyleEvalQuote(form, lenv, denv) { + const analysis = analyzeQuote(form); + if (isError(analysis)) return analysis; + const [literal] = analysis; return literal; } -function plainrecEvalProgn(form, lenv, denv) { - const [forms] = analyzeProgn(form); - return plainrecEvalForms(forms, lenv, denv); +function directstyleEvalProgn(form, lenv, denv) { + const analysis = analyzeProgn(form); + if (isError(analysis)) return analysis; + const [serialForms] = analysis; + return directstyleEvalSerialForms(serialForms, lenv, denv); } -function plainrecEvalForms(forms, lenv, denv) { - if (forms === EVLEmptyList.NIL) { +function directstyleEvalSerialForms(serialForms, lenv, denv) { + if (serialForms === EVLEmptyList.NIL) { return EVLVoid.VOID; - } else if (forms.cdr === EVLEmptyList.NIL) { - return plainrecEvalForm(forms.car, lenv, denv); } else { - plainrecEvalForm(forms.car, lenv, denv); - return plainrecEvalForms(forms.cdr, lenv, denv); + return directstyleEvalSerialFormForms(serialForms, lenv, denv); + } +} + +function directstyleEvalSerialFormForms(serialForms, lenv, denv) { + if (serialForms.cdr === EVLEmptyList.NIL) { + return directstyleEvalForm(serialForms.car, lenv, denv); + } else { + const outcome = directstyleEvalForm(serialForms.car, lenv, denv); + if (isAbruptCompletion(outcome)) return outcome; + return directstyleEvalSerialFormForms(serialForms.cdr, lenv, denv); } } -function plainrecEvalIf(form, lenv, denv) { - const [testForm, thenForm, elseForm] = analyzeIf(form); - const test = plainrecEvalForm(testForm, lenv, denv).primaryValue(); +function directstyleEvalIf(form, lenv, denv) { + const analysis = analyzeIf(form); + if (isError(analysis)) return analysis; + const [testForm, thenForm, elseForm] = analysis; + const outcome = directstyleEvalForm(testForm, lenv, denv); + if (isAbruptCompletion(outcome)) return outcome; + const test = outcome.primaryValue(); switch (test) { case EVLBoolean.TRUE: - return plainrecEvalForm(thenForm, lenv, denv); + return directstyleEvalForm(thenForm, lenv, denv); case EVLBoolean.FALSE: - return plainrecEvalForm(elseForm, lenv, denv); + return directstyleEvalForm(elseForm, lenv, denv); default: - ifTestFormError(); + return new TestFormTypeError(); + } +} + +function directstyleEvalForEach(form, lenv, denv) { + const analysis = analyzeForEach(form); + if (isError(analysis)) return analysis; + const [functionForm, listForm] = analysis; + const outcome = directstyleEvalForm(functionForm, lenv, denv); + if (isAbruptCompletion(outcome)) return outcome; + const fn = outcome.primaryValue(); + if (!(fn instanceof EVLFunction)) { + return new FunctionFormTypeError(); + } + const outcome2 = directstyleEvalForm(listForm, lenv, denv); + if (isAbruptCompletion(outcome2)) return outcome2; + const list = outcome2.primaryValue(); + if (!isProperList(list)) { + return new ListFormTypeError(); + } + return directstyleForEach(fn, list, denv); +} + +function directstyleForEach(fn, list, denv) { + while (list !== EVLEmptyList.NIL) { + if (list instanceof EVLCons) { + const outcome = directstyleInvoke(false, fn, [list.car], denv); + if (isAbruptCompletion(outcome)) return outcome; + list = list.cdr; + } else { + throw new CannotHappen('directstyleForEach'); // list is a proper list + } } + return EVLVoid.VOID; } -function plainrecEvalLambda(scope, namespace, macro, form, lenv, denv) { - const [parameters, rest, forms] = analyzeLambda(form); - return new EVLClosure(scope, namespace, macro, parameters, rest, forms, lenv); +function directstyleEvalLambda(scope, namespace, macro, form, lenv, denv) { + const analysis = analyzeLambda(form); + if (isError(analysis)) return analysis; + const [parameters, rest, serialForms] = analysis; + return new EVLClosure(scope, namespace, macro, parameters, rest, serialForms, lenv); } -function plainrecEvalRef(scope, namespace, form, lenv, denv) { - const [variable] = analyzeRef(form); +function directstyleEvalRef(scope, namespace, form, lenv, denv) { + const analysis = analyzeRef(form); + if (isError(analysis)) return analysis; + const [variable] = analysis; switch (scope) { case LEX_SCOPE: return lenv.ref(namespace, variable); case DYN_SCOPE: return denv.ref(namespace, variable); default: - throw new CannotHappen('plainrecEvalRef'); + throw new CannotHappen('directstyleEvalRef'); } } -function plainrecEvalSet(scope, namespace, form, lenv, denv) { - const [variable, valueForm] = analyzeSet(form); - const value = plainrecEvalForm(valueForm, lenv, denv).primaryValue(); +function directstyleEvalSet(scope, namespace, form, lenv, denv) { + const analysis = analyzeSet(form); + if (isError(analysis)) return analysis; + const [variable, valueForm] = analysis; + const outcome = directstyleEvalForm(valueForm, lenv, denv); + if (isAbruptCompletion(outcome)) return outcome; + const value = outcome.primaryValue(); switch (scope) { case LEX_SCOPE: return lenv.set(namespace, variable, value); case DYN_SCOPE: return denv.set(namespace, variable, value); default: - throw new CannotHappen('plainrecEvalSet'); + throw new CannotHappen('directstyleEvalSet'); } } -function plainrecEvalCatchErrors(form, lenv, denv) { - const [tryForm] = analyzeCatchErrors(form); - try { - plainrecEvalForm(tryForm, lenv, denv); - } catch (exception) { - return new EVLString(exception.name); +function directstyleEvalBlock(form, lenv, denv) { + const analysis = analyzeBlock(form); + if (isError(analysis)) return analysis; + const [blockName, serialForms] = analysis; + const exitTag = new EVLVariable('exit-tag'); + const elenv = new Frame(BLK_NS, [blockName], [exitTag], lenv); + const edenv = new Frame(XIT_NS, [exitTag], [EVLVoid.VOID], denv); + const outcome = directstyleEvalSerialForms(serialForms, elenv, edenv); + if (isNonlocalExit(outcome) && outcome.exitTag === exitTag) { + return outcome.values; + } else { + return outcome; } - return EVLVoid.VOID; } -function plainrecEvalCall(mv, apply, form, lenv, denv) { - const [operator, operands] = analyzeCall(mv, apply, form); - const fn = plainrecEvalOperator(operator, lenv, denv).primaryValue(); - const macro = operator instanceof EVLVariable && fn instanceof EVLClosure && fn.macro; - const args = plainrecEvalOperands(mv, macro, operands, [], lenv, denv); - return plainrecInvokeFun(apply, macro, fn, args, lenv, denv); +function directstyleEvalReturnFrom(form, lenv, denv) { + const analysis = analyzeReturnFrom(form); + if (isError(analysis)) return analysis; + const [blockName, valuesForm] = analysis; + const exitTag = lenv.ref(BLK_NS, blockName); + if (exitTag === null) { + return new NoBlock(blockName); + } + const exitPoint = denv.ref(XIT_NS, exitTag); + if (exitPoint === null) { + return new NoBlockExitPoint(blockName); + } + const outcome = directstyleEvalForm(valuesForm, lenv, denv); + if (isAbruptCompletion(outcome)) return outcome; + return new NonlocalExit(exitTag, outcome); +} + +function directstyleEvalCatch(form, lenv, denv) { + const analysis = analyzeCatch(form); + if (isError(analysis)) return analysis; + const [exitTagForm, serialForms] = analysis; + const outcome = directstyleEvalForm(exitTagForm, lenv, denv); + if (isAbruptCompletion(outcome)) return outcome; + const exitTag = outcome.primaryValue(); + if (!(exitTag instanceof EVLVariable)) { + return new ExitTagFormTypeError(); + } + const edenv = new Frame(XIT_NS, [exitTag], [EVLVoid.VOID], denv); + const outcome2 = directstyleEvalSerialForms(serialForms, lenv, edenv); + if (isNonlocalExit(outcome2) && outcome2.exitTag === exitTag) { + return outcome2.values; + } else { + return outcome2; + } +} + +function directstyleEvalThrow(form, lenv, denv) { + const analysis = analyzeThrow(form); + if (isError(analysis)) return analysis; + const [exitTagForm, valuesForm] = analysis; + const outcome = directstyleEvalForm(exitTagForm, lenv, denv); + if (isAbruptCompletion(outcome)) return outcome; + const exitTag = outcome.primaryValue(); + if (!(exitTag instanceof EVLVariable)) { + return new ExitTagFormTypeError(); + } + const exitPoint = denv.ref(XIT_NS, exitTag); + if (exitPoint === null) { + return new NoCatchExitPoint(exitTag); + } + const outcome2 = directstyleEvalForm(valuesForm, lenv, denv); + if (isAbruptCompletion(outcome2)) return outcome2; + return new NonlocalExit(exitTag, outcome2); +} + +function directstyleEvalHandlerBind(form, lenv, denv) { + const analysis = analyzeHandlerBind(form); + if (isError(analysis)) return analysis; + const [handlerForm, serialForms] = analysis; + const outcome = directstyleEvalForm(handlerForm, lenv, denv); + if (isAbruptCompletion(outcome)) return outcome; + const handler = outcome.primaryValue(); + if (!(handler instanceof EVLFunction)) { + return new HandlerFormTypeError(); + } + const outcome2 = directstyleEvalSerialForms(serialForms, lenv, denv); + if (isError(outcome2)) { + const outcome3 = directstyleInvoke(false, handler, [outcome2.category, outcome2.description], denv); + if (isAbruptCompletion(outcome3)) { + return outcome3; + } else { + return outcome2; + } + } else { + return outcome2; + } +} + +function directstyleEvalUnwindProtect(form, lenv, denv) { + const analysis = analyzeUnwindProtect(form); + if (isError(analysis)) return analysis; + const [protectedForm, cleanupForms] = analysis; + const outcome = directstyleEvalForm(protectedForm, lenv, denv); + const outcome2 = directstyleEvalSerialForms(cleanupForms, lenv, denv); + if (isAbruptCompletion(outcome2)) { + return outcome2; + } else { + return outcome; + } +} + +function directstyleEvalCall(mv, apply, form, lenv, denv) { + const analysis = analyzeCall(mv, apply, form, lenv); + if (isError(analysis)) return analysis; + const [macroCall, operator, operands] = analysis; + if (macroCall) { + return directstyleEvalMacroCall(form, operator, operands, lenv, denv); + } else { + return directstyleEvalFunctionCall(mv, apply, operator, operands, lenv, denv); + } +} + +function directstyleEvalMacroCall(form, macro, macroOperands, lenv, denv) { + const args = listToArray(macroOperands); + const outcome = directstyleInvoke(false, macro, args, denv); + if (isAbruptCompletion(outcome)) return outcome; + const expansion = outcome.primaryValue(); + if (optimizeMacroCalls) { + alterForm(form, expansion); + } + return directstyleEvalForm(expansion, lenv, denv); +} + +function directstyleEvalFunctionCall(mv, apply, operatorForm, operandForms, lenv, denv) { + const outcome = directstyleEvalOperatorForm(operatorForm, lenv, denv); + if (isAbruptCompletion(outcome)) return outcome; + const fn = outcome.primaryValue(); + if (!(fn instanceof EVLFunction)) { + return new OperatorFormTypeError(); + } + return directstyleEvalOperandForms(mv, apply, fn, operandForms, [], lenv, denv); } -function plainrecEvalOperator(operator, lenv, denv) { - if (operator instanceof EVLVariable) { - return lenv.ref(FUN_NS, operator); +function directstyleEvalOperatorForm(operatorForm, lenv, denv) { + if (operatorForm instanceof EVLVariable) { + return lenv.ref(FUN_NS, operatorForm); } else { - return plainrecEvalForm(operator, lenv, denv); + return directstyleEvalForm(operatorForm, lenv, denv); } } -function plainrecEvalOperands(mv, macro, operands, args, lenv, denv) { - if (operands === EVLEmptyList.NIL) { - return args; +function directstyleEvalOperandForms(mv, apply, fn, operandForms, args, lenv, denv) { + if (operandForms === EVLEmptyList.NIL) { + return directstyleInvoke(apply, fn, args, denv); } else { - if (macro) { - args.push(operands.car); - return plainrecEvalOperands(mv, macro, operands.cdr, args, lenv, denv); + const outcome = directstyleEvalForm(operandForms.car, lenv, denv); + if (isAbruptCompletion(outcome)) return outcome; + if (mv) { + outcome.allValues().forEach(value => args.push(value)); } else { - const result = plainrecEvalForm(operands.car, lenv, denv); - if (mv) { - result.allValues().forEach(value => args.push(value)); - } else { - args.push(result.primaryValue()); - } - return plainrecEvalOperands(mv, macro, operands.cdr, args, lenv, denv); + args.push(outcome.primaryValue()); } + return directstyleEvalOperandForms(mv, apply, fn, operandForms.cdr, args, lenv, denv); } } -function plainrecInvokeFun(apply, macro, fn, args, lenv, denv) { +function directstyleInvoke(apply, fn, args, denv) { if (fn instanceof EVLPrimitiveFunction) { const values = pairPrimFunParameters(apply, args, fn.arityMin, fn.arityMax); + if (isError(values)) return values; return fn.jsFunction(values); } else if (fn instanceof EVLClosure) { const values = pairClosureParameters(apply, args, fn.parameters, fn.rest); + if (isError(values)) return values; switch (fn.scope) { case LEX_SCOPE: const elenv = new Frame(fn.namespace, fn.parameters, values, fn.lenv); - if (macro) { - const expansion = plainrecEvalForms(fn.forms, elenv, denv).primaryValue(); - return plainrecEvalForm(expansion, lenv, denv); - } else { - return plainrecEvalForms(fn.forms, elenv, denv); - } + return directstyleEvalSerialForms(fn.serialForms, elenv, denv); case DYN_SCOPE: const edenv = new Frame(fn.namespace, fn.parameters, values, denv); - return plainrecEvalForms(fn.forms, fn.lenv, edenv); + return directstyleEvalSerialForms(fn.serialForms, fn.lenv, edenv); default: - throw new CannotHappen('plainrecInvokeFun'); + throw new CannotHappen('directstyleInvoke'); } } else { - callOperatorFormError(); + throw new CannotHappen('directstyleInvoke'); } } @@ -2217,7 +2501,7 @@ function cpsEval(form) { function cpsEvalForm(form, lenv, denv, k) { if (form instanceof EVLEmptyList) { - emptyListError(); + return k(new EmptyListError()); } else if (form instanceof EVLCons) { switch (form.car) { case quoteVariable: @@ -2226,6 +2510,8 @@ function cpsEvalForm(form, lenv, denv, k) { return cpsEvalProgn(form, lenv, denv, k); case ifVariable: return cpsEvalIf(form, lenv, denv, k); + case _forEachVariable: + return cpsEvalForEach(form, lenv, denv, k); case _vlambdaVariable: return cpsEvalLambda(LEX_SCOPE, VAL_NS, false, form, lenv, denv, k); case _mlambdaVariable: @@ -2246,10 +2532,18 @@ function cpsEvalForm(form, lenv, denv, k) { return cpsEvalRef(DYN_SCOPE, VAL_NS, form, lenv, denv, k); case dsetVariable: return cpsEvalSet(DYN_SCOPE, VAL_NS, form, lenv, denv, k); - case _forEachVariable: - return cpsEvalForEach(form, lenv, denv, k); - case _catchErrorsVariable: - return cpsEvalCatchErrors(form, lenv, denv, k); + case blockVariable: + return cpsEvalBlock(form, lenv, denv, k); + case returnFromVariable: + return cpsEvalReturnFrom(form, lenv, denv, k); + case catchVariable: + return cpsEvalCatch(form, lenv, denv, k); + case throwVariable: + return cpsEvalThrow(form, lenv, denv, k); + case _handlerBindVariable: + return cpsEvalHandlerBind(form, lenv, denv, k); + case unwindProtectVariable: + return cpsEvalUnwindProtect(form, lenv, denv, k); case applyVariable: return cpsEvalCall(false, true, form, lenv, denv, k); case multipleValueCallVariable: @@ -2266,58 +2560,116 @@ function cpsEvalForm(form, lenv, denv, k) { } } -const cpsEndCont = result => result; +const cpsEndCont = outcome => outcome; function cpsEvalQuote(form, lenv, denv, k) { - const [literal] = analyzeQuote(form); + const analysis = analyzeQuote(form); + if (isError(analysis)) return k(analysis); + const [literal] = analysis; return k(literal); } function cpsEvalProgn(form, lenv, denv, k) { - const [forms] = analyzeProgn(form); - return cpsEvalForms(forms, lenv, denv, k); + const analysis = analyzeProgn(form); + if (isError(analysis)) return k(analysis); + const [serialForms] = analysis; + return cpsEvalSerialForms(serialForms, lenv, denv, k); } -function cpsEvalForms(forms, lenv, denv, k) { - if (forms === EVLEmptyList.NIL) { +function cpsEvalSerialForms(serialForms, lenv, denv, k) { + if (serialForms === EVLEmptyList.NIL) { return k(EVLVoid.VOID); - } else if (forms.cdr === EVLEmptyList.NIL) { - return cpsEvalForm(forms.car, lenv, denv, k); + } else { + return cpsEvalSerialFormForms(serialForms, lenv, denv, k); + } +} + +function cpsEvalSerialFormForms(serialForms, lenv, denv, k) { + if (serialForms.cdr === EVLEmptyList.NIL) { + return cpsEvalForm(serialForms.car, lenv, denv, k); } else { return cpsEvalForm( - forms.car, lenv, denv, - result => { // ButLastFormCont - return cpsEvalForms(forms.cdr, lenv, denv, k); + serialForms.car, lenv, denv, + outcome => { // SerialFormCont + if (isAbruptCompletion(outcome)) return k(outcome); + return cpsEvalSerialFormForms(serialForms.cdr, lenv, denv, k); } ); } } function cpsEvalIf(form, lenv, denv, k) { - const [testForm, thenForm, elseForm] = analyzeIf(form); + const analysis = analyzeIf(form); + if (isError(analysis)) return k(analysis); + const [testForm, thenForm, elseForm] = analysis; return cpsEvalForm( testForm, lenv, denv, - result => { // IfTestFormCont - const test = result.primaryValue(); + outcome => { // IfTestFormCont + if (isAbruptCompletion(outcome)) return k(outcome); + const test = outcome.primaryValue(); switch (test) { case EVLBoolean.TRUE: return cpsEvalForm(thenForm, lenv, denv, k); case EVLBoolean.FALSE: return cpsEvalForm(elseForm, lenv, denv, k); default: - ifTestFormError(); + return k(new TestFormTypeError()); + } + } + ); +} + +function cpsEvalForEach(form, lenv, denv, k) { + const analysis = analyzeForEach(form); + if (isError(analysis)) return k(analysis); + const [functionForm, listForm] = analysis; + return cpsEvalForm( + functionForm, lenv, denv, + outcome => { // ForEachFunctionFormCont + if (isAbruptCompletion(outcome)) return k(outcome); + const fn = outcome.primaryValue(); + if (!(fn instanceof EVLFunction)) { + return k(new FunctionFormTypeError()); } + return cpsEvalForm( + listForm, lenv, denv, + outcome => { // ForEachListFormCont + if (isAbruptCompletion(outcome)) return k(outcome); + const list = outcome.primaryValue(); + if (!isProperList(list)) { + return k(new ListFormTypeError()); + } + return cpsForEach(fn, list, denv, k); + } + ); } ); } +function cpsForEach(fn, list, denv, k) { + while (list !== EVLEmptyList.NIL) { + if (list instanceof EVLCons) { + const outcome = cpsInvoke(false, fn, [list.car], denv, cpsEndCont); + if (isAbruptCompletion(outcome)) return k(outcome); + list = list.cdr; + } else { + throw new CannotHappen('cpsForEach'); // list is a proper list + } + } + return k(EVLVoid.VOID); +} + function cpsEvalLambda(scope, namespace, macro, form, lenv, denv, k) { - const [parameters, rest, forms] = analyzeLambda(form); - return k(new EVLClosure(scope, namespace, macro, parameters, rest, forms, lenv)); + const analysis = analyzeLambda(form); + if (isError(analysis)) return k(analysis); + const [parameters, rest, serialForms] = analysis; + return k(new EVLClosure(scope, namespace, macro, parameters, rest, serialForms, lenv)); } function cpsEvalRef(scope, namespace, form, lenv, denv, k) { - const [variable] = analyzeRef(form); + const analysis = analyzeRef(form); + if (isError(analysis)) return k(analysis); + const [variable] = analysis; switch (scope) { case LEX_SCOPE: return k(lenv.ref(namespace, variable)); @@ -2329,11 +2681,14 @@ function cpsEvalRef(scope, namespace, form, lenv, denv, k) { } function cpsEvalSet(scope, namespace, form, lenv, denv, k) { - const [variable, valueForm] = analyzeSet(form); + const analysis = analyzeSet(form); + if (isError(analysis)) return k(analysis); + const [variable, valueForm] = analysis; return cpsEvalForm( valueForm, lenv, denv, - result => { // SetValueFormCont - const value = result.primaryValue() + outcome => { // SetValueFormCont + if (isAbruptCompletion(outcome)) return k(outcome); + const value = outcome.primaryValue() switch (scope) { case LEX_SCOPE: return k(lenv.set(namespace, variable, value)); @@ -2346,115 +2701,239 @@ function cpsEvalSet(scope, namespace, form, lenv, denv, k) { ); } -function cpsEvalForEach(form, lenv, denv, k) { - const [functionForm, listForm] = analyzeForEach(form); +function cpsEvalBlock(form, lenv, denv, k) { + const analysis = analyzeBlock(form); + if (isError(analysis)) return k(analysis); + const [blockName, serialForms] = analysis; + const exitTag = new EVLVariable('exit-tag'); + const elenv = new Frame(BLK_NS, [blockName], [exitTag], lenv); + const edenv = new Frame(XIT_NS, [exitTag], [EVLVoid.VOID], denv); + return cpsEvalSerialForms( + serialForms, elenv, edenv, + outcome => { // BlockSerialFormsCont + if (isNonlocalExit(outcome) && outcome.exitTag === exitTag) { + return k(outcome.values); + } else { + return k(outcome); + } + } + ); +} + +function cpsEvalReturnFrom(form, lenv, denv, k) { + const analysis = analyzeReturnFrom(form); + if (isError(analysis)) return k(analysis); + const [blockName, valuesForm] = analysis; + const exitTag = lenv.ref(BLK_NS, blockName); + if (exitTag === null) { + return k(new NoBlock(blockName)); + } + const exitPoint = denv.ref(XIT_NS, exitTag); + if (exitPoint === null) { + return k(new NoBlockExitPoint(blockName)); + } return cpsEvalForm( - functionForm, lenv, denv, - result => { // ForEachFunctionFormCont - const fn = result.primaryValue(); - if (!(fn instanceof EVLFunction)) { - forEachFunctionFormError(); + valuesForm, lenv, denv, + outcome => { // ReturnFromValuesFormCont + if (isAbruptCompletion(outcome)) return k(outcome); + return k(new NonlocalExit(exitTag, outcome)); + } + ); +} + +function cpsEvalCatch(form, lenv, denv, k) { + const analysis = analyzeCatch(form); + if (isError(analysis)) return k(analysis); + const [exitTagForm, serialForms] = analysis; + return cpsEvalForm( + exitTagForm, lenv, denv, + outcome => { // CatchExitTagFormCont + if (isAbruptCompletion(outcome)) return k(outcome); + const exitTag = outcome.primaryValue(); + if (!(exitTag instanceof EVLVariable)) { + return k(new ExitTagFormTypeError()); } - return cpsEvalForm( - listForm, lenv, denv, - result => { // ForEachListFormCont - let list = result.primaryValue(); - while (list !== EVLEmptyList.NIL) { - if (list instanceof EVLCons) { - cpsInvokeFun(false, false, fn, [list.car], lenv, denv, cpsEndCont); - list = list.cdr; - } else { - forEachListFormError(); - } + const edenv = new Frame(XIT_NS, [exitTag], [EVLVoid.VOID], denv); + return cpsEvalSerialForms( + serialForms, lenv, edenv, + outcome2 => { // CatchSerialFormsCont + if (isNonlocalExit(outcome2) && outcome2.exitTag === exitTag) { + return k(outcome2.values); + } else { + return k(outcome2); } - return k(EVLVoid.VOID); } ); } ); } -function cpsEvalCatchErrors(form, lenv, denv, k) { - const [tryForm] = analyzeCatchErrors(form); - try { - cpsEvalForm(tryForm, lenv, denv, cpsEndCont); - } catch (exception) { - return k(new EVLString(exception.name)); - } - return k(EVLVoid.VOID); +function cpsEvalThrow(form, lenv, denv, k) { + const analysis = analyzeThrow(form); + if (isError(analysis)) return k(analysis); + const [exitTagForm, valuesForm] = analysis; + return cpsEvalForm( + exitTagForm, lenv, denv, + outcome => { // ThrowExitTagFormCont + if (isAbruptCompletion(outcome)) return k(outcome); + const exitTag = outcome.primaryValue(); + if (!(exitTag instanceof EVLVariable)) { + return k(new ExitTagFormTypeError()); + } + const exitPoint = denv.ref(XIT_NS, exitTag); + if (exitPoint === null) { + return k(new NoCatchExitPoint(exitTag)); + } + return cpsEvalForm( + valuesForm, lenv, denv, + outcome2 => { // ThrowValuesFormCont + if (isAbruptCompletion(outcome2)) return k(outcome2); + return k(new NonlocalExit(exitTag, outcome2)); + } + ); + } + ); } -function cpsEvalCall(mv, apply, form, lenv, denv, k) { - const [operator, operands] = analyzeCall(mv, apply, form); - return cpsEvalOperator( - operator, lenv, denv, - result => { // OperatorCont - const fn = result.primaryValue(); - const macro = operator instanceof EVLVariable && fn instanceof EVLClosure && fn.macro; - return cpsEvalOperands( - mv, macro, operands, [], lenv, denv, - args => { // OperandsCont - return cpsInvokeFun(apply, macro, fn, args, lenv, denv, k); +function cpsEvalHandlerBind(form, lenv, denv, k) { + const analysis = analyzeHandlerBind(form); + if (isError(analysis)) return k(analysis); + const [handlerForm, serialForms] = analysis; + return cpsEvalForm( + handlerForm, lenv, denv, + outcome => { // HandlerBindHandlerFormCont + if (isAbruptCompletion(outcome)) return k(outcome); + const handler = outcome.primaryValue(); + if (!(handler instanceof EVLFunction)) { + return k(new HandlerFormTypeError()); + } + return cpsEvalSerialForms( + serialForms, lenv, denv, + outcome2 => { // HandlerBindSerialFormsCont + if (isError(outcome2)) { + return cpsInvoke( + false, handler, [outcome2.category, outcome2.description], denv, + outcome3 => { // HandlerBindInvocationCont + if (isAbruptCompletion(outcome3)) { + return k(outcome3); + } else { + return k(outcome2); + } + } + ); + } else { + return k(outcome2); + } + } + ); + } + ); +} + +function cpsEvalUnwindProtect(form, lenv, denv, k) { + const analysis = analyzeUnwindProtect(form); + if (isError(analysis)) return k(analysis); + const [protectedForm, cleanupForms] = analysis; + return cpsEvalForm( + protectedForm, lenv, denv, + outcome => { // UnwindProtectProtectedFormCont + return cpsEvalSerialForms( + cleanupForms, lenv, denv, + outcome2 => { // UnwindProtectCleanupFormsCont + if (isAbruptCompletion(outcome2)) { + return k(outcome2); + } else { + return k(outcome); + } } ); } ); } -function cpsEvalOperator(operator, lenv, denv, k) { - if (operator instanceof EVLVariable) { - return k(lenv.ref(FUN_NS, operator)); +function cpsEvalCall(mv, apply, form, lenv, denv, k) { + const analysis = analyzeCall(mv, apply, form, lenv); + if (isError(analysis)) return k(analysis); + const [macroCall, operator, operands] = analysis; + if (macroCall) { + return cpsEvalMacroCall(form, operator, operands, lenv, denv, k); + } else { + return cpsEvalFunctionCall(mv, apply, operator, operands, lenv, denv, k); + } +} + +function cpsEvalMacroCall(form, macro, macroOperands, lenv, denv, k) { + const args = listToArray(macroOperands); + const outcome = cpsInvoke(false, macro, args, denv, cpsEndCont); + if (isAbruptCompletion(outcome)) return k(outcome); + const expansion = outcome.primaryValue(); + if (optimizeMacroCalls) { + alterForm(form, expansion); + } + return cpsEvalForm(expansion, lenv, denv, k); +} + +function cpsEvalFunctionCall(mv, apply, operatorForm, operandForms, lenv, denv, k) { + return cpsEvalOperatorForm( + operatorForm, lenv, denv, + outcome => { // FunctionCallOperatorFormCont + if (isAbruptCompletion(outcome)) return k(outcome); + const fn = outcome.primaryValue(); + if (!(fn instanceof EVLFunction)) { + return k(new OperatorFormTypeError()); + } + return cpsEvalOperandForms(mv, apply, fn, operandForms, [], lenv, denv, k); + } + ); +} + +function cpsEvalOperatorForm(operatorForm, lenv, denv, k) { + if (operatorForm instanceof EVLVariable) { + return k(lenv.ref(FUN_NS, operatorForm)); } else { - return cpsEvalForm(operator, lenv, denv, k); + return cpsEvalForm(operatorForm, lenv, denv, k); } } -function cpsEvalOperands(mv, macro, operands, args, lenv, denv, k) { - if (operands === EVLEmptyList.NIL) { - return k(args); +function cpsEvalOperandForms(mv, apply, fn, operandForms, args, lenv, denv, k) { + if (operandForms === EVLEmptyList.NIL) { + return cpsInvoke(apply, fn, args, denv, k); } else { - if (macro) { - args.push(operands.car); - return cpsEvalOperands(mv, macro, operands.cdr, args, lenv, denv, k); - } else { - return cpsEvalForm( - operands.car, lenv, denv, - result => { // OperandCont - if (mv) { - result.allValues().forEach(value => args.push(value)); - } else { - args.push(result.primaryValue()); - } - return cpsEvalOperands(mv, macro, operands.cdr, args, lenv, denv, k); + return cpsEvalForm( + operandForms.car, lenv, denv, + outcome => { // FunctionCallOperandFormCont + if (isAbruptCompletion(outcome)) return k(outcome); + if (mv) { + outcome.allValues().forEach(value => args.push(value)); + } else { + args.push(outcome.primaryValue()); } - ); - } + return cpsEvalOperandForms(mv, apply, fn, operandForms.cdr, args, lenv, denv, k); + } + ); } } -function cpsInvokeFun(apply, macro, fn, args, lenv, denv, k) { +function cpsInvoke(apply, fn, args, denv, k) { if (fn instanceof EVLPrimitiveFunction) { const values = pairPrimFunParameters(apply, args, fn.arityMin, fn.arityMax); + if (isError(values)) return k(values); return k(fn.jsFunction(values)); } else if (fn instanceof EVLClosure) { const values = pairClosureParameters(apply, args, fn.parameters, fn.rest); + if (isError(values)) return k(values); switch (fn.scope) { case LEX_SCOPE: const elenv = new Frame(fn.namespace, fn.parameters, values, fn.lenv); - if (macro) { - const expansion = cpsEvalForms(fn.forms, elenv, denv, cpsEndCont).primaryValue(); - return cpsEvalForm(expansion, lenv, denv, k); - } else { - return cpsEvalForms(fn.forms, elenv, denv, k); - } + return cpsEvalSerialForms(fn.serialForms, elenv, denv, k); case DYN_SCOPE: const edenv = new Frame(fn.namespace, fn.parameters, values, denv); - return cpsEvalForms(fn.forms, fn.lenv, edenv, k); + return cpsEvalSerialForms(fn.serialForms, fn.lenv, edenv, k); default: - throw new CannotHappen('cpsInvokeFun'); + throw new CannotHappen('cpsInvoke'); } } else { - callOperatorFormError(); + throw new CannotHappen('cpsInvoke'); } } @@ -2468,7 +2947,7 @@ function oocpsEval(form) { function oocpsEvalForm(form, lenv, denv, k) { if (form instanceof EVLEmptyList) { - emptyListError(); + return k.invoke(new EmptyListError()); } else if (form instanceof EVLCons) { switch (form.car) { case quoteVariable: @@ -2477,6 +2956,8 @@ function oocpsEvalForm(form, lenv, denv, k) { return oocpsEvalProgn(form, lenv, denv, k); case ifVariable: return oocpsEvalIf(form, lenv, denv, k); + case _forEachVariable: + return oocpsEvalForEach(form, lenv, denv, k); case _vlambdaVariable: return oocpsEvalLambda(LEX_SCOPE, VAL_NS, false, form, lenv, denv, k); case _mlambdaVariable: @@ -2497,10 +2978,18 @@ function oocpsEvalForm(form, lenv, denv, k) { return oocpsEvalRef(DYN_SCOPE, VAL_NS, form, lenv, denv, k); case dsetVariable: return oocpsEvalSet(DYN_SCOPE, VAL_NS, form, lenv, denv, k); - case _forEachVariable: - return oocpsEvalForEach(form, lenv, denv, k); - case _catchErrorsVariable: - return oocpsEvalCatchErrors(form, lenv, denv, k); + case blockVariable: + return oocpsEvalBlock(form, lenv, denv, k); + case returnFromVariable: + return oocpsEvalReturnFrom(form, lenv, denv, k); + case catchVariable: + return oocpsEvalCatch(form, lenv, denv, k); + case throwVariable: + return oocpsEvalThrow(form, lenv, denv, k); + case _handlerBindVariable: + return oocpsEvalHandlerBind(form, lenv, denv, k); + case unwindProtectVariable: + return oocpsEvalUnwindProtect(form, lenv, denv, k); case applyVariable: return oocpsEvalCall(false, true, form, lenv, denv, k); case multipleValueCallVariable: @@ -2518,60 +3007,73 @@ function oocpsEvalForm(form, lenv, denv, k) { } class OOCPSCont { // abstract class - constructor(lenv, denv, k) { - this.lenv = lenv; - this.denv = denv; + constructor(k) { this.k = k; } } class OOCPSEndCont extends OOCPSCont { constructor() { - super(null, null, null); + super(null); } - invoke(result) { - return result; + invoke(outcome) { + return outcome; } } const oocpsEndCont = new OOCPSEndCont(); function oocpsEvalQuote(form, lenv, denv, k) { - const [literal] = analyzeQuote(form); + const analysis = analyzeQuote(form); + if (isError(analysis)) return k.invoke(analysis); + const [literal] = analysis; return k.invoke(literal); } function oocpsEvalProgn(form, lenv, denv, k) { - const [forms] = analyzeProgn(form); - return oocpsEvalForms(forms, lenv, denv, k); + const analysis = analyzeProgn(form); + if (isError(analysis)) return k.invoke(analysis); + const [serialForms] = analysis; + return oocpsEvalSerialForms(serialForms, lenv, denv, k); } -function oocpsEvalForms(forms, lenv, denv, k) { - if (forms === EVLEmptyList.NIL) { +function oocpsEvalSerialForms(serialForms, lenv, denv, k) { + if (serialForms === EVLEmptyList.NIL) { return k.invoke(EVLVoid.VOID); - } else if (forms.cdr === EVLEmptyList.NIL) { - return oocpsEvalForm(forms.car, lenv, denv, k); + } else { + return oocpsEvalSerialFormForms(serialForms, lenv, denv, k); + } +} + +function oocpsEvalSerialFormForms(serialForms, lenv, denv, k) { + if (serialForms.cdr === EVLEmptyList.NIL) { + return oocpsEvalForm(serialForms.car, lenv, denv, k); } else { return oocpsEvalForm( - forms.car, lenv, denv, - new OOCPSButLastFormCont(forms, lenv, denv, k) + serialForms.car, lenv, denv, + new OOCPSSerialFormCont(serialForms, lenv, denv, k) ); } } -class OOCPSButLastFormCont extends OOCPSCont { - constructor(forms, lenv, denv, k) { - super(lenv, denv, k); - this.forms = forms; +class OOCPSSerialFormCont extends OOCPSCont { + constructor(serialForms, lenv, denv, k) { + super(k); + this.serialForms = serialForms; + this.lenv = lenv; + this.denv = denv; } - invoke(result) { - const {forms, lenv, denv, k} = this; - return oocpsEvalForms(forms.cdr, lenv, denv, k); + invoke(outcome) { + const {serialForms, lenv, denv, k} = this; + if (isAbruptCompletion(outcome)) return k.invoke(outcome); + return oocpsEvalSerialFormForms(serialForms.cdr, lenv, denv, k); } } function oocpsEvalIf(form, lenv, denv, k) { - const [testForm, thenForm, elseForm] = analyzeIf(form); + const analysis = analyzeIf(form); + if (isError(analysis)) return k.invoke(analysis); + const [testForm, thenForm, elseForm] = analysis; return oocpsEvalForm( testForm, lenv, denv, new OOCPSIfTestFormCont(thenForm, elseForm, lenv, denv, k) @@ -2580,31 +3082,99 @@ function oocpsEvalIf(form, lenv, denv, k) { class OOCPSIfTestFormCont extends OOCPSCont { constructor(thenForm, elseForm, lenv, denv, k) { - super(lenv, denv, k); + super(k); this.thenForm = thenForm; this.elseForm = elseForm; + this.lenv = lenv; + this.denv = denv; } - invoke(result) { + invoke(outcome) { const {thenForm, elseForm, lenv, denv, k} = this; - const test = result.primaryValue(); + if (isAbruptCompletion(outcome)) return k.invoke(outcome); + const test = outcome.primaryValue(); switch (test) { case EVLBoolean.TRUE: return oocpsEvalForm(thenForm, lenv, denv, k); case EVLBoolean.FALSE: return oocpsEvalForm(elseForm, lenv, denv, k); default: - ifTestFormError(); + return k.invoke(new TestFormTypeError()); + } + } +} + +function oocpsEvalForEach(form, lenv, denv, k) { + const analysis = analyzeForEach(form); + if (isError(analysis)) return k.invoke(analysis); + const [functionForm, listForm] = analysis; + return oocpsEvalForm( + functionForm, lenv, denv, + new OOCPSForEachFunctionFormCont(listForm, lenv, denv, k) + ); +} + +class OOCPSForEachFunctionFormCont extends OOCPSCont { + constructor(listForm, lenv, denv, k) { + super(k); + this.listForm = listForm; + this.lenv = lenv; + this.denv = denv; + } + invoke(outcome) { + const {listForm, lenv, denv, k} = this; + if (isAbruptCompletion(outcome)) return k.invoke(outcome); + const fn = outcome.primaryValue(); + if (!(fn instanceof EVLFunction)) { + return k.invoke(new FunctionFormTypeError()); + } + return oocpsEvalForm( + listForm, lenv, denv, + new OOCPSForEachListFormCont(fn, denv, k) + ); + } +} + +class OOCPSForEachListFormCont extends OOCPSCont { + constructor(fn, denv, k) { + super(k); + this.fn = fn; + this.denv = denv; + } + invoke(outcome) { + const {fn, denv, k} = this; + if (isAbruptCompletion(outcome)) return k.invoke(outcome); + const list = outcome.primaryValue(); + if (!isProperList(list)) { + return k.invoke(new ListFormTypeError()); + } + return oocpsForEach(fn, list, denv, k); + } +} + +function oocpsForEach(fn, list, denv, k) { + while (list !== EVLEmptyList.NIL) { + if (list instanceof EVLCons) { + const outcome = oocpsInvoke(false, fn, [list.car], denv, oocpsEndCont); + if (isAbruptCompletion(outcome)) return k.invoke(outcome); + list = list.cdr; + } else { + throw new CannotHappen('oocpsForEach'); // list is a proper list } } + return k.invoke(EVLVoid.VOID); } function oocpsEvalLambda(scope, namespace, macro, form, lenv, denv, k) { - const [parameters, rest, forms] = analyzeLambda(form); - return k.invoke(new EVLClosure(scope, namespace, macro, parameters, rest, forms, lenv)); + const analysis = analyzeLambda(form); + if (isError(analysis)) return k.invoke(analysis); + const [parameters, rest, serialForms] = analysis; + return k.invoke(new EVLClosure(scope, namespace, macro, parameters, rest, serialForms, lenv)); } function oocpsEvalRef(scope, namespace, form, lenv, denv, k) { - const [variable] = analyzeRef(form); + const analysis = analyzeRef(form); + if (isError(analysis)) return k.invoke(analysis); + const [variable] = analysis; switch (scope) { case LEX_SCOPE: return k.invoke(lenv.ref(namespace, variable)); @@ -2616,7 +3186,9 @@ function oocpsEvalRef(scope, namespace, form, lenv, denv, k) { } function oocpsEvalSet(scope, namespace, form, lenv, denv, k) { - const [variable, valueForm] = analyzeSet(form); + const analysis = analyzeSet(form); + if (isError(analysis)) return k.invoke(analysis); + const [variable, valueForm] = analysis; return oocpsEvalForm( valueForm, lenv, denv, new OOCPSSetValueFormCont(scope, namespace, variable, lenv, denv, k) @@ -2625,14 +3197,17 @@ function oocpsEvalSet(scope, namespace, form, lenv, denv, k) { class OOCPSSetValueFormCont extends OOCPSCont { constructor(scope, namespace, variable, lenv, denv, k) { - super(lenv, denv, k); + super(k); this.scope = scope; this.namespace = namespace; this.variable = variable; + this.lenv = lenv; + this.denv = denv; } - invoke(result) { + invoke(outcome) { const {scope, namespace, variable, lenv, denv, k} = this; - const value = result.primaryValue() + if (isAbruptCompletion(outcome)) return k.invoke(outcome); + const value = outcome.primaryValue() switch (scope) { case LEX_SCOPE: return k.invoke(lenv.set(namespace, variable, value)); @@ -2644,168 +3219,375 @@ class OOCPSSetValueFormCont extends OOCPSCont { } } -function oocpsEvalForEach(form, lenv, denv, k) { - const [functionForm, listForm] = analyzeForEach(form); +function oocpsEvalBlock(form, lenv, denv, k) { + const analysis = analyzeBlock(form); + if (isError(analysis)) return k.invoke(analysis); + const [blockName, serialForms] = analysis; + const exitTag = new EVLVariable('exit-tag'); + const elenv = new Frame(BLK_NS, [blockName], [exitTag], lenv); + const edenv = new Frame(XIT_NS, [exitTag], [EVLVoid.VOID], denv); + return oocpsEvalSerialForms( + serialForms, elenv, edenv, + new OOCPSBlockSerialFormsCont(exitTag, k) + ); +} + +class OOCPSBlockSerialFormsCont extends OOCPSCont { + constructor(exitTag, k) { + super(k); + this.exitTag = exitTag; + } + invoke(outcome) { + const {exitTag, k} = this; + if (isNonlocalExit(outcome) && outcome.exitTag === exitTag) { + return k.invoke(outcome.values); + } else { + return k.invoke(outcome); + } + } +} + +function oocpsEvalReturnFrom(form, lenv, denv, k) { + const analysis = analyzeReturnFrom(form); + if (isError(analysis)) return k.invoke(analysis); + const [blockName, valuesForm] = analysis; + const exitTag = lenv.ref(BLK_NS, blockName); + if (exitTag === null) { + return k.invoke(new NoBlock(blockName)); + } + const exitPoint = denv.ref(XIT_NS, exitTag); + if (exitPoint === null) { + return k.invoke(new NoBlockExitPoint(blockName)); + } return oocpsEvalForm( - functionForm, lenv, denv, - new OOCPSForEachFunctionFormCont(listForm, lenv, denv, k) + valuesForm, lenv, denv, + new OOCPSReturnFromValuesFormCont(exitTag, k) ); } -class OOCPSForEachFunctionFormCont extends OOCPSCont { - constructor(listForm, lenv, denv, k) { - super(lenv, denv, k); - this.listForm = listForm; +class OOCPSReturnFromValuesFormCont extends OOCPSCont { + constructor(exitTag, k) { + super(k); + this.exitTag = exitTag; } - invoke(result) { - const {listForm, lenv, denv, k} = this; - const fn = result.primaryValue(); - if (!(fn instanceof EVLFunction)) { - forEachFunctionFormError(); + invoke(outcome) { + const {exitTag, k} = this; + if (isAbruptCompletion(outcome)) return k.invoke(outcome); + return k.invoke(new NonlocalExit(exitTag, outcome)); + } +} + +function oocpsEvalCatch(form, lenv, denv, k) { + const analysis = analyzeCatch(form); + if (isError(analysis)) return k.invoke(analysis); + const [exitTagForm, serialForms] = analysis; + return oocpsEvalForm( + exitTagForm, lenv, denv, + new OOCPSCatchExitTagFormCont(serialForms, lenv, denv, k) + ); +} + +class OOCPSCatchExitTagFormCont extends OOCPSCont { + constructor(serialForms, lenv, denv, k) { + super(k); + this.serialForms = serialForms; + this.lenv = lenv; + this.denv = denv; + } + invoke(outcome) { + const {serialForms, lenv, denv, k} = this; + if (isAbruptCompletion(outcome)) return k.invoke(outcome); + const exitTag = outcome.primaryValue(); + if (!(exitTag instanceof EVLVariable)) { + return k.invoke(new ExitTagFormTypeError()); + } + const edenv = new Frame(XIT_NS, [exitTag], [EVLVoid.VOID], denv); + return oocpsEvalSerialForms( + serialForms, lenv, edenv, + new OOCPSCatchSerialFormsCont(exitTag, k) + ); + } +} + +class OOCPSCatchSerialFormsCont extends OOCPSCont { + constructor(exitTag, k) { + super(k); + this.exitTag = exitTag; + } + invoke(outcome) { + const {exitTag, k} = this; + if (isNonlocalExit(outcome) && outcome.exitTag === exitTag) { + return k.invoke(outcome.values); + } else { + return k.invoke(outcome); + } + } +} + +function oocpsEvalThrow(form, lenv, denv, k) { + const analysis = analyzeThrow(form); + if (isError(analysis)) return k.invoke(analysis); + const [exitTagForm, valuesForm] = analysis; + return oocpsEvalForm( + exitTagForm, lenv, denv, + new OOCPSThrowExitTagFormCont(valuesForm, lenv, denv, k) + ); +} + +class OOCPSThrowExitTagFormCont extends OOCPSCont { + constructor(valuesForm, lenv, denv, k) { + super(k); + this.valuesForm = valuesForm; + this.lenv = lenv; + this.denv = denv; + } + invoke(outcome) { + const {valuesForm, lenv, denv, k} = this; + if (isAbruptCompletion(outcome)) return k.invoke(outcome); + const exitTag = outcome.primaryValue(); + if (!(exitTag instanceof EVLVariable)) { + return k.invoke(new ExitTagFormTypeError()); + } + const exitPoint = denv.ref(XIT_NS, exitTag); + if (exitPoint === null) { + return k.invoke(new NoCatchExitPoint(exitTag)); } return oocpsEvalForm( - listForm, lenv, denv, - new OOCPSForEachListFormCont(fn, lenv, denv, k) + valuesForm, lenv, denv, + new OOCPSThrowValuesFormCont(exitTag, k) ); } } -class OOCPSForEachListFormCont extends OOCPSCont { - constructor(fn, lenv, denv, k) { - super(lenv, denv, k); - this.fn = fn; +class OOCPSThrowValuesFormCont extends OOCPSCont { + constructor(exitTag, k) { + super(k); + this.exitTag = exitTag; } - invoke(result) { - const {fn, lenv, denv, k} = this; - let list = result.primaryValue(); - while (list !== EVLEmptyList.NIL) { - if (list instanceof EVLCons) { - oocpsInvokeFun(false, false, fn, [list.car], lenv, denv, oocpsEndCont); - list = list.cdr; - } else { - forEachListFormError(); - } + invoke(outcome) { + const {exitTag, k} = this; + if (isAbruptCompletion(outcome)) return k.invoke(outcome); + return k.invoke(new NonlocalExit(exitTag, outcome)); + } +} + +function oocpsEvalHandlerBind(form, lenv, denv, k) { + const analysis = analyzeHandlerBind(form); + if (isError(analysis)) return k.invoke(analysis); + const [handlerForm, serialForms] = analysis; + return oocpsEvalForm( + handlerForm, lenv, denv, + new OOCPSHandlerBindHandlerFormCont(serialForms, lenv, denv, k) + ); +} + +class OOCPSHandlerBindHandlerFormCont extends OOCPSCont { + constructor(serialForms, lenv, denv, k) { + super(k); + this.serialForms = serialForms; + this.lenv = lenv; + this.denv = denv; + } + invoke(outcome) { + const {serialForms, lenv, denv, k} = this; + if (isAbruptCompletion(outcome)) return k.invoke(outcome); + const handler = outcome.primaryValue(); + if (!(handler instanceof EVLFunction)) { + return k.invoke(new HandlerFormTypeError()); } - return k.invoke(EVLVoid.VOID); + return oocpsEvalSerialForms( + serialForms, lenv, denv, + new OOCPSHandlerBindSerialFormsCont(handler, denv, k) + ); } } -function oocpsEvalCatchErrors(form, lenv, denv, k) { - const [tryForm] = analyzeCatchErrors(form); - try { - oocpsEvalForm(tryForm, lenv, denv, oocpsEndCont); - } catch (exception) { - return k.invoke(new EVLString(exception.name)); +class OOCPSHandlerBindSerialFormsCont extends OOCPSCont { + constructor(handler, denv, k) { + super(k); + this.handler = handler; + this.denv = denv; + } + invoke(outcome) { + const {handler, denv, k} = this; + if (isError(outcome)) { + return oocpsInvoke( + false, handler, [outcome.category, outcome.description], denv, + new OOCPSHandlerBindInvocationCont(outcome, k) + ); + } else { + return k.invoke(outcome); + } } - return k.invoke(EVLVoid.VOID); } -function oocpsEvalCall(mv, apply, form, lenv, denv, k) { - const [operator, operands] = analyzeCall(mv, apply, form); - return oocpsEvalOperator( - operator, lenv, denv, - new OOCPSOperatorCont(mv, apply, operator, operands, lenv, denv, k) +class OOCPSHandlerBindInvocationCont extends OOCPSCont { + constructor(serialFormsOutcome, k) { + super(k); + this.serialFormsOutcome = serialFormsOutcome; + } + invoke(outcome) { + const {serialFormsOutcome, k} = this; + if (isAbruptCompletion(outcome)) { + return k.invoke(outcome); + } else { + return k.invoke(serialFormsOutcome); + } + } +} + +function oocpsEvalUnwindProtect(form, lenv, denv, k) { + const analysis = analyzeUnwindProtect(form); + if (isError(analysis)) return k.invoke(analysis); + const [protectedForm, cleanupForms] = analysis; + return oocpsEvalForm( + protectedForm, lenv, denv, + new OOCPSUnwindProtectProtectedFormCont(cleanupForms, lenv, denv, k) ); } -function oocpsEvalOperator(operator, lenv, denv, k) { - if (operator instanceof EVLVariable) { - return k.invoke(lenv.ref(FUN_NS, operator)); +class OOCPSUnwindProtectProtectedFormCont extends OOCPSCont { + constructor(cleanupForms, lenv, denv, k) { + super(k); + this.cleanupForms = cleanupForms; + this.lenv = lenv; + this.denv = denv; + } + invoke(outcome) { + const {cleanupForms, lenv, denv, k} = this; + return oocpsEvalSerialForms( + cleanupForms, lenv, denv, + new OOCPSUnwindProtectCleanupFormsCont(outcome, k) + ); + } +} + +class OOCPSUnwindProtectCleanupFormsCont extends OOCPSCont { + constructor(protectedFormOutcome, k) { + super(k); + this.protectedFormOutcome = protectedFormOutcome; + } + invoke(outcome) { + const {protectedFormOutcome, k} = this; + if (isAbruptCompletion(outcome)) { + return k.invoke(outcome); + } else { + return k.invoke(protectedFormOutcome); + } + } +} + +function oocpsEvalCall(mv, apply, form, lenv, denv, k) { + const analysis = analyzeCall(mv, apply, form, lenv); + if (isError(analysis)) return k.invoke(analysis); + const [macroCall, operator, operands] = analysis; + if (macroCall) { + return oocpsEvalMacroCall(form, operator, operands, lenv, denv, k); } else { - return oocpsEvalForm(operator, lenv, denv, k); + return oocpsEvalFunctionCall(mv, apply, operator, operands, lenv, denv, k); } } -class OOCPSOperatorCont extends OOCPSCont { - constructor(mv, apply, operator, operands, lenv, denv, k) { - super(lenv, denv, k); - this.mv = mv; - this.apply = apply; - this.operator = operator; - this.operands = operands; - } - invoke(result) { - const {mv, apply, operator, operands, lenv, denv, k} = this; - const fn = result.primaryValue(); - const macro = operator instanceof EVLVariable && fn instanceof EVLClosure && fn.macro; - return oocpsEvalOperands( - mv, macro, operands, [], lenv, denv, - new OOCPSOperandsCont(apply, macro, fn, lenv, denv, k) - ); +function oocpsEvalMacroCall(form, macro, macroOperands, lenv, denv, k) { + const args = listToArray(macroOperands); + const outcome = oocpsInvoke(false, macro, args, denv, oocpsEndCont); + if (isAbruptCompletion(outcome)) return k.invoke(outcome); + const expansion = outcome.primaryValue(); + if (optimizeMacroCalls) { + alterForm(form, expansion); } + return oocpsEvalForm(expansion, lenv, denv, k); +} + +function oocpsEvalFunctionCall(mv, apply, operatorForm, operandForms, lenv, denv, k) { + return oocpsEvalOperatorForm( + operatorForm, lenv, denv, + new OOCPSFunctionCallOperatorFormCont(mv, apply, operandForms, lenv, denv, k) + ); } -function oocpsEvalOperands(mv, macro, operands, args, lenv, denv, k) { - if (operands === EVLEmptyList.NIL) { - return k.invoke(args); +function oocpsEvalOperatorForm(operatorForm, lenv, denv, k) { + if (operatorForm instanceof EVLVariable) { + return k.invoke(lenv.ref(FUN_NS, operatorForm)); } else { - if (macro) { - args.push(operands.car); - return oocpsEvalOperands(mv, macro, operands.cdr, args, lenv, denv, k); - } else { - return oocpsEvalForm( - operands.car, lenv, denv, - new OOCPSOperandCont(mv, macro, operands, args, lenv, denv, k) - ); - } + return oocpsEvalForm(operatorForm, lenv, denv, k); } } -class OOCPSOperandCont extends OOCPSCont { - constructor(mv, macro, operands, args, lenv, denv, k) { - super(lenv, denv, k); +class OOCPSFunctionCallOperatorFormCont extends OOCPSCont { + constructor(mv, apply, operandForms, lenv, denv, k) { + super(k); this.mv = mv; - this.macro = macro; - this.operands = operands; - this.args = args; + this.apply = apply; + this.operandForms = operandForms; + this.lenv = lenv; + this.denv = denv; } - invoke(result) { - const {mv, macro, operands, args, lenv, denv, k} = this; - if (mv) { - result.allValues().forEach(value => args.push(value)); - } else { - args.push(result.primaryValue()); + invoke(outcome) { + const {mv, apply, operandForms, lenv, denv, k} = this; + if (isAbruptCompletion(outcome)) return k.invoke(outcome); + const fn = outcome.primaryValue(); + if (!(fn instanceof EVLFunction)) { + return k.invoke(new OperatorFormTypeError()); } - return oocpsEvalOperands(mv, macro, operands.cdr, args, lenv, denv, k); + return oocpsEvalOperandForms(mv, apply, fn, operandForms, [], lenv, denv, k); + } +} + +function oocpsEvalOperandForms(mv, apply, fn, operandForms, args, lenv, denv, k) { + if (operandForms === EVLEmptyList.NIL) { + return oocpsInvoke(apply, fn, args, denv, k); + } else { + return oocpsEvalForm( + operandForms.car, lenv, denv, + new OOCPSFunctionCallOperandFormCont(mv, apply, fn, operandForms, args, lenv, denv, k) + ); } } -class OOCPSOperandsCont extends OOCPSCont { - constructor(apply, macro, fn, lenv, denv, k) { - super(lenv, denv, k); +class OOCPSFunctionCallOperandFormCont extends OOCPSCont { + constructor(mv, apply, fn, operandForms, args, lenv, denv, k) { + super(k); + this.mv = mv; this.apply = apply; - this.macro = macro; this.fn = fn; + this.operandForms = operandForms; + this.args = args; + this.lenv = lenv; + this.denv = denv; } - invoke(args) { - const {apply, macro, fn, lenv, denv, k} = this; - return oocpsInvokeFun(apply, macro, fn, args, lenv, denv, k); + invoke(outcome) { + const {mv, apply, fn, operandForms, args, lenv, denv, k} = this; + if (isAbruptCompletion(outcome)) return k.invoke(outcome); + if (mv) { + outcome.allValues().forEach(value => args.push(value)); + } else { + args.push(outcome.primaryValue()); + } + return oocpsEvalOperandForms(mv, apply, fn, operandForms.cdr, args, lenv, denv, k); } } -function oocpsInvokeFun(apply, macro, fn, args, lenv, denv, k) { +function oocpsInvoke(apply, fn, args, denv, k) { if (fn instanceof EVLPrimitiveFunction) { const values = pairPrimFunParameters(apply, args, fn.arityMin, fn.arityMax); + if (isError(values)) return k.invoke(values); return k.invoke(fn.jsFunction(values)); } else if (fn instanceof EVLClosure) { const values = pairClosureParameters(apply, args, fn.parameters, fn.rest); + if (isError(values)) return k.invoke(values); switch (fn.scope) { case LEX_SCOPE: const elenv = new Frame(fn.namespace, fn.parameters, values, fn.lenv); - if (macro) { - const expansion = oocpsEvalForms(fn.forms, elenv, denv, oocpsEndCont).primaryValue(); - return oocpsEvalForm(expansion, lenv, denv, k); - } else { - return oocpsEvalForms(fn.forms, elenv, denv, k); - } + return oocpsEvalSerialForms(fn.serialForms, elenv, denv, k); case DYN_SCOPE: const edenv = new Frame(fn.namespace, fn.parameters, values, denv); - return oocpsEvalForms(fn.forms, fn.lenv, edenv, k); + return oocpsEvalSerialForms(fn.serialForms, fn.lenv, edenv, k); default: - throw new CannotHappen('oocpsInvokeFun'); + throw new CannotHappen('oocpsInvoke'); } } else { - callOperatorFormError(); + throw new CannotHappen('oocpsInvoke'); } } @@ -2813,389 +3595,639 @@ function oocpsInvokeFun(apply, macro, fn, args, lenv, denv, k) { /* Stack-Based Object-Oriented CPS Evaluator */ /*********************************************/ -function sboocpsEval(form) { - const kStack = new SBOOCPSControlStack(); - kStack.push(sboocpsEndCont); - return sboocpsEvalForm(form, nullDefiniteEnv, kStack); -} +let sboocpsStack = null; -class SBOOCPSControlStack { +class SBOOCPSStack { constructor() { - this.stack = []; // element: SBOOCPSCont or Frame + this.stack = [sboocpsEndCont]; // array of SBOOCPSCont's and/or Frame's + this.denv = nullDefiniteEnv; } push(element) { - this.stack.push(element); + if (element instanceof SBOOCPSCont) { + this.stack.push(element); + } else if (element instanceof Frame) { + this.stack.push(element); + element.next = this.denv; + this.denv = element; + } else { + throw new CannotHappen('SBOOCPSStack.push'); + } } - invokeCont(result) { + invoke(outcome) { while (true) { const element = this.stack.pop(); if (element instanceof SBOOCPSCont) { - return element.invoke(result); - } - } - } - size() { - return this.stack.length; - } - trim(size) { - while (this.stack.length !== size) { - this.stack.pop(); - } - } - ref(namespace, variable) { - for (let i = this.stack.length - 1; i >= 0; i--) { - const element = this.stack[i]; - if (element instanceof Frame) { - const result = element.ref(namespace, variable); - if (result !== undefined) { - return result; - } - } - } - return GlobalEnv.ref(namespace, variable); - } - set(namespace, variable, value) { - for (let i = this.stack.length - 1; i >= 0; i--) { - const element = this.stack[i]; - if (element instanceof Frame) { - const result = element.set(namespace, variable, value); - if (result !== undefined) { - return result; - } + return element.invoke(outcome); + } else if (element instanceof Frame) { + this.denv = element.next; + } else { + throw new CannotHappen('SBOOCPSStack.invoke'); } } - return GlobalEnv.set(namespace, variable, value); } } -function sboocpsEvalForm(form, lenv, kStack) { +function sboocpsEval(form) { + sboocpsStack = new SBOOCPSStack(); + return sboocpsEvalForm(form, nullDefiniteEnv); +} + +function sboocpsEvalForm(form, lenv) { if (form instanceof EVLEmptyList) { - emptyListError(); + return sboocpsStack.invoke(new EmptyListError()); } else if (form instanceof EVLCons) { switch (form.car) { case quoteVariable: - return sboocpsEvalQuote(form, lenv, kStack); + return sboocpsEvalQuote(form, lenv); case prognVariable: - return sboocpsEvalProgn(form, lenv, kStack); + return sboocpsEvalProgn(form, lenv); case ifVariable: - return sboocpsEvalIf(form, lenv, kStack); + return sboocpsEvalIf(form, lenv); + case _forEachVariable: + return sboocpsEvalForEach(form, lenv); case _vlambdaVariable: - return sboocpsEvalLambda(LEX_SCOPE, VAL_NS, false, form, lenv, kStack); + return sboocpsEvalLambda(LEX_SCOPE, VAL_NS, false, form, lenv); case _mlambdaVariable: - return sboocpsEvalLambda(LEX_SCOPE, VAL_NS, true, form, lenv, kStack); + return sboocpsEvalLambda(LEX_SCOPE, VAL_NS, true, form, lenv); case _flambdaVariable: - return sboocpsEvalLambda(LEX_SCOPE, FUN_NS, false, form, lenv, kStack); + return sboocpsEvalLambda(LEX_SCOPE, FUN_NS, false, form, lenv); case _dlambdaVariable: - return sboocpsEvalLambda(DYN_SCOPE, VAL_NS, false, form, lenv, kStack); + return sboocpsEvalLambda(DYN_SCOPE, VAL_NS, false, form, lenv); case vrefVariable: - return sboocpsEvalRef(LEX_SCOPE, VAL_NS, form, lenv, kStack); + return sboocpsEvalRef(LEX_SCOPE, VAL_NS, form, lenv); case vsetVariable: - return sboocpsEvalSet(LEX_SCOPE, VAL_NS, form, lenv, kStack); + return sboocpsEvalSet(LEX_SCOPE, VAL_NS, form, lenv); case frefVariable: - return sboocpsEvalRef(LEX_SCOPE, FUN_NS, form, lenv, kStack); + return sboocpsEvalRef(LEX_SCOPE, FUN_NS, form, lenv); case fsetVariable: - return sboocpsEvalSet(LEX_SCOPE, FUN_NS, form, lenv, kStack); + return sboocpsEvalSet(LEX_SCOPE, FUN_NS, form, lenv); case drefVariable: - return sboocpsEvalRef(DYN_SCOPE, VAL_NS, form, lenv, kStack); + return sboocpsEvalRef(DYN_SCOPE, VAL_NS, form, lenv); case dsetVariable: - return sboocpsEvalSet(DYN_SCOPE, VAL_NS, form, lenv, kStack); - case _forEachVariable: - return sboocpsEvalForEach(form, lenv, kStack); - case _catchErrorsVariable: - return sboocpsEvalCatchErrors(form, lenv, kStack); + return sboocpsEvalSet(DYN_SCOPE, VAL_NS, form, lenv); + case blockVariable: + return sboocpsEvalBlock(form, lenv); + case returnFromVariable: + return sboocpsEvalReturnFrom(form, lenv); + case catchVariable: + return sboocpsEvalCatch(form, lenv); + case throwVariable: + return sboocpsEvalThrow(form, lenv); + case _handlerBindVariable: + return sboocpsEvalHandlerBind(form, lenv); + case unwindProtectVariable: + return sboocpsEvalUnwindProtect(form, lenv); case applyVariable: - return sboocpsEvalCall(false, true, form, lenv, kStack); + return sboocpsEvalCall(false, true, form, lenv); case multipleValueCallVariable: - return sboocpsEvalCall(true, false, form, lenv, kStack); + return sboocpsEvalCall(true, false, form, lenv); case multipleValueApplyVariable: - return sboocpsEvalCall(true, true, form, lenv, kStack); + return sboocpsEvalCall(true, true, form, lenv); default: - return sboocpsEvalCall(false, false, form, lenv, kStack); + return sboocpsEvalCall(false, false, form, lenv); } } else if (form instanceof EVLVariable) { - return kStack.invokeCont(lenv.ref(VAL_NS, form)); + return sboocpsStack.invoke(lenv.ref(VAL_NS, form)); } else { - return kStack.invokeCont(form); + return sboocpsStack.invoke(form); } } class SBOOCPSCont { // abstract class - constructor(lenv, kStack) { - this.lenv = lenv; - this.kStack = kStack; + constructor() { } } class SBOOCPSEndCont extends SBOOCPSCont { constructor() { - super(null, null); + super(); } - invoke(result) { - return result; + invoke(outcome) { + return outcome; } } const sboocpsEndCont = new SBOOCPSEndCont(); -function sboocpsEvalQuote(form, lenv, kStack) { - const [literal] = analyzeQuote(form); - return kStack.invokeCont(literal); +function sboocpsEvalQuote(form, lenv) { + const analysis = analyzeQuote(form); + if (isError(analysis)) return sboocpsStack.invoke(analysis); + const [literal] = analysis; + return sboocpsStack.invoke(literal); } -function sboocpsEvalProgn(form, lenv, kStack) { - const [forms] = analyzeProgn(form); - return sboocpsEvalForms(forms, lenv, kStack); +function sboocpsEvalProgn(form, lenv) { + const analysis = analyzeProgn(form); + if (isError(analysis)) return sboocpsStack.invoke(analysis); + const [serialForms] = analysis; + return sboocpsEvalSerialForms(serialForms, lenv); } -function sboocpsEvalForms(forms, lenv, kStack) { - if (forms === EVLEmptyList.NIL) { - return kStack.invokeCont(EVLVoid.VOID); - } else if (forms.cdr === EVLEmptyList.NIL) { - return sboocpsEvalForm(forms.car, lenv, kStack); +function sboocpsEvalSerialForms(serialForms, lenv) { + if (serialForms === EVLEmptyList.NIL) { + return sboocpsStack.invoke(EVLVoid.VOID); } else { - kStack.push(new SBOOCPSButLastFormCont(forms, lenv, kStack)); - return sboocpsEvalForm(forms.car, lenv, kStack); + return sboocpsEvalSerialFormForms(serialForms, lenv); } } -class SBOOCPSButLastFormCont extends SBOOCPSCont { - constructor(forms, lenv, kStack) { - super(lenv, kStack); - this.forms = forms; +function sboocpsEvalSerialFormForms(serialForms, lenv) { + if (serialForms.cdr === EVLEmptyList.NIL) { + return sboocpsEvalForm(serialForms.car, lenv); + } else { + sboocpsStack.push(new SBOOCPSSerialFormCont(serialForms, lenv)); + return sboocpsEvalForm(serialForms.car, lenv); + } +} + +class SBOOCPSSerialFormCont extends SBOOCPSCont { + constructor(serialForms, lenv) { + super(); + this.serialForms = serialForms; + this.lenv = lenv; } - invoke(result) { - const {forms, lenv, kStack} = this; - return sboocpsEvalForms(forms.cdr, lenv, kStack); + invoke(outcome) { + const {serialForms, lenv} = this; + if (isAbruptCompletion(outcome)) return sboocpsStack.invoke(outcome); + return sboocpsEvalSerialFormForms(serialForms.cdr, lenv); } } -function sboocpsEvalIf(form, lenv, kStack) { - const [testForm, thenForm, elseForm] = analyzeIf(form); - kStack.push(new SBOOCPSIfTestFormCont(thenForm, elseForm, lenv, kStack)); - return sboocpsEvalForm(testForm, lenv, kStack); +function sboocpsEvalIf(form, lenv) { + const analysis = analyzeIf(form); + if (isError(analysis)) return sboocpsStack.invoke(analysis); + const [testForm, thenForm, elseForm] = analysis; + sboocpsStack.push(new SBOOCPSIfTestFormCont(thenForm, elseForm, lenv)); + return sboocpsEvalForm(testForm, lenv); } class SBOOCPSIfTestFormCont extends SBOOCPSCont { - constructor(thenForm, elseForm, lenv, kStack) { - super(lenv, kStack); + constructor(thenForm, elseForm, lenv) { + super(); this.thenForm = thenForm; this.elseForm = elseForm; + this.lenv = lenv; } - invoke(result) { - const {thenForm, elseForm, lenv, kStack} = this; - const test = result.primaryValue(); + invoke(outcome) { + const {thenForm, elseForm, lenv} = this; + if (isAbruptCompletion(outcome)) return sboocpsStack.invoke(outcome); + const test = outcome.primaryValue(); switch (test) { case EVLBoolean.TRUE: - return sboocpsEvalForm(thenForm, lenv, kStack); + return sboocpsEvalForm(thenForm, lenv); case EVLBoolean.FALSE: - return sboocpsEvalForm(elseForm, lenv, kStack); + return sboocpsEvalForm(elseForm, lenv); default: - ifTestFormError(); + return sboocpsStack.invoke(new TestFormTypeError()); } } } -function sboocpsEvalLambda(scope, namespace, macro, form, lenv, kStack) { - const [parameters, rest, forms] = analyzeLambda(form); - return kStack.invokeCont(new EVLClosure(scope, namespace, macro, parameters, rest, forms, lenv)); +function sboocpsEvalForEach(form, lenv) { + const analysis = analyzeForEach(form); + if (isError(analysis)) return sboocpsStack.invoke(analysis); + const [functionForm, listForm] = analysis; + sboocpsStack.push(new SBOOCPSForEachFunctionFormCont(listForm, lenv)); + return sboocpsEvalForm(functionForm, lenv); } -function sboocpsEvalRef(scope, namespace, form, lenv, kStack) { - const [variable] = analyzeRef(form); +class SBOOCPSForEachFunctionFormCont extends SBOOCPSCont { + constructor(listForm, lenv) { + super(); + this.listForm = listForm; + this.lenv = lenv; + } + invoke(outcome) { + const {listForm, lenv} = this; + if (isAbruptCompletion(outcome)) return sboocpsStack.invoke(outcome); + const fn = outcome.primaryValue(); + if (!(fn instanceof EVLFunction)) { + return sboocpsStack.invoke(new FunctionFormTypeError()); + } + sboocpsStack.push(new SBOOCPSForEachListFormCont(fn)); + return sboocpsEvalForm(listForm, lenv); + } +} + +class SBOOCPSForEachListFormCont extends SBOOCPSCont { + constructor(fn) { + super(); + this.fn = fn; + } + invoke(outcome) { + const {fn} = this; + if (isAbruptCompletion(outcome)) return sboocpsStack.invoke(outcome); + const list = outcome.primaryValue(); + if (!isProperList(list)) { + return sboocpsStack.invoke(new ListFormTypeError()); + } + return sboocpsForEach(fn, list); + } +} + +function sboocpsForEach(fn, list) { + while (list !== EVLEmptyList.NIL) { + if (list instanceof EVLCons) { + sboocpsStack.push(sboocpsEndCont); + const outcome = sboocpsInvoke(false, fn, [list.car]); + if (isAbruptCompletion(outcome)) return sboocpsStack.invoke(outcome); + list = list.cdr; + } else { + throw new CannotHappen('sboocpsForEach'); // list is a proper list + } + } + return sboocpsStack.invoke(EVLVoid.VOID); +} + +function sboocpsEvalLambda(scope, namespace, macro, form, lenv) { + const analysis = analyzeLambda(form); + if (isError(analysis)) return sboocpsStack.invoke(analysis); + const [parameters, rest, serialForms] = analysis; + return sboocpsStack.invoke(new EVLClosure(scope, namespace, macro, parameters, rest, serialForms, lenv)); +} + +function sboocpsEvalRef(scope, namespace, form, lenv) { + const analysis = analyzeRef(form); + if (isError(analysis)) return sboocpsStack.invoke(analysis); + const [variable] = analysis; switch (scope) { case LEX_SCOPE: - return kStack.invokeCont(lenv.ref(namespace, variable)); + return sboocpsStack.invoke(lenv.ref(namespace, variable)); case DYN_SCOPE: - return kStack.invokeCont(kStack.ref(namespace, variable)); + return sboocpsStack.invoke(sboocpsStack.denv.ref(namespace, variable)); default: throw new CannotHappen('sboocpsEvalRef'); } } -function sboocpsEvalSet(scope, namespace, form, lenv, kStack) { - const [variable, valueForm] = analyzeSet(form); - kStack.push(new SBOOCPSSetValueFormCont(scope, namespace, variable, lenv, kStack)); - return sboocpsEvalForm(valueForm, lenv, kStack); +function sboocpsEvalSet(scope, namespace, form, lenv) { + const analysis = analyzeSet(form); + if (isError(analysis)) return sboocpsStack.invoke(analysis); + const [variable, valueForm] = analysis; + sboocpsStack.push(new SBOOCPSSetValueFormCont(scope, namespace, variable, lenv)); + return sboocpsEvalForm(valueForm, lenv); } class SBOOCPSSetValueFormCont extends SBOOCPSCont { - constructor(scope, namespace, variable, lenv, kStack) { - super(lenv, kStack); + constructor(scope, namespace, variable, lenv) { + super(); this.scope = scope; this.namespace = namespace; this.variable = variable; + this.lenv = lenv; } - invoke(result) { - const {scope, namespace, variable, lenv, kStack} = this; - const value = result.primaryValue() + invoke(outcome) { + const {scope, namespace, variable, lenv} = this; + if (isAbruptCompletion(outcome)) return sboocpsStack.invoke(outcome); + const value = outcome.primaryValue() switch (scope) { case LEX_SCOPE: - return kStack.invokeCont(lenv.set(namespace, variable, value)); + return sboocpsStack.invoke(lenv.set(namespace, variable, value)); case DYN_SCOPE: - return kStack.invokeCont(kStack.set(namespace, variable, value)); + return sboocpsStack.invoke(sboocpsStack.denv.set(namespace, variable, value)); default: throw new CannotHappen('SBOOCPSSetValueFormCont.invoke'); } } } -function sboocpsEvalForEach(form, lenv, kStack) { - const [functionForm, listForm] = analyzeForEach(form); - kStack.push(new SBOOCPSForEachFunctionFormCont(listForm, lenv, kStack)); - return sboocpsEvalForm(functionForm, lenv, kStack); +function sboocpsEvalBlock(form, lenv) { + const analysis = analyzeBlock(form); + if (isError(analysis)) return sboocpsStack.invoke(analysis); + const [blockName, serialForms] = analysis; + const exitTag = new EVLVariable('exit-tag'); + const elenv = new Frame(BLK_NS, [blockName], [exitTag], lenv); + sboocpsStack.push(new Frame(XIT_NS, [exitTag], [EVLVoid.VOID], null)); + sboocpsStack.push(new SBOOCPSBlockSerialFormsCont(exitTag)); + return sboocpsEvalSerialForms(serialForms, elenv); } -class SBOOCPSForEachFunctionFormCont extends SBOOCPSCont { - constructor(listForm, lenv, kStack) { - super(lenv, kStack); - this.listForm = listForm; +class SBOOCPSBlockSerialFormsCont extends SBOOCPSCont { + constructor(exitTag) { + super(); + this.exitTag = exitTag; } - invoke(result) { - const {listForm, lenv, kStack} = this; - const fn = result.primaryValue(); - if (!(fn instanceof EVLFunction)) { - forEachFunctionFormError(); + invoke(outcome) { + const {exitTag} = this; + if (isNonlocalExit(outcome) && outcome.exitTag === exitTag) { + return sboocpsStack.invoke(outcome.values); + } else { + return sboocpsStack.invoke(outcome); } - kStack.push(new SBOOCPSForEachListFormCont(fn, lenv, kStack)); - return sboocpsEvalForm(listForm, lenv, kStack); } } -class SBOOCPSForEachListFormCont extends SBOOCPSCont { - constructor(fn, lenv, kStack) { - super(lenv, kStack); - this.fn = fn; +function sboocpsEvalReturnFrom(form, lenv) { + const analysis = analyzeReturnFrom(form); + if (isError(analysis)) return sboocpsStack.invoke(analysis); + const [blockName, valuesForm] = analysis; + const exitTag = lenv.ref(BLK_NS, blockName); + if (exitTag === null) { + return sboocpsStack.invoke(new NoBlock(blockName)); } - invoke(result) { - const {fn, lenv, kStack} = this; - let list = result.primaryValue(); - while (list !== EVLEmptyList.NIL) { - if (list instanceof EVLCons) { - kStack.push(sboocpsEndCont); - sboocpsInvokeFun(false, false, fn, [list.car], lenv, kStack); - list = list.cdr; - } else { - forEachListFormError(); - } + const exitPoint = sboocpsStack.denv.ref(XIT_NS, exitTag); + if (exitPoint === null) { + return sboocpsStack.invoke(new NoBlockExitPoint(blockName)); + } + sboocpsStack.push(new SBOOCPSReturnFromValuesFormCont(exitTag)); + return sboocpsEvalForm(valuesForm, lenv); +} + +class SBOOCPSReturnFromValuesFormCont extends SBOOCPSCont { + constructor(exitTag) { + super(); + this.exitTag = exitTag; + } + invoke(outcome) { + const {exitTag} = this; + if (isAbruptCompletion(outcome)) return sboocpsStack.invoke(outcome); + return sboocpsStack.invoke(new NonlocalExit(exitTag, outcome)); + } +} + +function sboocpsEvalCatch(form, lenv) { + const analysis = analyzeCatch(form); + if (isError(analysis)) return sboocpsStack.invoke(analysis); + const [exitTagForm, serialForms] = analysis; + sboocpsStack.push(new SBOOCPSCatchExitTagFormCont(serialForms, lenv)); + return sboocpsEvalForm(exitTagForm, lenv); +} + +class SBOOCPSCatchExitTagFormCont extends SBOOCPSCont { + constructor(serialForms, lenv) { + super(); + this.serialForms = serialForms; + this.lenv = lenv; + } + invoke(outcome) { + const {serialForms, lenv} = this; + if (isAbruptCompletion(outcome)) return sboocpsStack.invoke(outcome); + const exitTag = outcome.primaryValue(); + if (!(exitTag instanceof EVLVariable)) { + return sboocpsStack.invoke(new ExitTagFormTypeError()); } - return kStack.invokeCont(EVLVoid.VOID); + sboocpsStack.push(new Frame(XIT_NS, [exitTag], [EVLVoid.VOID], null)); + sboocpsStack.push(new SBOOCPSCatchSerialFormsCont(exitTag)); + return sboocpsEvalSerialForms(serialForms, lenv); } } -function sboocpsEvalCatchErrors(form, lenv, kStack) { - const [tryForm] = analyzeCatchErrors(form); - const kStackSize = kStack.size(); - try { - kStack.push(sboocpsEndCont); - sboocpsEvalForm(tryForm, lenv, kStack); - } catch (exception) { - kStack.trim(kStackSize); - return kStack.invokeCont(new EVLString(exception.name)); +class SBOOCPSCatchSerialFormsCont extends SBOOCPSCont { + constructor(exitTag) { + super(); + this.exitTag = exitTag; + } + invoke(outcome) { + const {exitTag} = this; + if (isNonlocalExit(outcome) && outcome.exitTag === exitTag) { + return sboocpsStack.invoke(outcome.values); + } else { + return sboocpsStack.invoke(outcome); + } } - return kStack.invokeCont(EVLVoid.VOID); } -function sboocpsEvalCall(mv, apply, form, lenv, kStack) { - const [operator, operands] = analyzeCall(mv, apply, form); - kStack.push(new SBOOCPSOperatorCont(mv, apply, operator, operands, lenv, kStack)); - return sboocpsEvalOperator(operator, lenv, kStack); +function sboocpsEvalThrow(form, lenv) { + const analysis = analyzeThrow(form); + if (isError(analysis)) return sboocpsStack.invoke(analysis); + const [exitTagForm, valuesForm] = analysis; + sboocpsStack.push(new SBOOCPSThrowExitTagFormCont(valuesForm, lenv)); + return sboocpsEvalForm(exitTagForm, lenv); } -function sboocpsEvalOperator(operator, lenv, kStack) { - if (operator instanceof EVLVariable) { - return kStack.invokeCont(lenv.ref(FUN_NS, operator)); - } else { - return sboocpsEvalForm(operator, lenv, kStack); +class SBOOCPSThrowExitTagFormCont extends SBOOCPSCont { + constructor(valuesForm, lenv) { + super(); + this.valuesForm = valuesForm; + this.lenv = lenv; + } + invoke(outcome) { + const {valuesForm, lenv} = this; + if (isAbruptCompletion(outcome)) return sboocpsStack.invoke(outcome); + const exitTag = outcome.primaryValue(); + if (!(exitTag instanceof EVLVariable)) { + return sboocpsStack.invoke(new ExitTagFormTypeError()); + } + const exitPoint = sboocpsStack.denv.ref(XIT_NS, exitTag); + if (exitPoint === null) { + return sboocpsStack.invoke(new NoCatchExitPoint(exitTag)); + } + sboocpsStack.push(new SBOOCPSThrowValuesFormCont(exitTag)); + return sboocpsEvalForm(valuesForm, lenv); } } -class SBOOCPSOperatorCont extends SBOOCPSCont { - constructor(mv, apply, operator, operands, lenv, kStack) { - super(lenv, kStack); - this.mv = mv; - this.apply = apply; - this.operator = operator; - this.operands = operands; +class SBOOCPSThrowValuesFormCont extends SBOOCPSCont { + constructor(exitTag) { + super(); + this.exitTag = exitTag; } - invoke(result) { - const {mv, apply, operator, operands, lenv, kStack} = this; - const fn = result.primaryValue(); - const macro = operator instanceof EVLVariable && fn instanceof EVLClosure && fn.macro; - kStack.push(new SBOOCPSOperandsCont(apply, macro, fn, lenv, kStack)); - return sboocpsEvalOperands(mv, macro, operands, [], lenv, kStack); + invoke(outcome) { + const {exitTag} = this; + if (isAbruptCompletion(outcome)) return sboocpsStack.invoke(outcome); + return sboocpsStack.invoke(new NonlocalExit(exitTag, outcome)); } } -function sboocpsEvalOperands(mv, macro, operands, args, lenv, kStack) { - if (operands === EVLEmptyList.NIL) { - return kStack.invokeCont(args); - } else { - if (macro) { - args.push(operands.car); - return sboocpsEvalOperands(mv, macro, operands.cdr, args, lenv, kStack); +function sboocpsEvalHandlerBind(form, lenv) { + const analysis = analyzeHandlerBind(form); + if (isError(analysis)) return sboocpsStack.invoke(analysis); + const [handlerForm, serialForms] = analysis; + sboocpsStack.push(new SBOOCPSHandlerBindHandlerFormCont(serialForms, lenv)); + return sboocpsEvalForm(handlerForm, lenv); +} + +class SBOOCPSHandlerBindHandlerFormCont extends SBOOCPSCont { + constructor(serialForms, lenv) { + super(); + this.serialForms = serialForms; + this.lenv = lenv; + } + invoke(outcome) { + const {serialForms, lenv} = this; + if (isAbruptCompletion(outcome)) return sboocpsStack.invoke(outcome); + const handler = outcome.primaryValue(); + if (!(handler instanceof EVLFunction)) { + return sboocpsStack.invoke(new HandlerFormTypeError()); + } + sboocpsStack.push(new SBOOCPSHandlerBindSerialFormsCont(handler)); + return sboocpsEvalSerialForms(serialForms, lenv); + } +} + +class SBOOCPSHandlerBindSerialFormsCont extends SBOOCPSCont { + constructor(handler) { + super(); + this.handler = handler; + } + invoke(outcome) { + const {handler} = this; + if (isError(outcome)) { + sboocpsStack.push(new SBOOCPSHandlerBindInvocationCont(outcome)); + return sboocpsInvoke(false, handler, [outcome.category, outcome.description]); } else { - kStack.push(new SBOOCPSOperandCont(mv, macro, operands, args, lenv, kStack)); - return sboocpsEvalForm(operands.car, lenv, kStack); + return sboocpsStack.invoke(outcome); } } } -class SBOOCPSOperandCont extends SBOOCPSCont { - constructor(mv, macro, operands, args, lenv, kStack) { - super(lenv, kStack); - this.mv = mv; - this.macro = macro; - this.operands = operands; - this.args = args; +class SBOOCPSHandlerBindInvocationCont extends SBOOCPSCont { + constructor(serialFormsOutcome) { + super(); + this.serialFormsOutcome = serialFormsOutcome; } - invoke(result) { - const {mv, macro, operands, args, lenv, kStack} = this; - if (mv) { - result.allValues().forEach(value => args.push(value)); + invoke(outcome) { + const {serialFormsOutcome} = this; + if (isAbruptCompletion(outcome)) { + return sboocpsStack.invoke(outcome); + } else { + return sboocpsStack.invoke(serialFormsOutcome); + } + } +} + +function sboocpsEvalUnwindProtect(form, lenv) { + const analysis = analyzeUnwindProtect(form); + if (isError(analysis)) return sboocpsStack.invoke(analysis); + const [protectedForm, cleanupForms] = analysis; + sboocpsStack.push(new SBOOCPSUnwindProtectProtectedFormCont(cleanupForms, lenv)); + return sboocpsEvalForm(protectedForm, lenv); +} + +class SBOOCPSUnwindProtectProtectedFormCont extends SBOOCPSCont { + constructor(cleanupForms, lenv) { + super(); + this.cleanupForms = cleanupForms; + this.lenv = lenv; + } + invoke(outcome) { + const {cleanupForms, lenv} = this; + sboocpsStack.push(new SBOOCPSUnwindProtectCleanupFormsCont(outcome)); + return sboocpsEvalSerialForms(cleanupForms, lenv); + } +} + +class SBOOCPSUnwindProtectCleanupFormsCont extends SBOOCPSCont { + constructor(protectedFormOutcome) { + super(); + this.protectedFormOutcome = protectedFormOutcome; + } + invoke(outcome) { + const {protectedFormOutcome} = this; + if (isAbruptCompletion(outcome)) { + return sboocpsStack.invoke(outcome); } else { - args.push(result.primaryValue()); + return sboocpsStack.invoke(protectedFormOutcome); } - return sboocpsEvalOperands(mv, macro, operands.cdr, args, lenv, kStack); } } -class SBOOCPSOperandsCont extends SBOOCPSCont { - constructor(apply, macro, fn, lenv, kStack) { - super(lenv, kStack); +function sboocpsEvalCall(mv, apply, form, lenv) { + const analysis = analyzeCall(mv, apply, form, lenv); + if (isError(analysis)) return sboocpsStack.invoke(analysis); + const [macroCall, operator, operands] = analysis; + if (macroCall) { + return sboocpsEvalMacroCall(form, operator, operands, lenv); + } else { + return sboocpsEvalFunctionCall(mv, apply, operator, operands, lenv); + } +} + +function sboocpsEvalMacroCall(form, macro, macroOperands, lenv) { + const args = listToArray(macroOperands); + sboocpsStack.push(sboocpsEndCont); + const outcome = sboocpsInvoke(false, macro, args); + if (isAbruptCompletion(outcome)) return sboocpsStack.invoke(outcome); + const expansion = outcome.primaryValue(); + if (optimizeMacroCalls) { + alterForm(form, expansion); + } + return sboocpsEvalForm(expansion, lenv); +} + +function sboocpsEvalFunctionCall(mv, apply, operatorForm, operandForms, lenv) { + sboocpsStack.push(new SBOOCPSFunctionCallOperatorFormCont(mv, apply, operandForms, lenv)); + return sboocpsEvalOperatorForm(operatorForm, lenv); +} + +function sboocpsEvalOperatorForm(operatorForm, lenv) { + if (operatorForm instanceof EVLVariable) { + return sboocpsStack.invoke(lenv.ref(FUN_NS, operatorForm)); + } else { + return sboocpsEvalForm(operatorForm, lenv); + } +} + +class SBOOCPSFunctionCallOperatorFormCont extends SBOOCPSCont { + constructor(mv, apply, operandForms, lenv) { + super(); + this.mv = mv; + this.apply = apply; + this.operandForms = operandForms; + this.lenv = lenv; + } + invoke(outcome) { + const {mv, apply, operandForms, lenv} = this; + if (isAbruptCompletion(outcome)) return sboocpsStack.invoke(outcome); + const fn = outcome.primaryValue(); + if (!(fn instanceof EVLFunction)) { + return sboocpsStack.invoke(new OperatorFormTypeError()); + } + return sboocpsEvalOperandForms(mv, apply, fn, operandForms, [], lenv); + } +} + +function sboocpsEvalOperandForms(mv, apply, fn, operandForms, args, lenv) { + if (operandForms === EVLEmptyList.NIL) { + return sboocpsInvoke(apply, fn, args); + } else { + sboocpsStack.push(new SBOOCPSFunctionCallOperandFormCont(mv, apply, fn, operandForms, args, lenv)); + return sboocpsEvalForm(operandForms.car, lenv); + } +} + +class SBOOCPSFunctionCallOperandFormCont extends SBOOCPSCont { + constructor(mv, apply, fn, operandForms, args, lenv) { + super(); + this.mv = mv; this.apply = apply; - this.macro = macro; this.fn = fn; + this.operandForms = operandForms; + this.args = args; + this.lenv = lenv; } - invoke(args) { - const {apply, macro, fn, lenv, kStack} = this; - return sboocpsInvokeFun(apply, macro, fn, args, lenv, kStack); + invoke(outcome) { + const {mv, apply, fn, operandForms, args, lenv} = this; + if (isAbruptCompletion(outcome)) return sboocpsStack.invoke(outcome); + if (mv) { + outcome.allValues().forEach(value => args.push(value)); + } else { + args.push(outcome.primaryValue()); + } + return sboocpsEvalOperandForms(mv, apply, fn, operandForms.cdr, args, lenv); } } -function sboocpsInvokeFun(apply, macro, fn, args, lenv, kStack) { +function sboocpsInvoke(apply, fn, args) { if (fn instanceof EVLPrimitiveFunction) { const values = pairPrimFunParameters(apply, args, fn.arityMin, fn.arityMax); - return kStack.invokeCont(fn.jsFunction(values)); + if (isError(values)) return sboocpsStack.invoke(values); + return sboocpsStack.invoke(fn.jsFunction(values)); } else if (fn instanceof EVLClosure) { const values = pairClosureParameters(apply, args, fn.parameters, fn.rest); + if (isError(values)) return sboocpsStack.invoke(values); switch (fn.scope) { case LEX_SCOPE: const elenv = new Frame(fn.namespace, fn.parameters, values, fn.lenv); - if (macro) { - kStack.push(sboocpsEndCont); - const expansion = sboocpsEvalForms(fn.forms, elenv, kStack).primaryValue(); - return sboocpsEvalForm(expansion, lenv, kStack); - } else { - return sboocpsEvalForms(fn.forms, elenv, kStack); - } + return sboocpsEvalSerialForms(fn.serialForms, elenv); case DYN_SCOPE: - kStack.push(new Frame(fn.namespace, fn.parameters, values, undefined)); - return sboocpsEvalForms(fn.forms, fn.lenv, kStack); + sboocpsStack.push(new Frame(fn.namespace, fn.parameters, values, null)); + return sboocpsEvalSerialForms(fn.serialForms, fn.lenv); default: - throw new CannotHappen('sboocpsInvokeFun'); + throw new CannotHappen('sboocpsInvoke'); } } else { - callOperatorFormError(); + throw new CannotHappen('sboocpsInvoke'); } } @@ -3203,129 +4235,108 @@ function sboocpsInvokeFun(apply, macro, fn, args, lenv, kStack) { /* Trampoline Evaluator */ /************************/ -function trampolineEval(form) { - const kStack = new TrampolineControlStack(); - kStack.push(trampolineEndCont); - let bounce = new EvalReq(form, nullDefiniteEnv); - while (true) { - if (abortSignalArray !== null && abortSignalArray[0] === 1) { - throw new Aborted(); - } - if (bounce instanceof EvalReq) { - try { - bounce = trampolineEvalForm(bounce.form, bounce.lenv, kStack); - } catch(exception) { - bounce = kStack.handleError(exception); - } - } else { - const k = kStack.popCont(); - if (k instanceof TrampolineEndCont) { - return bounce; - } else { - try { - bounce = k.invoke(bounce); - } catch(exception) { - bounce = kStack.handleError(exception); - } - } - } - } -} +let trampolineStack = null; -class TrampolineControlStack { +class TrampolineStack { constructor() { - this.stack = []; // element: TrampolineCont, TrampolineErrorHandler, or Frame + this.stack = [trampolineEndCont]; // array of TrampolineCont's and/or Frame's + this.denv = nullDefiniteEnv; } push(element) { - this.stack.push(element); - } - popCont() { - while (true) { - const element = this.stack.pop(); - if (element instanceof TrampolineCont) { - return element; - } - } - } - handleError(exception) { - while (true) { - const element = this.stack.pop(); - if (element instanceof TrampolineErrorHandler) { - return new EVLString(exception.name); - } else if (element instanceof TrampolineEndCont) { - throw exception; - } + if (element instanceof TrampolineCont) { + this.stack.push(element); + } else if (element instanceof Frame) { + this.stack.push(element); + element.next = this.denv; + this.denv = element; + } else { + throw new CannotHappen('TrampolineStack.push'); } } - ref(namespace, variable) { - for (let i = this.stack.length - 1; i >= 0; i--) { - const element = this.stack[i]; - if (element instanceof Frame) { - const result = element.ref(namespace, variable); - if (result !== undefined) { - return result; - } - } +} + +function trampolineEval(form) { + trampolineStack = new TrampolineStack(); + let bounce = new EvalReq(form, nullDefiniteEnv); + while (true) { + if (abortSignalArray !== null && abortSignalArray[0] === 1) { + throw new Aborted(); } - return GlobalEnv.ref(namespace, variable); - } - set(namespace, variable, value) { - for (let i = this.stack.length - 1; i >= 0; i--) { - const element = this.stack[i]; - if (element instanceof Frame) { - const result = element.set(namespace, variable, value); - if (result !== undefined) { - return result; + if (bounce instanceof EvalReq) { + bounce = trampolineEvalForm(bounce.form, bounce.lenv); + } else if (bounce instanceof Outcome) { + while (true) { + const element = trampolineStack.stack.pop(); + if (element instanceof TrampolineEndCont) { + return element.invoke(bounce); + } else if (element instanceof TrampolineCont) { + bounce = element.invoke(bounce); + break; + } else if (element instanceof Frame) { + trampolineStack.denv = element.next; + } else { + throw new CannotHappen('trampolineEval'); } } + } else { + throw new CannotHappen('trampolineEval'); } - return GlobalEnv.set(namespace, variable, value); } } -function trampolineEvalForm(form, lenv, kStack) { +function trampolineEvalForm(form, lenv) { if (form instanceof EVLEmptyList) { - emptyListError(); + return new EmptyListError(); } else if (form instanceof EVLCons) { switch (form.car) { case quoteVariable: - return trampolineEvalQuote(form, lenv, kStack); + return trampolineEvalQuote(form, lenv); case prognVariable: - return trampolineEvalProgn(form, lenv, kStack); + return trampolineEvalProgn(form, lenv); case ifVariable: - return trampolineEvalIf(form, lenv, kStack); + return trampolineEvalIf(form, lenv); + case _forEachVariable: + return trampolineEvalForEach(form, lenv); case _vlambdaVariable: - return trampolineEvalLambda(LEX_SCOPE, VAL_NS, false, form, lenv, kStack); + return trampolineEvalLambda(LEX_SCOPE, VAL_NS, false, form, lenv); case _mlambdaVariable: - return trampolineEvalLambda(LEX_SCOPE, VAL_NS, true, form, lenv, kStack); + return trampolineEvalLambda(LEX_SCOPE, VAL_NS, true, form, lenv); case _flambdaVariable: - return trampolineEvalLambda(LEX_SCOPE, FUN_NS, false, form, lenv, kStack); + return trampolineEvalLambda(LEX_SCOPE, FUN_NS, false, form, lenv); case _dlambdaVariable: - return trampolineEvalLambda(DYN_SCOPE, VAL_NS, false, form, lenv, kStack); + return trampolineEvalLambda(DYN_SCOPE, VAL_NS, false, form, lenv); case vrefVariable: - return trampolineEvalRef(LEX_SCOPE, VAL_NS, form, lenv, kStack); + return trampolineEvalRef(LEX_SCOPE, VAL_NS, form, lenv); case vsetVariable: - return trampolineEvalSet(LEX_SCOPE, VAL_NS, form, lenv, kStack); + return trampolineEvalSet(LEX_SCOPE, VAL_NS, form, lenv); case frefVariable: - return trampolineEvalRef(LEX_SCOPE, FUN_NS, form, lenv, kStack); + return trampolineEvalRef(LEX_SCOPE, FUN_NS, form, lenv); case fsetVariable: - return trampolineEvalSet(LEX_SCOPE, FUN_NS, form, lenv, kStack); + return trampolineEvalSet(LEX_SCOPE, FUN_NS, form, lenv); case drefVariable: - return trampolineEvalRef(DYN_SCOPE, VAL_NS, form, lenv, kStack); + return trampolineEvalRef(DYN_SCOPE, VAL_NS, form, lenv); case dsetVariable: - return trampolineEvalSet(DYN_SCOPE, VAL_NS, form, lenv, kStack); - case _forEachVariable: - forEachNotImplemented(); - case _catchErrorsVariable: - return trampolineEvalCatchErrors(form, lenv, kStack); + return trampolineEvalSet(DYN_SCOPE, VAL_NS, form, lenv); + case blockVariable: + return trampolineEvalBlock(form, lenv); + case returnFromVariable: + return trampolineEvalReturnFrom(form, lenv); + case catchVariable: + return trampolineEvalCatch(form, lenv); + case throwVariable: + return trampolineEvalThrow(form, lenv); + case _handlerBindVariable: + return trampolineEvalHandlerBind(form, lenv); + case unwindProtectVariable: + return trampolineEvalUnwindProtect(form, lenv); case applyVariable: - return trampolineEvalCall(false, true, form, lenv, kStack); + return trampolineEvalCall(false, true, form, lenv); case multipleValueCallVariable: - return trampolineEvalCall(true, false, form, lenv, kStack); + return trampolineEvalCall(true, false, form, lenv); case multipleValueApplyVariable: - return trampolineEvalCall(true, true, form, lenv, kStack); + return trampolineEvalCall(true, true, form, lenv); default: - return trampolineEvalCall(false, false, form, lenv, kStack); + return trampolineEvalCall(false, false, form, lenv); } } else if (form instanceof EVLVariable) { return lenv.ref(VAL_NS, form); @@ -3335,345 +4346,566 @@ function trampolineEvalForm(form, lenv, kStack) { } class TrampolineCont { // abstract class - constructor(lenv, kStack) { - this.lenv = lenv; - this.kStack = kStack; + constructor() { } } class TrampolineEndCont extends TrampolineCont { constructor() { - super(null, null); + super(); + } + invoke(outcome) { + return outcome; } } const trampolineEndCont = new TrampolineEndCont(); -function trampolineEvalQuote(form, lenv, kStack) { - const [literal] = analyzeQuote(form); +function trampolineEvalQuote(form, lenv) { + const analysis = analyzeQuote(form); + if (isError(analysis)) return analysis; + const [literal] = analysis; return literal; } -function trampolineEvalProgn(form, lenv, kStack) { - const [forms] = analyzeProgn(form); - return trampolineEvalForms(forms, lenv, kStack); +function trampolineEvalProgn(form, lenv) { + const analysis = analyzeProgn(form); + if (isError(analysis)) return analysis; + const [serialForms] = analysis; + return trampolineEvalSerialForms(serialForms, lenv); } -function trampolineEvalForms(forms, lenv, kStack) { - if (forms === EVLEmptyList.NIL) { +function trampolineEvalSerialForms(serialForms, lenv) { + if (serialForms === EVLEmptyList.NIL) { return EVLVoid.VOID; - } else if (forms.cdr === EVLEmptyList.NIL) { - return new EvalReq(forms.car, lenv); } else { - kStack.push(new TrampolineButLastFormCont(forms, lenv, kStack)); - return new EvalReq(forms.car, lenv); + return trampolineEvalSerialFormForms(serialForms, lenv); } } -class TrampolineButLastFormCont extends TrampolineCont { - constructor(forms, lenv, kStack) { - super(lenv, kStack); - this.forms = forms; +function trampolineEvalSerialFormForms(serialForms, lenv) { + if (serialForms.cdr === EVLEmptyList.NIL) { + return new EvalReq(serialForms.car, lenv); + } else { + trampolineStack.push(new TrampolineSerialFormCont(serialForms, lenv)); + return new EvalReq(serialForms.car, lenv); + } +} + +class TrampolineSerialFormCont extends TrampolineCont { + constructor(serialForms, lenv) { + super(); + this.serialForms = serialForms; + this.lenv = lenv; } - invoke(result) { - const {forms, lenv, kStack} = this; - return trampolineEvalForms(forms.cdr, lenv, kStack); + invoke(outcome) { + const {serialForms, lenv} = this; + if (isAbruptCompletion(outcome)) return outcome; + return trampolineEvalSerialFormForms(serialForms.cdr, lenv); } } -function trampolineEvalIf(form, lenv, kStack) { - const [testForm, thenForm, elseForm] = analyzeIf(form); - kStack.push(new TrampolineIfTestFormCont(thenForm, elseForm, lenv, kStack)); +function trampolineEvalIf(form, lenv) { + const analysis = analyzeIf(form); + if (isError(analysis)) return analysis; + const [testForm, thenForm, elseForm] = analysis; + trampolineStack.push(new TrampolineIfTestFormCont(thenForm, elseForm, lenv)); return new EvalReq(testForm, lenv); } class TrampolineIfTestFormCont extends TrampolineCont { - constructor(thenForm, elseForm, lenv, kStack) { - super(lenv, kStack); + constructor(thenForm, elseForm, lenv) { + super(); this.thenForm = thenForm; this.elseForm = elseForm; + this.lenv = lenv; } - invoke(result) { - const {thenForm, elseForm, lenv, kStack} = this; - const test = result.primaryValue(); + invoke(outcome) { + const {thenForm, elseForm, lenv} = this; + if (isAbruptCompletion(outcome)) return outcome; + const test = outcome.primaryValue(); switch (test) { case EVLBoolean.TRUE: return new EvalReq(thenForm, lenv); case EVLBoolean.FALSE: return new EvalReq(elseForm, lenv); default: - ifTestFormError(); + return new TestFormTypeError(); } } } -function trampolineEvalLambda(scope, namespace, macro, form, lenv, kStack) { - const [parameters, rest, forms] = analyzeLambda(form); - return new EVLClosure(scope, namespace, macro, parameters, rest, forms, lenv); +function trampolineEvalForEach(form, lenv) { + const analysis = analyzeForEach(form); + if (isError(analysis)) return analysis; + const [functionForm, listForm] = analysis; + return new ForEachNotImplemented(); } -function trampolineEvalRef(scope, namespace, form, lenv, kStack) { - const [variable] = analyzeRef(form); +function trampolineEvalLambda(scope, namespace, macro, form, lenv) { + const analysis = analyzeLambda(form); + if (isError(analysis)) return analysis; + const [parameters, rest, serialForms] = analysis; + return new EVLClosure(scope, namespace, macro, parameters, rest, serialForms, lenv); +} + +function trampolineEvalRef(scope, namespace, form, lenv) { + const analysis = analyzeRef(form); + if (isError(analysis)) return analysis; + const [variable] = analysis; switch (scope) { case LEX_SCOPE: return lenv.ref(namespace, variable); case DYN_SCOPE: - return kStack.ref(namespace, variable); + return trampolineStack.denv.ref(namespace, variable); default: throw new CannotHappen('trampolineEvalRef'); } } -function trampolineEvalSet(scope, namespace, form, lenv, kStack) { - const [variable, valueForm] = analyzeSet(form); - kStack.push(new TrampolineSetValueFormCont(scope, namespace, variable, lenv, kStack)); +function trampolineEvalSet(scope, namespace, form, lenv) { + const analysis = analyzeSet(form); + if (isError(analysis)) return analysis; + const [variable, valueForm] = analysis; + trampolineStack.push(new TrampolineSetValueFormCont(scope, namespace, variable, lenv)); return new EvalReq(valueForm, lenv); } class TrampolineSetValueFormCont extends TrampolineCont { - constructor(scope, namespace, variable, lenv, kStack) { - super(lenv, kStack); + constructor(scope, namespace, variable, lenv) { + super(); this.scope = scope; this.namespace = namespace; this.variable = variable; + this.lenv = lenv; } - invoke(result) { - const {scope, namespace, variable, lenv, kStack} = this; - const value = result.primaryValue() + invoke(outcome) { + const {scope, namespace, variable, lenv} = this; + if (isAbruptCompletion(outcome)) return outcome; + const value = outcome.primaryValue() switch (scope) { case LEX_SCOPE: return lenv.set(namespace, variable, value); case DYN_SCOPE: - return kStack.set(namespace, variable, value); + return trampolineStack.denv.set(namespace, variable, value); default: throw new CannotHappen('TrampolineSetValueFormCont.invoke'); } } } -function trampolineEvalCatchErrors(form, lenv, kStack) { - const [tryForm] = analyzeCatchErrors(form); - kStack.push(new TrampolineErrorHandler()); - kStack.push(new TrampolineCatchErrorsTryFormCont(lenv, kStack)); - return new EvalReq(tryForm, lenv); +function trampolineEvalBlock(form, lenv) { + const analysis = analyzeBlock(form); + if (isError(analysis)) return analysis; + const [blockName, serialForms] = analysis; + const exitTag = new EVLVariable('exit-tag'); + const elenv = new Frame(BLK_NS, [blockName], [exitTag], lenv); + trampolineStack.push(new Frame(XIT_NS, [exitTag], [EVLVoid.VOID], null)); + trampolineStack.push(new TrampolineBlockSerialFormsCont(exitTag)); + return trampolineEvalSerialForms(serialForms, elenv); } -class TrampolineErrorHandler { +class TrampolineBlockSerialFormsCont extends TrampolineCont { + constructor(exitTag) { + super(); + this.exitTag = exitTag; + } + invoke(outcome) { + const {exitTag} = this; + if (isNonlocalExit(outcome) && outcome.exitTag === exitTag) { + return outcome.values; + } else { + return outcome; + } + } } -class TrampolineCatchErrorsTryFormCont extends TrampolineCont { - constructor(lenv, kStack) { - super(lenv, kStack); +function trampolineEvalReturnFrom(form, lenv) { + const analysis = analyzeReturnFrom(form); + if (isError(analysis)) return analysis; + const [blockName, valuesForm] = analysis; + const exitTag = lenv.ref(BLK_NS, blockName); + if (exitTag === null) { + return new NoBlock(blockName); } - invoke(result) { - const {lenv, kStack} = this; - return EVLVoid.VOID; + const exitPoint = trampolineStack.denv.ref(XIT_NS, exitTag); + if (exitPoint === null) { + return new NoBlockExitPoint(blockName); } + trampolineStack.push(new TrampolineReturnFromValuesFormCont(exitTag)); + return new EvalReq(valuesForm, lenv); } -function trampolineEvalCall(mv, apply, form, lenv, kStack) { - const [operator, operands] = analyzeCall(mv, apply, form); - kStack.push(new TrampolineOperatorCont(mv, apply, operator, operands, lenv, kStack)); - return trampolineEvalOperator(operator, lenv, kStack); +class TrampolineReturnFromValuesFormCont extends TrampolineCont { + constructor(exitTag) { + super(); + this.exitTag = exitTag; + } + invoke(outcome) { + const {exitTag} = this; + if (isAbruptCompletion(outcome)) return outcome; + return new NonlocalExit(exitTag, outcome); + } } -function trampolineEvalOperator(operator, lenv, kStack) { - if (operator instanceof EVLVariable) { - return lenv.ref(FUN_NS, operator); - } else { - return new EvalReq(operator, lenv); - } +function trampolineEvalCatch(form, lenv) { + const analysis = analyzeCatch(form); + if (isError(analysis)) return analysis; + const [exitTagForm, serialForms] = analysis; + trampolineStack.push(new TrampolineCatchExitTagFormCont(serialForms, lenv)); + return new EvalReq(exitTagForm, lenv); } -class TrampolineOperatorCont extends TrampolineCont { - constructor(mv, apply, operator, operands, lenv, kStack) { - super(lenv, kStack); - this.mv = mv; - this.apply = apply; - this.operator = operator; - this.operands = operands; +class TrampolineCatchExitTagFormCont extends TrampolineCont { + constructor(serialForms, lenv) { + super(); + this.serialForms = serialForms; + this.lenv = lenv; } - invoke(result) { - const {mv, apply, operator, operands, lenv, kStack} = this; - const fn = result.primaryValue(); - const macro = operator instanceof EVLVariable && fn instanceof EVLClosure && fn.macro; - kStack.push(new TrampolineOperandsCont(apply, macro, fn, lenv, kStack)); - return trampolineEvalOperands(mv, macro, operands, [], lenv, kStack); + invoke(outcome) { + const {serialForms, lenv} = this; + if (isAbruptCompletion(outcome)) return outcome; + const exitTag = outcome.primaryValue(); + if (!(exitTag instanceof EVLVariable)) { + return new ExitTagFormTypeError(); + } + trampolineStack.push(new Frame(XIT_NS, [exitTag], [EVLVoid.VOID], null)); + trampolineStack.push(new TrampolineCatchSerialFormsCont(exitTag)); + return trampolineEvalSerialForms(serialForms, lenv); } } -function trampolineEvalOperands(mv, macro, operands, args, lenv, kStack) { - if (operands === EVLEmptyList.NIL) { - return args; - } else { - if (macro) { - args.push(operands.car); - return trampolineEvalOperands(mv, macro, operands.cdr, args, lenv, kStack); +class TrampolineCatchSerialFormsCont extends TrampolineCont { + constructor(exitTag) { + super(); + this.exitTag = exitTag; + } + invoke(outcome) { + const {exitTag} = this; + if (isNonlocalExit(outcome) && outcome.exitTag === exitTag) { + return outcome.values; } else { - kStack.push(new TrampolineOperandCont(mv, macro, operands, args, lenv, kStack)); - return new EvalReq(operands.car, lenv); + return outcome; } } } -class TrampolineOperandCont extends TrampolineCont { - constructor(mv, macro, operands, args, lenv, kStack) { - super(lenv, kStack); - this.mv = mv; - this.macro = macro; - this.operands = operands; - this.args = args; +function trampolineEvalThrow(form, lenv) { + const analysis = analyzeThrow(form); + if (isError(analysis)) return analysis; + const [exitTagForm, valuesForm] = analysis; + trampolineStack.push(new TrampolineThrowExitTagFormCont(valuesForm, lenv)); + return new EvalReq(exitTagForm, lenv); +} + +class TrampolineThrowExitTagFormCont extends TrampolineCont { + constructor(valuesForm, lenv) { + super(); + this.valuesForm = valuesForm; + this.lenv = lenv; } - invoke(result) { - const {mv, macro, operands, args, lenv, kStack} = this; - if (mv) { - result.allValues().forEach(value => args.push(value)); - } else { - args.push(result.primaryValue()); + invoke(outcome) { + const {valuesForm, lenv} = this; + if (isAbruptCompletion(outcome)) return outcome; + const exitTag = outcome.primaryValue(); + if (!(exitTag instanceof EVLVariable)) { + return new ExitTagFormTypeError(); } - return trampolineEvalOperands(mv, macro, operands.cdr, args, lenv, kStack); + const exitPoint = trampolineStack.denv.ref(XIT_NS, exitTag); + if (exitPoint === null) { + return new NoCatchExitPoint(exitTag); + } + trampolineStack.push(new TrampolineThrowValuesFormCont(exitTag)); + return new EvalReq(valuesForm, lenv); } } -class TrampolineOperandsCont extends TrampolineCont { - constructor(apply, macro, fn, lenv, kStack) { - super(lenv, kStack); - this.apply = apply; - this.macro = macro; - this.fn = fn; +class TrampolineThrowValuesFormCont extends TrampolineCont { + constructor(exitTag) { + super(); + this.exitTag = exitTag; } - invoke(args) { - const {apply, macro, fn, lenv, kStack} = this; - return trampolineInvokeFun(apply, macro, fn, args, lenv, kStack); + invoke(outcome) { + const {exitTag} = this; + if (isAbruptCompletion(outcome)) return outcome; + return new NonlocalExit(exitTag, outcome); } } -function trampolineInvokeFun(apply, macro, fn, args, lenv, kStack) { - if (fn instanceof EVLPrimitiveFunction) { - const values = pairPrimFunParameters(apply, args, fn.arityMin, fn.arityMax); - return fn.jsFunction(values); - } else if (fn instanceof EVLClosure) { - const values = pairClosureParameters(apply, args, fn.parameters, fn.rest); - switch (fn.scope) { - case LEX_SCOPE: - const elenv = new Frame(fn.namespace, fn.parameters, values, fn.lenv); - if (macro) { - kStack.push(new TrampolineMacroCont(lenv, kStack)); - } - return trampolineEvalForms(fn.forms, elenv, kStack); - case DYN_SCOPE: - kStack.push(new Frame(fn.namespace, fn.parameters, values, undefined)); - return trampolineEvalForms(fn.forms, fn.lenv, kStack); - default: - throw new CannotHappen('trampolineInvokeFun'); +function trampolineEvalHandlerBind(form, lenv) { + const analysis = analyzeHandlerBind(form); + if (isError(analysis)) return analysis; + const [handlerForm, serialForms] = analysis; + trampolineStack.push(new TrampolineHandlerBindHandlerFormCont(serialForms, lenv)); + return new EvalReq(handlerForm, lenv); +} + +class TrampolineHandlerBindHandlerFormCont extends TrampolineCont { + constructor(serialForms, lenv) { + super(); + this.serialForms = serialForms; + this.lenv = lenv; + } + invoke(outcome) { + const {serialForms, lenv} = this; + if (isAbruptCompletion(outcome)) return outcome; + const handler = outcome.primaryValue(); + if (!(handler instanceof EVLFunction)) { + return new HandlerFormTypeError(); } - } else { - callOperatorFormError(); + trampolineStack.push(new TrampolineHandlerBindSerialFormsCont(handler)); + return trampolineEvalSerialForms(serialForms, lenv); } } -class TrampolineMacroCont extends TrampolineCont { - constructor(lenv, kStack) { - super(lenv, kStack); +class TrampolineHandlerBindSerialFormsCont extends TrampolineCont { + constructor(handler) { + super(); + this.handler = handler; } - invoke(result) { - const {lenv, kStack} = this; - const expansion = result.primaryValue(); - return new EvalReq(expansion, lenv); + invoke(outcome) { + const {handler} = this; + if (isError(outcome)) { + trampolineStack.push(new TrampolineHandlerBindInvocationCont(outcome)); + return trampolineInvoke(false, handler, [outcome.category, outcome.description]); + } else { + return outcome; + } } } -/**************************/ -/* Trampoline++ Evaluator */ -/**************************/ - -function trampolineppEval(form, lenv = null) { - if (lenv === null) { - form = trampolineppPreprocessForm(form, nullDefiniteEnv); - lenv = nullDefiniteEnv; +class TrampolineHandlerBindInvocationCont extends TrampolineCont { + constructor(serialFormsOutcome) { + super(); + this.serialFormsOutcome = serialFormsOutcome; } - const kStack = new TrampolineppControlStack(); - kStack.push(trampolineppEndCont); - let bounce = new EvalReq(form, lenv); - while (true) { - if (abortSignalArray !== null && abortSignalArray[0] === 1) { - throw new Aborted(); - } - if (bounce instanceof EvalReq) { - try { - bounce = bounce.form.eval(bounce.lenv, kStack); - } catch(exception) { - bounce = kStack.handleError(exception); - } + invoke(outcome) { + const {serialFormsOutcome} = this; + if (isAbruptCompletion(outcome)) { + return outcome; } else { - const k = kStack.popCont(); - if (k instanceof TrampolineppEndCont) { - return bounce; - } else { - try { - bounce = k.invoke(bounce); - } catch(exception) { - bounce = kStack.handleError(exception); - } - } + return serialFormsOutcome; } } } -class TrampolineppControlStack { - constructor() { - this.stack = []; // element: TrampolineppCont, TrampolineppErrorHandler, or Frame +function trampolineEvalUnwindProtect(form, lenv) { + const analysis = analyzeUnwindProtect(form); + if (isError(analysis)) return analysis; + const [protectedForm, cleanupForms] = analysis; + trampolineStack.push(new TrampolineUnwindProtectProtectedFormCont(cleanupForms, lenv)); + return new EvalReq(protectedForm, lenv); +} + +class TrampolineUnwindProtectProtectedFormCont extends TrampolineCont { + constructor(cleanupForms, lenv) { + super(); + this.cleanupForms = cleanupForms; + this.lenv = lenv; } - push(element) { - this.stack.push(element); + invoke(outcome) { + const {cleanupForms, lenv} = this; + trampolineStack.push(new TrampolineUnwindProtectCleanupFormsCont(outcome)); + return trampolineEvalSerialForms(cleanupForms, lenv); } - popCont() { - while (true) { - const element = this.stack.pop(); - if (element instanceof TrampolineppCont) { - return element; - } +} + +class TrampolineUnwindProtectCleanupFormsCont extends TrampolineCont { + constructor(protectedFormOutcome) { + super(); + this.protectedFormOutcome = protectedFormOutcome; + } + invoke(outcome) { + const {protectedFormOutcome} = this; + if (isAbruptCompletion(outcome)) { + return outcome; + } else { + return protectedFormOutcome; } } - handleError(exception) { - while (true) { - const element = this.stack.pop(); - if (element instanceof TrampolineppErrorHandler) { - return new EVLString(exception.name); - } else if (element instanceof TrampolineppEndCont) { - throw exception; - } +} + +function trampolineEvalCall(mv, apply, form, lenv) { + const analysis = analyzeCall(mv, apply, form, lenv); + if (isError(analysis)) return analysis; + const [macroCall, operator, operands] = analysis; + if (macroCall) { + return trampolineEvalMacroCall(form, operator, operands, lenv); + } else { + return trampolineEvalFunctionCall(mv, apply, operator, operands, lenv); + } +} + +function trampolineEvalMacroCall(form, macro, macroOperands, lenv) { + const args = listToArray(macroOperands); + trampolineStack.push(new TrampolineMacroCont(form, lenv)); + return trampolineInvoke(false, macro, args); +} + +class TrampolineMacroCont extends TrampolineCont { + constructor(form, lenv) { + super(); + this.form = form; + this.lenv = lenv; + } + invoke(outcome) { + const {form, lenv} = this; + if (isAbruptCompletion(outcome)) return outcome; + const expansion = outcome.primaryValue(); + if (optimizeMacroCalls) { + alterForm(form, expansion); } + return new EvalReq(expansion, lenv); } - ref(namespace, variable) { - for (let i = this.stack.length - 1; i >= 0; i--) { - const element = this.stack[i]; - if (element instanceof Frame) { - const result = element.ref(namespace, variable); - if (result !== undefined) { - return result; - } - } +} + +function trampolineEvalFunctionCall(mv, apply, operatorForm, operandForms, lenv) { + trampolineStack.push(new TrampolineFunctionCallOperatorFormCont(mv, apply, operandForms, lenv)); + return trampolineEvalOperatorForm(operatorForm, lenv); +} + +function trampolineEvalOperatorForm(operatorForm, lenv) { + if (operatorForm instanceof EVLVariable) { + return lenv.ref(FUN_NS, operatorForm); + } else { + return new EvalReq(operatorForm, lenv); + } +} + +class TrampolineFunctionCallOperatorFormCont extends TrampolineCont { + constructor(mv, apply, operandForms, lenv) { + super(); + this.mv = mv; + this.apply = apply; + this.operandForms = operandForms; + this.lenv = lenv; + } + invoke(outcome) { + const {mv, apply, operandForms, lenv} = this; + if (isAbruptCompletion(outcome)) return outcome; + const fn = outcome.primaryValue(); + if (!(fn instanceof EVLFunction)) { + return new OperatorFormTypeError(); } - return GlobalEnv.ref(namespace, variable); + return trampolineEvalOperandForms(mv, apply, fn, operandForms, [], lenv); } - set(namespace, variable, value) { - for (let i = this.stack.length - 1; i >= 0; i--) { - const element = this.stack[i]; - if (element instanceof Frame) { - const result = element.set(namespace, variable, value); - if (result !== undefined) { - return result; +} + +function trampolineEvalOperandForms(mv, apply, fn, operandForms, args, lenv) { + if (operandForms === EVLEmptyList.NIL) { + return trampolineInvoke(apply, fn, args); + } else { + trampolineStack.push(new TrampolineFunctionCallOperandFormCont(mv, apply, fn, operandForms, args, lenv)); + return new EvalReq(operandForms.car, lenv); + } +} + +class TrampolineFunctionCallOperandFormCont extends TrampolineCont { + constructor(mv, apply, fn, operandForms, args, lenv) { + super(); + this.mv = mv; + this.apply = apply; + this.fn = fn; + this.operandForms = operandForms; + this.args = args; + this.lenv = lenv; + } + invoke(outcome) { + const {mv, apply, fn, operandForms, args, lenv} = this; + if (isAbruptCompletion(outcome)) return outcome; + if (mv) { + outcome.allValues().forEach(value => args.push(value)); + } else { + args.push(outcome.primaryValue()); + } + return trampolineEvalOperandForms(mv, apply, fn, operandForms.cdr, args, lenv); + } +} + +function trampolineInvoke(apply, fn, args) { + if (fn instanceof EVLPrimitiveFunction) { + const values = pairPrimFunParameters(apply, args, fn.arityMin, fn.arityMax); + if (isError(values)) return values; + return fn.jsFunction(values); + } else if (fn instanceof EVLClosure) { + const values = pairClosureParameters(apply, args, fn.parameters, fn.rest); + if (isError(values)) return values; + switch (fn.scope) { + case LEX_SCOPE: + const elenv = new Frame(fn.namespace, fn.parameters, values, fn.lenv); + return trampolineEvalSerialForms(fn.serialForms, elenv); + case DYN_SCOPE: + trampolineStack.push(new Frame(fn.namespace, fn.parameters, values, null)); + return trampolineEvalSerialForms(fn.serialForms, fn.lenv); + default: + throw new CannotHappen('trampolineInvoke'); + } + } else { + throw new CannotHappen('trampolineInvoke'); + } +} + +/**************************/ +/* Trampoline++ Evaluator */ +/**************************/ + +let trampolineppStack = null; + +class TrampolineppStack { + constructor() { + this.stack = [trampolineppEndCont]; // array of TrampolineppCont's and/or Frame's + this.denv = nullDefiniteEnv; + } + push(element) { + if (element instanceof TrampolineppCont) { + this.stack.push(element); + } else if (element instanceof Frame) { + this.stack.push(element); + element.next = this.denv; + this.denv = element; + } else { + throw new CannotHappen('TrampolineppStack.push'); + } + } +} + +function trampolineppEval(form, lenv = null) { + if (lenv === null) { + form = trampolineppPreprocessForm(form, nullDefiniteEnv); + if (isAbruptCompletion(form)) return form; + lenv = nullDefiniteEnv; + } + trampolineppStack = new TrampolineppStack(); + let bounce = new EvalReq(form, lenv); + while (true) { + if (abortSignalArray !== null && abortSignalArray[0] === 1) { + throw new Aborted(); + } + if (bounce instanceof EvalReq) { + bounce = bounce.form.eval(bounce.lenv); + } else if (bounce instanceof Outcome) { + while (true) { + const element = trampolineppStack.stack.pop(); + if (element instanceof TrampolineppEndCont) { + return element.invoke(bounce); + } else if (element instanceof TrampolineppCont) { + bounce = element.invoke(bounce); + break; + } else if (element instanceof Frame) { + trampolineppStack.denv = element.next; + } else { + throw new CannotHappen('trampolineppEval'); } } + } else { + throw new CannotHappen('trampolineppEval'); } - return GlobalEnv.set(namespace, variable, value); } } function trampolineppPreprocessForm(form, lenv) { if (form instanceof EVLEmptyList) { - emptyListError(); + return new EmptyListError(); } else if (form instanceof EVLCons) { switch (form.car) { case quoteVariable: @@ -3682,6 +4914,8 @@ function trampolineppPreprocessForm(form, lenv) { return trampolineppPreprocessProgn(form, lenv); case ifVariable: return trampolineppPreprocessIf(form, lenv); + case _forEachVariable: + return trampolineppPreprocessForEach(form, lenv); case _vlambdaVariable: return trampolineppPreprocessLambda(LEX_SCOPE, VAL_NS, false, form, lenv); case _mlambdaVariable: @@ -3702,10 +4936,20 @@ function trampolineppPreprocessForm(form, lenv) { return trampolineppPreprocessRef(DYN_SCOPE, VAL_NS, form, lenv); case dsetVariable: return trampolineppPreprocessSet(DYN_SCOPE, VAL_NS, form, lenv); - case _forEachVariable: - return trampolineppPreprocessForEach(form, lenv); - case _catchErrorsVariable: - return trampolineppPreprocessCatchErrors(form, lenv); + case blockVariable: + return trampolineppPreprocessBlock(form, lenv); + case returnFromVariable: + return trampolineppPreprocessReturnFrom(form, lenv); + case catchVariable: + return trampolineppPreprocessCatch(form, lenv); + case throwVariable: + return trampolineppPreprocessThrow(form, lenv); + case _handlerBindVariable: + return trampolineppPreprocessHandlerBind(form, lenv); + case unwindProtectVariable: + return trampolineppPreprocessUnwindProtect(form, lenv); + case mletVariable: + return trampolineppPreprocessMlet(form, lenv); case applyVariable: return trampolineppPreprocessCall(false, true, form, lenv); case multipleValueCallVariable: @@ -3716,9 +4960,9 @@ function trampolineppPreprocessForm(form, lenv) { return trampolineppPreprocessCall(false, false, form, lenv); } } else if (form instanceof EVLVariable) { - return trampolineppPreprocessRef2(LEX_SCOPE, VAL_NS, form, lenv); + return trampolineppPreprocessAnalyzedRef(LEX_SCOPE, VAL_NS, form, lenv); } else { - return new TrampolineppQuote(form); + return trampolineppPreprocessAnalyzedQuote(form, lenv); } } @@ -3726,23 +4970,25 @@ function trampolineppPreprocessForms(forms, lenv) { if (forms === EVLEmptyList.NIL) { return EVLEmptyList.NIL; } else { - return new EVLCons( - trampolineppPreprocessForm(forms.car, lenv), - trampolineppPreprocessForms(forms.cdr, lenv) - ); + const preprocessedForm = trampolineppPreprocessForm(forms.car, lenv); + if (isAbruptCompletion(preprocessedForm)) return preprocessedForm; + const preprocessedForms = trampolineppPreprocessForms(forms.cdr, lenv); + if (isAbruptCompletion(preprocessedForms)) return preprocessedForms; + return new EVLCons(preprocessedForm, preprocessedForms); } } class TrampolineppCont { // abstract class - constructor(lenv, kStack) { - this.lenv = lenv; - this.kStack = kStack; + constructor() { } } class TrampolineppEndCont extends TrampolineppCont { constructor() { - super(null, null); + super(); + } + invoke(outcome) { + return outcome; } } @@ -3752,7 +4998,13 @@ class TrampolineppForm { // abstract class } function trampolineppPreprocessQuote(form, lenv) { - const [literal] = analyzeQuote(form); + const analysis = analyzeQuote(form); + if (isError(analysis)) return analysis; + const [literal] = analysis; + return trampolineppPreprocessAnalyzedQuote(literal, lenv); +} + +function trampolineppPreprocessAnalyzedQuote(literal, lenv) { return new TrampolineppQuote(literal); } @@ -3761,56 +5013,72 @@ class TrampolineppQuote extends TrampolineppForm { super(); this.literal = literal; } - eval(lenv, kStack) { + eval(lenv) { const {literal} = this; return literal; } } function trampolineppPreprocessProgn(form, lenv) { - const [forms] = analyzeProgn(form); - const preprocessedForms = trampolineppPreprocessForms(forms, lenv); - return new TrampolineppProgn(preprocessedForms); + const analysis = analyzeProgn(form); + if (isError(analysis)) return analysis; + const [serialForms] = analysis; + const preprocessedSerialForms = trampolineppPreprocessForms(serialForms, lenv); + if (isAbruptCompletion(preprocessedSerialForms)) return preprocessedSerialForms; + return new TrampolineppProgn(preprocessedSerialForms); } class TrampolineppProgn extends TrampolineppForm { - constructor(forms) { + constructor(serialForms) { super(); - this.forms = forms; + this.serialForms = serialForms; } - eval(lenv, kStack) { - const {forms} = this; - return trampolineppEvalForms(forms, lenv, kStack); + eval(lenv) { + const {serialForms} = this; + return trampolineppEvalSerialForms(serialForms, lenv); } } -function trampolineppEvalForms(forms, lenv, kStack) { - if (forms === EVLEmptyList.NIL) { +function trampolineppEvalSerialForms(serialForms, lenv) { + if (serialForms === EVLEmptyList.NIL) { return EVLVoid.VOID; - } else if (forms.cdr === EVLEmptyList.NIL) { - return new EvalReq(forms.car, lenv); } else { - kStack.push(new TrampolineppButLastFormCont(forms, lenv, kStack)); - return new EvalReq(forms.car, lenv); + return trampolineppEvalSerialFormForms(serialForms, lenv); + } +} + +function trampolineppEvalSerialFormForms(serialForms, lenv) { + if (serialForms.cdr === EVLEmptyList.NIL) { + return new EvalReq(serialForms.car, lenv); + } else { + trampolineppStack.push(new TrampolineppSerialFormCont(serialForms, lenv)); + return new EvalReq(serialForms.car, lenv); } } -class TrampolineppButLastFormCont extends TrampolineppCont { - constructor(forms, lenv, kStack) { - super(lenv, kStack); - this.forms = forms; +class TrampolineppSerialFormCont extends TrampolineppCont { + constructor(serialForms, lenv) { + super(); + this.serialForms = serialForms; + this.lenv = lenv; } - invoke(result) { - const {forms, lenv, kStack} = this; - return trampolineppEvalForms(forms.cdr, lenv, kStack); + invoke(outcome) { + const {serialForms, lenv} = this; + if (isAbruptCompletion(outcome)) return outcome; + return trampolineppEvalSerialFormForms(serialForms.cdr, lenv); } } function trampolineppPreprocessIf(form, lenv) { - const [testForm, thenForm, elseForm] = analyzeIf(form); + const analysis = analyzeIf(form); + if (isError(analysis)) return analysis; + const [testForm, thenForm, elseForm] = analysis; const preprocessedTestForm = trampolineppPreprocessForm(testForm, lenv); + if (isAbruptCompletion(preprocessedTestForm)) return preprocessedTestForm; const preprocessedThenForm = trampolineppPreprocessForm(thenForm, lenv); + if (isAbruptCompletion(preprocessedThenForm)) return preprocessedThenForm; const preprocessedElseForm = trampolineppPreprocessForm(elseForm, lenv); + if (isAbruptCompletion(preprocessedElseForm)) return preprocessedElseForm; return new TrampolineppIf(preprocessedTestForm, preprocessedThenForm, preprocessedElseForm); } @@ -3821,44 +5089,73 @@ class TrampolineppIf extends TrampolineppForm { this.thenForm = thenForm; this.elseForm = elseForm; } - eval(lenv, kStack) { + eval(lenv) { const {testForm, thenForm, elseForm} = this; - kStack.push(new TrampolineppIfTestFormCont(thenForm, elseForm, lenv, kStack)); + trampolineppStack.push(new TrampolineppIfTestFormCont(thenForm, elseForm, lenv)); return new EvalReq(testForm, lenv); } } class TrampolineppIfTestFormCont extends TrampolineppCont { - constructor(thenForm, elseForm, lenv, kStack) { - super(lenv, kStack); + constructor(thenForm, elseForm, lenv) { + super(); this.thenForm = thenForm; this.elseForm = elseForm; + this.lenv = lenv; } - invoke(result) { - const {thenForm, elseForm, lenv, kStack} = this; - const test = result.primaryValue(); + invoke(outcome) { + const {thenForm, elseForm, lenv} = this; + if (isAbruptCompletion(outcome)) return outcome; + const test = outcome.primaryValue(); switch (test) { case EVLBoolean.TRUE: return new EvalReq(thenForm, lenv); case EVLBoolean.FALSE: return new EvalReq(elseForm, lenv); default: - ifTestFormError(); + return new TestFormTypeError(); } } } +function trampolineppPreprocessForEach(form, lenv) { + const analysis = analyzeForEach(form); + if (isError(analysis)) return analysis; + const [functionForm, listForm] = analysis; + const preprocessedFunctionForm = trampolineppPreprocessForm(functionForm, lenv); + if (isAbruptCompletion(preprocessedFunctionForm)) return preprocessedFunctionForm; + const preprocessedListForm = trampolineppPreprocessForm(listForm, lenv); + if (isAbruptCompletion(preprocessedListForm)) return preprocessedListForm; + return new TrampolineppForEach(preprocessedFunctionForm, preprocessedListForm); +} + +class TrampolineppForEach extends TrampolineppForm { + constructor(functionForm, listForm) { + super(); + this.functionForm = functionForm; + this.listForm = listForm; + } + eval(lenv) { + const {functionForm, listForm} = this; + return new ForEachNotImplemented(); + } +} + function trampolineppPreprocessLambda(scope, namespace, macro, form, lenv) { - const [parameters, rest, forms] = analyzeLambda(form); + const analysis = analyzeLambda(form); + if (isError(analysis)) return analysis; + const [parameters, rest, serialForms] = analysis; switch (scope) { case LEX_SCOPE: { const elenv = new Frame(namespace, parameters, new Array(parameters.length).fill(null), lenv); - const preprocessedForms = trampolineppPreprocessForms(forms, elenv); - return new TrampolineppLambda(scope, namespace, macro, parameters, rest, preprocessedForms); + const preprocessedSerialForms = trampolineppPreprocessForms(serialForms, elenv); + if (isAbruptCompletion(preprocessedSerialForms)) return preprocessedSerialForms; + return new TrampolineppLambda(scope, namespace, macro, parameters, rest, preprocessedSerialForms); } case DYN_SCOPE: { - const preprocessedForms = trampolineppPreprocessForms(forms, lenv); - return new TrampolineppLambda(scope, namespace, macro, parameters, rest, preprocessedForms); + const preprocessedSerialForms = trampolineppPreprocessForms(serialForms, lenv); + if (isAbruptCompletion(preprocessedSerialForms)) return preprocessedSerialForms; + return new TrampolineppLambda(scope, namespace, macro, parameters, rest, preprocessedSerialForms); } default: throw new CannotHappen('trampolineppPreprocessLambda'); @@ -3866,67 +5163,53 @@ function trampolineppPreprocessLambda(scope, namespace, macro, form, lenv) { } class TrampolineppLambda extends TrampolineppForm { - constructor(scope, namespace, macro, parameters, rest, forms) { + constructor(scope, namespace, macro, parameters, rest, serialForms) { super(); this.scope = scope; this.namespace = namespace; this.macro = macro; this.parameters = parameters; this.rest = rest; - this.forms = forms; + this.serialForms = serialForms; } - eval(lenv, kStack) { - const {scope, namespace, macro, parameters, rest, forms} = this; - return new EVLClosure(scope, namespace, macro, parameters, rest, forms, lenv); + eval(lenv) { + const {scope, namespace, macro, parameters, rest, serialForms} = this; + return new EVLClosure(scope, namespace, macro, parameters, rest, serialForms, lenv); } } -const optimizeLexicalVariables = true; - function trampolineppPreprocessRef(scope, namespace, form, lenv) { - const [variable] = analyzeRef(form); - return new trampolineppPreprocessRef2(scope, namespace, variable, lenv); + const analysis = analyzeRef(form); + if (isError(analysis)) return analysis; + const [variable] = analysis; + return trampolineppPreprocessAnalyzedRef(scope, namespace, variable, lenv); } -function trampolineppPreprocessRef2(scope, namespace, variable, lenv) { - if (!optimizeLexicalVariables) { - return new TrampolineppRef(scope, namespace, variable); - } else { - switch (scope) { - case LEX_SCOPE: - const [i, j, value] = lenv.preprocessorRef(namespace, variable, 0); - if (i !== null && j !== null) { - return new TrampolineppLRef(i, j); - } else if (i === null && j === null) { - return new TrampolineppGRef(namespace, variable); - } else { - throw new CannotHappen('trampolineppPreprocessRef2'); - } - case DYN_SCOPE: - return new TrampolineppDRef(namespace, variable); - default: - throw new CannotHappen('trampolineppPreprocessRef2'); - } +function trampolineppPreprocessAnalyzedRef(scope, namespace, variable, lenv) { + switch (scope) { + case LEX_SCOPE: + const [global, i, j] = lenv.lookup(namespace, variable, 0); + if (global) { + return new TrampolineppGRef(namespace, variable); + } else { + return new TrampolineppLRef(i, j); + } + case DYN_SCOPE: + return new TrampolineppDRef(namespace, variable); + default: + throw new CannotHappen('trampolineppPreprocessAnalyzedRef'); } } -class TrampolineppRef extends TrampolineppForm { - constructor(scope, namespace, variable) { +class TrampolineppGRef extends TrampolineppForm { + constructor(namespace, variable) { super(); - this.scope = scope; this.namespace = namespace; this.variable = variable; } - eval(lenv, kStack) { - const {scope, namespace, variable} = this; - switch (scope) { - case LEX_SCOPE: - return lenv.ref(namespace, variable); - case DYN_SCOPE: - return kStack.ref(namespace, variable); - default: - throw new CannotHappen('trampolineppRef.eval'); - } + eval(lenv) { + const {namespace, variable} = this; + return GlobalEnv.ref(namespace, variable); } } @@ -3936,7 +5219,7 @@ class TrampolineppLRef extends TrampolineppForm { this.i = i; this.j = j; } - eval(lenv, kStack) { + eval(lenv) { const {i, j} = this; let frame = lenv; for (let n = i; n > 0; n--) { @@ -3946,87 +5229,64 @@ class TrampolineppLRef extends TrampolineppForm { } } -class TrampolineppGRef extends TrampolineppForm { - constructor(namespace, variable) { - super(); - this.namespace = namespace; - this.variable = variable; - } - eval(lenv, kStack) { - const {namespace, variable} = this; - return GlobalEnv.ref(namespace, variable); - } -} - class TrampolineppDRef extends TrampolineppForm { constructor(namespace, variable) { super(); this.namespace = namespace; this.variable = variable; } - eval(lenv, kStack) { + eval(lenv) { const {namespace, variable} = this; - return kStack.ref(namespace, variable); + return trampolineppStack.denv.ref(namespace, variable); } } function trampolineppPreprocessSet(scope, namespace, form, lenv) { - const [variable, valueForm] = analyzeSet(form); + const analysis = analyzeSet(form); + if (isError(analysis)) return analysis; + const [variable, valueForm] = analysis; const preprocessedValueForm = trampolineppPreprocessForm(valueForm, lenv); - if (!optimizeLexicalVariables) { - return new TrampolineppSet(scope, namespace, variable, preprocessedValueForm); - } else { - switch (scope) { - case LEX_SCOPE: - const [i, j, value] = lenv.preprocessorRef(namespace, variable, 0); - if (i !== null && j !== null) { - return new TrampolineppLSet(i, j, preprocessedValueForm); - } else if (i === null && j === null) { - return new TrampolineppGSet(namespace, variable, preprocessedValueForm); - } else { - throw new CannotHappen('trampolineppPreprocessSet'); - } - case DYN_SCOPE: - return new TrampolineppDSet(namespace, variable, preprocessedValueForm); - default: - throw new CannotHappen('trampolineppPreprocessSet'); - } + if (isAbruptCompletion(preprocessedValueForm)) return preprocessedValueForm; + switch (scope) { + case LEX_SCOPE: + const [global, i, j] = lenv.lookup(namespace, variable, 0); + if (global) { + return new TrampolineppGSet(namespace, variable, preprocessedValueForm); + } else { + return new TrampolineppLSet(i, j, preprocessedValueForm); + } + case DYN_SCOPE: + return new TrampolineppDSet(namespace, variable, preprocessedValueForm); + default: + throw new CannotHappen('trampolineppPreprocessSet'); } } -class TrampolineppSet extends TrampolineppForm { - constructor(scope, namespace, variable, valueForm) { +class TrampolineppGSet extends TrampolineppForm { + constructor(namespace, variable, valueForm) { super(); - this.scope = scope; this.namespace = namespace; this.variable = variable; this.valueForm = valueForm; } - eval(lenv, kStack) { - const {scope, namespace, variable, valueForm} = this; - kStack.push(new TrampolineppSetValueFormCont(scope, namespace, variable, lenv, kStack)); + eval(lenv) { + const {namespace, variable, valueForm} = this; + trampolineppStack.push(new TrampolineppGSetValueFormCont(namespace, variable)); return new EvalReq(valueForm, lenv); } } -class TrampolineppSetValueFormCont extends TrampolineppCont { - constructor(scope, namespace, variable, lenv, kStack) { - super(lenv, kStack); - this.scope = scope; +class TrampolineppGSetValueFormCont extends TrampolineppCont { + constructor(namespace, variable) { + super(); this.namespace = namespace; this.variable = variable; } - invoke(result) { - const {scope, namespace, variable, lenv, kStack} = this; - const value = result.primaryValue() - switch (scope) { - case LEX_SCOPE: - return lenv.set(namespace, variable, value); - case DYN_SCOPE: - return kStack.set(namespace, variable, value); - default: - throw new CannotHappen('TrampolineppSetValueFormCont.invoke'); - } + invoke(outcome) { + const {namespace, variable} = this; + if (isAbruptCompletion(outcome)) return outcome; + const value = outcome.primaryValue(); + return GlobalEnv.set(namespace, variable, value); } } @@ -4037,22 +5297,24 @@ class TrampolineppLSet extends TrampolineppForm { this.j = j; this.valueForm = valueForm; } - eval(lenv, kStack) { + eval(lenv) { const {i, j, valueForm} = this; - kStack.push(new TrampolineppLSetValueFormCont(i, j, lenv, kStack)); + trampolineppStack.push(new TrampolineppLSetValueFormCont(i, j, lenv)); return new EvalReq(valueForm, lenv); } } class TrampolineppLSetValueFormCont extends TrampolineppCont { - constructor(i, j, lenv, kStack) { - super(lenv, kStack); + constructor(i, j, lenv) { + super(); this.i = i; this.j = j; + this.lenv = lenv; } - invoke(result) { - const {i, j, lenv, kStack} = this; - const value = result.primaryValue(); + invoke(outcome) { + const {i, j, lenv} = this; + if (isAbruptCompletion(outcome)) return outcome; + const value = outcome.primaryValue(); let frame = lenv; for (let n = i; n > 0; n--) { frame = frame.next; @@ -4061,305 +5323,777 @@ class TrampolineppLSetValueFormCont extends TrampolineppCont { } } -class TrampolineppGSet extends TrampolineppForm { +class TrampolineppDSet extends TrampolineppForm { constructor(namespace, variable, valueForm) { super(); this.namespace = namespace; this.variable = variable; this.valueForm = valueForm; } - eval(lenv, kStack) { + eval(lenv) { const {namespace, variable, valueForm} = this; - kStack.push(new TrampolineppGSetValueFormCont(namespace, variable, lenv, kStack)); + trampolineppStack.push(new TrampolineppDSetValueFormCont(namespace, variable)); return new EvalReq(valueForm, lenv); } } -class TrampolineppGSetValueFormCont extends TrampolineppCont { - constructor(namespace, variable, lenv, kStack) { - super(lenv, kStack); +class TrampolineppDSetValueFormCont extends TrampolineppCont { + constructor(namespace, variable) { + super(); this.namespace = namespace; this.variable = variable; } - invoke(result) { - const {namespace, variable, lenv, kStack} = this; - const value = result.primaryValue(); - return GlobalEnv.set(namespace, variable, value); + invoke(outcome) { + const {namespace, variable} = this; + if (isAbruptCompletion(outcome)) return outcome; + const value = outcome.primaryValue(); + return trampolineppStack.denv.set(namespace, variable, value); } } -class TrampolineppDSet extends TrampolineppForm { - constructor(namespace, variable, valueForm) { +function trampolineppPreprocessBlock(form, lenv) { + const analysis = analyzeBlock(form); + if (isError(analysis)) return analysis; + const [blockName, serialForms] = analysis; + const elenv = new Frame(BLK_NS, [blockName], [null], lenv); + const preprocessedSerialForms = trampolineppPreprocessForms(serialForms, elenv); + if (isAbruptCompletion(preprocessedSerialForms)) return preprocessedSerialForms; + return new TrampolineppBlock(blockName, preprocessedSerialForms); +} + +class TrampolineppBlock extends TrampolineppForm { + constructor(blockName, serialForms) { super(); - this.namespace = namespace; - this.variable = variable; - this.valueForm = valueForm; + this.blockName = blockName; + this.serialForms = serialForms; } - eval(lenv, kStack) { - const {namespace, variable, valueForm} = this; - kStack.push(new TrampolineppDSetValueFormCont(namespace, variable, lenv, kStack)); - return new EvalReq(valueForm, lenv); + eval(lenv) { + const {blockName, serialForms} = this; + const exitTag = new EVLVariable('exit-tag'); + const elenv = new Frame(BLK_NS, [blockName], [exitTag], lenv); + trampolineppStack.push(new Frame(XIT_NS, [exitTag], [EVLVoid.VOID], null)); + trampolineppStack.push(new TrampolineppBlockSerialFormsCont(exitTag)); + return trampolineppEvalSerialForms(serialForms, elenv); } } -class TrampolineppDSetValueFormCont extends TrampolineppCont { - constructor(namespace, variable, lenv, kStack) { - super(lenv, kStack); - this.namespace = namespace; - this.variable = variable; +class TrampolineppBlockSerialFormsCont extends TrampolineppCont { + constructor(exitTag) { + super(); + this.exitTag = exitTag; + } + invoke(outcome) { + const {exitTag} = this; + if (isNonlocalExit(outcome) && outcome.exitTag === exitTag) { + return outcome.values; + } else { + return outcome; + } + } +} + +function trampolineppPreprocessReturnFrom(form, lenv) { + const analysis = analyzeReturnFrom(form); + if (isError(analysis)) return analysis; + const [blockName, valuesForm] = analysis; + const preprocessedValuesForm = trampolineppPreprocessForm(valuesForm, lenv); + if (isAbruptCompletion(preprocessedValuesForm)) return preprocessedValuesForm; + return new TrampolineppReturnFrom(blockName, preprocessedValuesForm); +} + +class TrampolineppReturnFrom extends TrampolineppForm { + constructor(blockName, valuesForm) { + super(); + this.blockName = blockName; + this.valuesForm = valuesForm; + } + eval(lenv) { + const {blockName, valuesForm} = this; + const exitTag = lenv.ref(BLK_NS, blockName); + if (exitTag === null) { + return new NoBlock(blockName); + } + const exitPoint = trampolineppStack.denv.ref(XIT_NS, exitTag); + if (exitPoint === null) { + return new NoBlockExitPoint(blockName); + } + trampolineppStack.push(new TrampolineppReturnFromValuesFormCont(exitTag)); + return new EvalReq(valuesForm, lenv); + } +} + +class TrampolineppReturnFromValuesFormCont extends TrampolineppCont { + constructor(exitTag) { + super(); + this.exitTag = exitTag; + } + invoke(outcome) { + const {exitTag} = this; + if (isAbruptCompletion(outcome)) return outcome; + return new NonlocalExit(exitTag, outcome); + } +} + +function trampolineppPreprocessCatch(form, lenv) { + const analysis = analyzeCatch(form); + if (isError(analysis)) return analysis; + const [exitTagForm, serialForms] = analysis; + const preprocessedExitTagForm = trampolineppPreprocessForm(exitTagForm, lenv); + if (isAbruptCompletion(preprocessedExitTagForm)) return preprocessedExitTagForm; + const preprocessedSerialForms = trampolineppPreprocessForms(serialForms, lenv); + if (isAbruptCompletion(preprocessedSerialForms)) return preprocessedSerialForms; + return new TrampolineppCatch(preprocessedExitTagForm, preprocessedSerialForms); +} + +class TrampolineppCatch extends TrampolineppForm { + constructor(exitTagForm, serialForms) { + super(); + this.exitTagForm = exitTagForm; + this.serialForms = serialForms; } - invoke(result) { - const {namespace, variable, lenv, kStack} = this; - const value = result.primaryValue(); - return kStack.set(namespace, variable, value); + eval(lenv) { + const {exitTagForm, serialForms} = this; + trampolineppStack.push(new TrampolineppCatchExitTagFormCont(serialForms, lenv)); + return new EvalReq(exitTagForm, lenv); + } +} + +class TrampolineppCatchExitTagFormCont extends TrampolineppCont { + constructor(serialForms, lenv) { + super(); + this.serialForms = serialForms; + this.lenv = lenv; + } + invoke(outcome) { + const {serialForms, lenv} = this; + if (isAbruptCompletion(outcome)) return outcome; + const exitTag = outcome.primaryValue(); + if (!(exitTag instanceof EVLVariable)) { + return new ExitTagFormTypeError(); + } + trampolineppStack.push(new Frame(XIT_NS, [exitTag], [EVLVoid.VOID], null)); + trampolineppStack.push(new TrampolineppCatchSerialFormsCont(exitTag)); + return trampolineppEvalSerialForms(serialForms, lenv); + } +} + +class TrampolineppCatchSerialFormsCont extends TrampolineppCont { + constructor(exitTag) { + super(); + this.exitTag = exitTag; + } + invoke(outcome) { + const {exitTag} = this; + if (isNonlocalExit(outcome) && outcome.exitTag === exitTag) { + return outcome.values; + } else { + return outcome; + } + } +} + +function trampolineppPreprocessThrow(form, lenv) { + const analysis = analyzeThrow(form); + if (isError(analysis)) return analysis; + const [exitTagForm, valuesForm] = analysis; + const preprocessedExitTagForm = trampolineppPreprocessForm(exitTagForm, lenv); + if (isAbruptCompletion(preprocessedExitTagForm)) return preprocessedExitTagForm; + const preprocessedValuesForm = trampolineppPreprocessForm(valuesForm, lenv); + if (isAbruptCompletion(preprocessedValuesForm)) return preprocessedValuesForm; + return new TrampolineppThrow(preprocessedExitTagForm, preprocessedValuesForm); +} + +class TrampolineppThrow extends TrampolineppForm { + constructor(exitTagForm, valuesForm) { + super(); + this.exitTagForm = exitTagForm; + this.valuesForm = valuesForm; + } + eval(lenv) { + const {exitTagForm, valuesForm} = this; + trampolineppStack.push(new TrampolineppThrowExitTagFormCont(valuesForm, lenv)); + return new EvalReq(exitTagForm, lenv); + } +} + +class TrampolineppThrowExitTagFormCont extends TrampolineppCont { + constructor(valuesForm, lenv) { + super(); + this.valuesForm = valuesForm; + this.lenv = lenv; + } + invoke(outcome) { + const {valuesForm, lenv} = this; + if (isAbruptCompletion(outcome)) return outcome; + const exitTag = outcome.primaryValue(); + if (!(exitTag instanceof EVLVariable)) { + return new ExitTagFormTypeError(); + } + const exitPoint = trampolineppStack.denv.ref(XIT_NS, exitTag); + if (exitPoint === null) { + return new NoCatchExitPoint(exitTag); + } + trampolineppStack.push(new TrampolineppThrowValuesFormCont(exitTag)); + return new EvalReq(valuesForm, lenv); + } +} + +class TrampolineppThrowValuesFormCont extends TrampolineppCont { + constructor(exitTag) { + super(); + this.exitTag = exitTag; + } + invoke(outcome) { + const {exitTag} = this; + if (isAbruptCompletion(outcome)) return outcome; + return new NonlocalExit(exitTag, outcome); + } +} + +function trampolineppPreprocessHandlerBind(form, lenv) { + const analysis = analyzeHandlerBind(form); + if (isError(analysis)) return analysis; + const [handlerForm, serialForms] = analysis; + const preprocessedHandlerForm = trampolineppPreprocessForm(handlerForm, lenv); + if (isAbruptCompletion(preprocessedHandlerForm)) return preprocessedHandlerForm; + const preprocessedSerialForms = trampolineppPreprocessForms(serialForms, lenv); + if (isAbruptCompletion(preprocessedSerialForms)) return preprocessedSerialForms; + return new TrampolineppHandlerBind(preprocessedHandlerForm, preprocessedSerialForms); +} + +class TrampolineppHandlerBind extends TrampolineppForm { + constructor(handlerForm, serialForms) { + super(); + this.handlerForm = handlerForm; + this.serialForms = serialForms; + } + eval(lenv) { + const {handlerForm, serialForms} = this; + trampolineppStack.push(new TrampolineppHandlerBindHandlerFormCont(serialForms, lenv)); + return new EvalReq(handlerForm, lenv); + } +} + +class TrampolineppHandlerBindHandlerFormCont extends TrampolineppCont { + constructor(serialForms, lenv) { + super(); + this.serialForms = serialForms; + this.lenv = lenv; + } + invoke(outcome) { + const {serialForms, lenv} = this; + if (isAbruptCompletion(outcome)) return outcome; + const handler = outcome.primaryValue(); + if (!(handler instanceof EVLFunction)) { + return new HandlerFormTypeError(); + } + trampolineppStack.push(new TrampolineppHandlerBindSerialFormsCont(handler)); + return trampolineppEvalSerialForms(serialForms, lenv); + } +} + +class TrampolineppHandlerBindSerialFormsCont extends TrampolineppCont { + constructor(handler) { + super(); + this.handler = handler; + } + invoke(outcome) { + const {handler} = this; + if (isError(outcome)) { + trampolineppStack.push(new TrampolineppHandlerBindInvocationCont(outcome)); + return trampolineppInvoke(false, handler, [outcome.category, outcome.description]); + } else { + return outcome; + } + } +} + +class TrampolineppHandlerBindInvocationCont extends TrampolineppCont { + constructor(serialFormsOutcome) { + super(); + this.serialFormsOutcome = serialFormsOutcome; + } + invoke(outcome) { + const {serialFormsOutcome} = this; + if (isAbruptCompletion(outcome)) { + return outcome; + } else { + return serialFormsOutcome; + } + } +} + +function trampolineppPreprocessUnwindProtect(form, lenv) { + const analysis = analyzeUnwindProtect(form); + if (isError(analysis)) return analysis; + const [protectedForm, cleanupForms] = analysis; + const preprocessedProtectedForm = trampolineppPreprocessForm(protectedForm, lenv); + if (isAbruptCompletion(preprocessedProtectedForm)) return preprocessedProtectedForm; + const preprocessedCleanupForms = trampolineppPreprocessForms(cleanupForms, lenv); + if (isAbruptCompletion(preprocessedCleanupForms)) return preprocessedCleanupForms; + return new TrampolineppUnwindProtect(preprocessedProtectedForm, preprocessedCleanupForms); +} + +class TrampolineppUnwindProtect extends TrampolineppForm { + constructor(protectedForm, cleanupForms) { + super(); + this.protectedForm = protectedForm + this.cleanupForms = cleanupForms; + } + eval(lenv) { + const {protectedForm, cleanupForms} = this; + trampolineppStack.push(new TrampolineppUnwindProtectProtectedFormCont(cleanupForms, lenv)); + return new EvalReq(protectedForm, lenv); + } +} + +class TrampolineppUnwindProtectProtectedFormCont extends TrampolineppCont { + constructor(cleanupForms, lenv) { + super(); + this.cleanupForms = cleanupForms; + this.lenv = lenv; + } + invoke(outcome) { + const {cleanupForms, lenv} = this; + trampolineppStack.push(new TrampolineppUnwindProtectCleanupFormsCont(outcome)); + return trampolineppEvalSerialForms(cleanupForms, lenv); + } +} + +class TrampolineppUnwindProtectCleanupFormsCont extends TrampolineppCont { + constructor(protectedFormOutcome) { + super(); + this.protectedFormOutcome = protectedFormOutcome; + } + invoke(outcome) { + const {protectedFormOutcome} = this; + if (isAbruptCompletion(outcome)) { + return outcome; + } else { + return protectedFormOutcome; + } + } +} + +function trampolineppPreprocessMlet(form, lenv) { + const analysis = analyzeMlet(form); + if (isError(analysis)) return analysis; + const [mletBindings, serialForms] = analysis; + const variables = mletBindings.map(mletBinding => mletBinding[0]); + const values = []; + for (const mletBinding of mletBindings) { + const [variable, parameterList, mlambdaSerialForms] = mletBinding; + const mlambda = new EVLCons(mlambdaVariable, new EVLCons(parameterList, mlambdaSerialForms)); + const _mlambda = trampolineppPreprocessForm(mlambda, nullDefiniteEnv); + if (isAbruptCompletion(_mlambda)) return _mlambda; + if (!(_mlambda instanceof TrampolineppLambda) || !_mlambda.macro) { + throw new CannotHappen('trampolineppPreprocessMlet'); + } + values.push(_mlambda.eval(nullDefiniteEnv)); + } + const elenv = new Frame(FUN_NS, variables, values, lenv); + const preprocessedSerialForms = trampolineppPreprocessForms(serialForms, elenv); + if (isAbruptCompletion(preprocessedSerialForms)) return preprocessedSerialForms; + return new TrampolineppMlet(mletBindings, preprocessedSerialForms); +} + +class TrampolineppMlet extends TrampolineppForm { + constructor(mletBindings, serialForms) { + super(); + this.mletBindings = mletBindings; + this.serialForms = serialForms; + } + eval(lenv) { + const {mletBindings, serialForms} = this; + const variables = mletBindings.map(mletBinding => mletBinding[0]); + const values = mletBindings.map(mletBinding => EVLVoid.VOID); + const elenv = new Frame(FUN_NS, variables, values, lenv); + return trampolineppEvalSerialForms(serialForms, elenv); + } +} + +function trampolineppPreprocessCall(mv, apply, form, lenv) { + const analysis = analyzeCall(mv, apply, form, lenv); + if (isError(analysis)) return analysis; + const [macroCall, operator, operands] = analysis; + if (macroCall) { + return trampolineppPreprocessMacroCall(operator, operands, lenv); + } else { + return trampolineppPreprocessFunctionCall(mv, apply, operator, operands, lenv); + } +} + +function trampolineppPreprocessMacroCall(macro, macroOperands, lenv) { + const args = listToArray(macroOperands); + const values = pairClosureParameters(false, args, macro.parameters, macro.rest); + if (isError(values)) return values; + const elenv = new Frame(macro.namespace, macro.parameters, values, macro.lenv); + const outcome = trampolineppEval(new TrampolineppProgn(macro.serialForms), elenv); + if (isAbruptCompletion(outcome)) return outcome; + const expansion = outcome.primaryValue(); + return trampolineppPreprocessForm(expansion, lenv); +} + +function trampolineppPreprocessFunctionCall(mv, apply, operatorForm, operandForms, lenv) { + const preprocessedOperatorForm = trampolineppPreprocessOperatorForm(operatorForm, lenv); + if (isAbruptCompletion(preprocessedOperatorForm)) return preprocessedOperatorForm; + const preprocessedOperandForms = trampolineppPreprocessForms(operandForms, lenv); + if (isAbruptCompletion(preprocessedOperandForms)) return preprocessedOperandForms; + return new TrampolineppFunctionCall(mv, apply, preprocessedOperatorForm, preprocessedOperandForms); +} + +function trampolineppPreprocessOperatorForm(operatorForm, lenv) { + if (operatorForm instanceof EVLVariable) { + return trampolineppPreprocessAnalyzedRef(LEX_SCOPE, FUN_NS, operatorForm, lenv); + } else { + return trampolineppPreprocessForm(operatorForm, lenv); + } +} + +class TrampolineppFunctionCall extends TrampolineppForm { + constructor(mv, apply, operatorForm, operandForms) { + super(); + this.mv = mv; + this.apply = apply; + this.operatorForm = operatorForm; + this.operandForms = operandForms; + } + eval(lenv) { + const {mv, apply, operatorForm, operandForms} = this; + trampolineppStack.push(new TrampolineppFunctionCallOperatorFormCont(mv, apply, operandForms, lenv)); + return trampolineppEvalOperatorForm(operatorForm, lenv); + } +} + +function trampolineppEvalOperatorForm(operatorForm, lenv) { + return new EvalReq(operatorForm, lenv); +} + +class TrampolineppFunctionCallOperatorFormCont extends TrampolineppCont { + constructor(mv, apply, operandForms, lenv) { + super(); + this.mv = mv; + this.apply = apply; + this.operandForms = operandForms; + this.lenv = lenv; + } + invoke(outcome) { + const {mv, apply, operandForms, lenv} = this; + if (isAbruptCompletion(outcome)) return outcome; + const fn = outcome.primaryValue(); + if (!(fn instanceof EVLFunction)) { + return new OperatorFormTypeError(); + } + return trampolineppEvalOperandForms(mv, apply, fn, operandForms, [], lenv); + } +} + +function trampolineppEvalOperandForms(mv, apply, fn, operandForms, args, lenv) { + if (operandForms === EVLEmptyList.NIL) { + return trampolineppInvoke(apply, fn, args); + } else { + trampolineppStack.push(new TrampolineppFunctionCallOperandFormCont(mv, apply, fn, operandForms, args, lenv)); + return new EvalReq(operandForms.car, lenv); + } +} + +class TrampolineppFunctionCallOperandFormCont extends TrampolineppCont { + constructor(mv, apply, fn, operandForms, args, lenv) { + super(); + this.mv = mv; + this.apply = apply; + this.fn = fn; + this.operandForms = operandForms; + this.args = args; + this.lenv = lenv; + } + invoke(outcome) { + const {mv, apply, fn, operandForms, args, lenv} = this; + if (isAbruptCompletion(outcome)) return outcome; + if (mv) { + outcome.allValues().forEach(value => args.push(value)); + } else { + args.push(outcome.primaryValue()); + } + return trampolineppEvalOperandForms(mv, apply, fn, operandForms.cdr, args, lenv); + } +} + +function trampolineppInvoke(apply, fn, args) { + if (fn instanceof EVLPrimitiveFunction) { + const values = pairPrimFunParameters(apply, args, fn.arityMin, fn.arityMax); + if (isError(values)) return values; + return fn.jsFunction(values); + } else if (fn instanceof EVLClosure) { + const values = pairClosureParameters(apply, args, fn.parameters, fn.rest); + if (isError(values)) return values; + switch (fn.scope) { + case LEX_SCOPE: + const elenv = new Frame(fn.namespace, fn.parameters, values, fn.lenv); + return trampolineppEvalSerialForms(fn.serialForms, elenv); + case DYN_SCOPE: + trampolineppStack.push(new Frame(fn.namespace, fn.parameters, values, null)); + return trampolineppEvalSerialForms(fn.serialForms, fn.lenv); + default: + throw new CannotHappen('trampolineppInvoke'); + } + } else { + throw new CannotHappen('trampolineppInvoke'); + } +} + +/**********************************/ +/* Primitive Function Definitions */ +/**********************************/ + +const primitiveFunctions = new Map(); + +function primitiveFunction(name, arityMin, arityMax, jsFunction) { + primitiveFunctions.set(name, [arityMin, arityMax, jsFunction]); +} + +function checkArgumentType(args, n, constructor) { + const arg = args[n]; + if (arg instanceof constructor) { + return arg; + } else { + return new ArgumentTypeError(n, constructor); + } +} + +/**********/ +/* Bounce */ +/**********/ + +class Bounce { // abstract class +} + +/**********************/ +/* Evaluation Request */ +/**********************/ + +class EvalReq extends Bounce { + constructor(form, lenv) { + super(); + this.form = form; + this.lenv = lenv; + } +} + +/***********/ +/* Outcome */ +/***********/ + +class Outcome extends Bounce { // abstract class + constructor() { + super(); } } -function trampolineppPreprocessForEach(form, lenv) { - const [functionForm, listForm] = analyzeForEach(form); - const preprocessedFunctionForm = trampolineppPreprocessForm(functionForm, lenv); - const preprocessedListForm = trampolineppPreprocessForm(listForm, lenv); - return new TrampolineppForEach(preprocessedFunctionForm, preprocessedListForm); -} +/*********************/ +/* Abrupt Completion */ +/*********************/ -class TrampolineppForEach extends TrampolineppForm { - constructor(functionForm, listForm) { +class AbruptCompletion extends Outcome { // abstract class + constructor() { super(); - this.functionForm = functionForm; - this.listForm = listForm; - } - eval(lenv, kStack) { - forEachNotImplemented(); } } -function trampolineppPreprocessCatchErrors(form, lenv) { - const [tryForm] = analyzeCatchErrors(form); - const preprocessedTryForm = trampolineppPreprocessForm(tryForm, lenv); - return new TrampolineppCatchErrors(preprocessedTryForm); +function isAbruptCompletion(outcome) { + return outcome instanceof AbruptCompletion; } -class TrampolineppCatchErrors extends TrampolineppForm { - constructor(tryForm) { +/***********************************/ +/* Abrupt Completion of Type Error */ +/***********************************/ + +class AbruptCompletionError extends AbruptCompletion { + constructor(category, description) { super(); - this.tryForm = tryForm; - } - eval(lenv, kStack) { - const {tryForm} = this; - kStack.push(new TrampolineppErrorHandler()); - kStack.push(new TrampolineppCatchErrorsTryFormCont(lenv, kStack)); - return new EvalReq(tryForm, lenv); + this.category = ensureEVLString(category); + this.description = ensureEVLString(description); } } -class TrampolineppErrorHandler { +function isError(outcome) { + return outcome instanceof AbruptCompletionError; } -class TrampolineppCatchErrorsTryFormCont extends TrampolineppCont { - constructor(lenv, kStack) { - super(lenv, kStack); - } - invoke(result) { - const {lenv, kStack} = this; - return EVLVoid.VOID; +class EmptyListError extends AbruptCompletionError { + constructor() { + super('empty-list-error', 'The empty list does not evaluate.'); } } -function trampolineppPreprocessCall(mv, apply, form, lenv) { - const [operator, operands] = analyzeCall(mv, apply, form); - if (operator instanceof EVLVariable) { - const [i, j, fn] = lenv.preprocessorRef(FUN_NS, operator, 0); - if (fn instanceof EVLClosure && fn.macro) { - const values = pairClosureParameters(false, listToArray(operands), fn.parameters, fn.rest); - const elenv = new Frame(fn.namespace, fn.parameters, values, fn.lenv); - const expansion = trampolineppEval(new TrampolineppProgn(fn.forms), elenv).primaryValue(); - return trampolineppPreprocessForm(expansion, lenv); - } else { - const preprocessedOperator = trampolineppPreprocessRef2(LEX_SCOPE, FUN_NS, operator, lenv); - const preprocessedOperands = trampolineppPreprocessForms(operands, lenv); - return new TrampolineppCall(mv, apply, preprocessedOperator, preprocessedOperands); - } - } else if (isMacroLet(operator, operands)) { - const preprocessedOperands = trampolineppPreprocessForms(operands, lenv); - const [parameters, rest, forms] = analyzeLambda(operator); - const values = listToArray(preprocessedOperands).map(preprocessedOperand => preprocessedOperand.eval(nullDefiniteEnv, null)); - const elenv = new Frame(FUN_NS, parameters, values, lenv); - const preprocessedForms = trampolineppPreprocessForms(forms, elenv); - const preprocessedOperator = new TrampolineppLambda(LEX_SCOPE, FUN_NS, false, parameters, rest, preprocessedForms); - return new TrampolineppCall(mv, apply, preprocessedOperator, preprocessedOperands); - } else { - const preprocessedOperator = trampolineppPreprocessForm(operator, lenv); - const preprocessedOperands = trampolineppPreprocessForms(operands, lenv); - return new TrampolineppCall(mv, apply, preprocessedOperator, preprocessedOperands); +class MalformedForm extends AbruptCompletionError { + constructor(formName) { + super('malformed-form', `Malformed ${formName}.`); } } -function isMacroLet(operator, operands) { - if (!(operator instanceof EVLCons)) return false; - if (operator.car !== _flambdaVariable) return false; - while (operands !== EVLEmptyList.NIL) { - const operand = operands.car; - if (!(operand instanceof EVLCons)) return false; - if (operand.car !== mlambdaVariable) return false; - operands = operands.cdr; +class ForEachNotImplemented extends AbruptCompletionError { + constructor() { + super('not-implemented', 'The _for-each form is not implemented.'); } - return true; } -class TrampolineppCall extends TrampolineppForm { - constructor(mv, apply, operator, operands) { - super(); - this.mv = mv; - this.apply = apply; - this.operator = operator; - this.operands = operands; +class UnboundVariable extends AbruptCompletionError { + constructor(variable, namespace) { + super('unbound-variable', `The variable '${variable.name}' is unbound in the ${namespace} namespace.`); } - eval(lenv, kStack) { - const {mv, apply, operator, operands} = this; - kStack.push(new TrampolineppOperatorCont(mv, apply, operands, lenv, kStack)); - return trampolineppEvalOperator(operator, lenv, kStack); +} + +class TestFormTypeError extends AbruptCompletionError { + constructor() { + super('form-type-error', 'The test form does not evaluate to a boolean.'); } } -function trampolineppEvalOperator(operator, lenv, kStack) { - return new EvalReq(operator, lenv); +class FunctionFormTypeError extends AbruptCompletionError { + constructor() { + super('form-type-error', 'The function form does not evaluate to a function.'); + } } -class TrampolineppOperatorCont extends TrampolineppCont { - constructor(mv, apply, operands, lenv, kStack) { - super(lenv, kStack); - this.mv = mv; - this.apply = apply; - this.operands = operands; +class ListFormTypeError extends AbruptCompletionError { + constructor() { + super('form-type-error', 'The list form does not evaluate to a proper list.'); } - invoke(result) { - const {mv, apply, operands, lenv, kStack} = this; - const fn = result.primaryValue(); - kStack.push(new TrampolineppOperandsCont(apply, fn, lenv, kStack)); - return trampolineppEvalOperands(mv, operands, [], lenv, kStack); +} + +class ExitTagFormTypeError extends AbruptCompletionError { + constructor() { + super('form-type-error', 'The exit-tag form does not evaluate to a variable.'); } } -function trampolineppEvalOperands(mv, operands, args, lenv, kStack) { - if (operands === EVLEmptyList.NIL) { - return args; - } else { - kStack.push(new TrampolineppOperandCont(mv, operands, args, lenv, kStack)); - return new EvalReq(operands.car, lenv); +class HandlerFormTypeError extends AbruptCompletionError { + constructor() { + super('form-type-error', 'The handler form does not evaluate to a function.'); } } -class TrampolineppOperandCont extends TrampolineppCont { - constructor(mv, operands, args, lenv, kStack) { - super(lenv, kStack); - this.mv = mv; - this.operands = operands; - this.args = args; +class OperatorFormTypeError extends AbruptCompletionError { + constructor() { + super('form-type-error', 'The operator form does not evaluate to a function.'); } - invoke(result) { - const {mv, operands, args, lenv, kStack} = this; - if (mv) { - result.allValues().forEach(value => args.push(value)); - } else { - args.push(result.primaryValue()); - } - return trampolineppEvalOperands(mv, operands.cdr, args, lenv, kStack); +} + +const ordinalRules = new Intl.PluralRules('en-US', {type: 'ordinal'}); +const ordinalSuffixes = new Map([['one', 'st'], ['two', 'nd'], ['few', 'rd'], ['other', 'th']]); + +function ordinalNumber(n) { + return n + ordinalSuffixes.get(ordinalRules.select(n)); +} + +class ArgumentTypeError extends AbruptCompletionError { + constructor(n, constructor) { + super('argument-type-error', `The ${ordinalNumber(n + 1)} argument is not of type ${constructor.name}.`); } } -class TrampolineppOperandsCont extends TrampolineppCont { - constructor(apply, fn, lenv, kStack) { - super(lenv, kStack); - this.apply = apply; - this.fn = fn; +class LengthNotNonnegativeInteger extends AbruptCompletionError { + constructor() { + super('argument-value-error', 'The length is not an nonnegative integer.'); } - invoke(args) { - const {apply, fn, lenv, kStack} = this; - return trampolineppInvokeFun(apply, fn, args, lenv, kStack); +} + +class IndexNotNonnegativeInteger extends AbruptCompletionError { + constructor() { + super('argument-value-error', 'The index is not an nonnegative integer.'); } } -function trampolineppInvokeFun(apply, fn, args, lenv, kStack) { - if (fn instanceof EVLPrimitiveFunction) { - const values = pairPrimFunParameters(apply, args, fn.arityMin, fn.arityMax); - return fn.jsFunction(values); - } else if (fn instanceof EVLClosure) { - const values = pairClosureParameters(apply, args, fn.parameters, fn.rest); - switch (fn.scope) { - case LEX_SCOPE: - const elenv = new Frame(fn.namespace, fn.parameters, values, fn.lenv); - return trampolineppEvalForms(fn.forms, elenv, kStack); - case DYN_SCOPE: - kStack.push(new Frame(fn.namespace, fn.parameters, values, undefined)); - return trampolineppEvalForms(fn.forms, fn.lenv, kStack); - default: - throw new CannotHappen('trampolineppInvokeFun'); - } - } else { - callOperatorFormError(); +class IndexOutOfBounds extends AbruptCompletionError { + constructor() { + super('argument-value-error', 'The index is out of bounds.'); } } -/**************************************/ -/* Primitive Function Definitions (1) */ -/**************************************/ +class NoBlock extends AbruptCompletionError { + constructor(blockName) { + super('no-block', `No block named '${blockName}'.`); + } +} -const primitiveFunctions = new Map(); +class NoBlockExitPoint extends AbruptCompletionError { + constructor(blockName) { + super('no-block-exit-point', `No exit point for block named '${blockName}'.`); + } +} -function primitiveFunction(name, arityMin, arityMax, jsFunction) { - primitiveFunctions.set(name, [arityMin, arityMax, jsFunction]); +class NoCatchExitPoint extends AbruptCompletionError { + constructor(exitTag) { + super('no-catch-exit-point', `No exit point with exit tag '${exitTag}'.`); + } } -const ordinalRules = new Intl.PluralRules('en-US', {type: 'ordinal'}); -const ordinalSuffixes = new Map([['one', 'st'], ['two', 'nd'], ['few', 'rd'], ['other', 'th']]); +class RunawayNonlocalExit extends AbruptCompletionError { + constructor() { + super('runaway-nonlocal-exit', 'Runaway nonlocal exit.'); + } +} -function ordinalNumber(n) { - return n + ordinalSuffixes.get(ordinalRules.select(n)); +class TooFewArguments extends AbruptCompletionError { + constructor() { + super('too-few-arguments', 'Too few arguments.'); + } } -function checkType(args, n, constructor) { - const arg = args[n]; - if (arg instanceof constructor) { - return arg; - } else { - throw new EvaluatorError(`The ${ordinalNumber(n + 1)} argument is not of type ${constructor.name}.`); +class TooManyArguments extends AbruptCompletionError { + constructor() { + super('too-many-arguments', 'Too many arguments.'); } } -/**********/ -/* Bounce */ -/**********/ +class SpreadError extends AbruptCompletionError { + constructor() { + super('spread-error', 'Malformed spreadable sequence of objects.'); + } +} -class Bounce { // abstract class +class ProgramError extends AbruptCompletionError { + constructor(description) { + super('program-error', description); + } } -/**********************/ -/* Evaluation Request */ -/**********************/ +/*******************************************/ +/* Abrupt Completion of Type Nonlocal Exit */ +/*******************************************/ -class EvalReq extends Bounce { - constructor(form, lenv) { +class NonlocalExit extends AbruptCompletion { + constructor(exitTag, values) { super(); - this.form = form; - this.lenv = lenv; + this.exitTag = exitTag; + this.values = values; } } +function isNonlocalExit(outcome) { + return outcome instanceof NonlocalExit; +} + /**********/ /* Result */ /**********/ -class Result extends Bounce { // abstract class +class Result extends Outcome { // abstract class constructor() { super(); } } -/**************/ -/* EVLObjects */ -/**************/ +function isNormalCompletion(outcome) { + return outcome instanceof Result; +} + +/*******************/ +/* Multiple Values */ +/*******************/ -class EVLObjects extends Result { +class MultipleValues extends Result { constructor(objects) { super(); this.objects = objects; @@ -4372,9 +6106,9 @@ class EVLObjects extends Result { } } -/*************/ -/* EVLObject */ -/*************/ +/******************************/ +/* Primitive Data Type object */ +/******************************/ class EVLObject extends Result { // abstract class constructor() { @@ -4403,9 +6137,9 @@ primitiveFunction('eql?', 2, 2, function(args) { return evlBoolean(args[0].eql(args[1])); }); -/***********/ -/* EVLVoid */ -/***********/ +/****************************/ +/* Primitive Data Type void */ +/****************************/ class EVLVoid extends EVLObject { constructor() { @@ -4427,9 +6161,9 @@ primitiveFunction('void?', 1, 1, function(args) { return evlBoolean(args[0] instanceof EVLVoid); }); -/**************/ -/* EVLBoolean */ -/**************/ +/*******************************/ +/* Primitive Data Type boolean */ +/*******************************/ class EVLBoolean extends EVLObject { constructor(jsValue) { @@ -4454,9 +6188,9 @@ primitiveFunction('boolean?', 1, 1, function(args) { return evlBoolean(args[0] instanceof EVLBoolean); }); -/*************/ -/* EVLNumber */ -/*************/ +/******************************/ +/* Primitive Data Type number */ +/******************************/ class EVLNumber extends EVLObject { constructor(jsValue) { @@ -4480,74 +6214,96 @@ primitiveFunction('number?', 1, 1, function(args) { }); primitiveFunction('_+', 2, 2, function(args) { - const x = checkType(args, 0, EVLNumber).jsValue; - const y = checkType(args, 1, EVLNumber).jsValue; - return new EVLNumber(x + y); + const x = checkArgumentType(args, 0, EVLNumber); + if (isError(x)) return x; + const y = checkArgumentType(args, 1, EVLNumber); + if (isError(y)) return y; + return new EVLNumber(x.jsValue + y.jsValue); }); primitiveFunction('_-', 2, 2, function(args) { - const x = checkType(args, 0, EVLNumber).jsValue; - const y = checkType(args, 1, EVLNumber).jsValue; - return new EVLNumber(x - y); + const x = checkArgumentType(args, 0, EVLNumber); + if (isError(x)) return x; + const y = checkArgumentType(args, 1, EVLNumber); + if (isError(y)) return y; + return new EVLNumber(x.jsValue - y.jsValue); }); primitiveFunction('_*', 2, 2, function(args) { - const x = checkType(args, 0, EVLNumber).jsValue; - const y = checkType(args, 1, EVLNumber).jsValue; - return new EVLNumber(x * y); + const x = checkArgumentType(args, 0, EVLNumber); + if (isError(x)) return x; + const y = checkArgumentType(args, 1, EVLNumber); + if (isError(y)) return y; + return new EVLNumber(x.jsValue * y.jsValue); }); primitiveFunction('_/', 2, 2, function(args) { - const x = checkType(args, 0, EVLNumber).jsValue; - const y = checkType(args, 1, EVLNumber).jsValue; - return new EVLNumber(x / y); + const x = checkArgumentType(args, 0, EVLNumber); + if (isError(x)) return x; + const y = checkArgumentType(args, 1, EVLNumber); + if (isError(y)) return y; + return new EVLNumber(x.jsValue / y.jsValue); }); primitiveFunction('%', 2, 2, function(args) { - const x = checkType(args, 0, EVLNumber).jsValue; - const y = checkType(args, 1, EVLNumber).jsValue; - return new EVLNumber(x % y); + const x = checkArgumentType(args, 0, EVLNumber); + if (isError(x)) return x; + const y = checkArgumentType(args, 1, EVLNumber); + if (isError(y)) return y; + return new EVLNumber(x.jsValue % y.jsValue); }); primitiveFunction('=', 2, 2, function(args) { - const x = checkType(args, 0, EVLNumber).jsValue; - const y = checkType(args, 1, EVLNumber).jsValue; - return evlBoolean(x === y); + const x = checkArgumentType(args, 0, EVLNumber); + if (isError(x)) return x; + const y = checkArgumentType(args, 1, EVLNumber); + if (isError(y)) return y; + return evlBoolean(x.jsValue === y.jsValue); }); primitiveFunction('/=', 2, 2, function(args) { - const x = checkType(args, 0, EVLNumber).jsValue; - const y = checkType(args, 1, EVLNumber).jsValue; - return evlBoolean(x !== y); + const x = checkArgumentType(args, 0, EVLNumber); + if (isError(x)) return x; + const y = checkArgumentType(args, 1, EVLNumber); + if (isError(y)) return y; + return evlBoolean(x.jsValue !== y.jsValue); }); primitiveFunction('<', 2, 2, function(args) { - const x = checkType(args, 0, EVLNumber).jsValue; - const y = checkType(args, 1, EVLNumber).jsValue; - return evlBoolean(x < y); + const x = checkArgumentType(args, 0, EVLNumber); + if (isError(x)) return x; + const y = checkArgumentType(args, 1, EVLNumber); + if (isError(y)) return y; + return evlBoolean(x.jsValue < y.jsValue); }); primitiveFunction('<=', 2, 2, function(args) { - const x = checkType(args, 0, EVLNumber).jsValue; - const y = checkType(args, 1, EVLNumber).jsValue; - return evlBoolean(x <= y); + const x = checkArgumentType(args, 0, EVLNumber); + if (isError(x)) return x; + const y = checkArgumentType(args, 1, EVLNumber); + if (isError(y)) return y; + return evlBoolean(x.jsValue <= y.jsValue); }); primitiveFunction('>', 2, 2, function(args) { - const x = checkType(args, 0, EVLNumber).jsValue; - const y = checkType(args, 1, EVLNumber).jsValue; - return evlBoolean(x > y); + const x = checkArgumentType(args, 0, EVLNumber); + if (isError(x)) return x; + const y = checkArgumentType(args, 1, EVLNumber); + if (isError(y)) return y; + return evlBoolean(x.jsValue > y.jsValue); }); primitiveFunction('>=', 2, 2, function(args) { - const x = checkType(args, 0, EVLNumber).jsValue; - const y = checkType(args, 1, EVLNumber).jsValue; - return evlBoolean(x >= y); + const x = checkArgumentType(args, 0, EVLNumber); + if (isError(x)) return x; + const y = checkArgumentType(args, 1, EVLNumber); + if (isError(y)) return y; + return evlBoolean(x.jsValue >= y.jsValue); }); -/****************/ -/* EVLCharacter */ -/****************/ +/*********************************/ +/* Primitive Data Type character */ +/*********************************/ class EVLCharacter extends EVLObject { constructor(jsValue) { @@ -4570,9 +6326,9 @@ primitiveFunction('character?', 1, 1, function(args) { return evlBoolean(args[0] instanceof EVLCharacter); }); -/*************/ -/* EVLString */ -/*************/ +/******************************/ +/* Primitive Data Type string */ +/******************************/ class EVLString extends EVLObject { constructor(jsValue) { @@ -4591,13 +6347,17 @@ class EVLString extends EVLObject { } } +function ensureEVLString(x) { + return x instanceof EVLString ? x : new EVLString(x); +} + primitiveFunction('string?', 1, 1, function(args) { return evlBoolean(args[0] instanceof EVLString); }); -/*************/ -/* EVLSymbol */ -/*************/ +/******************************/ +/* Primitive Data Type symbol */ +/******************************/ class EVLSymbol extends EVLObject { // abstract class constructor(name) { @@ -4610,9 +6370,9 @@ primitiveFunction('symbol?', 1, 1, function(args) { return evlBoolean(args[0] instanceof EVLSymbol); }); -/**************/ -/* EVLKeyword */ -/**************/ +/*******************************/ +/* Primitive Data Type keyword */ +/*******************************/ class EVLKeyword extends EVLSymbol { constructor(name) { @@ -4638,19 +6398,21 @@ primitiveFunction('keyword?', 1, 1, function(args) { }); primitiveFunction('make-keyword', 1, 1, function(args) { - const name = checkType(args, 0, EVLString).jsValue; - return new EVLKeyword(name); + const name = checkArgumentType(args, 0, EVLString); + if (isError(name)) return name; + return new EVLKeyword(name.jsValue); }); -/***************/ -/* EVLVariable */ -/***************/ +/********************************/ +/* Primitive Data Type variable */ +/********************************/ class EVLVariable extends EVLSymbol { constructor(name) { super(name); this.value = null; // EVLObject or null this.function = null; // EVLObject or null + this.plist = EVLEmptyList.NIL; } toString() { return escapeCharacters(this.name, escapeProtoTokenCharacter); @@ -4667,84 +6429,144 @@ function internVariable(name) { return variable; } -const notVariable = internVariable('not'); -const andVariable = internVariable('and'); -const orVariable = internVariable('or'); -const quoteVariable = internVariable('quote'); -const quasiquoteVariable = internVariable('quasiquote'); -const unquoteVariable = internVariable('unquote'); -const unquoteSplicingVariable = internVariable('unquote-splicing'); -const prognVariable = internVariable('progn'); -const ifVariable = internVariable('if'); -const _vlambdaVariable = internVariable('_vlambda'); -const _mlambdaVariable = internVariable('_mlambda'); -const mlambdaVariable = internVariable('mlambda'); // mlet -const _flambdaVariable = internVariable('_flambda'); -const _dlambdaVariable = internVariable('_dlambda'); -const vrefVariable = internVariable('vref'); -const vsetVariable = internVariable('vset!'); -const frefVariable = internVariable('fref'); -const fsetVariable = internVariable('fset!'); -const drefVariable = internVariable('dref'); -const dsetVariable = internVariable('dset!'); -const _forEachVariable = internVariable('_for-each'); -const _catchErrorsVariable = internVariable('_catch-errors'); -const applyVariable = internVariable('apply'); -const multipleValueCallVariable = internVariable('multiple-value-call'); -const multipleValueApplyVariable = internVariable('multiple-value-apply'); - primitiveFunction('variable?', 1, 1, function(args) { return evlBoolean(args[0] instanceof EVLVariable); }); primitiveFunction('make-variable', 1, 1, function(args) { - const name = checkType(args, 0, EVLString).jsValue; - return new EVLVariable(name); + const name = checkArgumentType(args, 0, EVLString); + if (isError(name)) return name; + return new EVLVariable(name.jsValue); }); primitiveFunction('variable-value', 1, 1, function(args) { - const variable = checkType(args, 0, EVLVariable); + const variable = checkArgumentType(args, 0, EVLVariable); + if (isError(variable)) return variable; return nullToVoid(variable.value); }); primitiveFunction('variable-set-value!', 2, 2, function(args) { - const variable = checkType(args, 0, EVLVariable); + const variable = checkArgumentType(args, 0, EVLVariable); + if (isError(variable)) return variable; return variable.value = args[1]; }); primitiveFunction('variable-value-bound?', 1, 1, function(args) { - const variable = checkType(args, 0, EVLVariable); + const variable = checkArgumentType(args, 0, EVLVariable); + if (isError(variable)) return variable; return evlBoolean(variable.value !== null); }); primitiveFunction('variable-unbind-value!', 1, 1, function(args) { - const variable = checkType(args, 0, EVLVariable); + const variable = checkArgumentType(args, 0, EVLVariable); + if (isError(variable)) return variable; return variable.value = null, EVLVoid.VOID; }); primitiveFunction('variable-function', 1, 1, function(args) { - const variable = checkType(args, 0, EVLVariable); + const variable = checkArgumentType(args, 0, EVLVariable); + if (isError(variable)) return variable; return nullToVoid(variable.function); }); primitiveFunction('variable-set-function!', 2, 2, function(args) { - const variable = checkType(args, 0, EVLVariable); + const variable = checkArgumentType(args, 0, EVLVariable); + if (isError(variable)) return variable; return variable.function = args[1]; }); primitiveFunction('variable-function-bound?', 1, 1, function(args) { - const variable = checkType(args, 0, EVLVariable); + const variable = checkArgumentType(args, 0, EVLVariable); + if (isError(variable)) return variable; return evlBoolean(variable.function !== null); }); primitiveFunction('variable-unbind-function!', 1, 1, function(args) { - const variable = checkType(args, 0, EVLVariable); + const variable = checkArgumentType(args, 0, EVLVariable); + if (isError(variable)) return variable; return variable.function = null, EVLVoid.VOID; }); -/***********/ -/* EVLList */ -/***********/ +function plistGet(variable, key) { + let cons = null; + let plist = variable.plist; + while (plist !== EVLEmptyList.NIL) { + if (plist.car === key) { + return [cons, plist]; + } else { + cons = plist.cdr; + plist = cons.cdr; + } + } + return [cons, plist]; +} + +primitiveFunction('variable-plist-ref', 2, 2, function(args) { + const variable = checkArgumentType(args, 0, EVLVariable); + if (isError(variable)) return variable; + const key = checkArgumentType(args, 1, EVLKeyword); + if (isError(key)) return key; + const [cons, plist] = plistGet(variable, key); + if (plist !== EVLEmptyList.NIL) { + return plist.cdr.car; + } else { + return EVLVoid.VOID; + } +}); + +primitiveFunction('variable-plist-set!', 3, 3, function(args) { + const variable = checkArgumentType(args, 0, EVLVariable); + if (isError(variable)) return variable; + const key = checkArgumentType(args, 1, EVLKeyword); + if (isError(key)) return key; + const value = args[2]; + const [cons, plist] = plistGet(variable, key); + if (plist !== EVLEmptyList.NIL) { + return plist.cdr.car = value; + } else { + if (cons !== null) { + cons.cdr = new EVLCons(key, new EVLCons(value, EVLEmptyList.NIL)); + } else { + variable.plist = new EVLCons(key, new EVLCons(value, EVLEmptyList.NIL)); + } + return value; + } +}); + +primitiveFunction('variable-plist-bound?', 2, 2, function(args) { + const variable = checkArgumentType(args, 0, EVLVariable); + if (isError(variable)) return variable; + const key = checkArgumentType(args, 1, EVLKeyword); + if (isError(key)) return key; + const [cons, plist] = plistGet(variable, key); + if (plist !== EVLEmptyList.NIL) { + return EVLBoolean.TRUE; + } else { + return EVLBoolean.FALSE; + } +}); + +primitiveFunction('variable-plist-unbind!', 2, 2, function(args) { + const variable = checkArgumentType(args, 0, EVLVariable); + if (isError(variable)) return variable; + const key = checkArgumentType(args, 1, EVLKeyword); + if (isError(key)) return key; + const [cons, plist] = plistGet(variable, key); + if (plist !== EVLEmptyList.NIL) { + if (cons !== null) { + cons.cdr = plist.cdr.cdr; + } else { + variable.plist = plist.cdr.cdr; + } + return EVLVoid.VOID; + } else { + return EVLVoid.VOID; + } +}); + +/****************************/ +/* Primitive Data Type list */ +/****************************/ class EVLList extends EVLObject { // abstract class constructor() { @@ -4778,9 +6600,23 @@ primitiveFunction('list?', 1, 1, function(args) { return evlBoolean(args[0] instanceof EVLList); }); -/****************/ -/* EVLEmptyList */ -/****************/ +primitiveFunction('_make-list', 1, 2, function(args) { + const number = checkArgumentType(args, 0, EVLNumber); + if (isError(number)) return number; + const length = number.jsValue; + if (!Number.isInteger(length) || length < 0) { + return new LengthNotNonnegativeInteger(); + } + let list = EVLEmptyList.NIL; + for (let n = 0; n < length; n++) { + list = new EVLCons(EVLVoid.VOID, list); + } + return list; +}); + +/**********************************/ +/* Primitive Data Type empty-list */ +/**********************************/ class EVLEmptyList extends EVLList { constructor() { @@ -4795,9 +6631,9 @@ primitiveFunction('empty-list?', 1, 1, function(args) { return evlBoolean(args[0] instanceof EVLEmptyList); }); -/***********/ -/* EVLCons */ -/***********/ +/****************************/ +/* Primitive Data Type cons */ +/****************************/ class EVLCons extends EVLList { constructor(car, cdr) { @@ -4816,28 +6652,32 @@ primitiveFunction('cons', 2, 2, function(args) { }); primitiveFunction('car', 1, 1, function(args) { - const cons = checkType(args, 0, EVLCons); + const cons = checkArgumentType(args, 0, EVLCons); + if (isError(cons)) return cons; return cons.car; }); primitiveFunction('set-car!', 2, 2, function(args) { - const cons = checkType(args, 0, EVLCons); + const cons = checkArgumentType(args, 0, EVLCons); + if (isError(cons)) return cons; return cons.car = args[1]; }); primitiveFunction('cdr', 1, 1, function(args) { - const cons = checkType(args, 0, EVLCons); + const cons = checkArgumentType(args, 0, EVLCons); + if (isError(cons)) return cons; return cons.cdr; }); primitiveFunction('set-cdr!', 2, 2, function(args) { - const cons = checkType(args, 0, EVLCons); + const cons = checkArgumentType(args, 0, EVLCons); + if (isError(cons)) return cons; return cons.cdr = args[1]; }); -/*************/ -/* EVLVector */ -/*************/ +/******************************/ +/* Primitive Data Type vector */ +/******************************/ class EVLVector extends EVLObject { constructor(elements) { @@ -4865,9 +6705,81 @@ primitiveFunction('vector?', 1, 1, function(args) { return evlBoolean(args[0] instanceof EVLVector); }); -/***************/ -/* EVLFunction */ -/***************/ +primitiveFunction('make-vector', 1, 2, function(args) { + const number = checkArgumentType(args, 0, EVLNumber); + if (isError(number)) return number; + const length = number.jsValue; + if (!Number.isInteger(length) || length < 0) { + return new LengthNotNonnegativeInteger(); + } + return new EVLVector(new Array(length).fill(args.length === 1 ? null : args[1])); +}); + +primitiveFunction('vector-length', 1, 1, function(args) { + const vector = checkArgumentType(args, 0, EVLVector); + if (isError(vector)) return vector; + return new EVLNumber(vector.elements.length); +}); + +function checkVectorIndex(elements, number) { + const length = elements.length; + const index = number.jsValue; + if (!Number.isInteger(index) || index < 0) { + return new IndexNotNonnegativeInteger(); + } else if (0 <= index && index < length) { + return index; + } else { + return new IndexOutOfBounds(); + } +} + +primitiveFunction('vector-ref', 2, 2, function(args) { + const vector = checkArgumentType(args, 0, EVLVector); + if (isError(vector)) return vector; + const number = checkArgumentType(args, 1, EVLNumber); + if (isError(number)) return number; + const elements = vector.elements; + const index = checkVectorIndex(elements, number); + if (isError(index)) return index; + return nullToVoid(elements[index]); +}); + +primitiveFunction('vector-set!', 3, 3, function(args) { + const vector = checkArgumentType(args, 0, EVLVector); + if (isError(vector)) return vector; + const number = checkArgumentType(args, 1, EVLNumber); + if (isError(number)) return number; + const elements = vector.elements; + const index = checkVectorIndex(elements, number); + if (isError(index)) return index; + return elements[index] = args[2]; +}); + +primitiveFunction('vector-bound?', 2, 2, function(args) { + const vector = checkArgumentType(args, 0, EVLVector); + if (isError(vector)) return vector; + const number = checkArgumentType(args, 1, EVLNumber); + if (isError(number)) return number; + const elements = vector.elements; + const index = checkVectorIndex(elements, number); + if (isError(index)) return index; + return evlBoolean(elements[index] !== null); +}); + +primitiveFunction('vector-unbind!', 2, 2, function(args) { + const vector = checkArgumentType(args, 0, EVLVector); + if (isError(vector)) return vector; + const number = checkArgumentType(args, 1, EVLNumber); + if (isError(number)) return number; + const elements = vector.elements; + const index = checkVectorIndex(elements, number); + if (isError(index)) return index; + return elements[index] = null, EVLVoid.VOID; +}); + +/********************************/ +/* Primitive Data Type function */ +/********************************/ class EVLFunction extends EVLObject { // abstract class constructor() { @@ -4879,9 +6791,9 @@ primitiveFunction('function?', 1, 1, function(args) { return evlBoolean(args[0] instanceof EVLFunction); }); -/************************/ -/* EVLPrimitiveFunction */ -/************************/ +/******************************************/ +/* Primitive Data Type primitive-function */ +/******************************************/ class EVLPrimitiveFunction extends EVLFunction { constructor(arityMin, arityMax, jsFunction) { @@ -4899,19 +6811,19 @@ primitiveFunction('primitive-function?', 1, 1, function(args) { return evlBoolean(args[0] instanceof EVLPrimitiveFunction); }); -/**************/ -/* EVLClosure */ -/**************/ +/*******************************/ +/* Primitive Data Type closure */ +/*******************************/ class EVLClosure extends EVLFunction { - constructor(scope, namespace, macro, parameters, rest, forms, lenv) { + constructor(scope, namespace, macro, parameters, rest, serialForms, lenv) { super(); this.scope = scope; this.namespace = namespace; this.macro = macro; this.parameters = parameters; this.rest = rest; - this.forms = forms; + this.serialForms = serialForms; this.lenv = lenv; } toString() { @@ -4928,32 +6840,69 @@ primitiveFunction('closure?', 1, 1, function(args) { /*************************************/ primitiveFunction('values', 0, null, function(args) { - return new EVLObjects(args); + return new MultipleValues(args); }); primitiveFunction('error', 1, 1, function(args) { - const message = checkType(args, 0, EVLString).jsValue; - throw new Error(message); + const description = checkArgumentType(args, 0, EVLString); + if (isError(description)) return description; + return new ProgramError(description); }); primitiveFunction('now', 0, 0, function(args) { return new EVLNumber(Date.now()); }); -/**************************************/ -/* Primitive Function Definitions (2) */ -/**************************************/ +/*************************************************/ +/* Primitive Function Definitions (Second Steps) */ +/*************************************************/ for (const [name, [arityMin, arityMax, jsFunction]] of primitiveFunctions) { GlobalEnv.set(FUN_NS, internVariable(name), new EVLPrimitiveFunction(arityMin, arityMax, jsFunction)); } +/***************************************/ +/* Variables (Special Operators, etc.) */ +/***************************************/ + +const notVariable = internVariable('not'); +const andVariable = internVariable('and'); +const orVariable = internVariable('or'); +const quoteVariable = internVariable('quote'); +const quasiquoteVariable = internVariable('quasiquote'); +const unquoteVariable = internVariable('unquote'); +const unquoteSplicingVariable = internVariable('unquote-splicing'); +const prognVariable = internVariable('progn'); +const ifVariable = internVariable('if'); +const _forEachVariable = internVariable('_for-each'); +const _vlambdaVariable = internVariable('_vlambda'); +const _mlambdaVariable = internVariable('_mlambda'); +const _flambdaVariable = internVariable('_flambda'); +const _dlambdaVariable = internVariable('_dlambda'); +const vrefVariable = internVariable('vref'); +const vsetVariable = internVariable('vset!'); +const frefVariable = internVariable('fref'); +const fsetVariable = internVariable('fset!'); +const drefVariable = internVariable('dref'); +const dsetVariable = internVariable('dset!'); +const blockVariable = internVariable('block'); +const returnFromVariable = internVariable('return-from'); +const catchVariable = internVariable('catch'); +const throwVariable = internVariable('throw'); +const _handlerBindVariable = internVariable('_handler-bind'); +const unwindProtectVariable = internVariable('unwind-protect'); +const mletVariable = internVariable('mlet'); +const mlambdaVariable = internVariable('mlambda'); +const applyVariable = internVariable('apply'); +const multipleValueCallVariable = internVariable('multiple-value-call'); +const multipleValueApplyVariable = internVariable('multiple-value-apply'); + /****************************/ /* Interface (Command Line) */ /****************************/ const evaluatorOptions = [ - '--plainrec', + '--directstyle', '--cps', '--oocps', '--sboocps', @@ -5008,7 +6957,7 @@ if (isRunningInsideNode) { function usage() { console.log('usage:'); - console.log('--plainrec: selects the plain recursive evaluator'); + console.log('--directstyle: selects the direct style evaluator'); console.log('--cps: selects the continuation passing style evaluator'); console.log('--oocps: selects the object-oriented CPS evaluator'); console.log('--sboocps: selects the stack-based object-oriented CPS evaluator'); diff --git a/system-files/evl2html.css b/system-files/evl2html.css deleted file mode 100644 index 617559a..0000000 --- a/system-files/evl2html.css +++ /dev/null @@ -1,24 +0,0 @@ -/* SPDX-FileCopyrightText: Copyright (c) 2024-2025 Raphaël Van Dyck */ -/* SPDX-License-Identifier: BSD-3-Clause */ - -html { - font-family: Arial, sans-serif; - font-size: 14pt; - line-height: 1.4; -} - -p.syntax > span, p.primitivefunction > span, p.macro > span, p.function > span { - background-color: lightgray; -} - -pre.blockcode, div.indentation { - font-family: monospace; -} - -div.blockcomment, span.eolcomment { - font-family: Arial, sans-serif; -} - -span.eolcomment::before { - content: '\2014 '; /* em dash followed by a single space */ -} diff --git a/system-files/evl2html.js b/system-files/evl2html.js deleted file mode 100644 index e74dde6..0000000 --- a/system-files/evl2html.js +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-FileCopyrightText: Copyright (c) 2024-2025 Raphaël Van Dyck -// SPDX-License-Identifier: BSD-3-Clause - -/******************/ -/* Event Handlers */ -/******************/ - -window.addEventListener('focus', event => { - window.parent.dispatchEvent(new CustomEvent('iframeFocus', {detail: windowId})); -}); - -window.addEventListener('keydown', event => { - if (event.ctrlKey && (event.altKey || event.metaKey)) { - window.parent.dispatchEvent(new CustomEvent('iframeKeyDown', {detail: event})); - event.preventDefault(); - } -}); diff --git a/system-files/evl2html.xslt b/system-files/evl2html.xslt index 0960a90..51e09d8 100644 --- a/system-files/evl2html.xslt +++ b/system-files/evl2html.xslt @@ -6,6 +6,11 @@ + + + + + @@ -43,35 +48,53 @@ - -

                + +

                +      
                +    
                +
                + +
                LinuxWindowsmacOSCommand
                -

                +
                - - + + - +
                - -

                - Syntax: -

                + + + + + + + + + + + + + + + + +

                Special form:

                +
                + +

                Macro call:

                +
                + +

                Plain function call:

                -

                - Primitive function: -

                +

                Primitive function:

                -

                - Macro: -

                -
                - -

                - Function: -

                +

                Macro:

                +
                + +

                Nonprimitive function:

                diff --git a/system-files/mantle.evl b/system-files/mantle.evl index 527762c..4a8fffd 100644 --- a/system-files/mantle.evl +++ b/system-files/mantle.evl @@ -4,11 +4,11 @@ Mantle
                Bootstrapping +

                The top-level forms composing a source file cannot occur in any order. Two obvious constraints are that (1) a function must be defined before any call to the function is evaluated and (2) a macro must be defined before any call to the macro is expanded. In all evaluators except the trampoline++ evaluator, a macro call is expanded each time it is evaluated or, if macro calls are optimized, each time it is evaluated for the first time after an evaluation of the top-level form containing it. (The expansion of an optimized macro call is forgotten each time the top-level form containing it is evaluated.) In the trampoline++ evaluator, a macro call is expanded each time the top-level form containing it is evaluated. It is clear that an order that satisfies the constraints imposed by the trampoline++ evaluator automatically satisfies the constraints imposed by the other evaluators.

                +

                The purpose of the bootstrapping section is to define some essential functions and macros in an order that satisfies the constraints imposed by the trampoline++ evaluator. In particular, the order must be such that, for each macro, the macro and all the functions directly or indirectly invoked from the macro are defined before the first occurrence of a call to the macro. As the functions and macros defined in the bootstrapping section are only documented later in the chapter, it is probably better to skim through the bootstrapping section on first reading.

                -Targets: <code>not</code>, <code>and</code>, and <code>or</code> -(fset! list - (_vlambda list list)) - +Boolean Operators +

                By the end of this section, the function not and the macros and and or will be available.

                (fset! not (_vlambda (boolean) (if boolean #f #t))) @@ -36,251 +36,283 @@ (if (empty-list? test-forms) #f (error "Error expanding or form."))))) +
                +New Functions Invoked by the Macros +(fset! list + (_vlambda list list)) +
                -Targets: <code>vlambda</code>, <code>mlambda</code>, <code>flambda</code>, and <code>dlambda</code> -(fset! list-reverse - (_vlambda (list) - (list-reverse/2 list '()))) - -(fset! list-reverse/2 - (_vlambda (list acc) - (if (cons? list) - (list-reverse/2 (cdr list) (cons (car list) acc)) - acc))) - -(fset! caar (_vlambda (x) (car (car x)))) -(fset! cdar (_vlambda (x) (cdr (car x)))) -(fset! cadr (_vlambda (x) (car (cdr x)))) -(fset! cddr (_vlambda (x) (cdr (cdr x)))) -(fset! caaar (_vlambda (x) (car (car (car x))))) -(fset! cdaar (_vlambda (x) (cdr (car (car x))))) -(fset! cadar (_vlambda (x) (car (cdr (car x))))) -(fset! cddar (_vlambda (x) (cdr (cdr (car x))))) -(fset! caadr (_vlambda (x) (car (car (cdr x))))) -(fset! cdadr (_vlambda (x) (cdr (car (cdr x))))) -(fset! caddr (_vlambda (x) (car (cdr (cdr x))))) -(fset! cdddr (_vlambda (x) (cdr (cdr (cdr x))))) -(fset! caaaar (_vlambda (x) (car (car (car (car x)))))) -(fset! cdaaar (_vlambda (x) (cdr (car (car (car x)))))) -(fset! cadaar (_vlambda (x) (car (cdr (car (car x)))))) -(fset! cddaar (_vlambda (x) (cdr (cdr (car (car x)))))) -(fset! caadar (_vlambda (x) (car (car (cdr (car x)))))) -(fset! cdadar (_vlambda (x) (cdr (car (cdr (car x)))))) -(fset! caddar (_vlambda (x) (car (cdr (cdr (car x)))))) -(fset! cdddar (_vlambda (x) (cdr (cdr (cdr (car x)))))) -(fset! caaadr (_vlambda (x) (car (car (car (cdr x)))))) -(fset! cdaadr (_vlambda (x) (cdr (car (car (cdr x)))))) -(fset! cadadr (_vlambda (x) (car (cdr (car (cdr x)))))) -(fset! cddadr (_vlambda (x) (cdr (cdr (car (cdr x)))))) -(fset! caaddr (_vlambda (x) (car (car (cdr (cdr x)))))) -(fset! cdaddr (_vlambda (x) (cdr (car (cdr (cdr x)))))) -(fset! cadddr (_vlambda (x) (car (cdr (cdr (cdr x)))))) -(fset! cddddr (_vlambda (x) (cdr (cdr (cdr (cdr x)))))) - +Branching +

                By the end of this section, the macros when, cond, and econd will be available.

                +(fset! when + (_mlambda (test-form . serial-forms) + (list 'if test-form (cons 'progn serial-forms) #v))) + +(fset! cond + (_mlambda clauses + (cond/fn clauses #f))) + +(fset! econd + (_mlambda clauses + (cond/fn clauses #t))) + +(fset! cond/fn + (_vlambda (clauses error?) + (if (else-clause? clauses) + (if (proper-list? (cdar clauses)) + (cons 'progn (cdar clauses)) + (error "Error expanding cond form.")) + (if (cons? clauses) + (if (and + (cons? (car clauses)) + (proper-list? (cdar clauses))) + (list + 'if + (caar clauses) + (cons 'progn (cdar clauses)) + (cond/fn (cdr clauses) error?)) + (error "Error expanding cond form.")) + (if (empty-list? clauses) + (if error? + (list 'error "Fell through econd.") + #v) + (error "Error expanding cond form.")))))) + +(fset! else-clause? + (_vlambda (clauses) + (and + (cons? clauses) + (cons? (car clauses)) + (eq? (caar clauses) 'else) + (empty-list? (cdr clauses))))) +
                +New Functions Invoked by the Macros +(fset! caar + (_vlambda (x) + (car (car x)))) + +(fset! cdar + (_vlambda (x) + (cdr (car x)))) + +(fset! proper-list? + (_vlambda (object) + (or + (and + (cons? object) + (proper-list? (cdr object))) + (empty-list? object)))) +
                +
                +
                +Abstracting +

                By the end of this section, the macros vlambda, mlambda, flambda, and dlambda will be available.

                (fset! vlambda - (_mlambda (parameter-list . forms) - (lambda/fn '_vlambda parameter-list forms))) + (_mlambda (fancy-parameter-list . body) + (lambda/fn '_vlambda fancy-parameter-list body))) (fset! mlambda - (_mlambda (parameter-list . forms) - (lambda/fn '_mlambda parameter-list forms))) + (_mlambda (fancy-parameter-list . body) + (lambda/fn '_mlambda fancy-parameter-list body))) (fset! flambda - (_mlambda (parameter-list . forms) - (lambda/fn '_flambda parameter-list forms))) + (_mlambda (fancy-parameter-list . body) + (lambda/fn '_flambda fancy-parameter-list body))) (fset! dlambda - (_mlambda (parameter-list . forms) - (lambda/fn '_dlambda parameter-list forms))) + (_mlambda (fancy-parameter-list . body) + (lambda/fn '_dlambda fancy-parameter-list body))) (fset! lambda/fn - (_vlambda (kind parameter-list forms) - (cons kind (cons (cl2scm-parameter-list parameter-list '()) forms)))) - -(fset! cl2scm-parameter-list - (_vlambda (parameter-list required-parameters) - (if (rest-parameter? parameter-list) - (make-scm-parameter-list - (list-reverse required-parameters) - (cadr parameter-list)) - (if (cons? parameter-list) - (if (variable? (car parameter-list)) - (cl2scm-parameter-list - (cdr parameter-list) - (cons (car parameter-list) required-parameters)) - (error "Error expanding lambda form.")) - (if (empty-list? parameter-list) - (make-scm-parameter-list - (list-reverse required-parameters) - '()) - (error "Error expanding lambda form.")))))) + (_vlambda (lambda fancy-parameter-list body) + (cons + lambda + (cons + (fancy-to-plain-parameter-list fancy-parameter-list '()) + body)))) + +(fset! fancy-to-plain-parameter-list + (_vlambda (fancy-parameter-list required-parameters) + (cond ((rest-parameter? fancy-parameter-list) + (make-plain-parameter-list + (list-reverse required-parameters) + (cadr fancy-parameter-list))) + ((cons? fancy-parameter-list) + (cond ((variable? (car fancy-parameter-list)) + (fancy-to-plain-parameter-list + (cdr fancy-parameter-list) + (cons (car fancy-parameter-list) required-parameters))) + (else + (error "Error expanding lambda form.")))) + ((empty-list? fancy-parameter-list) + (make-plain-parameter-list + (list-reverse required-parameters) + '())) + (else + (error "Error expanding lambda form."))))) (fset! rest-parameter? - (_vlambda (parameter-list) + (_vlambda (fancy-parameter-list) (and - (cons? parameter-list) - (eq? (car parameter-list) '&rest) - (cons? (cdr parameter-list)) - (variable? (cadr parameter-list)) - (empty-list? (cddr parameter-list))))) + (cons? fancy-parameter-list) + (eq? (car fancy-parameter-list) '&rest) + (cons? (cdr fancy-parameter-list)) + (variable? (cadr fancy-parameter-list)) + (empty-list? (cddr fancy-parameter-list))))) -(fset! make-scm-parameter-list +(fset! make-plain-parameter-list (_vlambda (required-parameters rest-parameter) - (if (cons? required-parameters) - (cons - (car required-parameters) - (make-scm-parameter-list - (cdr required-parameters) - rest-parameter)) - rest-parameter))) + (cond ((cons? required-parameters) + (cons + (car required-parameters) + (make-plain-parameter-list + (cdr required-parameters) + rest-parameter))) + (else + rest-parameter)))) +
                +New Functions Invoked by the Macros +(fset! cadr + (_vlambda (x) + (car (cdr x)))) + +(fset! cddr + (_vlambda (x) + (cdr (cdr x)))) + +(fset! list-reverse + (_vlambda (list) + (list-reverse/internal list '()))) + +(fset! list-reverse/internal + (_vlambda (list acc) + (cond ((cons? list) + (list-reverse/internal (cdr list) (cons (car list) acc))) + (else + acc)))) +
                -Targets: <code>vdef</code>, <code>fdef</code>, and <code>mdef</code> +Defining +

                By the end of this section, the macros vdef, fdef, and mdef will be available.

                (fset! vdef - (mlambda (variable form) + (mlambda (variable value-form) (list 'progn (list 'variable-set-value! (list 'quote variable) - form) + value-form) (list 'quote variable)))) (fset! fdef - (mlambda (variable parameter-list &rest forms) + (mlambda (variable fancy-parameter-list &rest body) (list 'progn (list 'variable-set-function! (list 'quote variable) - (cons 'vlambda (cons parameter-list forms))) + (cons 'vlambda (cons fancy-parameter-list body))) (list 'quote variable)))) (fset! mdef - (mlambda (variable parameter-list &rest forms) + (mlambda (variable fancy-parameter-list &rest body) (list 'progn (list 'variable-set-function! (list 'quote variable) - (cons 'mlambda (cons parameter-list forms))) + (cons 'mlambda (cons fancy-parameter-list body))) (list 'quote variable))))
                -Targets: <code>cond</code> and <code>econd</code> -(fdef proper-list? (object) - (or - (and - (cons? object) - (proper-list? (cdr object))) - (empty-list? object))) - -(mdef cond (&rest clauses) - (cond/fn clauses #f)) - -(mdef econd (&rest clauses) - (cond/fn clauses #t)) - -(fdef cond/fn (clauses error?) - (if (else-clause? clauses) - (if (proper-list? (cdar clauses)) - (cons 'progn (cdar clauses)) - (error "Error expanding cond form.")) - (if (cons? clauses) - (if (and - (cons? (car clauses)) - (proper-list? (cdar clauses))) - (list - 'if - (caar clauses) - (cons 'progn (cdar clauses)) - (cond/fn (cdr clauses) error?)) - (error "Error expanding cond form.")) - (if (empty-list? clauses) - (if error? - (list 'error "Fell through econd.") - #v) - (error "Error expanding cond form."))))) - -(fdef else-clause? (clauses) - (and - (cons? clauses) - (cons? (car clauses)) - (eq? (caar clauses) 'else) - (empty-list? (cdr clauses)))) -
                -
                -Targets: <code>vlet</code>, <code>flet</code>, <code>mlet</code>, <code>dlet</code>, <code>vlet*</code>, <code>flet*</code>, <code>dlet*</code>, and <code>fletrec</code> -(mdef vlet (bindings &rest forms) (let/fn 'vlet bindings forms '() '())) -(mdef flet (bindings &rest forms) (let/fn 'flet bindings forms '() '())) -(mdef mlet (bindings &rest forms) (let/fn 'mlet bindings forms '() '())) -(mdef dlet (bindings &rest forms) (let/fn 'dlet bindings forms '() '())) +Binding +

                By the end of this section, the macros vlet, flet, mlet, dlet, vlet*, flet*, dlet*, and fletrec will be available.

                +(mdef vlet (bindings &rest body) + (let/fn 'vlet bindings body '() '())) + +(mdef flet (bindings &rest body) + (let/fn 'flet bindings body '() '())) -(mdef vlet* (bindings &rest forms) (let*/fn 'vlet bindings forms)) -(mdef flet* (bindings &rest forms) (let*/fn 'flet bindings forms)) -(mdef dlet* (bindings &rest forms) (let*/fn 'dlet bindings forms)) +(mdef mlet (bindings &rest body) + (let/fn 'mlet bindings body '() '())) -(mdef fletrec (bindings &rest forms) (fletrec/fn bindings forms '() '() '())) +(mdef dlet (bindings &rest body) + (let/fn 'dlet bindings body '() '())) -(fdef let/fn (kind bindings forms binding-variables binding-forms) +(mdef vlet* (bindings &rest body) + (let*/fn 'vlet bindings body)) + +(mdef flet* (bindings &rest body) + (let*/fn 'flet bindings body)) + +(mdef dlet* (bindings &rest body) + (let*/fn 'dlet bindings body)) + +(mdef fletrec (bindings &rest body) + (fletrec/fn bindings body '() '() '())) + +(fdef let/fn (let bindings body binding-variables binding-forms) (cond ((cons? bindings) - (cond ((let-binding? kind (car bindings)) - (let/fn kind - (cdr bindings) - forms - (cons - (let-binding-variable (car bindings)) - binding-variables) - (cons - (let-binding-form kind (car bindings)) - binding-forms))) + (cond ((let-binding? let (car bindings)) + (let/fn + let + (cdr bindings) + body + (cons + (let-binding-variable (car bindings)) + binding-variables) + (cons + (let-binding-form let (car bindings)) + binding-forms))) (else (error "Error expanding let form.")))) ((empty-list? bindings) (cons (cons - (let-lambda kind) + (let-lambda let) (cons (list-reverse binding-variables) - forms)) + body)) (list-reverse binding-forms))) (else (error "Error expanding let form.")))) -(fdef let*/fn (kind bindings forms) +(fdef let*/fn (let bindings body) (cond ((cons? bindings) - (cond ((let-binding? kind (car bindings)) + (cond ((let-binding? let (car bindings)) (list (list - (let-lambda kind) + (let-lambda let) (list (let-binding-variable (car bindings))) - (let*/fn kind (cdr bindings) forms)) - (let-binding-form kind (car bindings)))) - (let + (let*/fn let (cdr bindings) body)) + (let-binding-form let (car bindings)))) + (else (error "Error expanding let* form.")))) ((empty-list? bindings) - (cons 'progn forms)) + (cons 'progn body)) (else (error "Error expanding let* form.")))) -(fdef fletrec/fn (bindings forms binding-variables binding-forms set-forms) +(fdef fletrec/fn (bindings body binding-variables binding-forms set-forms) (cond ((cons? bindings) (cond ((let-binding? 'flet (car bindings)) - (fletrec/fn (cdr bindings) - forms - (cons - (let-binding-variable (car bindings)) - binding-variables) - (cons - #v dummy value - binding-forms) - (cons - (list - 'fset! - (let-binding-variable (car bindings)) - (let-binding-form 'flet (car bindings))) - set-forms))) + (fletrec/fn + (cdr bindings) + body + (cons + (let-binding-variable (car bindings)) + binding-variables) + (cons + #v dummy value + binding-forms) + (cons + (list + 'fset! + (let-binding-variable (car bindings)) + (let-binding-form 'flet (car bindings))) + set-forms))) (else (error "Error expanding fletrec form.")))) ((empty-list? bindings) @@ -289,49 +321,49 @@ (let-lambda 'flet) (cons (list-reverse binding-variables) - (cons (cons 'progn set-forms) forms))) + (cons (cons 'progn set-forms) body))) (list-reverse binding-forms))) (else (error "Error expanding fletrec form.")))) -(fdef let-lambda (kind) - (econd ((eq? kind 'vlet) +(fdef let-lambda (let) + (econd ((eq? let 'vlet) '_vlambda) - ((eq? kind 'flet) + ((eq? let 'flet) '_flambda) - ((eq? kind 'mlet) + ((eq? let 'mlet) '_flambda) - ((eq? kind 'dlet) + ((eq? let 'dlet) '_dlambda))) -(fdef let-binding? (kind binding) +(fdef let-binding? (let binding) (econd ((or - (eq? kind 'vlet) - (eq? kind 'dlet)) + (eq? let 'vlet) + (eq? let 'dlet)) (and (cons? binding) (variable? (car binding)) (cons? (cdr binding)) (empty-list? (cddr binding)))) ((or - (eq? kind 'flet) - (eq? kind 'mlet)) + (eq? let 'flet) + (eq? let 'mlet)) (and (cons? binding) (variable? (car binding)) (cons? (cdr binding)) - (parameter-list? (cadr binding)) + (fancy-parameter-list? (cadr binding)) (proper-list? (cddr binding)))))) -(fdef parameter-list? (parameter-list) - (cond ((rest-parameter? parameter-list) +(fdef fancy-parameter-list? (object) + (cond ((rest-parameter? object) #t) - ((cons? parameter-list) - (cond ((variable? (car parameter-list)) - (parameter-list? (cdr parameter-list))) + ((cons? object) + (cond ((variable? (car object)) + (fancy-parameter-list? (cdr object))) (else #f))) - ((empty-list? parameter-list) + ((empty-list? object) #t) (else #f))) @@ -339,57 +371,170 @@ (fdef let-binding-variable (binding) (car binding)) -(fdef let-binding-form (kind binding) - (econd ((eq? kind 'vlet) +(fdef let-binding-form (let binding) + (econd ((eq? let 'vlet) (cadr binding)) - ((eq? kind 'flet) + ((eq? let 'flet) (cons 'vlambda (cdr binding))) - ((eq? kind 'mlet) + ((eq? let 'mlet) (cons 'mlambda (cdr binding))) - ((eq? kind 'dlet) + ((eq? let 'dlet) (cadr binding))))
                -
                -Testing -(vdef *tests* '()) -(test <expected-value-form> <form>) -(test-mv <expected-values-form> <form>) -(test-error <form>) -(mdef test (expected-value-form form) - (test/fn expected-value-form form)) +Quoting +

                By the end of this section, the macro quasiquote will be available.

                +(fdef quasiquotation? (template) + (and + (cons? template) + (eq? (car template) 'quasiquote) + (empty-list? (cddr template)))) -(mdef test-mv (expected-values-form form) - (test/fn expected-values-form (list 'multiple-value-call 'list form))) +(fdef unquotation? (template) + (and + (cons? template) + (eq? (car template) 'unquote) + (empty-list? (cddr template)))) -(mdef test-error (form) - (test/fn #t (list 'string? (list '_catch-errors form)))) +(fdef splicing-unquotation? (template) + (and + (cons? template) + (eq? (car template) 'unquote-splicing) + (empty-list? (cddr template)))) + +(mdef quasiquote (template) + (quasiquote/F template)) + +(fdef quasiquote/F (template) + (cond ((quasiquotation? template) + (quasiquote/F (quasiquote/F (cadr template)))) + ((unquotation? template) + (cadr template)) + ((splicing-unquotation? template) + (error "Unexpected splicing unquotation.")) + ((cons? template) + (cons + 'list-append + (fletrec ((rec (template) + (cond ((or + (quasiquotation? template) + (unquotation? template) + (splicing-unquotation? template)) + (list (quasiquote/F template))) + ((cons? template) + (cons + (quasiquote/F-prime (car template)) + (rec (cdr template)))) + ((empty-list? template) + '()) + (else + (list (quasiquote/F template)))))) + (rec template)))) + (else + (list 'quote template)))) -(fdef test/fn (expected-result-form form) - (list - 'progn - (list - 'vset! - '*tests* - (list - 'cons - (list 'vlambda '() (list 'list (list 'quote form) form expected-result-form)) - '*tests*)) - #v)) +(fdef quasiquote/F-prime (template) + (cond ((splicing-unquotation? template) + (cadr template)) + (else + (list 'list (quasiquote/F template))))) -(fdef run-test (test) - (vlet ((form-result-expected-result - ((vref test)))) - (vlet ((form - (car form-result-expected-result)) - (result - (cadr form-result-expected-result)) - (expected-result - (caddr form-result-expected-result))) - (cond ((equal? result expected-result) - (list 'PASSED)) - (else - (list 'FAILED form result expected-result)))))) +(mdef unquote (form) + (error "Unexpected unquotation.")) + +(mdef unquote-splicing (form) + (error "Unexpected splicing unquotation.")) +
                +New Functions Invoked by the Macro Expansions +

                The function list-append is not invoked by the macro quasiquote but by the expansions of the macro quasiquote that build lists. The function list-append must therefore be defined before any such expansion is evaluated.

                +(fdef list-append (&rest lists) + (vlet ((list '()) + (last-cons #v)) + (fletrec ((rec (first-list rest-lists) + (cond ((cons? first-list) + (vlet ((new-cons + (cons (car first-list) '()))) + (cond ((cons? last-cons) + (set-cdr! last-cons new-cons)) + (else + (vset! list new-cons))) + (vset! last-cons new-cons)) + (rec (cdr first-list) rest-lists)) + ((not (empty-list? first-list)) + (when (cons? rest-lists) + (error "Each argument except the last must be a proper list.")) + (cond ((cons? last-cons) + (set-cdr! last-cons first-list)) + (else + (vset! list first-list)))) + ((cons? rest-lists) + (rec (car rest-lists) (cdr rest-lists))) + (else + #v)))) + (rec '() lists)) + list)) +
                +
                +
                +Miscellaneous +(fdef fresh-variable () + (make-variable "fresh")) + +(mdef values->list (values-form) + `(multiple-value-call list ,values-form)) +
                + +
                +Testing +

                This section implements a testing facility.

                +(test $\metavar{expected-outcome-form}$ $\metavar{form-under-test}$) +

                The macro adds a test checking that the primary value of the form under test is equal? to the value of the expected-outcome form.

                +(test-mv $\metavar{expected-outcome-form}$ $\metavar{form-under-test}$) +

                The macro adds a test checking that the list whose elements are the values of the form under test is equal? to the value of the expected-outcome form.

                +(test-error $\metavar{expected-outcome-form}$ $\metavar{form-under-test}$) +

                The macro adds a test checking that the evaluation of the form under test completes abruptly for a reason of type error carrying a category equal? to the value of the expected-outcome form.

                +(run-tests) +

                The function runs the tests.

                +(vdef *tests* '()) + +(mdef test (expected-outcome-form form-under-test) + (test/fn + form-under-test + expected-outcome-form + form-under-test)) + +(mdef test-mv (expected-outcome-form form-under-test) + (test/fn + form-under-test + expected-outcome-form + `(values->list ,form-under-test))) + +(mdef test-error (expected-outcome-form form-under-test) + (vlet ((blk + (fresh-variable))) + (test/fn + form-under-test + expected-outcome-form + `(block ,blk + (_handler-bind + (vlambda (category description) + (return-from ,blk category)) + ,form-under-test + #v))))) + +(fdef test/fn (form-under-test expected-outcome-form outcome-form) + `(progn + (vset! + *tests* + (cons + (vlambda () + (list ',form-under-test ,expected-outcome-form ,outcome-form)) + *tests*)) + #v)) + +#+(or trampoline trampolinepp) +(fdef run-tests () + (run-tests/rec)) (fdef run-tests/rec () (vlet ((passed 0) @@ -397,12 +542,12 @@ (start-time (now))) (fletrec ((rec (tests) (cond ((cons? tests) - (vlet ((outcome + (vlet ((result (run-test (car tests)))) - (econd ((eq? (car outcome) 'PASSED) + (econd ((eq? (car result) 'PASSED) (vset! passed (+ passed 1))) - ((eq? (car outcome) 'FAILED) - (vset! failed (cons outcome failed))))) + ((eq? (car result) 'FAILED) + (vset! failed (cons result failed))))) (rec (cdr tests))) (else #v)))) @@ -412,77 +557,476 @@ (else (list-reverse failed))))) +#+(or directstyle cps oocps sboocps) +(fdef run-tests () + (run-tests/iter)) + (fdef run-tests/iter () (vlet ((passed 0) (failed '()) (start-time (now))) (_for-each (vlambda (test) - (vlet ((outcome + (vlet ((result (run-test test))) - (econd ((eq? (car outcome) 'PASSED) + (econd ((eq? (car result) 'PASSED) (vset! passed (+ passed 1))) - ((eq? (car outcome) 'FAILED) - (vset! failed (cons outcome failed)))))) + ((eq? (car result) 'FAILED) + (vset! failed (cons result failed)))))) *tests*) (cond ((empty-list? failed) (values passed (- (now) start-time))) (else (list-reverse failed))))) -#+plainrec -(fdef run-tests () - (run-tests/rec)) +(fdef run-test (test) + (vlet ((form-under-test&expected-outcome&outcome + ((vref test)))) + (vlet ((form-under-test + (car form-under-test&expected-outcome&outcome)) + (expected-outcome + (cadr form-under-test&expected-outcome&outcome)) + (outcome + (caddr form-under-test&expected-outcome&outcome))) + (cond ((equal? expected-outcome outcome) + (list 'PASSED)) + (else + (list 'FAILED form-under-test expected-outcome outcome)))))) +
                +
                +Quoting +(quote $\metavar{literal}$) +(test '() '()) +(quasiquote $\metavar{template}$) + + + $\metavar{template}$ + $\Coloneq$ + $\metavar{quasiquotation}$ | $\metavar{unquotation}$ | $\metavar{splicing-unquotation}$ | $\metavar{list-template}$ | $\metavar{atom}$ + + + $\metavar{quasiquotation}$ + $\Coloneq$ + (quasiquote $\metavar{form}$) + + + $\metavar{unquotation}$ + $\Coloneq$ + (unquote $\metavar{form}$) + + + $\metavar{splicing-unquotation}$ + $\Coloneq$ + (unquote-splicing $\metavar{form}$) + + + $\metavar{list-template}$ + $\Coloneq$ + $\metavar{proper-list-template}$ | $\metavar{dotted-list-template}$ + + + $\metavar{proper-list-template}$ + $\Coloneq$ + ($\metavar{template}$+) + + + $\metavar{dotted-list-template}$ + $\Coloneq$ + ($\metavar{template}$+ . $\metavar{template}$) + + + $\metavar{atom}$ + $\Coloneq$ + $\void$ | $\boolean$ | $\number$ | $\character$ | $\string$ | $\keyword$ | $\emptylist$ | $\vector$ | $\primitivefunction$ | $\closure$ + + +

                Quasiquotations, unquotations, and splicing unquotation are usually abbreviated as follows:

                +
                  +
                • (quasiquote $\mlvar{form}$) is usually abbreviated as `$\mlvar{form}$
                • +
                • (unquote $\mlvar{form}$) is usually abbreviated as ,$\mlvar{form}$
                • +
                • (unquote-splicing $\mlvar{form}$) is usually abbreviated as ,@$\mlvar{form}$
                • +
                +

                A quasiquotation that does not contain any nested quasiquotations evaluates to an object that is identical to the template except for the following:

                +
                  +
                • Each unquotation is replaced by the primary value of the its form.
                • +
                • Each splicing unquotation is replaced by the elements of the primary value of the its form, which must be a proper list. We will see below that, semantically, a splicing unquotation can only occur inside a proper-list template or inside a dotted-list template at a position other than the end. The elements of the primary value of a splicing unquotation's form are spliced into the containing list.
                • +
                +

                By way of example, the quasiquotation inside

                +
                (vlet ((test-form '(do-it?))
                       (serial-forms '(1 2 3 'done)))
                  `(if ,test-form (progn ,@serial-forms) #v))
                +

                evaluates to

                +
                (if (do-it?) (progn 1 2 3 (quote done)) #v)
                +

                The context-free grammar for templates is ambiguous. The following rules are used to resolve the ambiguities:

                +
                  +
                • (quasiquote $\metavar{form}$) is interpreted as a quasiquotation, not as a proper-list template.
                • +
                • (unquote $\metavar{form}$) is interpreted as an unquotation, not as a proper-list template.
                • +
                • (unquote-splicing $\metavar{form}$) is interpreted as a splicing unquotation, not as a proper-list template.
                • +
                • ($\metavar{template}$+ quasiquote $\metavar{form}$) is interpreted as a dotted-list template ending with an quasiquotation, not as a proper-list template.
                • +
                • ($\metavar{template}$+ unquote $\metavar{form}$) is interpreted as a dotted-list template ending with an unquotation, not as a proper-list template.
                • +
                • ($\metavar{template}$+ unquote-splicing $\metavar{form}$) is interpreted as a dotted-list template ending with a splicing unquotation, not as a proper-list template.
                • +
                • The end of a dotted-list template is never the empty list.
                • +
                • The end of a dotted-list template is never a cons, unless the end of the dotted-list template is a quasiquotation, an unquotation, or a splicing unquotation.
                • +
                +

                The macro call (quasiquote $\mlvar{template}$) expands into a form that evaluates to an object that is equal? to the value of the form $\mathcal{F}(\mlvar{template})$ computed by the functions $\mathcal{F}$ and $\mathcal{F}'$ defined below.

                +

                The function $\mathcal{F}$ is defined as follows:

                +
                  +
                • $\mathcal{F}($(quasiquote $\mlvar{template}$)$)=\mathcal{F}(\mathcal{F}(\mlvar{template}))$
                • +
                • $\mathcal{F}($(unquote $\mlvar{form}$)$)=\mlvar{form}$
                • +
                • $\mathcal{F}($(unquote-splicing $\mlvar{form}$)$)$ is an error
                • +
                • $\mathcal{F}($($\mlvar{template}_1\ldots\mlvar{template}_{n\ge1}$)$)=\,$(list-append $\mathcal{F}'(\mlvar{template}_1)\ldots\mathcal{F}'(\mlvar{template}_n)$)
                • +
                • $\mathcal{F}($($\mlvar{template}_1\ldots\mlvar{template}_{n\ge1}$ . $\mlvar{template}_{n+1}$)$)=\,$(list-append $\mathcal{F}'(\mlvar{template}_1)\ldots\mathcal{F}'(\mlvar{template}_n)$ $\mathcal{F}(\mlvar{template}_{n+1})$)
                • +
                • $\mathcal{F}(\mlvar{atom})=\,$(quote $\mlvar{atom}$)
                • +
                +

                The end of a dotted-list template, which can only be a quasiquotation, an unquotation, a splicing unquotation, or an atom other than the empty list, is processed by the function $\mathcal{F}$, not by the function $\mathcal{F}'$.

                +

                The function $\mathcal{F}'$ is defined as follows:

                +
                  +
                • If $\mlvar{template}=\,$(unquote-splicing $\mlvar{form}$), then $\mathcal{F}'(\mlvar{template})=\mlvar{form}$.
                • +
                • Otherwise, $\mathcal{F}'(\mlvar{template})=\,$(list $\mathcal{F}(\mlvar{template})$).
                • +
                +

                By way of example,

                +
                $\mathcal{F}($(if ,test-form (progn ,@serial-forms) #v)$)$
                +

                is equal to

                +
                (list-append
                 (list (quote if))
                 (list test-form)
                 (list (list-append (list (quote progn)) serial-forms))
                 (list (quote #v)))
                +

                The bootstrapping section contains an implementation of the macro quasiquote that is a direct translation of the functions $\mathcal{F}$ and $\mathcal{F}'$, without any optimizations.

                +(test '(() (1) (2 3)) `(,'() ,'(1) ,'(2 3))) +(test '(1 2 3) `(,@'() ,@'(1) ,@'(2 3))) -#+cps -(fdef run-tests () - (run-tests/iter)) +(test + '(if (do-it?) (progn 1 2 3 (quote done)) #v) + (vlet ((test-form '(do-it?)) + (serial-forms '(1 2 3 'done))) + `(if ,test-form (progn ,@serial-forms) #v))) + +(test 'a `a) +(test 'a `,'a) +(test '(a b) `(a b)) +(test '(a b) `(,'a b)) +(test '(a b) `(,@'(a) b)) +(test '(a b) `(a ,'b)) +(test '(a b) `(a ,@'(b))) +(test '(a . b) `(a . b)) +(test '(a . b) `(,'a . b)) +(test '(a . b) `(,@'(a) . b)) +(test '(a . b) `(a . ,'b)) +
                +
                +Sequencing +(progn $\metavar{serial-form}$*) +(test-mv '(#v) (progn)) +(test-mv '() (progn (values))) +(test-mv '(1) (progn 1)) +(test-mv '(1) (progn (values 1))) +(test-mv '(1 2) (progn (values 1 2))) +(test-mv '() (progn #v (values))) +(test-mv '(1) (progn #v 1)) +(test-mv '(1) (progn #v (values 1))) +(test-mv '(1 2) (progn #v (values 1 2))) +
                +
                +Branching +(if $\metavar{test-form}$ $\metavar{then-form}$ $\metavar{else-form}$) +(test-mv '() (if #t (values) #v)) +(test-mv '(1) (if #t 1 #v)) +(test-mv '(1) (if #t (values 1) #v)) +(test-mv '(1 2) (if #t (values 1 2) #v)) +(test-mv '() (if #f #v (values))) +(test-mv '(1) (if #f #v 1)) +(test-mv '(1) (if #f #v (values 1))) +(test-mv '(1 2) (if #f #v (values 1 2))) +(when $\metavar{test-form}$ $\metavar{serial-form}$*) +

                The test form is evaluated. Let $\mlvar{test}$ be the primary value of the test form. If $\mlvar{test}$ is not a boolean, then the evaluation of the macro call completes abruptly for a reason of type error. If $\mlvar{test}$ is the boolean #t, then the serial forms are evaluated in sequence from left to right and the macro call evaluates to the values of the last serial form or #v if there are no serial forms. If $\mlvar{test}$ is the boolean #f, then the serial forms are not evaluated and the macro call evaluates to #v.

                +

                The macro when is implemented in the bootstrapping section.

                +(test 'done (when #t 1 2 3 'done)) +(test #v (when #f 1 2 3 'done)) +(cond $\metavar{clause}$* $\metavar{else-clause}$?) +(econd $\metavar{clause}$* $\metavar{else-clause}$?) + + + $\metavar{clause}$ + $\Coloneq$ + ($\metavar{test-form}$ $\metavar{serial-form}$*) + + + $\metavar{else-clause}$ + $\Coloneq$ + (else $\metavar{serial-form}$*) + + +

                A last clause that matches an else clause is always interpreted as an else clause.

                +

                The test forms are evaluated in sequence from left to right and there are three cases to consider.

                +

                Case 1, a test form evaluates to an object that is not a boolean: The following test forms are not evaluated and the macro call completes abruptly for a reason of type error.

                +

                Case 2, a test form evaluates to the boolean #t: The following test forms are not evaluated. Instead, the serial forms of the clause containing the test form are evaluated from left to right and the macro call evaluates to the values of the last serial form or #v if there are no serial forms.

                +

                Case 3, all test forms evaluate to the boolean #f: If there is an else clause, then the serial forms of the else clause are evaluated from left to right and the macro call evaluates to the values of the last serial form or #v if there are no serial forms. If there is no else clause and the macro is cond, then the macro call evaluates to #v. If there is no else clause and the macro is econd, then the macro call completes abruptly for a reason of type error.

                +

                The macros cond and econd are implemented in the bootstrapping section.

                +(test #v (cond)) +(test 0 (cond (else 0))) +(test 1 (cond (#t 1))) +(test #v (cond (#f 1))) +(test 1 (cond (#t 1) (else 0))) +(test 0 (cond (#f 1) (else 0))) +(test 1 (cond (#t 1) (#t 2))) +(test 1 (cond (#t 1) (#f 2))) +(test 2 (cond (#f 1) (#t 2))) +(test #v (cond (#f 1) (#f 2))) +(test 1 (cond (#t 1) (#t 2) (else 0))) +(test 1 (cond (#t 1) (#f 2) (else 0))) +(test 2 (cond (#f 1) (#t 2) (else 0))) +(test 0 (cond (#f 1) (#f 2) (else 0))) -#+oocps -(fdef run-tests () - (run-tests/iter)) +(test-error "program-error" (econd)) +(test 0 (econd (else 0))) +(test 1 (econd (#t 1))) +(test-error "program-error" (econd (#f 1))) +(test 1 (econd (#t 1) (else 0))) +(test 0 (econd (#f 1) (else 0))) +(test 1 (econd (#t 1) (#t 2))) +(test 1 (econd (#t 1) (#f 2))) +(test 2 (econd (#f 1) (#t 2))) +(test-error "program-error" (econd (#f 1) (#f 2))) +(test 1 (econd (#t 1) (#t 2) (else 0))) +(test 1 (econd (#t 1) (#f 2) (else 0))) +(test 2 (econd (#f 1) (#t 2) (else 0))) +(test 0 (econd (#f 1) (#f 2) (else 0))) +
                +
                +Iterating +(_for-each $\metavar{function-form}$ $\metavar{list-form}$) +#+(or directstyle cps oocps sboocps) +(test + '(3 2 1) + (vlet ((acc '())) + (_for-each (vlambda (x) (vset! acc (cons x acc))) '(1 2 3)) + acc)) +(loop $\metavar{serial-form}$*) +

                The macro creates an infinite loop repeatedly evaluating the serial forms in sequence from left to right.

                +(mdef loop (&rest serial-forms) + `(fletrec ((rec () + ,@serial-forms + (rec))) + (rec))) -#+sboocps -(fdef run-tests () - (run-tests/iter)) +(test + 10 + (vlet ((n 0)) + (block blk + (loop + (vset! n (+ n 1)) + (when (= n 10) + (return-from blk n)))))) +(test-loop $\number$) +

                The function returns the number of milliseconds it takes to loop $\number$ times.

                +(fdef test-loop (n) + (vlet ((start-time (now))) + (fletrec ((rec (n) + (when (> n 0) + (rec (- n 1))))) + (rec n)) + (- (now) start-time))) -#+trampoline -(fdef run-tests () - (run-tests/rec)) +(test #t (number? (test-loop 10))) +
                +
                +Abstracting +(_vlambda $\metavar{plain-parameter-list}$ $\metavar{body}$) +(_mlambda $\metavar{plain-parameter-list}$ $\metavar{body}$) +(_flambda $\metavar{plain-parameter-list}$ $\metavar{body}$) +(_dlambda $\metavar{plain-parameter-list}$ $\metavar{body}$) + + + $\metavar{plain-parameter-list}$ + $\Coloneq$ + $\variable$ | ($\variable$*) | ($\variable$+ . $\variable$) + + +(test '(1 2) ((_vlambda x (vref x)) 1 2)) +(test '(1 2) ((_vlambda (x y) (list (vref x) (vref y))) 1 2)) +(test '(1 (2)) ((_vlambda (x . y) (list (vref x) (vref y))) 1 2)) -#+trampolinepp -(fdef run-tests () - (run-tests/rec)) +(test '(1 2) ((_mlambda x (vref x)) 1 2)) +(test '(1 2) ((_mlambda (x y) (list (vref x) (vref y))) 1 2)) +(test '(1 (2)) ((_mlambda (x . y) (list (vref x) (vref y))) 1 2)) + +(test '(1 2) ((_flambda x (fref x)) 1 2)) +(test '(1 2) ((_flambda (x y) (list (fref x) (fref y))) 1 2)) +(test '(1 (2)) ((_flambda (x . y) (list (fref x) (fref y))) 1 2)) + +(test '(1 2) ((_dlambda x (dref x)) 1 2)) +(test '(1 2) ((_dlambda (x y) (list (dref x) (dref y))) 1 2)) +(test '(1 (2)) ((_dlambda (x . y) (list (dref x) (dref y))) 1 2)) +(vlambda $\metavar{fancy-parameter-list}$ $\metavar{body}$) +(mlambda $\metavar{fancy-parameter-list}$ $\metavar{body}$) +(flambda $\metavar{fancy-parameter-list}$ $\metavar{body}$) +(dlambda $\metavar{fancy-parameter-list}$ $\metavar{body}$) + + + $\metavar{fancy-parameter-list}$ + $\Coloneq$ + ($\variable$* {&rest $\variable$}?) + + +

                Calls to the macros vlambda, mlambda, flambda, and dlambda are expanded as follows:

                + + + + + + + + + + + + + + + + + + + + + +
                Macro callExpansion
                (vlambda $\mlvar{fancy-parameter-list}$ $\mlvar{body}$)(_vlambda $\mlvar{plain-parameter-list}$ $\mlvar{body}$)
                (mlambda $\mlvar{fancy-parameter-list}$ $\mlvar{body}$)(_mlambda $\mlvar{plain-parameter-list}$ $\mlvar{body}$)
                (flambda $\mlvar{fancy-parameter-list}$ $\mlvar{body}$)(_flambda $\mlvar{plain-parameter-list}$ $\mlvar{body}$)
                (dlambda $\mlvar{fancy-parameter-list}$ $\mlvar{body}$)(_dlambda $\mlvar{plain-parameter-list}$ $\mlvar{body}$)
                +

                The correspondence between fancy parameter lists and plain parameter lists is as follows:

                + + + + + + + + + + + + + + + + + +
                Fancy parameter listPlain parameter list
                (&rest $\var$)$\var$
                ($\var_1\ldots\var_{n\ge0}$)($\var_1\ldots\var_n$)
                ($\var_1\ldots\var_{n\ge1}$ &rest $\var_{n+1}$)($\var_1\ldots\var_n$ . $\var_{n+1}$)
                +

                The macros vlambda, mlambda, flambda and dlambda are implemented in the bootstrapping section.

                +(test '(1 2) ((vlambda (&rest x) (vref x)) 1 2)) +(test '(1 2) ((vlambda (x y) (list (vref x) (vref y))) 1 2)) +(test '(1 (2)) ((vlambda (x &rest y) (list (vref x) (vref y))) 1 2)) + +(test '(1 2) ((mlambda (&rest x) (vref x)) 1 2)) +(test '(1 2) ((mlambda (x y) (list (vref x) (vref y))) 1 2)) +(test '(1 (2)) ((mlambda (x &rest y) (list (vref x) (vref y))) 1 2)) + +(test '(1 2) ((flambda (&rest x) (fref x)) 1 2)) +(test '(1 2) ((flambda (x y) (list (fref x) (fref y))) 1 2)) +(test '(1 (2)) ((flambda (x &rest y) (list (fref x) (fref y))) 1 2)) + +(test '(1 2) ((dlambda (&rest x) (dref x)) 1 2)) +(test '(1 2) ((dlambda (x y) (list (dref x) (dref y))) 1 2)) +(test '(1 (2)) ((dlambda (x &rest y) (list (dref x) (dref y))) 1 2)) +
                +
                +Destructuring Lists +(destructuring-bind $\metavar{fancy-parameter-list}$ $\metavar{list-form}$ $\metavar{body}$) +

                The list form is evaluated in the current lexical environment. The body is then evaluated with respect to the lexical environment extending the current lexical environment to bind, in the value namespace, the parameters of the fancy parameter list to the elements of the primary value of the list form, which must be a proper list.

                +(mdef destructuring-bind (fancy-parameter-list list-form &rest body) + `(apply (vlambda ,fancy-parameter-list ,@body) ,list-form)) + +(test '(1 (2 3)) (destructuring-bind (x &rest y) '(1 2 3) (list x y))) +
                +
                +Producing and Consuming Multiple Values +(values $\object_1\ldots\object_n$) +

                The function converts its arguments into values: when invoked on the arguments $\object_1,\ldots,\object_n$, the function returns the values $\object_1,\ldots,\object_n$.

                +(test-mv '() (values)) +(test-mv '(1) (values 1)) +(test-mv '(1 2) (values 1 2)) +(multiple-value-bind $\metavar{fancy-parameter-list}$ $\metavar{values-form}$ $\metavar{body}$) +

                The values form is evaluated in the current lexical environment. The body is then evaluated with respect to the lexical environment extending the current lexical environment to bind, in the value namespace, the parameters of the fancy parameter list to the values of the values form.

                +(mdef multiple-value-bind (fancy-parameter-list values-form &rest body) + `(multiple-value-call (vlambda ,fancy-parameter-list ,@body) ,values-form)) + +(test '(1 (2 3)) (multiple-value-bind (x &rest y) (values 1 2 3) (list x y))) +(list->values $\list$) +

                The function converts the elements of $\list$, which must be a proper list, into values.

                +(fdef list->values (list) + (apply values list)) + +(test-mv '(1 2 3) (list->values '(1 2 3))) +(values->list $\metavar{values-form}$) +

                The macro collects the values of the values form into a list.

                +

                The macro values->list is implemented in the bootstrapping section.

                +(test '(1 2 3) (values->list (values 1 2 3)))
                Defining -(vdef <variable> <form>) -(fdef <variable> <common-lisp-parameter-list> <form>*) -(mdef <variable> <common-lisp-parameter-list> <form>*) -<common-lisp-parameter-list> ::= (<variable>* [&rest <variable>]) -The macros vdef, fdef, and mdef are defined in the bootstrapping section. +(vdef $\metavar{variable}$ $\metavar{value-form}$) +

                The macro defines a global variable by ensuring that the variable is bound in the value namespace of the global environment to the primary value of the value form. The macro call evaluates to the variable.

                +(fdef $\metavar{variable}$ $\metavar{fancy-parameter-list}$ $\metavar{body}$) +

                The macro defines a global function by ensuring that the variable is bound in the function namespace of the global environment to the closure resulting from the evaluation of the vlambda form (vlambda $\metavar{fancy-parameter-list}$ $\metavar{body}$). The macro call evaluates to the variable. This description amends the description given in the tutorial by replacing the special operator _vlambda by the macro vlambda.

                +(mdef $\metavar{variable}$ $\metavar{fancy-parameter-list}$ $\metavar{body}$) +

                The macro defines a global macro by ensuring that the variable is bound in the function namespace of the global environment to the closure resulting from the evaluation of the mlambda form (mlambda $\metavar{fancy-parameter-list}$ $\metavar{body}$). The macro call evaluates to the variable. This description amends the description given in the tutorial by replacing the special operator _mlambda by the macro mlambda.

                +

                The macros vdef, fdef, and mdef are implemented in the bootstrapping section.

                Binding -(vlet (<value-binding>*) <form>*) -(flet (<function-binding>*) <form>*) -(mlet (<function-binding>*) <form>*) -(dlet (<value-binding>*) <form>*) -(vlet* (<value-binding>*) <form>*) -(flet* (<function-binding>*) <form>*) -(dlet* (<value-binding>*) <form>*) -(fletrec (<function-binding>*) <form>*) -<value-binding> ::= (<variable> <form>) -<function-binding> ::= (<variable> <common-lisp-parameter-list> <form>*) -<common-lisp-parameter-list> ::= (<variable>* [&rest <variable>]) -The macros vlet, flet, mlet, dlet, vlet*, flet*, dlet*, and fletrec are defined in the bootstrapping section. +(vlet ($\metavar{value-binding}$*) $\metavar{body}$) +(flet ($\metavar{function-binding}$*) $\metavar{body}$) +(mlet ($\metavar{function-binding}$*) $\metavar{body}$) +(dlet ($\metavar{value-binding}$*) $\metavar{body}$) +(vlet* ($\metavar{value-binding}$*) $\metavar{body}$) +(flet* ($\metavar{function-binding}$*) $\metavar{body}$) +(dlet* ($\metavar{value-binding}$*) $\metavar{body}$) +(fletrec ($\metavar{function-binding}$*) $\metavar{body}$) + + + $\metavar{value-binding}$ + $\Coloneq$ + ($\metavar{variable}$ $\metavar{value-form}$) + + + $\metavar{function-binding}$ + $\Coloneq$ + ($\metavar{variable}$ $\metavar{fancy-parameter-list}$ $\metavar{body}$) + + +

                The macro call

                +
                (vlet (($\mlvar{variable}_1$ $\mlvar{value-form}_1$) … ($\mlvar{variable}_n$ $\mlvar{value-form}_n$)) $\mlvar{body}$)
                +

                is evaluated as follows:

                +
                For $i$ from $1$ to $n$, the value form $\mlvar{value-form}_i$ is evaluated with respect to the current lexical and dynamic environments. The serial forms composing the body $\mlvar{body}$ are then evaluated in sequence from left to right with respect to (1) the lexical environment extending the current lexical environment to bind, in the value namespace, the variable $\variable_i$ to the primary value of $\mlvar{value-form}_i$ (for all $i$ from $1$ to $n$) and (2) the current dynamic environment. The macro call evaluates to the values of the last serial form or #v if there are no serial forms.
                +

                The macro call

                +
                (flet (($\mlvar{variable}_1$ $\mlvar{fancypl}_1$ $\mlvar{body}_1$) … ($\mlvar{variable}_n$ $\mlvar{fancypl}_n$ $\mlvar{body}_n$)) $\mlvar{body}$)
                +

                is evaluated as follows:

                +
                For $i$ from $1$ to $n$, the vlambda form $\mlvar{vlambda}_i$ = (vlambda $\mlvar{fancypl}_i$ $\mlvar{body}_i$) is evaluated with respect to the current lexical and dynamic environments. The serial forms composing the body $\mlvar{body}$ are then evaluated in sequence from left to right with respect to (1) the lexical environment extending the current lexical environment to bind, in the function namespace, the variable $\variable_i$ to the value of $\mlvar{vlambda}_i$ (for all $i$ from $1$ to $n$) and (2) the current dynamic environment. The macro call evaluates to the values of the last serial form or #v if there are no serial forms.
                +

                The macro call

                +
                (mlet (($\mlvar{variable}_1$ $\mlvar{fancypl}_1$ $\mlvar{body}_1$) … ($\mlvar{variable}_n$ $\mlvar{fancypl}_n$ $\mlvar{body}_n$)) $\mlvar{body}$)
                +

                is evaluated as follows:

                +
                For $i$ from $1$ to $n$, the mlambda form $\mlvar{mlambda}_i$ = (mlambda $\mlvar{fancypl}_i$ $\mlvar{body}_i$) is evaluated with respect to the current lexical and dynamic environments. The serial forms composing the body $\mlvar{body}$ are then evaluated in sequence from left to right with respect to (1) the lexical environment extending the current lexical environment to bind, in the function namespace, the variable $\variable_i$ to the value of $\mlvar{mlambda}_i$ (for all $i$ from $1$ to $n$) and (2) the current dynamic environment. The macro call evaluates to the values of the last serial form or #v if there are no serial forms.
                +

                The macro call

                +
                (dlet (($\mlvar{variable}_1$ $\mlvar{value-form}_1$) … ($\mlvar{variable}_n$ $\mlvar{value-form}_n$)) $\mlvar{body}$)
                +

                is evaluated as follows:

                +
                For $i$ from $1$ to $n$, the value form $\mlvar{value-form}_i$ is evaluated with respect to the current lexical and dynamic environments. The serial forms composing the body $\mlvar{body}$ are then evaluated in sequence from left to right with respect to (1) the current lexical environment and (2) the dynamic environment extending the current dynamic environment to bind, in the value namespace, the variable $\variable_i$ to the primary value of $\mlvar{value-form}_i$ (for all $i$ from $1$ to $n$). The macro call evaluates to the values of the last serial form or #v if there are no serial forms.
                +

                The macro call

                +
                (vlet* (($\mlvar{variable}_1$ $\mlvar{value-form}_1$) … ($\mlvar{variable}_n$ $\mlvar{value-form}_n$)) $\mlvar{body}$)
                +

                is evaluated as follows:

                +
                For $i$ from $1$ to $n$, the value form $\mlvar{value-form}_i$ is evaluated with respect to $\env_{i-1}$ and the current dynamic environment, where $\env_0$ is the current lexical environment and $\env_i$ is the lexical environment extending $\env_{i-1}$ to bind, in the value namespace, the variable $\variable_i$ to the primary value of $\mlvar{value-form}_i$. The serial forms composing the body $\mlvar{body}$ are then evaluated in sequence from left to right with respect to $\env_n$ and the current dynamic environment. The macro call evaluates to the values of the last serial form or #v if there are no serial forms.
                +

                The macro call

                +
                (flet* (($\mlvar{variable}_1$ $\mlvar{fancypl}_1$ $\mlvar{body}_1$) … ($\mlvar{variable}_n$ $\mlvar{fancypl}_n$ $\mlvar{body}_n$)) $\mlvar{body}$)
                +

                is evaluated as follows:

                +
                For $i$ from $1$ to $n$, the vlambda form $\mlvar{vlambda}_i$ = (vlambda $\mlvar{fancypl}_i$ $\mlvar{body}_i$) is evaluated with respect to $\env_{i-1}$ and the current dynamic environment, where $\env_0$ is the current lexical environment and $\env_i$ is the lexical environment extending $\env_{i-1}$ to bind, in the function namespace, the variable $\variable_i$ to the value of $\mlvar{vlambda}_i$. The serial forms composing the body $\mlvar{body}$ are then evaluated in sequence from left to right with respect to $\env_n$ and the current dynamic environment. The macro call evaluates to the values of the last serial form or #v if there are no serial forms.
                +

                The macro call

                +
                (dlet* (($\mlvar{variable}_1$ $\mlvar{value-form}_1$) … ($\mlvar{variable}_n$ $\mlvar{value-form}_n$)) $\mlvar{body}$)
                +

                is evaluated as follows:

                +
                For $i$ from $1$ to $n$, the value form $\mlvar{value-form}_i$ is evaluated with respect to the current lexical environment and $\env_{i-1}$, where $\env_0$ is the current dynamic environment and $\env_i$ is the dynamic environment extending $\env_{i-1}$ to bind, in the value namespace, the variable $\variable_i$ to the primary value of $\mlvar{value-form}_i$. The serial forms composing the body $\mlvar{body}$ are then evaluated in sequence from left to right with respect to the current lexical environment and $\env_n$. The macro call evaluates to the values of the last serial form or #v if there are no serial forms.
                +

                The macro call

                +
                (fletrec (($\mlvar{variable}_1$ $\mlvar{fancypl}_1$ $\mlvar{body}_1$) … ($\mlvar{variable}_n$ $\mlvar{fancypl}_n$ $\mlvar{body}_n$)) $\mlvar{body}$)
                +

                is evaluated as follows:

                +
                Let $\env$ be the lexical environment extending the current lexical environment to bind, in the function namespace, the variable $\variable_i$ to #v (for all $i$ from $1$ to $n$). The value is unimportant as it will not be referenced before it has been altered. For $i$ from $1$ to $n$, the vlambda form $\mlvar{vlambda}_i$ = (vlambda $\mlvar{fancypl}_i$ $\mlvar{body}_i$) is evaluated with respect to $\env$ and the current dynamic environment. The environment $\env$, which has been captured by the closures resulting from the evaluations of the vlambda forms, is then altered to bind, in the function namespace, the variable $\variable_i$ to the value of $\mlvar{vlambda}_i$ (for all $i$ from $1$ to $n$). The serial forms composing the body $\mlvar{body}$ are then evaluated in sequence from left to right with respect to $\env$ and the current dynamic environment. The macro call evaluates to the values of the last serial form or #v if there are no serial forms.
                +

                The macros vlet, flet, mlet, dlet, vlet*, flet*, dlet*, and fletrec are implemented in the bootstrapping section.

                (test 1 (vlet ((x 1)) (vlet ((x 2) (y (vref x))) (vref y)))) (test 2 (vlet ((x 1)) (vlet* ((x 2) (y (vref x))) (vref y)))) (test 1 (flet ((x () 1)) (flet ((x () 2) (y () ((fref x)))) ((fref y))))) (test 2 (flet ((x () 1)) (flet* ((x () 2) (y () ((fref x)))) ((fref y))))) -(test '(error "bar") (mlet ((foo (x) (list 'quote x))) (foo (error "bar")))) +(test 6 (mlet ((foo (&rest args) (cons '+ args))) (foo 1 2 3))) (test 1 (dlet ((x 1)) (dlet ((x 2) (y (dref x))) (dref y)))) (test 2 (dlet ((x 1)) (dlet* ((x 2) (y (dref x))) (dref y)))) @@ -508,57 +1052,137 @@ (odd? 3))))
                -Abstracting -(_vlambda <scheme-parameter-list> <form>*) -(_mlambda <scheme-parameter-list> <form>*) -(_flambda <scheme-parameter-list> <form>*) -(_dlambda <scheme-parameter-list> <form>*) -<scheme-parameter-list> ::= <variable> -<scheme-parameter-list> ::= (<variable>*) -<scheme-parameter-list> ::= (<variable>+ . <variable>) -(test '(1 2) ((_vlambda x (vref x)) 1 2)) -(test '(1 2) ((_vlambda (x y) (list (vref x) (vref y))) 1 2)) -(test '(1 (2)) ((_vlambda (x . y) (list (vref x) (vref y))) 1 2)) - -(test '(1 2) ((_mlambda x (vref x)) 1 2)) -(test '(1 2) ((_mlambda (x y) (list (vref x) (vref y))) 1 2)) -(test '(1 (2)) ((_mlambda (x . y) (list (vref x) (vref y))) 1 2)) - -(test '(1 2) ((_flambda x (fref x)) 1 2)) -(test '(1 2) ((_flambda (x y) (list (fref x) (fref y))) 1 2)) -(test '(1 (2)) ((_flambda (x . y) (list (fref x) (fref y))) 1 2)) - -(test '(1 2) ((_dlambda x (dref x)) 1 2)) -(test '(1 2) ((_dlambda (x y) (list (dref x) (dref y))) 1 2)) -(test '(1 (2)) ((_dlambda (x . y) (list (dref x) (dref y))) 1 2)) -(vlambda <common-lisp-parameter-list> <form>*) -(mlambda <common-lisp-parameter-list> <form>*) -(flambda <common-lisp-parameter-list> <form>*) -(dlambda <common-lisp-parameter-list> <form>*) -<common-lisp-parameter-list> ::= (<variable>* [&rest <variable>]) -The macros vlambda, mlambda, flambda and dlambda are defined in the bootstrapping section. -(test '(1 2) ((vlambda (&rest x) (vref x)) 1 2)) -(test '(1 2) ((vlambda (x y) (list (vref x) (vref y))) 1 2)) -(test '(1 (2)) ((vlambda (x &rest y) (list (vref x) (vref y))) 1 2)) +Referencing and Assigning +(vref $\metavar{variable}$) +(vset! $\metavar{variable}$ $\metavar{value-form}$) +(fref $\metavar{variable}$) +(fset! $\metavar{variable}$ $\metavar{value-form}$) +(dref $\metavar{variable}$) +(dset! $\metavar{variable}$ $\metavar{value-form}$) +(test '(1 2 2) ((vlambda (x) (list (vref x) (vset! x 2) (vref x))) 1)) +(test '(1 2 2) ((flambda (x) (list (fref x) (fset! x 2) (fref x))) 1)) +(test '(1 2 2) ((dlambda (x) (list (dref x) (dset! x 2) (dref x))) 1)) -(test '(1 2) ((mlambda (&rest x) (vref x)) 1 2)) -(test '(1 2) ((mlambda (x y) (list (vref x) (vref y))) 1 2)) -(test '(1 (2)) ((mlambda (x &rest y) (list (vref x) (vref y))) 1 2)) +(test + 3 + (vlet ((counter 0)) + (flet ((counter++ () + (vset! counter (+ counter 1)))) + (counter++) + (counter++) + (counter++) + counter))) -(test '(1 2) ((flambda (&rest x) (fref x)) 1 2)) -(test '(1 2) ((flambda (x y) (list (fref x) (fref y))) 1 2)) -(test '(1 (2)) ((flambda (x &rest y) (list (fref x) (fref y))) 1 2)) +(test + 3 + (flet ((counter++ () + (dset! counter (+ (dref counter) 1)))) + (dlet ((counter 0)) + (counter++) + (counter++) + (counter++) + (dref counter)))) +
                +
                +Generalized Variables +
                +
                +Abrupt Completions of Type <code>nonlocal-exit</code> +(block $\metavar{block-name}$ $\metavar{serial-form}$*) +(return-from $\metavar{block-name}$ $\metavar{values-form}$) +(test-mv '(1 2) (block blk (values 1 2))) +(test-mv '(3 4) (block blk (return-from blk (values 3 4)) (values 1 2))) +(catch $\metavar{exit-tag-form}$ $\metavar{serial-form}$*) +(throw $\metavar{exit-tag-form}$ $\metavar{values-form}$) +(test-mv '(1 2) (catch 'blk (values 1 2))) +(test-mv '(3 4) (catch 'blk (throw 'blk (values 3 4)) (values 1 2))) +
                +
                +Abrupt Completions of Type <code>error</code> +(error $\string$) +(test-error "program-error" (error "foo")) +(_handler-bind $\metavar{handler-form}$ $\metavar{serial-form}$*) +(test-mv + '(1 2) + (block blk + (_handler-bind + (vlambda (category description) + (return-from blk (values 3 4))) + (values 1 2)))) + +(test-mv + '(1 2) + (block blk + (_handler-bind + (vlambda (category description) + #v) + (values 1 2)))) + +(test-mv + '(3 4) + (block blk + (_handler-bind + (vlambda (category description) + (return-from blk (values 3 4))) + (error "foo")))) + +(test-error + "program-error" + (block blk + (_handler-bind + (vlambda (category description) + #v) + (error "foo")))) +(ignore-errors $\metavar{serial-form}$*) +

                The serial forms are evaluated in sequence from left to right. If the evaluation of any serial form completes abruptly for any reason, then the following serial forms are not evaluated and the evaluation of the macro call proceeds as follows:

                +
                Let $\mlvar{reason}$ be the reason for the abrupt completion of the serial form. If $\mlvar{reason}$ is of type nonlocal-exit, then the evaluation of the macro call completes abruptly for the reason $\mlvar{reason}$. Otherwise, $\mlvar{reason}$ is necessarily of type error and the evaluation of the macro call completes normally and produces the following three values: #v, the category carried by $\mlvar{reason}$, and the description carried by $\mlvar{reason}$.
                +

                Otherwise, the evaluations of the serial forms all complete normally and the evaluation of the macro call completes normally and produces the values of the last serial form or #v if there are no serial forms.

                +(mdef ignore-errors (&rest serial-forms) + (vlet ((blk + (fresh-variable))) + `(block ,blk + (_handler-bind + (vlambda (category description) + (return-from ,blk (values #v category description))) + ,@serial-forms)))) + +(test-mv '(1 2 3) (ignore-errors (values 1 2 3))) +(test-mv '(#v "program-error" "foo") (ignore-errors (error "foo"))) +
                +
                +Protecting against Abrupt Completions +(unwind-protect $\metavar{protected-form}$ $\metavar{cleanup-form}$*) +(test + '(#t 1 2 #f) + (vlet ((open #t)) + (multiple-value-call + list + open + (ignore-errors + (unwind-protect + (values 1 2) + (vset! open #f))) + open))) -(test '(1 2) ((dlambda (&rest x) (dref x)) 1 2)) -(test '(1 2) ((dlambda (x y) (list (dref x) (dref y))) 1 2)) -(test '(1 (2)) ((dlambda (x &rest y) (list (dref x) (dref y))) 1 2)) +(test + '(#t #v "program-error" "foo" #f) + (vlet ((open #t)) + (multiple-value-call + list + open + (ignore-errors + (unwind-protect + (error "foo") + (vset! open #f))) + open)))
                -Invoking -(<operator> <operand>*) -(apply <operator> <operand>*) -(multiple-value-call <operator> <operand>*) -(multiple-value-apply <operator> <operand>*) +Calling +(apply $\metavar{operator-form}$ $\metavar{operand-form}$*) +(multiple-value-call $\metavar{operator-form}$ $\metavar{operand-form}$*) +(multiple-value-apply $\metavar{operator-form}$ $\metavar{operand-form}$*) +($\metavar{macro-operator}$ $\metavar{macro-operand}$*) +($\metavar{operator-form}$ $\metavar{operand-form}$*) (test '(#v 1 2 3) ((_vlambda x x) (values) 1 (values 2) (values 3 4))) @@ -576,7 +1200,6 @@ (multiple-value-apply (_vlambda x x) (values) 1 (values 2) (values 3 4) '())) (test 3 (_+ 1 2)) - (test 3 (apply _+ 1 2 '())) (test 3 (apply _+ 1 '(2))) (test 3 (apply _+ '(1 2))) @@ -614,141 +1237,8 @@ (test #t (vlet ((list (list 1 2 3))) (eq? list (apply (_vlambda x x) list))))
                -Quoting -(quote <object>) -(test '() '()) -All objects but lists and variables are self-evaluating. -
                -
                -Referencing and Assigning Variables -(vref <variable>) -(vset! <variable> <value-form>) -(fref <variable>) -(fset! <variable> <value-form>) -(dref <variable>) -(dset! <variable> <value-form>) -<variable> -A <variable> not in operator position is equivalent to (vref <variable>). -A <variable> in operator position is equivalent to (fref <variable>). -(test '(1 2 2) ((vlambda (x) (list (vref x) (vset! x 2) (vref x))) 1)) -(test '(1 2 2) ((vlambda (x) (list (vref x) (vset! x (values 2)) (vref x))) 1)) -(test '(1 2 2) ((flambda (x) (list (fref x) (fset! x 2) (fref x))) 1)) -(test '(1 2 2) ((flambda (x) (list (fref x) (fset! x (values 2)) (fref x))) 1)) -(test '(1 2 2) ((dlambda (x) (list (dref x) (dset! x 2) (dref x))) 1)) -(test '(1 2 2) ((dlambda (x) (list (dref x) (dset! x (values 2)) (dref x))) 1)) - -(test 3 (vlet ((counter 0)) - (flet ((counter++ () - (vset! counter (+ counter 1)))) - (counter++) - (counter++) - (counter++) - counter))) - -(test 3 (flet ((counter++ () - (dset! counter (+ (dref counter) 1)))) - (dlet ((counter 0)) - (counter++) - (counter++) - (counter++) - (dref counter)))) -
                -
                -Sequencing -(progn <form>*) -(test-mv '(#v) (progn)) -(test-mv '() (progn (values))) -(test-mv '(1) (progn 1)) -(test-mv '(1) (progn (values 1))) -(test-mv '(1 2) (progn (values 1 2))) -(test-mv '() (progn #v (values))) -(test-mv '(1) (progn #v 1)) -(test-mv '(1) (progn #v (values 1))) -(test-mv '(1 2) (progn #v (values 1 2))) -
                -
                -Branching -(if <test-form> <then-form> <else-form>) -(test-mv '() (if #t (values) #v)) -(test-mv '(1) (if #t 1 #v)) -(test-mv '(1) (if #t (values 1) #v)) -(test-mv '(1 2) (if #t (values 1 2) #v)) -(test-mv '() (if #f #v (values))) -(test-mv '(1) (if #f #v 1)) -(test-mv '(1) (if #f #v (values 1))) -(test-mv '(1 2) (if #f #v (values 1 2))) -(test 1 (if (values #t) 1 2)) -(test 2 (if (values #f) 1 2)) -(cond <clause>* [<else-clause>]) -(econd <clause>* [<else-clause>]) -<clause> ::= (<test-form> <form>*) -<else-clause> ::= (else <form>*) -The macros cond and econd are defined in the bootstrapping section. -(test #v (cond)) -(test 0 (cond (else 0))) -(test 1 (cond (#t 1))) -(test #v (cond (#f 1))) -(test 1 (cond (#t 1) (else 0))) -(test 0 (cond (#f 1) (else 0))) -(test 1 (cond (#t 1) (#t 2))) -(test 1 (cond (#t 1) (#f 2))) -(test 2 (cond (#f 1) (#t 2))) -(test #v (cond (#f 1) (#f 2))) -(test 1 (cond (#t 1) (#t 2) (else 0))) -(test 1 (cond (#t 1) (#f 2) (else 0))) -(test 2 (cond (#f 1) (#t 2) (else 0))) -(test 0 (cond (#f 1) (#f 2) (else 0))) - -(test-error (econd)) -(test 0 (econd (else 0))) -(test 1 (econd (#t 1))) -(test-error (econd (#f 1))) -(test 1 (econd (#t 1) (else 0))) -(test 0 (econd (#f 1) (else 0))) -(test 1 (econd (#t 1) (#t 2))) -(test 1 (econd (#t 1) (#f 2))) -(test 2 (econd (#f 1) (#t 2))) -(test-error (econd (#f 1) (#f 2))) -(test 1 (econd (#t 1) (#t 2) (else 0))) -(test 1 (econd (#t 1) (#f 2) (else 0))) -(test 2 (econd (#f 1) (#t 2) (else 0))) -(test 0 (econd (#f 1) (#f 2) (else 0))) -
                -
                -Iterating -(fdef loop () - (loop)) - -(fdef test-loop (n) - (vlet ((start-time (now))) - (fletrec ((rec (n) - (if (> n 0) (rec (- n 1)) #v))) - (rec n)) - (- (now) start-time))) -
                -
                -Returning Multiple Values -(values <object>*) -(test-mv '() (values)) -(test-mv '(1) (values 1)) -(test-mv '(1 2) (values 1 2)) -
                -
                -Signaling and Handling Conditions -(error <string>) -(test-error (error "foo")) -(_catch-errors <form>*) -(test #v (_catch-errors :no-error)) -(test "Error" (_catch-errors (error "foo"))) -
                -
                -Interacting with the Environment -(now) -(test #t (number? (now))) -
                -
                -Primitive Datatype <code>object</code> -(object? <object>) +Primitive Data Type <code>object</code> +(object? $\object$) (test #t (object? #v)) (test #t (object? #t)) (test #t (object? #f)) @@ -762,21 +1252,44 @@ (test #t (object? #(1 2 3))) (test #t (object? (fref eq?))) (test #t (object? (fref equal?))) -(eq? <object> <object>) -(eql? <object> <object>) -(equal? <object> <object>) -(fdef equal? (x y) - (cond ((eql? x y) +(eq? $\object_1$ $\object_2$) +

                The function returns #t if and only if the two objects are one and the same. In other words, the function returns #t if and only if the two objects have the same address in the heap.

                +(eql? $\object_1$ $\object_2$) +

                If both objects are of type number, then the function returns #t if and only if the two objects represent the same mathematical number. Otherwise, if both objects are of type character, then the function returns #t if and only if the two objects represent the same UTF-$16$ code unit. Otherwise, if both objects are of type string, then the function returns #t if and only if the two objects represent the same indexed sequence of UTF-$16$ code units. Otherwise, the function returns #t if and only if the two objects are eq?.

                +(equal? $\object_1$ $\object_2$) +

                If both objects are of type cons, then the function returns #t if and only if the cars of the two objects are equal? and the cdrs of the two objects are equal?. Otherwise, if both objects are of type vector, then the function returns #t if and only if the two objects have the same length and their corresponding elements are equal?. Otherwise, the function returns #t if and only if the two objects are eql?. Note that, when testing the sameness of two objects of type vector, the function does not distinguish between an index with no associated value and an index associated with the value #v.

                +(fdef equal? (object-1 object-2) + (cond ((eql? object-1 object-2) #t) ((and - (cons? x) - (cons? y)) + (cons? object-1) + (cons? object-2)) (and - (equal? (car x) (car y)) - (equal? (cdr x) (cdr y)))) + (equal? (car object-1) (car object-2)) + (equal? (cdr object-1) (cdr object-2)))) + ((and + (vector? object-1) + (vector? object-2)) + (vector-equal? object-1 object-2)) (else #f))) +(fdef vector-equal? (vector-1 vector-2) + (fletrec ((rec (vector-1 vector-2 i) + (cond ((>= i 0) + (and + (equal? (vector-ref vector-1 i) (vector-ref vector-2 i)) + (rec vector-1 vector-2 (- i 1)))) + (else + #t)))) + (vlet ((vector-1-length + (vector-length vector-1)) + (vector-2-length + (vector-length vector-2))) + (and + (= vector-1-length vector-2-length) + (rec vector-1 vector-2 (- vector-1-length 1)))))) + (test #t (eq? #v #v)) (test #t (eql? #v #v)) (test #t (equal? #v #v)) @@ -789,15 +1302,15 @@ (test #t (eql? #f #f)) (test #t (equal? #f #f)) -(test #f (eq? 0 0)) +(test #f (eq? 0 0)) actually unspecified (test #t (eql? 0 0)) (test #t (equal? 0 0)) -(test #f (eq? #"_" #"_")) +(test #f (eq? #"_" #"_")) actually unspecified (test #t (eql? #"_" #"_")) (test #t (equal? #"_" #"_")) -(test #f (eq? "foo" "foo")) +(test #f (eq? "foo" "foo")) actually unspecified (test #t (eql? "foo" "foo")) (test #t (equal? "foo" "foo")) @@ -813,35 +1326,38 @@ (test #t (eql? '() '())) (test #t (equal? '() '())) -(test #f (eq? '(1 2 3) '(1 2 3))) -(test #f (eql? '(1 2 3) '(1 2 3))) +(test #f (eq? '(1 2 3) '(1 2 3))) actually unspecified +(test #f (eql? '(1 2 3) '(1 2 3))) actually unspecified (test #t (equal? '(1 2 3) '(1 2 3))) -(test #f (eq? #(1 2 3) #(1 2 3))) -(test #f (eql? #(1 2 3) #(1 2 3))) -(test #f (equal? #(1 2 3) #(1 2 3))) FIXME +(test #f (eq? #(1 2 3) #(1 2 3))) actually unspecified +(test #f (eql? #(1 2 3) #(1 2 3))) actually unspecified +(test #t (equal? #(1 2 3) #(1 2 3)))
                -Primitive Datatype <code>void</code> -(void? <object>) +Primitive Data Type <code>void</code> +(void? $\object$) (test #t (void? #v)) -(value? <object>) +(value? $\object$) +

                The function returns #f if $\object$ is #v and #t otherwise.

                (fdef value? (object) (not (void? object))) (test #f (value? #v))
                -Primitive Datatype <code>boolean</code> -(boolean? <object>) +Primitive Data Type <code>boolean</code> +(boolean? $\object$) (test #t (boolean? #t)) (test #t (boolean? #f)) -(not <boolean>) -The function not is defined in the bootstrapping section. +(not $\boolean$) +

                The function returns #t if $\boolean$ is #f and #f if $\boolean$ is #t.

                +

                The function not is implemented in the bootstrapping section.

                (test #f (not #t)) (test #t (not #f)) -(and <test-form>*) -The macro and is defined in the bootstrapping section. +(and $\metavar{test-form}$*) +

                The macro returns #t if all its arguments are #t and #f otherwise. The exact behavior of the macro is as follows: The test forms are evaluated in sequence from left to right. If a test form evaluates to an object that is not a boolean, then the following test forms are not evaluated and the evaluation of the macro call completes abruptly for a reason of type error. If a test form evaluates to #f, then the following test forms are not evaluated and the macro call evaluates to #f. If all test forms evaluate to #t, then the macro call evaluates to #t.

                +

                The macro and is implemented in the bootstrapping section.

                (test #t (and)) (test #t (and #t)) (test #f (and #f)) @@ -849,8 +1365,9 @@ (test #f (and #t #f)) (test #f (and #f #t)) (test #f (and #f #f)) -(or <test-form>*) -The macro or is defined in the bootstrapping section. +(or $\metavar{test-form}$*) +

                The macro returns #f if all its arguments are #f and #t otherwise. The exact behavior of the macro is as follows: The test forms are evaluated in sequence from left to right. If a test form evaluates to an object that is not a boolean, then the following test forms are not evaluated and the evaluation of the macro call completes abruptly for a reason of type error. If a test form evaluates to #t, then the following test forms are not evaluated and the macro call evaluates to #t. If all test forms evaluate to #f, then the macro call evaluates to #f.

                +

                The macro or is implemented in the bootstrapping section.

                (test #f (or)) (test #t (or #t)) (test #f (or #f)) @@ -860,20 +1377,21 @@ (test #f (or #f #f))
                -Primitive Datatype <code>number</code> -(number? <object>) +Primitive Data Type <code>number</code> +(number? $\object$) (test #t (number? 0)) -(_+ <number> <number>) -(_- <number> <number>) -(_* <number> <number>) -(_/ <number> <number>) -(% <number> <number>) +(_+ $\number_1$ $\number_2$) +(_- $\number_1$ $\number_2$) +(_* $\number_1$ $\number_2$) +(_/ $\number_1$ $\number_2$) +(% $\number_1$ $\number_2$) (test 8 (_+ 6 2)) (test 4 (_- 6 2)) (test 12 (_* 6 2)) (test 3 (_/ 6 2)) (test 0 (% 6 2)) -(+ <number>*) +(+ $\number_1\ldots\number_n$) +

                If the function is invoked on zero numbers, then the number 0 is returned. If the function is invoked on one number, then that number is returned. If the function is invoked on more than one number, then the result of adding those numbers from left to right is returned: the second number is added to the first number, then the third number is added to the partial result just computed, …

                (fdef + (&rest numbers) (fletrec ((rec (numbers acc) (cond ((empty-list? numbers) @@ -886,7 +1404,8 @@ (test 1 (+ 1)) (test 3 (+ 1 2)) (test 6 (+ 1 2 3)) -(- <number>+) +(- $\number_1\ldots\number_n$) +

                If the function is invoked on zero numbers, then the invocation completes abruptly for a reason of type error. If the function is invoked on one number, then the opposite of that number is returned. If the function is invoked on more than one number, then the result of subtracting those numbers from left to right is returned: the second number is subtracted from the first number, then the third number is subtracted from the partial result just computed, …

                (fdef - (&rest numbers) (fletrec ((rec (numbers acc) (cond ((empty-list? numbers) @@ -904,7 +1423,8 @@ (test -1 (- 0 1)) (test -3 (- 0 1 2)) (test -6 (- 0 1 2 3)) -(* <number>*) +(* $\number_1\ldots\number_n$) +

                If the function is invoked on zero numbers, then the number 1 is returned. If the function is invoked on one number, then that number is returned. If the function is invoked on more than one number, then the result of multiplying those numbers from left to right is returned: the first number is multiplied by the second number, then the partial result just computed is multiplied by the third number, …

                (fdef * (&rest numbers) (fletrec ((rec (numbers acc) (cond ((empty-list? numbers) @@ -917,7 +1437,8 @@ (test 2 (* 2)) (test 8 (* 2 4)) (test 64 (* 2 4 8)) -(/ <number>+) +(/ $\number_1\ldots\number_n$) +

                If the function is invoked on zero numbers, then the invocation completes abruptly for a reason of type error. If the function is invoked on one number, then the inverse of that number is returned. If the function is invoked on more than one number, then the result of dividing those numbers from left to right is returned: the first number is divided by the second number, then the partial result just computed is divided by the third number, …

                (fdef / (&rest numbers) (fletrec ((rec (numbers acc) (cond ((empty-list? numbers) @@ -935,12 +1456,12 @@ (test 0.5 (/ 1 2)) (test 0.125 (/ 1 2 4)) (test 0.015625 (/ 1 2 4 8)) -(= <number> <number>) -(/= <number> <number>) -(< <number> <number>) -(<= <number> <number>) -(> <number> <number>) -(>= <number> <number>) +(= $\number_1$ $\number_2$) +(/= $\number_1$ $\number_2$) +(< $\number_1$ $\number_2$) +(<= $\number_1$ $\number_2$) +(> $\number_1$ $\number_2$) +(>= $\number_1$ $\number_2$) (test '(#f #t #f) (list (= -1 0) (= 0 0) (= 1 0))) (test '(#t #f #t) (list (/= -1 0) (/= 0 0) (/= 1 0))) (test '(#t #f #f) (list (< -1 0) (< 0 0) (< 1 0))) @@ -949,8 +1470,8 @@ (test '(#f #t #t) (list (>= -1 0) (>= 0 0) (>= 1 0)))
                -Primitive Datatype <code>character</code> -(character? <object>) +Primitive Data Type <code>character</code> +(character? $\object$) (test #t (character? #"_")) (test '(#"\U{D83E}" #"\U{DD77}") '(#"🥷")) @@ -964,47 +1485,47 @@ (test '(#"\U{D83E}" #"\U{DD77}" #"\U{D83C}" #"\U{DFFF}") '(#"🥷🏿"))
                -Primitive Datatype <code>string</code> -(string? <object>) +Primitive Data Type <code>string</code> +(string? $\object$) (test #t (string? "foo"))
                -Primitive Datatype <code>symbol</code> -(symbol? <object>) +Primitive Data Type <code>symbol</code> +(symbol? $\object$) (test #t (symbol? :foo)) (test #t (symbol? 'foo))
                -Primitive Datatype <code>keyword</code> -(keyword? <object>) +Primitive Data Type <code>keyword</code> +(keyword? $\object$) (test #t (keyword? :foo)) -(make-keyword <string>) +(make-keyword $\string$) (test #f (eq? (make-keyword "foo") (make-keyword "foo"))) -(fresh-keyword) +(fresh-keyword) +

                The function returns a new uninterned keyword.

                (fdef fresh-keyword () (make-keyword "fresh")) (test #t (keyword? (fresh-keyword)))
                -Primitive Datatype <code>variable</code> -(variable? <object>) +Primitive Data Type <code>variable</code> +(variable? $\object$) (test #t (variable? 'foo)) -(make-variable <string>) +(make-variable $\string$) (test #f (eq? (make-variable "foo") (make-variable "foo"))) -(fresh-variable) -(fdef fresh-variable () - (make-variable "fresh")) - +(fresh-variable) +

                The function returns a new uninterned variable.

                +

                The function fresh-variable is implemented in the bootstrapping section.

                (test #t (variable? (fresh-variable))) -(variable-value <variable>) -(variable-set-value! <variable> <object>) -(variable-value-bound? <variable>) -(variable-unbind-value! <variable>) +(variable-value $\variable$) +(variable-set-value! $\variable$ $\object$) +(variable-value-bound? $\variable$) +(variable-unbind-value! $\variable$) (test '(#f #v :foo #t :foo #v #f #v) (vlet ((variable - (make-variable "foo"))) + (fresh-variable))) (list (variable-value-bound? variable) (variable-value variable) @@ -1014,14 +1535,14 @@ (variable-unbind-value! variable) (variable-value-bound? variable) (variable-value variable)))) -(variable-function <variable>) -(variable-set-function! <variable> <object>) -(variable-function-bound? <variable>) -(variable-unbind-function! <variable>) +(variable-function $\variable$) +(variable-set-function! $\variable$ $\object$) +(variable-function-bound? $\variable$) +(variable-unbind-function! $\variable$) (test '(#f #v :foo #t :foo #v #f #v) (vlet ((variable - (make-variable "foo"))) + (fresh-variable))) (list (variable-function-bound? variable) (variable-function variable) @@ -1031,146 +1552,190 @@ (variable-unbind-function! variable) (variable-function-bound? variable) (variable-function variable)))) +(variable-plist-ref $\variable$ $\keyword$) +(variable-plist-set! $\variable$ $\keyword$ $\object$) +(variable-plist-bound? $\variable$ $\keyword$) +(variable-plist-unbind! $\variable$ $\keyword$) +(test '(#f #v :bar #t :bar #v #f #v) + (vlet ((variable + (fresh-variable))) + (list + (variable-plist-bound? variable :foo) + (variable-plist-ref variable :foo) + (variable-plist-set! variable :foo :bar) + (variable-plist-bound? variable :foo) + (variable-plist-ref variable :foo) + (variable-plist-unbind! variable :foo) + (variable-plist-bound? variable :foo) + (variable-plist-ref variable :foo))))
                -Primitive Datatype <code>list</code> -(list? <object>) +Primitive Data Type <code>list</code> +(list? $\object$) (test #t (list? '())) (test #t (list? '(1 2 3))) (test #t (list? '(1 2 3 . 4))) -(proper-list? <object>) -The function proper-list? is defined in the bootstrapping section. +(proper-list? $\object$) +

                The function return #t if $\object$ is a proper list and #f otherwise.

                +

                The function proper-list? is implemented in the bootstrapping section.

                (test #t (proper-list? '())) (test #t (proper-list? '(1 2 3))) (test #f (proper-list? '(1 2 3 . 4))) -(list <object>*) -The function list is defined in the bootstrapping section. +(list $\object_1\ldots\object_n$) +

                The function collects its arguments into a list: when invoked on the arguments $\object_1,\ldots,\object_n$, the function returns a list whose elements are $\object_1,\ldots,\object_n$.

                +

                The function list is implemented in the bootstrapping section.

                (test '() (list)) (test '(1 2 3) (list 1 2 3)) -(list-reverse <list>) -The function list-reverse is defined in the bootstrapping section. +(list-reverse $\list$) +

                The function returns a list whose elements are the elements of $\list$, which must be a proper list, in reverse order.

                +

                The function list-reverse is implemented in the bootstrapping section.

                (test '() (list-reverse '())) (test '(3 2 1) (list-reverse '(1 2 3))) -(list->values <list>) -(values->list <form>) -(fdef list->values (list) - (apply values list)) - -(mdef values->list (form) - (list 'multiple-value-call 'list form)) - -(test '(1 2 3) (values->list (list->values '(1 2 3)))) -(_for-each <function-form> <list-form>) -#+(or cps oocps sboocps) -(test - '(3 2 1) - (vlet ((acc '())) - (_for-each - (vlambda (x) (vset! acc (cons x acc))) - '(1 2 3)) - acc)) +(list-append $\list_1\ldots\list_n$) +

                Each argument except the last must be a proper list. The last argument can be any object. Let $\list$ be a list whose elements are the elements of the first proper list from first to last followed by the elements of the second proper list from first to last followed by … followed by the elements of the last proper list from first to last. If each argument is a proper list, then the function returns $\list$. Otherwise, if $\list$ is the empty list, then the function returns the last argument. Otherwise, the function returns $\list$ after setting the cdr of its last cons to the last argument.

                +

                The function list-append is implemented in the bootstrapping section.

                +(test '() (list-append)) +(test 1 (list-append 1)) +(test '(1) (list-append '(1))) +(test '(1 . 2) (list-append '(1) 2)) +(test '(1 . 2) (list-append '(1 . 2))) +(test '(1 2) (list-append '(1 2))) +(test '(1 2) (list-append '(1) '(2))) +(test '(1 2 . 3) (list-append '(1 2) 3)) +(test '(1 2 . 3) (list-append '(1) '(2) 3)) +(test '(1 2 . 3) (list-append '(1 2 . 3))) +(test '(1 2 . 3) (list-append '(1) '(2 . 3))) +(test '(1 2 3) (list-append '(1 2 3))) +(test '(1 2 3) (list-append '(1 2) '(3))) +(test '(1 2 3) (list-append '(1) '(2 3))) +(test '(1 2 3) (list-append '(1) '(2) '(3)))
                -Primitive Datatype <code>empty-list</code> -(empty-list? <object>) +Primitive Data Type <code>empty-list</code> +(empty-list? $\object$) (test #t (empty-list? '()))
                -Primitive Datatype <code>cons</code> -(cons? <object>) +Primitive Data Type <code>cons</code> +(cons? $\object$) (test #t (cons? '(1 2 3))) -(cons <object> <object>) +(cons $\object$ $\object$) (test '(1 2 3) (cons 1 (cons 2 (cons 3 '())))) -(car <cons>) -(cdr <cons>) -(caar <cons>) -(cdar <cons>) -(cadr <cons>) -(cddr <cons>) -(caaar <cons>) -(cdaar <cons>) -(cadar <cons>) -(cddar <cons>) -(caadr <cons>) -(cdadr <cons>) -(caddr <cons>) -(cdddr <cons>) -(caaaar <cons>) -(cdaaar <cons>) -(cadaar <cons>) -(cddaar <cons>) -(caadar <cons>) -(cdadar <cons>) -(caddar <cons>) -(cdddar <cons>) -(caaadr <cons>) -(cdaadr <cons>) -(cadadr <cons>) -(cddadr <cons>) -(caaddr <cons>) -(cdaddr <cons>) -(cadddr <cons>) -(cddddr <cons>) - -The functions caar, ..., cddddr are defined in the bootstrapping section. -(set-car! <cons> <object>) -(set-cdr! <cons> <object>) -(set-caar! <cons> <object>) -(set-cdar! <cons> <object>) -(set-cadr! <cons> <object>) -(set-cddr! <cons> <object>) -(set-caaar! <cons> <object>) -(set-cdaar! <cons> <object>) -(set-cadar! <cons> <object>) -(set-cddar! <cons> <object>) -(set-caadr! <cons> <object>) -(set-cdadr! <cons> <object>) -(set-caddr! <cons> <object>) -(set-cdddr! <cons> <object>) -(set-caaaar! <cons> <object>) -(set-cdaaar! <cons> <object>) -(set-cadaar! <cons> <object>) -(set-cddaar! <cons> <object>) -(set-caadar! <cons> <object>) -(set-cdadar! <cons> <object>) -(set-caddar! <cons> <object>) -(set-cdddar! <cons> <object>) -(set-caaadr! <cons> <object>) -(set-cdaadr! <cons> <object>) -(set-cadadr! <cons> <object>) -(set-cddadr! <cons> <object>) -(set-caaddr! <cons> <object>) -(set-cdaddr! <cons> <object>) -(set-cadddr! <cons> <object>) -(set-cddddr! <cons> <object>) -(fdef set-caar! (x y) (set-car! (car x) y)) -(fdef set-cdar! (x y) (set-cdr! (car x) y)) -(fdef set-cadr! (x y) (set-car! (cdr x) y)) -(fdef set-cddr! (x y) (set-cdr! (cdr x) y)) -(fdef set-caaar! (x y) (set-car! (caar x) y)) -(fdef set-cdaar! (x y) (set-cdr! (caar x) y)) -(fdef set-cadar! (x y) (set-car! (cdar x) y)) -(fdef set-cddar! (x y) (set-cdr! (cdar x) y)) -(fdef set-caadr! (x y) (set-car! (cadr x) y)) -(fdef set-cdadr! (x y) (set-cdr! (cadr x) y)) -(fdef set-caddr! (x y) (set-car! (cddr x) y)) -(fdef set-cdddr! (x y) (set-cdr! (cddr x) y)) -(fdef set-caaaar! (x y) (set-car! (caaar x) y)) -(fdef set-cdaaar! (x y) (set-cdr! (caaar x) y)) -(fdef set-cadaar! (x y) (set-car! (cdaar x) y)) -(fdef set-cddaar! (x y) (set-cdr! (cdaar x) y)) -(fdef set-caadar! (x y) (set-car! (cadar x) y)) -(fdef set-cdadar! (x y) (set-cdr! (cadar x) y)) -(fdef set-caddar! (x y) (set-car! (cddar x) y)) -(fdef set-cdddar! (x y) (set-cdr! (cddar x) y)) -(fdef set-caaadr! (x y) (set-car! (caadr x) y)) -(fdef set-cdaadr! (x y) (set-cdr! (caadr x) y)) -(fdef set-cadadr! (x y) (set-car! (cdadr x) y)) -(fdef set-cddadr! (x y) (set-cdr! (cdadr x) y)) -(fdef set-caaddr! (x y) (set-car! (caddr x) y)) -(fdef set-cdaddr! (x y) (set-cdr! (caddr x) y)) -(fdef set-cadddr! (x y) (set-car! (cdddr x) y)) -(fdef set-cddddr! (x y) (set-cdr! (cdddr x) y)) +(car $\cons$) +(cdr $\cons$) +(caar $\cons$) +(cdar $\cons$) +(cadr $\cons$) +(cddr $\cons$) +(caaar $\cons$) +(cdaar $\cons$) +(cadar $\cons$) +(cddar $\cons$) +(caadr $\cons$) +(cdadr $\cons$) +(caddr $\cons$) +(cdddr $\cons$) +(caaaar $\cons$) +(cdaaar $\cons$) +(cadaar $\cons$) +(cddaar $\cons$) +(caadar $\cons$) +(cdadar $\cons$) +(caddar $\cons$) +(cdddar $\cons$) +(caaadr $\cons$) +(cdaadr $\cons$) +(cadadr $\cons$) +(cddadr $\cons$) +(caaddr $\cons$) +(cdaddr $\cons$) +(cadddr $\cons$) +(cddddr $\cons$) +

                The function caar returns the car of the car of $\cons$, …, the function cddddr returns the cdr of the cdr of the cdr of the cdr of $\cons$.

                +

                The functions caar, cdar, cadr, and cddr are implemented in the bootstrapping section.

                +(fdef caaar (cons) (car (car (car cons)))) +(fdef cdaar (cons) (cdr (car (car cons)))) +(fdef cadar (cons) (car (cdr (car cons)))) +(fdef cddar (cons) (cdr (cdr (car cons)))) +(fdef caadr (cons) (car (car (cdr cons)))) +(fdef cdadr (cons) (cdr (car (cdr cons)))) +(fdef caddr (cons) (car (cdr (cdr cons)))) +(fdef cdddr (cons) (cdr (cdr (cdr cons)))) +(fdef caaaar (cons) (car (car (car (car cons))))) +(fdef cdaaar (cons) (cdr (car (car (car cons))))) +(fdef cadaar (cons) (car (cdr (car (car cons))))) +(fdef cddaar (cons) (cdr (cdr (car (car cons))))) +(fdef caadar (cons) (car (car (cdr (car cons))))) +(fdef cdadar (cons) (cdr (car (cdr (car cons))))) +(fdef caddar (cons) (car (cdr (cdr (car cons))))) +(fdef cdddar (cons) (cdr (cdr (cdr (car cons))))) +(fdef caaadr (cons) (car (car (car (cdr cons))))) +(fdef cdaadr (cons) (cdr (car (car (cdr cons))))) +(fdef cadadr (cons) (car (cdr (car (cdr cons))))) +(fdef cddadr (cons) (cdr (cdr (car (cdr cons))))) +(fdef caaddr (cons) (car (car (cdr (cdr cons))))) +(fdef cdaddr (cons) (cdr (car (cdr (cdr cons))))) +(fdef cadddr (cons) (car (cdr (cdr (cdr cons))))) +(fdef cddddr (cons) (cdr (cdr (cdr (cdr cons))))) +(set-car! $\cons$ $\object$) +(set-cdr! $\cons$ $\object$) +(set-caar! $\cons$ $\object$) +(set-cdar! $\cons$ $\object$) +(set-cadr! $\cons$ $\object$) +(set-cddr! $\cons$ $\object$) +(set-caaar! $\cons$ $\object$) +(set-cdaar! $\cons$ $\object$) +(set-cadar! $\cons$ $\object$) +(set-cddar! $\cons$ $\object$) +(set-caadr! $\cons$ $\object$) +(set-cdadr! $\cons$ $\object$) +(set-caddr! $\cons$ $\object$) +(set-cdddr! $\cons$ $\object$) +(set-caaaar! $\cons$ $\object$) +(set-cdaaar! $\cons$ $\object$) +(set-cadaar! $\cons$ $\object$) +(set-cddaar! $\cons$ $\object$) +(set-caadar! $\cons$ $\object$) +(set-cdadar! $\cons$ $\object$) +(set-caddar! $\cons$ $\object$) +(set-cdddar! $\cons$ $\object$) +(set-caaadr! $\cons$ $\object$) +(set-cdaadr! $\cons$ $\object$) +(set-cadadr! $\cons$ $\object$) +(set-cddadr! $\cons$ $\object$) +(set-caaddr! $\cons$ $\object$) +(set-cdaddr! $\cons$ $\object$) +(set-cadddr! $\cons$ $\object$) +(set-cddddr! $\cons$ $\object$) +

                The function set-caar! sets the car of the car of $\cons$ to $\object$, …, the function set-cddddr! sets the cdr of the cdr of the cdr of the cdr of $\cons$ to $\object$.

                +(fdef set-caar! (cons object) (set-car! (car cons) object)) +(fdef set-cdar! (cons object) (set-cdr! (car cons) object)) +(fdef set-cadr! (cons object) (set-car! (cdr cons) object)) +(fdef set-cddr! (cons object) (set-cdr! (cdr cons) object)) +(fdef set-caaar! (cons object) (set-car! (caar cons) object)) +(fdef set-cdaar! (cons object) (set-cdr! (caar cons) object)) +(fdef set-cadar! (cons object) (set-car! (cdar cons) object)) +(fdef set-cddar! (cons object) (set-cdr! (cdar cons) object)) +(fdef set-caadr! (cons object) (set-car! (cadr cons) object)) +(fdef set-cdadr! (cons object) (set-cdr! (cadr cons) object)) +(fdef set-caddr! (cons object) (set-car! (cddr cons) object)) +(fdef set-cdddr! (cons object) (set-cdr! (cddr cons) object)) +(fdef set-caaaar! (cons object) (set-car! (caaar cons) object)) +(fdef set-cdaaar! (cons object) (set-cdr! (caaar cons) object)) +(fdef set-cadaar! (cons object) (set-car! (cdaar cons) object)) +(fdef set-cddaar! (cons object) (set-cdr! (cdaar cons) object)) +(fdef set-caadar! (cons object) (set-car! (cadar cons) object)) +(fdef set-cdadar! (cons object) (set-cdr! (cadar cons) object)) +(fdef set-caddar! (cons object) (set-car! (cddar cons) object)) +(fdef set-cdddar! (cons object) (set-cdr! (cddar cons) object)) +(fdef set-caaadr! (cons object) (set-car! (caadr cons) object)) +(fdef set-cdaadr! (cons object) (set-cdr! (caadr cons) object)) +(fdef set-cadadr! (cons object) (set-car! (cdadr cons) object)) +(fdef set-cddadr! (cons object) (set-cdr! (cdadr cons) object)) +(fdef set-caaddr! (cons object) (set-car! (caddr cons) object)) +(fdef set-cdaddr! (cons object) (set-cdr! (caddr cons) object)) +(fdef set-cadddr! (cons object) (set-car! (cdddr cons) object)) +(fdef set-cddddr! (cons object) (set-cdr! (cdddr cons) object)) (fdef test-cr () (cons @@ -1189,56 +1754,98 @@ (cons #v #v) (cons #v #v))))) -(test 'x (vlet ((tree (test-cr))) (set-car! tree 'x) (car tree))) -(test 'x (vlet ((tree (test-cr))) (set-cdr! tree 'x) (cdr tree))) -(test 'x (vlet ((tree (test-cr))) (set-caar! tree 'x) (caar tree))) -(test 'x (vlet ((tree (test-cr))) (set-cdar! tree 'x) (cdar tree))) -(test 'x (vlet ((tree (test-cr))) (set-cadr! tree 'x) (cadr tree))) -(test 'x (vlet ((tree (test-cr))) (set-cddr! tree 'x) (cddr tree))) -(test 'x (vlet ((tree (test-cr))) (set-caaar! tree 'x) (caaar tree))) -(test 'x (vlet ((tree (test-cr))) (set-cdaar! tree 'x) (cdaar tree))) -(test 'x (vlet ((tree (test-cr))) (set-cadar! tree 'x) (cadar tree))) -(test 'x (vlet ((tree (test-cr))) (set-cddar! tree 'x) (cddar tree))) -(test 'x (vlet ((tree (test-cr))) (set-caadr! tree 'x) (caadr tree))) -(test 'x (vlet ((tree (test-cr))) (set-cdadr! tree 'x) (cdadr tree))) -(test 'x (vlet ((tree (test-cr))) (set-caddr! tree 'x) (caddr tree))) -(test 'x (vlet ((tree (test-cr))) (set-cdddr! tree 'x) (cdddr tree))) -(test 'x (vlet ((tree (test-cr))) (set-caaaar! tree 'x) (caaaar tree))) -(test 'x (vlet ((tree (test-cr))) (set-cdaaar! tree 'x) (cdaaar tree))) -(test 'x (vlet ((tree (test-cr))) (set-cadaar! tree 'x) (cadaar tree))) -(test 'x (vlet ((tree (test-cr))) (set-cddaar! tree 'x) (cddaar tree))) -(test 'x (vlet ((tree (test-cr))) (set-caadar! tree 'x) (caadar tree))) -(test 'x (vlet ((tree (test-cr))) (set-cdadar! tree 'x) (cdadar tree))) -(test 'x (vlet ((tree (test-cr))) (set-caddar! tree 'x) (caddar tree))) -(test 'x (vlet ((tree (test-cr))) (set-cdddar! tree 'x) (cdddar tree))) -(test 'x (vlet ((tree (test-cr))) (set-caaadr! tree 'x) (caaadr tree))) -(test 'x (vlet ((tree (test-cr))) (set-cdaadr! tree 'x) (cdaadr tree))) -(test 'x (vlet ((tree (test-cr))) (set-cadadr! tree 'x) (cadadr tree))) -(test 'x (vlet ((tree (test-cr))) (set-cddadr! tree 'x) (cddadr tree))) -(test 'x (vlet ((tree (test-cr))) (set-caaddr! tree 'x) (caaddr tree))) -(test 'x (vlet ((tree (test-cr))) (set-cdaddr! tree 'x) (cdaddr tree))) -(test 'x (vlet ((tree (test-cr))) (set-cadddr! tree 'x) (cadddr tree))) -(test 'x (vlet ((tree (test-cr))) (set-cddddr! tree 'x) (cddddr tree))) +(test 'foo (vlet ((tree (test-cr))) (set-car! tree 'foo) (car tree))) +(test 'foo (vlet ((tree (test-cr))) (set-cdr! tree 'foo) (cdr tree))) +(test 'foo (vlet ((tree (test-cr))) (set-caar! tree 'foo) (caar tree))) +(test 'foo (vlet ((tree (test-cr))) (set-cdar! tree 'foo) (cdar tree))) +(test 'foo (vlet ((tree (test-cr))) (set-cadr! tree 'foo) (cadr tree))) +(test 'foo (vlet ((tree (test-cr))) (set-cddr! tree 'foo) (cddr tree))) +(test 'foo (vlet ((tree (test-cr))) (set-caaar! tree 'foo) (caaar tree))) +(test 'foo (vlet ((tree (test-cr))) (set-cdaar! tree 'foo) (cdaar tree))) +(test 'foo (vlet ((tree (test-cr))) (set-cadar! tree 'foo) (cadar tree))) +(test 'foo (vlet ((tree (test-cr))) (set-cddar! tree 'foo) (cddar tree))) +(test 'foo (vlet ((tree (test-cr))) (set-caadr! tree 'foo) (caadr tree))) +(test 'foo (vlet ((tree (test-cr))) (set-cdadr! tree 'foo) (cdadr tree))) +(test 'foo (vlet ((tree (test-cr))) (set-caddr! tree 'foo) (caddr tree))) +(test 'foo (vlet ((tree (test-cr))) (set-cdddr! tree 'foo) (cdddr tree))) +(test 'foo (vlet ((tree (test-cr))) (set-caaaar! tree 'foo) (caaaar tree))) +(test 'foo (vlet ((tree (test-cr))) (set-cdaaar! tree 'foo) (cdaaar tree))) +(test 'foo (vlet ((tree (test-cr))) (set-cadaar! tree 'foo) (cadaar tree))) +(test 'foo (vlet ((tree (test-cr))) (set-cddaar! tree 'foo) (cddaar tree))) +(test 'foo (vlet ((tree (test-cr))) (set-caadar! tree 'foo) (caadar tree))) +(test 'foo (vlet ((tree (test-cr))) (set-cdadar! tree 'foo) (cdadar tree))) +(test 'foo (vlet ((tree (test-cr))) (set-caddar! tree 'foo) (caddar tree))) +(test 'foo (vlet ((tree (test-cr))) (set-cdddar! tree 'foo) (cdddar tree))) +(test 'foo (vlet ((tree (test-cr))) (set-caaadr! tree 'foo) (caaadr tree))) +(test 'foo (vlet ((tree (test-cr))) (set-cdaadr! tree 'foo) (cdaadr tree))) +(test 'foo (vlet ((tree (test-cr))) (set-cadadr! tree 'foo) (cadadr tree))) +(test 'foo (vlet ((tree (test-cr))) (set-cddadr! tree 'foo) (cddadr tree))) +(test 'foo (vlet ((tree (test-cr))) (set-caaddr! tree 'foo) (caaddr tree))) +(test 'foo (vlet ((tree (test-cr))) (set-cdaddr! tree 'foo) (cdaddr tree))) +(test 'foo (vlet ((tree (test-cr))) (set-cadddr! tree 'foo) (cadddr tree))) +(test 'foo (vlet ((tree (test-cr))) (set-cddddr! tree 'foo) (cddddr tree)))
                -Primitive Datatype <code>vector</code> -(vector? <object>) +Primitive Data Type <code>vector</code> +(vector? $\object$) (test #t (vector? #(1 2 3))) +(make-vector $\number$ $\object$) +(test + '(3 #f #v) + (vlet ((vector + (make-vector 3))) + (list + (vector-length vector) + (vector-bound? vector 1) + (vector-ref vector 1)))) + +(test + '(3 #t #v) + (vlet ((vector + (make-vector 3 #v))) + (list + (vector-length vector) + (vector-bound? vector 1) + (vector-ref vector 1)))) +(vector-length $\vector$) +(test 3 (vector-length #(1 2 3))) +(vector-ref $\vector$ $\number$) +(vector-set! $\vector$ $\number$ $\object$) +(vector-bound? $\vector$ $\number$) +(vector-unbind! $\vector$ $\number$) +(test + '(#f #v :foo #t :foo #v #f #v) + (vlet ((vector + (make-vector 3))) + (list + (vector-bound? vector 1) + (vector-ref vector 1) + (vector-set! vector 1 :foo) + (vector-bound? vector 1) + (vector-ref vector 1) + (vector-unbind! vector 1) + (vector-bound? vector 1) + (vector-ref vector 1))))
                -Primitive Datatype <code>function</code> -(function? <object>) +Primitive Data Type <code>function</code> +(function? $\object$) (test #t (function? (fref eq?))) (test #t (function? (fref equal?)))
                -Primitive Datatype <code>primitive-function</code> -(primitive-function? <object>) +Primitive Data Type <code>primitive-function</code> +(primitive-function? $\object$) (test #t (primitive-function? (fref eq?)))
                -Primitive Datatype <code>closure</code> -(closure? <object>) +Primitive Data Type <code>closure</code> +(closure? $\object$) (test #t (closure? (fref equal?)))
                +
                +Miscellaneous +(now) +(test #t (number? (now))) +