<html>
<head>
<meta charset="utf-8">
+ <title>Implementation Notes</title>
<link rel="stylesheet" href="___cssURL___"/>
<script src="___jsURL___"></script>
<script>const windowId = ___windowId___;</script>
</head>
<body>
+ <div class="preamble">
+ $\DeclareMathOperator{\bindingmathop}{binding}$
+ </div>
+ <h1>Implementation Notes</h1>
+ <p>The implementation notes document the JavaScript file <code>/system/core.js</code>, 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.</p>
+ <p>The JavaScript file implements five interpreter-based evaluators:</p>
+ <ul>
+ <li>Plain Recursive (plainrec)</li>
+ <li>Continuation Passing Style (cps)</li>
+ <li>Object-Oriented CPS (oocps)</li>
+ <li>Stack-Based Object-Oriented CPS (sboocps)</li>
+ <li>Trampoline (trampoline)</li>
+ <li>Trampoline++ (trampolinepp)</li>
+ </ul>
+ <p>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.</p>
+ <p>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.</p>
+ <p>Here are quick links to the sections:</p>
+ <ol>
+ <li><a href="#global-variables">Global Variables</a></li>
+ <li><a href="#interface-ide">Interface (IDE)</a></li>
+ <li><a href="#errors">Errors</a></li>
+ <li><a href="#tokenizer">Tokenizer</a></li>
+ <li><a href="#reader">Reader</a></li>
+ <li><a href="#evl-to-xml-converter">EVL to XML Converter</a></li>
+ <li><a href="#form-analyzer">Form Analyzer</a></li>
+ <li><a href="#scope-extent-combinations">Scope-Extent Combinations</a></li>
+ <li><a href="#namespaces">Namespaces</a></li>
+ <li><a href="#global-environment">Global Environment</a></li>
+ <li><a href="#lexical-and-dynamic-environments">Lexical and Dynamic Environments</a></li>
+ <li><a href="#pairing-parameters-with-arguments">Pairing Parameters with Arguments</a></li>
+ <li><a href="#generic-evaluator">Generic Evaluator</a></li>
+ <li><a href="#plainrec">Plain Recursive Evaluator</a></li>
+ <li><a href="#cps">Continuation Passing Style Evaluator</a></li>
+ <li><a href="#oocps">Object-Oriented CPS Evaluator</a></li>
+ <li><a href="#sboocps">Stack-Based Object-Oriented CPS Evaluator</a></li>
+ <li><a href="#trampoline">Trampoline Evaluator</a></li>
+ <li><a href="#trampolinepp">Trampoline++ Evaluator</a></li>
+ <li><a href="#primitive-function-definitions-1">Primitive Function Definitions (1)</a></li>
+ <li><a href="#bounce">Bounce</a></li>
+ <li><a href="#evaluation-request">Evaluation Request</a></li>
+ <li><a href="#result">Result</a></li>
+ <li><a href="#evlobjects">EVLObjects</a></li>
+ <li><a href="#evlobject">EVLObject</a></li>
+ <li><a href="#evlvoid">EVLVoid</a></li>
+ <li><a href="#evlboolean">EVLBoolean</a></li>
+ <li><a href="#evlnumber">EVLNumber</a></li>
+ <li><a href="#evlcharacter">EVLCharacter</a></li>
+ <li><a href="#evlstring">EVLString</a></li>
+ <li><a href="#evlsymbol">EVLSymbol</a></li>
+ <li><a href="#evlkeyword">EVLKeyword</a></li>
+ <li><a href="#evlvariable">EVLVariable</a></li>
+ <li><a href="#evllist">EVLList</a></li>
+ <li><a href="#evlemptylist">EVLEmptyList</a></li>
+ <li><a href="#evlcons">EVLCons</a></li>
+ <li><a href="#evlvector">EVLVector</a></li>
+ <li><a href="#evlfunction">EVLFunction</a></li>
+ <li><a href="#evlprimitivefunction">EVLPrimitiveFunction</a></li>
+ <li><a href="#evlclosure">EVLClosure</a></li>
+ <li><a href="#miscellaneous-primitive-functions">Miscellaneous Primitive Functions</a></li>
+ <li><a href="#primitive-function-definitions-2">Primitive Function Definitions (2)</a></li>
+ <li><a href="#interface-command-line">Interface (Command Line)</a></li>
+ </ol>
+ <h2 id="global-variables">Global Variables</h2>
+ <p>The constant <code>isRunningInsideNode</code> is true if the JavaScript file is running inside a Node.js runtime environment and false otherwise.</p>
+ <p>The variable <code>abortSignalArray</code> contains a shared array used by the IDE to abort the current evaluation without terminating the web worker.</p>
+ <p>The variable <code>selectedEvaluator</code> contains the name of the selected evaluator.</p>
+ <h2 id="interface-ide">Interface (IDE)</h2>
+ <p>This section implements the interface used by the IDE to control the evaluator and the EVL to XML converter.</p>
+ <p>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.</p>
+ <p>The main thread and the web worker thread communicate through messages and shared arrays.</p>
+ <h3>Messages</h3>
+ <p>The request messages sent by the main thread to the web worker thread are objects containing the following properties:</p>
+ <ul>
+ <li><code>id</code> (required): A unique integer id used to pair responses with requests.</li>
+ <li><code>action</code> (required): An integer specifying the requested action.</li>
+ <li><code>input</code> (optional): Some data.</li>
+ </ul>
+ <p>The response messages sent by the web worker thread to the main thread are objects containing the following properties:</p>
+ <ul>
+ <li><code>id</code> (required): A unique integer id used to pair responses with requests.</li>
+ <li><code>status</code> (required): An integer specifying the status of the processing of the requested action.</li>
+ <li><code>output</code> (optional): Some data.</li>
+ </ul>
+ <p>When the processing of the requested action fails because of an error, the response message contains the following properties:</p>
+ <ul>
+ <li><code>id</code>: A unique integer id used to pair responses with requests.</li>
+ <li><code>status</code>: The value of the constant <code>ERROR</code>.</li>
+ <li><code>output</code>: A string describing the error.</li>
+ </ul>
+ <p>When the processing of the requested action fails because of the abortion of an evaluation, the response message contains the following properties:</p>
+ <ul>
+ <li><code>id</code>: A unique integer id used to pair responses with requests.</li>
+ <li><code>status</code>: The value of the constant <code>ABORTED</code>.</li>
+ </ul>
+ <p>The following sections describe the messages currently implemented. The response messages <code>ERROR</code> and <code>ABORTED</code> are omitted from the descriptions. The property <code>id</code> is omitted from the descriptions.</p>
+ <h4><code>INITIALIZE</code></h4>
+ <p>This message is used to request the initialization of the web worker.</p>
+ <p>Request message:</p>
+ <ul>
+ <li><code>action</code>: The value of the constant <code>INITIALIZE</code>.</li>
+ <li><code>input</code>: An object containing the following properties:
+ <ul>
+ <li><code>abortSignalBuffer</code>: The shared buffer under the shared array.</li>
+ <li><code>selectedEvaluator</code>: The name of the selected evaluator.</li>
+ <li><code>evlFiles</code>: The contents of some EVLambda source files to load.</li>
+ </ul>
+ </li>
+ </ul>
+ <p>Response message when the processing of the requested action succeeds:</p>
+ <ul>
+ <li><code>status</code>: The value of the constant <code>SUCCESS</code>.</li>
+ <li><code>output</code>: An array containing the printable representations of the values of the last form of the last EVLambda source file.</li>
+ </ul>
+ <h4><code>EVALUATE_FIRST_FORM</code></h4>
+ <p>This message is used to request the evaluation of the first form contained in some input string.</p>
+ <p>Request message:</p>
+ <ul>
+ <li><code>action</code>: The value of the constant <code>EVALUATE_FIRST_FORM</code>.</li>
+ <li><code>input</code>: The input string.</li>
+ </ul>
+ <p>Response message when the processing of the requested action fails because the input string does not contain any forms:</p>
+ <ul>
+ <li><code>status</code>: The value of the constant <code>FOUND_NO_FORM</code>.</li>
+ </ul>
+ <p>Response message when the processing of the requested action succeeds:</p>
+ <ul>
+ <li><code>status</code>: The value of the constant <code>SUCCESS</code>.</li>
+ <li><code>output</code>: An array containing the printable representations of the values of the first form contained in the input string.</li>
+ </ul>
+ <h4><code>EVALUATE_ALL_FORMS</code></h4>
+ <p>This message is used to request the evaluation of all of the forms contained in some input string.</p>
+ <p>Request message:</p>
+ <ul>
+ <li><code>action</code>: The value of the constant <code>EVALUATE_ALL_FORMS</code>.</li>
+ <li><code>input</code>: The input string.</li>
+ </ul>
+ <p>Response message when the processing of the requested action succeeds:</p>
+ <ul>
+ <li><code>status</code>: The value of the constant <code>SUCCESS</code>.</li>
+ <li><code>output</code>: An array containing the printable representations of the values of the last form contained in the input string.</li>
+ </ul>
+ <h4><code>CONVERT_EVL_TO_XML</code></h4>
+ <p>This message is used to request the conversion of some input string from EVL to XML.</p>
+ <p>Request message:</p>
+ <ul>
+ <li><code>action</code>: The value of the constant <code>CONVERT_EVL_TO_XML</code>.</li>
+ <li><code>input</code>: The input string.</li>
+ </ul>
+ <p>Response message when the processing of the requested action succeeds:</p>
+ <ul>
+ <li><code>status</code>: The value of the constant <code>SUCCESS</code>.</li>
+ <li><code>output</code>: A string containing the result of the conversion.</li>
+ </ul>
+ <h3>Shared Arrays</h3>
+ <h4><code>abortSignalArray</code></h4>
+ <p>The shared array contained in the variable <code>abortSignalArray</code> 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.</p>
+ <h2 id="errors">Errors</h2>
+ <p>This section defines some custom error types.</p>
+ <h2 id="tokenizer">Tokenizer</h2>
+ <p>For the most part, the tokenizer is implemented as described in the reference manual. The main difference is that the tokens are produced on demand instead of all at once before the parsing begins.</p>
+ <p>On each invocation, the tokenizer sets the following properties:</p>
+ <ul>
+ <li><code>whitespace</code>: The run of whitespace preceding the lexeme associated with the next token.</li>
+ <li><code>lexeme</code>: The lexeme associated with the next token.</li>
+ <li><code>category</code>: The category of the next token.</li>
+ <li><code>value</code>: The value of the next token or <code>null</code> if tokens of this category have no value.</li>
+ </ul>
+ <h2 id="reader">Reader</h2>
+ <p>The reader is a <a href="https://en.wikipedia.org/wiki/Recursive_descent_parser" target="_blank">recursive descent parser</a> getting its tokens from the tokenizer.</p>
+ <p>The read-time conditionalization facility is integrated into the reader.</p>
+ <p>The central function is the function <code>readObject</code>, which returns one of the following:</p>
+ <ul>
+ <li>The next object.</li>
+ <li>The value of the constant <code>DOT</code>.</li>
+ <li>The value of the constant <code>CLOSING_PARENTHESIS</code>.</li>
+ <li>The value of the constant <code>XML_END_TAG</code>.</li>
+ <li>The value of the constant <code>EOI</code>.</li>
+ </ul>
+ <p>The function <code>readObject</code> treats XML elements as comments and skips them. However, the objects contained inside XML elements are not completely invisible because a callback can be attached to the tokenizer that will be invoked on each object encountered while skipping XML elements.</p>
+ <p>Most of the other functions invoke the function <code>readObject</code> and use a <code>switch</code> statement to decide what to do depending on the result of the invocation.</p>
+ <p>The main function is the function <code>read</code>, which invokes the function <code>readObject</code> and, depending on the result of the invocation, does one of the following:</p>
+ <ul>
+ <li>If the function <code>readObject</code> returns the next object, then the function <code>read</code> returns that object.</li>
+ <li>If the function <code>readObject</code> returns <code>DOT</code>, <code>CLOSING_PARENTHESIS</code>, or <code>XML_END_TAG</code>, then the function <code>read</code> throws an exception.</li>
+ <li>If the function <code>readObject</code> returns <code>EOI</code>, then the function <code>read</code> returns <code>null</code> to signal the invoker that the end of the input has been reached.</li>
+ </ul>
+ <p>In order to evaluates all of the forms contained in an EVLambda source file, the function <code>read</code> is invoked in a loop. If the function <code>read</code> returns an object, then that object is evaluated and the loop continues. If the function <code>read</code> returns <code>null</code>, then the loop terminates.</p>
+ <p>When the source file is a plain EVLambda source file, the function <code>read</code> returns each of the forms in turn and all of the forms get evaluated.</p>
+ <p>When the source file is a documented EVLambda source file, the first invocation of the function <code>read</code> returns <code>null</code> (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.</p>
+ <h2 id="evl-to-xml-converter">EVL to XML Converter</h2>
+ <p>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.</p>
+ <p>The EVL to XML converter uses the following context-free grammar to model the input:</p>
+ <table class="ebnf">
+ <tr>
+ <td class="lhs">$\metavar{input}$</td>
+ <td class="def">$\Coloneq$</td>
+ <td class="rhs"><code>beginning-of-input {$\metavar{top-level-whitespace}$ $\metavar{construct}$}* $\metavar{top-level-whitespace}$ end-of-input</code></td>
+ </tr>
+ <tr>
+ <td class="lhs">$\metavar{construct}$</td>
+ <td class="def">$\Coloneq$</td>
+ <td class="rhs"><code>$\metavar{xml-construct}$ | $\metavar{evl-construct}$</code></td>
+ </tr>
+ <tr>
+ <td class="lhs">$\metavar{xml-construct}$</td>
+ <td class="def">$\Coloneq$</td>
+ <td class="rhs"><code>xml-start-tag {$\metavar{xml-whitespace}$ $\metavar{construct}$}* $\metavar{xml-whitespace}$ xml-end-tag | xml-empty-element-tag | xml-comment</code></td>
+ </tr>
+ <tr>
+ <td class="lhs">$\metavar{evl-construct}$</td>
+ <td class="def">$\Coloneq$</td>
+ <td class="rhs"><code>void | boolean | number | hash-string-construct | string | keyword | variable | quote | quasiquote | unquote | unquote-splicing | hash-plus | hash-minus | dot | {left-parenthesis | hash-left-parenthesis} {$\metavar{evl-whitespace}$ $\metavar{construct}$}* $\metavar{evl-whitespace}$ right-parenthesis</code></td>
+ </tr>
+ </table>
+ <p>The main differences between the context-free grammar used by the EVL to XML converter and the context-free grammar used by the parser are the following:</p>
+ <ul>
+ <li>New tokens have been added to represent the beginning and the end of the input.</li>
+ <li>New tokens have been added to represent runs of whitespace. Runs of whitespace can be empty and can occur in three different contexts: top-level context, XML context , and EVL context.</li>
+ <li>Hash-string constructs are not decomposed into characters.</li>
+ <li>The grammar recognizes the tokens introducing abbreviations but it does not recognize abbreviations as units.</li>
+ <li>The grammar recognizes the tokens introducing read-time conditionals but it does not recognize read-time conditionals as units.</li>
+ <li>The grammar recognizes the dot but it does not distinguish between proper lists and dotted lists.</li>
+ <li>The value of a token is the lexeme associated with the token.</li>
+ </ul>
+ <p>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:</p>
+ <ul>
+ <li>Whether the run of whitespace occurs in top-level context, in XML context, or in EVL context.</li>
+ <li>Whether the token preceding the run of whitespace is <code>beginning-of-input</code>, an XML token, or an EVL token.</li>
+ <li>Whether the token following the run of whitespace is <code>end-of-input</code>, an XML token, or an EVL token.</li>
+ </ul>
+ <p>The XML tokens are the following tokens: <code>xml-start-tag</code>, <code>xml-end-tag</code>, <code>xml-empty-element-tag</code>, and <code>xml-comment</code>.</p>
+ <p>The EVL tokens are the end-of-line and end-of-last-line comments (which are treated as a unit) and the following tokens: <code>void</code>, <code>boolean</code>, <code>number</code>, <code>hash-string-construct</code>, <code>string</code>, <code>keyword</code>, <code>variable</code>, <code>quote</code>, <code>quasiquote</code>, <code>unquote</code>, <code>unquote-splicing</code>, <code>hash-plus</code>, <code>hash-minus</code>, <code>dot</code>, <code>left-parenthesis</code>, <code>hash-left-parenthesis</code>, and <code>right-parenthesis</code>.</p>
+ <p>The end-of-line and end-of-last-line comments are treated as EVL tokens because they are not delimited from the surrounding code by additional tags.</p>
+ <p>Tags are never added before or after the runs of whitespace occurring in top-level context.</p>
+ <p>The runs of whitespace occurring in XML context are processed as follows:</p>
+ <dl>
+ <dt><code>$\metavar{xmltok}$$\metavar{ws}$$\metavar{evltok}$</code> is transformed into</dt>
+ <dd><code>$\metavar{xmltok}$$\metavar{ws}$<toplevelcode><blockcode>$\metavar{evltok}$</code></dd>
+ <dt><code>$\metavar{evltok}$$\metavar{ws}$$\metavar{evltok}$</code>, where $\metavar{ws}$ contains at least one blank line, is transformed into</dt>
+ <dd><code>$\metavar{evltok}$</blockcode></toplevelcode>$\metavar{ws}$<toplevelcode><blockcode>$\metavar{evltok}$</code></dd>
+ <dt><code>$\metavar{evltok}$$\metavar{ws}$$\metavar{evltok}$</code>, where $\metavar{ws}$ contains no blank lines, is transformed into</dt>
+ <dd><code>$\metavar{evltok}$$\metavar{ws}$$\metavar{evltok}$</code></dd>
+ <dt><code>$\metavar{evltok}$$\metavar{ws}$$\metavar{xmltok}$</code> is transformed into</dt>
+ <dd><code>$\metavar{evltok}$</toplevelcode></blockcode>$\metavar{ws}$$\metavar{xmltok}$</code></dd>
+ <dt><code>$\metavar{xmltok}$$\metavar{ws}$$\metavar{xmltok}$</code> is transformed into</dt>
+ <dd><code>$\metavar{xmltok}$$\metavar{ws}$$\metavar{xmltok}$</code></dd>
+ </dl>
+ <p>The runs of whitespace occurring in EVL context are processed as follows:</p>
+ <dl>
+ <dt><code>$\metavar{evltok}$$\metavar{ws}$$\metavar{xmltok}$</code> is transformed into</dt>
+ <dd><code>$\metavar{evltok}$</blockcode><indentation style="…"><blockcomment>$\metavar{ws}$$\metavar{xmltok}$</code></dd>
+ <dt><code>$\metavar{xmltok}$$\metavar{ws}$$\metavar{xmltok}$</code> is transformed into</dt>
+ <dd><code>$\metavar{xmltok}$$\metavar{ws}$$\metavar{xmltok}$</code></dd>
+ <dt><code>$\metavar{xmltok}$$\metavar{ws}$$\metavar{evltok}$</code> is transformed into</dt>
+ <dd><code>$\metavar{xmltok}$</blockcomment></indentation><blockcode>$\metavar{ws}$$\metavar{evltok}$</code></dd>
+ <dt><code>$\metavar{evltok}$$\metavar{ws}$$\metavar{evltok}$</code> is transformed into</dt>
+ <dd><code>$\metavar{evltok}$$\metavar{ws}$$\metavar{evltok}$</code></dd>
+ </dl>
+ <h2 id="form-analyzer">Form Analyzer</h2>
+ <p>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.</p>
+ <h2 id="scope-extent-combinations">Scope-Extent Combinations</h2>
+ <p>The scope-extent combinations are identified using the members of the following enumeration:</p>
+ <ul>
+ <li><code>LEX_SCOPE</code>: lexical scope and indefinite extent (= lexical environment)</li>
+ <li><code>DYN_SCOPE</code>: indefinite scope and dynamic extent (= dynamic environment)</li>
+ </ul>
+ <h2 id="namespaces">Namespaces</h2>
+ <p>The namespaces are identified using the members of the following enumeration:</p>
+ <ul>
+ <li><code>VAL_NS</code>: value namespace</li>
+ <li><code>FUN_NS</code>: function namespace</li>
+ </ul>
+ <h2 id="global-environment">Global Environment</h2>
+ <p>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.</p>
+ <p>Variables are represented by instances of the JavaScript class <code>EVLVariable</code>. The property <code>value</code> 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 <code>null</code> otherwise. The property <code>function</code> 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 <code>null</code> otherwise.</p>
+ <h2 id="lexical-and-dynamic-environments">Lexical and Dynamic Environments</h2>
+ <p>The user manual specifies the lexical and dynamic environments as follows:</p>
+ <ul>
+ <li>Each lexical or dynamic environment consists of two sets of bindings: one for the value namespace and one for the function namespace.</li>
+ <li>Each lexical or dynamic environment is the result of extending zero or more times in sequence an empty environment.</li>
+ <li>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.</li>
+ </ul>
+ <p>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.</p>
+ <p>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 $\bindingmathop(i,j)$ the binding between $\var_{i,j}$ and $\obj_{i,j}$ created by the $i$-th extension.</p>
+ <p>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:</p>
+ <blockquote>
+ Initialize $i$ to $n$.<br>
+ While $i>0$, do the following:
+ <blockquote>
+ 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 $\bindingmathop(i,j)$ is the binding for the variable $\var$ in the namespace $\ns$ of the environment $\env$. Otherwise, decrement $i$ by $1$.
+ </blockquote>
+ The variable $\var$ in unbound in the namespace $\ns$ of the environment $\env$.
+ </blockquote>
+ <p>By selecting the greatest $i$ satisfying the conditions, the lookup rule correctly handles the possibility of shadowing of a binding by another binding.</p>
+ <p>Lexical and dynamic environments are represented by instances of the classes <code>NullDefiniteEnv</code> and <code>Frame</code>, which are the two concrete subclasses of the abstract class <code>DefiniteEnv</code>.</p>
+ <p>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.</p>
+ <p>Instances of <code>Frame</code>, which represent single extensions, have four properties:</p>
+ <ul>
+ <li><code>namespace</code>: <code>VAL_NS</code> or <code>FUN_NS</code></li>
+ <li><code>variables</code>: an array of distinct variables</li>
+ <li><code>values</code>: an array of objects</li>
+ <li><code>next</code>: an instance of <code>NullDefiniteEnv</code> or <code>Frame</code></li>
+ </ul>
+ <p>The two arrays <code>variables</code> and <code>values</code> must have the same length $m\ge0$. The array <code>variables</code> contains the variables $\var_1,\ldots,\var_m$ and the array <code>values</code> contains the objects $\obj_1,\ldots,\obj_m$. The property <code>next</code> is used to create chains of instances of <code>Frame</code>. Each chain is terminated by an instance of <code>NullDefiniteEnv</code>, which represents an empty environment.</p>
+ <h2 id="pairing-parameters-with-arguments">Pairing Parameters with Arguments</h2>
+ <p>The parameters are paired with the arguments by the following functions:</p>
+ <table class="plain">
+ <tr><th>Function</th><th>Function call</th><th>Rest parameter</th></tr>
+ <tr><td><code>pairPrimFunParametersNoApply</code></td><td>plain function call<br>multiple-value-call-form</td><td>N/A</td></tr>
+ <tr><td><code>pairPrimFunParametersApply</code></td><td>apply<br>multiple-value-apply-form</td><td>N/A</td></tr>
+ <tr><td><code>pairClosureParametersNoApplyNoRest</code></td><td>plain function call<br>multiple-value-call-form</td><td>no</td></tr>
+ <tr><td><code>pairClosureParametersNoApplyRest</code></td><td>plain function call<br>multiple-value-call-form</td><td>yes</td></tr>
+ <tr><td><code>pairClosureParametersApplyNoRest</code></td><td>apply<br>multiple-value-apply-form</td><td>no</td></tr>
+ <tr><td><code>pairClosureParametersApplyRest</code></td><td>apply<br>multiple-value-apply-form</td><td>yes</td></tr>
+ </table>
+ <p>The function <code>pairClosureParametersApplyRest</code> reuses conses from the last element of the spreadable sequence of objects when possible. To illustrate, let us consider the following function calls:</p>
+ <dl>
+ <dt><code>(apply (_vlambda a (list a)) $x=\code{1}$ $y=\code{2}$ $z=\code{'(3 4)}$)</code></dt>
+ <dd><code>a</code> is paired with <code>(cons $x$ (cons $y$ $z$))$\,=\,$(1 2 3 4)</code> ($2$ new conses and $2$ reused conses)</dd>
+ <dt><code>(apply (_vlambda (a . b) (list a b)) $x=\code{1}$ $y=\code{2}$ $z=\code{'(3 4)}$)</code></dt>
+ <dd><code>a</code> is paired with <code>$x$$\,=\,$1</code></dd>
+ <dd><code>b</code> is paired with <code>(cons $y$ $z$)$\,=\,$(2 3 4)</code> ($1$ new cons and $2$ reused conses)</dd>
+ <dt><code>(apply (_vlambda (a b . c) (list a b c)) $x=\code{1}$ $y=\code{2}$ $z=\code{'(3 4)}$)</code></dt>
+ <dd><code>a</code> is paired with <code>$x$$\,=\,$1</code></dd>
+ <dd><code>b</code> is paired with <code>$y$$\,=\,$2</code></dd>
+ <dd><code>c</code> is paired with <code>$z$$\,=\,$(3 4)</code> ($0$ new conses and $2$ reused conses)</dd>
+ <dt><code>(apply (_vlambda (a b c . d) (list a b c d)) $x=\code{1}$ $y=\code{2}$ $z=\code{'(3 4)}$)</code></dt>
+ <dd><code>a</code> is paired with <code>$x$$\,=\,$1</code></dd>
+ <dd><code>b</code> is paired with <code>$y$$\,=\,$2</code></dd>
+ <dd><code>c</code> is paired with <code>(car $z$)$\,=\,$3</code></dd>
+ <dd><code>d</code> is paired with <code>(cdr $z$)$\,=\,$(4)</code> ($0$ new conses and $1$ reused cons)</dd>
+ <dt><code>(apply (_vlambda (a b c d . e) (list a b c d e)) $x=\code{1}$ $y=\code{2}$ $z=\code{'(3 4)}$)</code></dt>
+ <dd><code>a</code> is paired with <code>$x$$\,=\,$1</code></dd>
+ <dd><code>b</code> is paired with <code>$y$$\,=\,$2</code></dd>
+ <dd><code>c</code> is paired with <code>(car $z$)$\,=\,$3</code></dd>
+ <dd><code>d</code> is paired with <code>(car (cdr $z$))$\,=\,$4</code></dd>
+ <dd><code>e</code> is paired with <code>(cdr (cdr $z$))$\,=\,$()</code> ($0$ new conses and $0$ reused conses)</dd>
+ <dt><code>(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)}$)</code></dt>
+ <dd>ERROR: Too few arguments.</dd>
+ </dl>
+ <h2 id="generic-evaluator">Generic Evaluator</h2>
+ <p>The generic evaluator simply dispatches the form to evaluate to the selected evaluator.</p>
+ <h2 id="plainrec">Plain Recursive Evaluator</h2>
+ <h2 id="cps">Continuation Passing Style Evaluator</h2>
+ <h2 id="oocps">Object-Oriented CPS Evaluator</h2>
+ <h2 id="sboocps">Stack-Based Object-Oriented CPS Evaluator</h2>
+ <h2 id="trampoline">Trampoline Evaluator</h2>
+ <h2 id="trampolinepp">Trampoline++ Evaluator</h2>
+ <h2 id="primitive-function-definitions-1">Primitive Function Definitions (1)</h2>
+ <p>Defining a primitive function is a two-step process. First, the function <code>primitiveFunction</code> is used to add to the Map contained in the variable <code>primitiveFunctions</code> 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.</p>
+ <p>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.</p>
+ <p>The second step is postponed to the section <a href="#primitive-function-definitions-2">Primitive Function Definitions (2)</a> because it cannot happen before the JavaScript class EVLPrimitiveFunction has been defined.</p>
+ <p>The actual number of arguments is checked against the minimum and maximum numbers of arguments by the code implementing the invocation of primitive functions.</p>
+ <p>The types of the arguments are checked by the JavaScript functions implementing the primitive functions.</p>
+ <h2 id="bounce">Bounce</h2>
+ <p>A bounce is what is sent back to the trampoline. A bounce is either an evaluation request or a result.</p>
+ <h2 id="evaluation-request">Evaluation Request</h2>
+ <p>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.</p>
+ <h2 id="result">Result</h2>
+ <p>A result is either an instance of the class <code>EVLObjects</code> or an instance of a concrete subclass of the abstract class <code>EVLObject</code>.</p>
+ <h2 id="evlobjects">EVLObjects</h2>
+ <p>An instance of the class <code>EVLOjects</code> is a result consisting of a sequence of zero or more objects.</p>
+ <p>The method <code>primaryValue</code> returns <code>#v</code> if the result consists of a sequence of zero objects and the first object otherwise.</p>
+ <p>The method <code>allValues</code> returns an array containing all of the objects.</p>
+ <h2 id="evlobject">EVLObject</h2>
+ <p>An instance of a concrete subclass of the abstract class <code>EVLObject</code> is two things at once: an object and a result consisting of a single object.</p>
+ <p>The method <code>primaryValue</code> returns the single object.</p>
+ <p>The method <code>allValues</code> returns an array containing the single object.</p>
+ <p>The method <code>eql</code> implements the default behavior of the equality predicate <code>eql?</code> (that is, pointer equality). The method is overridden in the classes <code>EVLNumber</code>, <code>EVLCharacter</code>, and <code>EVLString</code>.</p>
+ <p>The method <code>toString</code> is overridden in some subclasses of the abstract class <code>EVLObject</code> to return the printable representation of <code>this</code>.</p>
+ <h2 id="evlvoid">EVLVoid</h2>
+ <p>An instance of the class <code>EVLVoid</code> is an object of type <code>void</code>.</p>
+ <h2 id="evlboolean">EVLBoolean</h2>
+ <p>An instance of the class <code>EVLBoolean</code> is an object of type <code>boolean</code>.</p>
+ <h2 id="evlnumber">EVLNumber</h2>
+ <p>An instance of the class <code>EVLNumber</code> is an object of type <code>number</code>.</p>
+ <h2 id="evlcharacter">EVLCharacter</h2>
+ <p>An instance of the class <code>EVLCharacter</code> is an object of type <code>character</code>.</p>
+ <h2 id="evlstring">EVLString</h2>
+ <p>An instance of the class <code>EVLString</code> is an object of type <code>string</code>.</p>
+ <h2 id="evlsymbol">EVLSymbol</h2>
+ <p>An instance of a concrete subclass of the abstract class <code>EVLSymbol</code> is an object of type <code>symbol</code>.</p>
+ <h2 id="evlkeyword">EVLKeyword</h2>
+ <p>An instance of the class <code>EVLKeyword</code> is an object of type <code>keyword</code>.</p>
+ <h2 id="evlvariable">EVLVariable</h2>
+ <p>An instance of the class <code>EVLVariable</code> is an object of type <code>variable</code>.</p>
+ <h2 id="evllist">EVLList</h2>
+ <p>An instance of a concrete subclass of the abstract class <code>EVLList</code> is an object of type <code>list</code>.</p>
+ <h2 id="evlemptylist">EVLEmptyList</h2>
+ <p>An instance of the class <code>EVLEmptyList</code> is an object of type <code>empty-list</code>.</p>
+ <h2 id="evlcons">EVLCons</h2>
+ <p>An instance of the class <code>EVLCons</code> is an object of type <code>cons</code>.</p>
+ <h2 id="evlvector">EVLVector</h2>
+ <p>An instance of the class <code>EVLVector</code> is an object of type <code>vector</code>.</p>
+ <h2 id="evlfunction">EVLFunction</h2>
+ <p>An instance of a concrete subclass of the abstract class <code>EVLFunction</code> is an object of type <code>function</code>.</p>
+ <h2 id="evlprimitivefunction">EVLPrimitiveFunction</h2>
+ <p>An instance of the class <code>EVLPrimitiveFunction</code> is an object of type <code>primitive-function</code>.</p>
+ <h2 id="evlclosure">EVLClosure</h2>
+ <p>An instance of the class <code>EVLClosure</code> is an object of type <code>closure</code>.</p>
+ <h2 id="miscellaneous-primitive-functions">Miscellaneous Primitive Functions</h2>
+ <p>This section implements the primitive functions <code>values</code>, <code>error</code>, and <code>now</code>.</p>
+ <h2 id="primitive-function-definitions-2">Primitive Function Definitions (2)</h2>
+ <p>The second steps of the primitive function definitions all occur in this section.</p>
+ <h2 id="interface-command-line">Interface (Command Line)</h2>
+ <p>This section implements the command-line interface used to control the evaluator and the EVL to XML converter.</p>
+ <p>The syntax of the command line to control the evaluator is as follows:</p>
+ <blockquote><code>node core.js { --plainrec | --cps | --oocps | --sboocps | --trampoline | --trampoliepp }? { -l $\metavar{file}$ | -e $\metavar{form}$ }*</code></blockquote>
+ <p>The options and arguments have the following meanings:</p>
+ <ul>
+ <li><code>--plainrec</code>: selects the plain recursive evaluator</li>
+ <li><code>--cps</code>: selects the continuation passing style evaluator</li>
+ <li><code>--oocps</code>: selects the object-oriented CPS evaluator</li>
+ <li><code>--sboocps</code>: selects the stack-based object-oriented CPS evaluator</li>
+ <li><code>--trampoline</code>: selects the trampoline evaluator</li>
+ <li><code>--trampolinepp</code>: selects the trampoline++ evaluator (DEFAULT)</li>
+ <li><code>-l $\metavar{file}$</code>: loads the EVL file</li>
+ <li><code>-e $\metavar{form}$</code>: evaluates the form</li>
+ </ul>
+ <p>The syntax of the command line to control the EVL to XML converter is as follows:</p>
+ <blockquote><code>node core.js --convert $\metavar{file}$</code></blockquote>
+ <p>The options and arguments have the following meanings:</p>
+ <ul>
+ <li><code>--convert $\metavar{file}$</code>: converts the EVL file to XML</li>
+ </ul>
</body>
</html>
// SPDX-FileCopyrightText: Copyright (c) 2024-2025 Raphaël Van Dyck
// SPDX-License-Identifier: BSD-3-Clause
-/*************/
-/* Interface */
-/*************/
+/********************/
+/* Global Variables */
+/********************/
+
+const isRunningInsideNode = (typeof process !== 'undefined') && (process.release.name === 'node');
+
+let abortSignalArray = null;
+let selectedEvaluator = null;
+
+/*******************/
+/* Interface (IDE) */
+/*******************/
const FOUND_NO_FORM = 0;
-const COMPLETED_NORMALLY = 1;
-const COMPLETED_ABNORMALLY = 2;
+const SUCCESS = 1;
+const ERROR = 2;
const ABORTED = 3;
const TERMINATED = 4;
const INITIALIZE = 0;
const EVALUATE_FIRST_FORM = 1;
const EVALUATE_ALL_FORMS = 2;
-const CONVERT_TO_XML = 3;
+const CONVERT_EVL_TO_XML = 3;
-let signalArray = null;
-let selectedEvaluator = null;
-
-if (typeof onmessage !== 'undefined') { // web worker
+if (!isRunningInsideNode) {
onmessage = (event) => {
const {id, action, input} = event.data;
let response = null;
case EVALUATE_ALL_FORMS:
response = evaluateAllForms(input);
break;
- case CONVERT_TO_XML:
- response = convertToXML(input);
+ case CONVERT_EVL_TO_XML:
+ response = convertEVLToXML(input);
break;
+ default:
+ throw new CannotHappen('onmessage');
}
if (response !== null) {
postMessage({id: id, ...response});
return {status: FOUND_NO_FORM};
}
-function completedNormally(output) {
- return {status: COMPLETED_NORMALLY, output: output};
+function success(output) {
+ return {status: SUCCESS, output: output};
}
-function completedAbnormally(exception) {
+function abortedOrError(exception) {
if (exception instanceof Aborted) {
return {status: ABORTED};
} else {
- return {status: COMPLETED_ABNORMALLY, output: exception.message};
+ return {status: ERROR, output: exception.message};
}
}
function initialize(input) {
- signalArray = new Uint8Array(input.signalBuffer);
- signalArray[0] = 0;
+ abortSignalArray = new Uint8Array(input.abortSignalBuffer);
selectedEvaluator = input.selectedEvaluator;
- GlobalEnv.set(VAL_NS, internVariable('*features*'), new EVLCons(internVariable(selectedEvaluator), EVLEmptyList.NIL));
+ initializeFeatureList([selectedEvaluator]);
let lastResult = EVLVoid.VOID;
for (const evlFile of input.evlFiles) {
const tokenizer = new Tokenizer(evlFile);
try {
object = read(tokenizer);
} catch(exception) {
- return completedAbnormally(exception);
+ return abortedOrError(exception);
}
if (object === null) {
break;
try {
lastResult = genericEval(object);
} catch(exception) {
- return completedAbnormally(exception);
+ return abortedOrError(exception);
}
}
}
}
const output = lastResult.allValues().map(object => object.toString());
- return completedNormally(output);
+ return success(output);
}
function evaluateFirstForm(text) {
- signalArray[0] = 0;
+ if (abortSignalArray !== null) {
+ abortSignalArray[0] = 0;
+ }
const tokenizer = new Tokenizer(text);
let object = null;
try {
object = read(tokenizer);
} catch(exception) {
- if (exception instanceof TruncatedToken || exception instanceof UnexpectedEndOfFile) {
+ if (exception instanceof TruncatedToken || exception instanceof UnexpectedEndOfInput) {
return foundNoForm();
} else {
- return completedAbnormally(exception);
+ return abortedOrError(exception);
}
}
if (object === null) {
try {
result = genericEval(object);
} catch(exception) {
- return completedAbnormally(exception);
+ return abortedOrError(exception);
}
const output = result.allValues().map(object => object.toString());
- return completedNormally(output);
+ return success(output);
}
}
function evaluateAllForms(text) {
- signalArray[0] = 0;
+ if (abortSignalArray !== null) {
+ abortSignalArray[0] = 0;
+ }
let lastResult = EVLVoid.VOID;
const tokenizer = new Tokenizer(text);
tokenizer.callback = object => lastResult = genericEval(object);
try {
object = read(tokenizer);
} catch(exception) {
- return completedAbnormally(exception);
+ return abortedOrError(exception);
}
if (object === null) {
break;
try {
lastResult = genericEval(object);
} catch(exception) {
- return completedAbnormally(exception);
+ return abortedOrError(exception);
}
}
}
const output = lastResult.allValues().map(object => object.toString());
- return completedNormally(output);
+ return success(output);
}
-function convertToXML(text) {
- signalArray[0] = 0;
+function convertEVLToXML(text) {
const tokenizer = new Tokenizer(text, true);
let xml = null;
try {
- xml = doConvertToXML(tokenizer);
+ xml = doConvertEVLToXML(tokenizer);
} catch(exception) {
- return completedAbnormally(exception);
+ return abortedOrError(exception);
}
- return completedNormally(xml);
+ return success(xml);
}
/**********/
}
}
-class ConverterError extends Error {
+class EVLToXMLConverterError extends Error {
constructor(message) {
super(message);
- this.name = 'ConverterError';
+ this.name = 'EVLToXMLConverterError';
}
}
-class SyntaxAnalyzerError extends Error {
+class FormAnalyzerError extends Error {
constructor(message) {
super(message);
- this.name = 'SyntaxAnalyzerError';
+ this.name = 'FormAnalyzerError';
}
}
}
}
-// token types
+// token categories
const QUOTE = 0;
const QUASIQUOTE = 1;
const UNQUOTE = 2;
const VOID = 10; // value is EVLVoid.VOID
const BOOLEAN = 11; // value is EVLBoolean.TRUE or EVLBoolean.FALSE
const CHARACTER = 12; // value is an EVLCharacter
-const XML_START_TAG = 13; // value is an XML element name (javascript string)
-const XML_END_TAG = 14; // value is an XML element name (javascript string)
-const XML_EMPTY_ELEMENT_TAG = 15; // value is an XML element name (javascript string)
+const XML_START_TAG = 13; // value is an XML element name
+const XML_END_TAG = 14; // value is an XML element name
+const XML_EMPTY_ELEMENT_TAG = 15; // value is an XML element name
const XML_COMMENT = 16;
const DOT = 17; // the dot of dotted lists
const NUMBER = 18; // value is an EVLNumber
const KEYWORD = 19; // value is an EVLKeyword
const VARIABLE = 20; // value is an EVLVariable
-const EOF = 21;
+const BOI = 21; // beginning of input
+const EOI = 22; // end of input
const codePointRegExp = /^[a-fA-F0-9]+$/;
const numberRegExp = /^[+-]?[0-9]+(?:\.[0-9]+)?$/;
function ensureCodePoint (charOrCodePoint) {
if (typeof charOrCodePoint === "number") {
+ // charOrCodePoint is a JavaScript number
return charOrCodePoint;
} else {
+ // charOrCodePoint is a JavaScript string of one or two UTF-16 code units
return charOrCodePoint.codePointAt(0);
}
}
function isControlCharacter(charOrCodePoint) {
const codePoint = ensureCodePoint(charOrCodePoint);
- return (0x00 <= codePoint && codePoint <= 0x1F) || (0x7F <= codePoint && codePoint <= 0x9F);
+ return (0x0000 <= codePoint && codePoint <= 0x001F) || (0x007F <= codePoint && codePoint <= 0x009F);
}
function isNoncharacter(charOrCodePoint) {
function isWhitespaceCharacter(charOrCodePoint) {
// https://www.unicode.org/Public/UCD/latest/ucd/PropList.txt
- // Pattern_White_Space
+ // Whitespace =
+ // 0009..000D <control-0009>..<control-000D>
+ // 0020 SPACE
+ // 0085 <control-0085>
+ // 00A0 NO-BREAK SPACE
+ // 1680 OGHAM SPACE MARK
+ // 2000..200A EN QUAD..HAIR SPACE
+ // 2028 LINE SEPARATOR
+ // 2029 PARAGRAPH SEPARATOR
+ // 202F NARROW NO-BREAK SPACE
+ // 205F MEDIUM MATHEMATICAL SPACE
+ // 3000 IDEOGRAPHIC SPACE
+ // https://www.unicode.org/L2/L2005/05012r-pattern.html
+ // Pattern_Whitespace = Whitespace + Left-to-Right Mark + Right-to-Left Mark -
+ // 00A0 NO-BREAK SPACE
+ // 1680 OGHAM SPACE MARK
+ // 180E MONGOLIAN VOWEL SEPARATOR
+ // 2000..200A EN QUAD..HAIR SPACE
+ // 202F NARROW NO-BREAK SPACE
+ // 205F MEDIUM MATHEMATICAL SPACE
+ // 3000 IDEOGRAPHIC SPACE
const codePoint = ensureCodePoint(charOrCodePoint);
return (
codePoint === 0x0009 || // Horizontal Tab
}
class Tokenizer {
- constructor(text, convertToXML = false) {
+ constructor(text, convertEVLToXML = false) {
this.text = text;
- this.convertToXML = convertToXML;
+ this.convertEVLToXML = convertEVLToXML;
this.position = 0;
- this.xmlStack = []; // element: XML element name
+ this.xmlStack = []; // array of XML element names
this.savedCodeUnits = '';
}
peekCharacter(position = this.position) {
- let char = null; // one or two UTF-16 code units
+ let char = null; // JavaScript string of one or two UTF-16 code units
const codeUnit = this.text.charCodeAt(position);
if (isTrailingSurrogate(codeUnit)) {
throw new TokenizerError('Lone surrogate.');
this.position += char.length;
}
nextToken() {
- this.whitespace = ''; // whitespace preceding the lexeme
+ this.whitespace = '';
this.lexeme = '';
if (this.savedCodeUnits.length !== 0) {
- this.type = CHARACTER;
+ this.category = CHARACTER;
this.value = new EVLCharacter(this.savedCodeUnits.charAt(0));
this.savedCodeUnits = this.savedCodeUnits.substring(1);
} else {
- this.type = null;
+ this.category = null;
this.value = null;
const pureXML = this.xmlStack.length !== 0 && !['chapter', 'section'].includes(this.xmlStack[this.xmlStack.length - 1]);
- while (this.type === null) {
+ while (this.category === null) {
this.skipWhitespace(pureXML);
if (this.position === this.text.length) {
- this.type = EOF;
+ this.category = EOI;
} else {
this.readToken(pureXML);
}
}
}
skipWhitespace(pureXML) {
- // When pure XML is true, XML character data is treated as whitespace.
+ // When pureXML is true, XML character data is treated as whitespace.
while (true) {
if (this.position === this.text.length) {
break;
switch (char) {
case "'":
this.consumeCharacter(char);
- this.type = QUOTE;
+ this.category = QUOTE;
break;
case '`':
this.consumeCharacter(char);
- this.type = QUASIQUOTE;
+ this.category = QUASIQUOTE;
break;
case ',':
this.consumeCharacter(char);
if (this.position === this.text.length) {
- this.type = UNQUOTE;
+ this.category = UNQUOTE;
} else {
const char2 = this.peekCharacter();
if (char2 === '@') {
this.consumeCharacter(char2);
- this.type = UNQUOTE_SPLICING;
+ this.category = UNQUOTE_SPLICING;
} else {
- this.type = UNQUOTE;
+ this.category = UNQUOTE;
}
}
break;
case '"':
this.consumeCharacter(char);
const string = readString(this);
- this.type = STRING;
+ this.category = STRING;
this.value = new EVLString(string);
break;
case '(':
this.consumeCharacter(char);
- this.type = OPENING_PARENTHESIS;
+ this.category = OPENING_PARENTHESIS;
break;
case ')':
this.consumeCharacter(char);
- this.type = CLOSING_PARENTHESIS;
+ this.category = CLOSING_PARENTHESIS;
break;
case '#':
this.consumeCharacter(char);
default:
const protoToken = readProtoToken(this);
if (protoToken === '.') {
- this.type = DOT;
+ this.category = DOT;
} else if (numberRegExp.test(protoToken)) {
- this.type = NUMBER;
+ this.category = NUMBER;
this.value = new EVLNumber(Number.parseFloat(protoToken));
} else if (keywordRegExp.test(protoToken)) {
- this.type = KEYWORD;
+ this.category = KEYWORD;
this.value = internKeyword(protoToken.substring(1));
} else if (variableRegExp.test(protoToken)) {
- this.type = VARIABLE;
+ this.category = VARIABLE;
this.value = internVariable(protoToken);
} else {
throw new TokenizerError('Malformed proto-token.');
let position = 0;
const length = chars.length;
while (position < length) {
- let char = null; // one or two UTF-16 code units
+ let char = null; // JavaScript string of one or two UTF-16 code units
const codeUnit = chars.charCodeAt(position);
if (isTrailingSurrogate(codeUnit)) {
escapedChars += unicodeEscape(codeUnit);
}
function readEscapeSequence(tokenizer) {
+ // reads {xyz}, returns xyz
let chars = '';
if (tokenizer.position === tokenizer.text.length) {
throw new TruncatedToken('Truncated escape sequence.');
}
switch (char) {
case '(':
- tokenizer.type = HASH_OPENING_PARENTHESIS;
+ tokenizer.category = HASH_OPENING_PARENTHESIS;
break;
case '+':
- tokenizer.type = HASH_PLUS;
+ tokenizer.category = HASH_PLUS;
break;
case '-':
- tokenizer.type = HASH_MINUS;
+ tokenizer.category = HASH_MINUS;
break;
case 'v':
- tokenizer.type = VOID;
+ tokenizer.category = VOID;
tokenizer.value = EVLVoid.VOID;
break;
case 't':
- tokenizer.type = BOOLEAN;
+ tokenizer.category = BOOLEAN;
tokenizer.value = EVLBoolean.TRUE;
break;
case 'f':
- tokenizer.type = BOOLEAN;
+ tokenizer.category = BOOLEAN;
tokenizer.value = EVLBoolean.FALSE;
break;
case '"':
const string = readString(tokenizer);
- if (tokenizer.convertToXML) {
- tokenizer.type = CHARACTER;
- tokenizer.value = new EVLCharacter(string);
+ if (tokenizer.convertEVLToXML) {
+ tokenizer.category = CHARACTER;
+ tokenizer.value = null; // the value is ignored by the EVL to XML converter
} else if (arg !== '') {
const index = Number.parseInt(arg);
if (index < string.length) {
- tokenizer.type = CHARACTER;
+ tokenizer.category = CHARACTER;
tokenizer.value = new EVLCharacter(string.charAt(index));
} else {
throw new TokenizerError('Index out of bounds.');
}
} else if (string.length !== 0) {
- tokenizer.type = CHARACTER;
+ tokenizer.category = CHARACTER;
tokenizer.value = new EVLCharacter(string.charAt(0));
tokenizer.savedCodeUnits = string.substring(1);
}
tokenizer.lexeme = tokenizer.text.slice(tokenizer.position, position);
tokenizer.position = position;
if (isXMLComment) {
- tokenizer.type = XML_COMMENT;
+ tokenizer.category = XML_COMMENT;
} else if (isXMLEndTag) {
if (tokenizer.xmlStack.length === 0) {
throw new TokenizerError('Unexpected XML end tag.');
throw new TokenizerError('Unmatched XML tags.');
}
tokenizer.xmlStack.pop();
- tokenizer.type = XML_END_TAG;
+ tokenizer.category = XML_END_TAG;
tokenizer.value = name;
} else if (isXMLEmptyElementTag) {
- tokenizer.type = XML_EMPTY_ELEMENT_TAG;
+ tokenizer.category = XML_EMPTY_ELEMENT_TAG;
tokenizer.value = name;
} else {
tokenizer.xmlStack.push(name);
- tokenizer.type = XML_START_TAG;
+ tokenizer.category = XML_START_TAG;
tokenizer.value = name;
}
}
}
}
-class UnexpectedEndOfFile extends ReaderError {
+class UnexpectedEndOfInput extends ReaderError {
constructor() {
- super('Unexpected end-of-file.');
- this.name = 'UnexpectedEndOfFile';
+ super('Unexpected end-of-input.');
+ this.name = 'UnexpectedEndOfInput';
}
}
throw new UnexpectedClosingParenthesis();
case XML_END_TAG:
throw new UnexpectedXMLEndTag();
- case EOF:
+ case EOI:
return null;
default:
return object;
}
function readObject(tokenizer) {
- // Returns DOT, CLOSING_PARENTHESIS, XML_END_TAG, EOF, or an object.
+ // Returns DOT, CLOSING_PARENTHESIS, XML_END_TAG, EOI, or an object.
// XML elements are skipped because they are treated as comments.
while (true) {
tokenizer.nextToken();
- switch (tokenizer.type) {
+ switch (tokenizer.category) {
case VOID:
case BOOLEAN:
case NUMBER:
break; // skip
case XML_COMMENT:
break; // skip
- case EOF:
- return EOF;
+ case EOI:
+ return EOI;
default:
throw new CannotHappen('readObject');
}
throw new UnexpectedClosingParenthesis();
case XML_END_TAG:
throw new UnexpectedXMLEndTag();
- case EOF:
- throw new UnexpectedEndOfFile();
+ case EOI:
+ throw new UnexpectedEndOfInput();
default:
return new EVLCons(variable, new EVLCons(object, EVLEmptyList.NIL));
}
}
function readReadTimeConditional(tokenizer, polarity) {
- const featureExpression = readReadTimeConditionalFeatureExpression(tokenizer);
+ const featureExpression = readFeatureExpression(tokenizer);
+ const conditionalizedObject = readConditionalizedObject(tokenizer)
if (evaluateFeatureExpression(featureExpression) === polarity) {
- return readReadTimeConditionalObject(tokenizer);
+ return conditionalizedObject;
} else {
- return readReadTimeConditionalObject(tokenizer), null;
+ return null;
}
}
-function readReadTimeConditionalFeatureExpression(tokenizer) {
+function readFeatureExpression(tokenizer) {
const object = readObject(tokenizer);
switch (object) {
case DOT:
throw new UnexpectedClosingParenthesis();
case XML_END_TAG:
throw new UnexpectedXMLEndTag();
- case EOF:
- throw new UnexpectedEndOfFile();
+ case EOI:
+ throw new UnexpectedEndOfInput();
default:
return object;
}
}
-function readReadTimeConditionalObject(tokenizer) {
+function readConditionalizedObject(tokenizer) {
const object = readObject(tokenizer);
switch (object) {
case DOT:
throw new UnexpectedClosingParenthesis();
case XML_END_TAG:
throw new UnexpectedXMLEndTag();
- case EOF:
- throw new UnexpectedEndOfFile();
+ case EOI:
+ throw new UnexpectedEndOfInput();
default:
return object;
}
}
}
+function initializeFeatureList(features) {
+ let list = EVLEmptyList.NIL;
+ let lastCons = null;
+ for (const feature of features) {
+ const newCons = new EVLCons(internVariable(feature), EVLEmptyList.NIL);
+ if (lastCons === null) {
+ list = newCons;
+ } else {
+ lastCons.cdr = newCons;
+ }
+ lastCons = newCons;
+ }
+ GlobalEnv.set(VAL_NS, internVariable('*features*'), list);
+}
+
function evaluateSymbolFeatureExpression(featureExpression) {
let list = GlobalEnv.ref(VAL_NS, internVariable('*features*'));
while (list !== EVLEmptyList.NIL) {
break loop;
case XML_END_TAG:
throw new UnexpectedXMLEndTag();
- case EOF:
- throw new UnexpectedEndOfFile();
+ case EOI:
+ throw new UnexpectedEndOfInput();
default:
const newCons = new EVLCons(object, EVLEmptyList.NIL);
if (lastCons === null) {
throw new ReaderError('Malformed dotted list.');
case XML_END_TAG:
throw new UnexpectedXMLEndTag();
- case EOF:
- throw new UnexpectedEndOfFile();
+ case EOI:
+ throw new UnexpectedEndOfInput();
default:
lastCons.cdr = object;
break
return list;
case XML_END_TAG:
throw new UnexpectedXMLEndTag();
- case EOF:
- throw new UnexpectedEndOfFile();
+ case EOI:
+ throw new UnexpectedEndOfInput();
default:
throw new ReaderError('Malformed dotted list.');
}
break loop;
case XML_END_TAG:
throw new UnexpectedXMLEndTag();
- case EOF:
- throw new UnexpectedEndOfFile();
+ case EOI:
+ throw new UnexpectedEndOfInput();
default:
elements.push(object);
break;
} else {
throw new ReaderError('Unmatched XML tags.');
}
- case EOF:
- throw new UnexpectedEndOfFile();
+ case EOI:
+ throw new UnexpectedEndOfInput();
default:
const callback = tokenizer.callback;
if (callback !== undefined) {
/* EVL to XML Converter */
/************************/
-const TOPLEVEL = 100; // top level context
-const ABBREVIATION = 101; // abbreviation context
-const RTC1 = 102; // context between #+ or #- and feature expression
-const RTC2 = 103; // context between feature expression and object
-const SEQUENCE = 104; // list or vector context
+const XML_TOKEN = 100;
+const EVL_TOKEN = 101;
+const EOL_COMMENT = 102;
-const ABSTRACT_BOF = 0; // beginning-of-file abstract token
-const ABSTRACT_EVL = 1; // EVL abstract token
-const ABSTRACT_XML = 2; // XML abstract token
-const ABSTRACT_EOL_COMMENT = 3; // end-of-line comment
-const ABSTRACT_EOF = 4; // end-of-file abstract token
+const XML_CONTEXT = 0;
+const EVL_CONTEXT = 1;
-function doConvertToXML(tokenizer) {
+function doConvertEVLToXML(tokenizer) {
let xml = '';
- const contextStack = [TOPLEVEL]; // element: TOPLEVEL, ABBREVIATION, RTC1, RTC2, SEQUENCE, or XML element name
- let previousAbstractToken = ABSTRACT_BOF;
- let context = TOPLEVEL;
- let abstractToken = null;
- while ((abstractToken = abstractRead(tokenizer, contextStack)) !== ABSTRACT_EOF) {
- if (context === TOPLEVEL) {
- // BOF <evl-object|xml-element> <evl-object|xml-element> EOF
- // ^^^ ^^^ ^^^
- xml += tokenizer.whitespace; // whitespace is written as is
- } else if ([ABBREVIATION, RTC1, RTC2, SEQUENCE].includes(context)) {
- // ' <xml-element> <xml-element> <evl-object>
- // ^^^ ^^^ ^^^
- // #+ <xml-element> <xml-element> <evl-object> <xml-element> <xml-element> <evl-object>
- // ^^^ ^^^ ^^^
- // #+ <xml-element> <xml-element> <evl-object> <xml-element> <xml-element> <evl-object>
- // ^^^ ^^^ ^^^
- // ( <evl-object|xml-element> <xml-element|xml-element> )
- // ^^^ ^^^ ^^^
- xml += convertEVL(previousAbstractToken, tokenizer.whitespace, abstractToken); // whitespace is converted by convertEVL
- } else if (['chapter', 'section'].includes(context)) {
- // <chapter> <evl-object|xml-element> <evl-object|xml-element> </chapter>
- // ^^^ ^^^ ^^^
- // <section> <evl-object|xml-element> <evl-object|xml-element> </section>
- // ^^^ ^^^ ^^^
- xml += convertXML(previousAbstractToken, tokenizer.whitespace, abstractToken); // whitespace is converted by convertXML
- } else {
- // <para> <xml-element> <xml-element> </para>
- // ^^^ ^^^ ^^^
- xml += tokenizer.whitespace; // whitespace (= character data) is written as is
- }
- if (abstractToken === ABSTRACT_EVL) {
- xml += xmlEscape(tokenizer.lexeme); // lexeme is xml escaped
- } else {
- xml += tokenizer.lexeme; // lexeme is written as is
- }
- previousAbstractToken = abstractToken;
+ const contextStack = [];
+ let previousToken = BOI;
+ let context = contextStack[contextStack.length - 1];
+ let token = null;
+ while ((token = sketchyRead(tokenizer, contextStack)) !== EOI) {
+ if (context === XML_CONTEXT) {
+ xml += convertXMLWhitespace(previousToken, tokenizer.whitespace, token);
+ } else if (context = EVL_CONTEXT) {
+ xml += convertEVLWhitespace(previousToken, tokenizer.whitespace, token);
+ } else { // top-level context
+ xml += tokenizer.whitespace;
+ }
+ if (token === EVL_TOKEN) {
+ xml += xmlEscape(tokenizer.lexeme);
+ } else { // XML_TOKEN or EOL_COMMENT
+ xml += tokenizer.lexeme;
+ }
+ previousToken = token;
context = contextStack[contextStack.length - 1];
}
- xml += tokenizer.whitespace; // whitespace is written as is
+ xml += tokenizer.whitespace;
return xml;
}
-// Example: BOF[1]<chapter>[2]([3]xxx[4])[5]</chapter>[6]EOF
-// whitespace [1] is processed in top level context
-// whitespace [2] is processed in chapter context
-// whitespace [3] is processed in sequence context
-// whitespace [4] is processed in sequence context
-// whitespace [5] is processed in chapter context
-// whitespace [6] is processed in top level context
-
function xmlEscape(string) {
return string.replace(/[<>&]/g, function (char) {
switch (char) {
});
}
-function abstractRead(tokenizer, contextStack) {
+function sketchyRead(tokenizer, contextStack) {
tokenizer.nextToken();
- switch (tokenizer.type) {
+ switch (tokenizer.category) {
case VOID:
case BOOLEAN:
case NUMBER:
- case CHARACTER:
+ case CHARACTER: // full hash-string construct
case STRING:
case KEYWORD:
case VARIABLE:
- updateContextStackForEVLObject(contextStack);
- return ABSTRACT_EVL;
case QUOTE:
case QUASIQUOTE:
case UNQUOTE:
case UNQUOTE_SPLICING:
- contextStack.push(ABBREVIATION); // enter abbreviation context
- return ABSTRACT_EVL;
case HASH_PLUS:
case HASH_MINUS:
- contextStack.push(RTC1); // enter rtc1 context
- return ABSTRACT_EVL;
+ case DOT:
+ return EVL_TOKEN;
case OPENING_PARENTHESIS:
case HASH_OPENING_PARENTHESIS:
- contextStack.push(SEQUENCE); // enter sequence context
- return ABSTRACT_EVL;
- case DOT:
- return ABSTRACT_EVL;
+ contextStack.push(EVL_CONTEXT);
+ return EVL_TOKEN;
case CLOSING_PARENTHESIS:
- if (contextStack[contextStack.length - 1] !== SEQUENCE) {
- throw new ConverterError('Unexpected closing parenthesis.');
+ if (contextStack[contextStack.length - 1] !== EVL_CONTEXT) {
+ throw new EVLToXMLConverterError('Unexpected closing parenthesis.');
}
- contextStack.pop(); // exit sequence context
- updateContextStackForEVLObject(contextStack);
- return ABSTRACT_EVL;
+ contextStack.pop();
+ return EVL_TOKEN;
case XML_START_TAG:
if (tokenizer.value === 'comment') {
- abstractReadEndOfLineComment(tokenizer, contextStack);
- return ABSTRACT_EOL_COMMENT;
+ readEndOfLineComment(tokenizer);
+ return EOL_COMMENT;
} else {
- contextStack.push(tokenizer.value); // enter XML element name context
- return ABSTRACT_XML;
+ contextStack.push(XML_CONTEXT);
+ return XML_TOKEN;
}
case XML_END_TAG:
- if (typeof contextStack[contextStack.length - 1] !== 'string') {
- throw new ConverterError('Unexpected XML end tag.');
- }
- if (contextStack[contextStack.length - 1] !== tokenizer.value) {
- throw new ConverterError('Unmatched XML tags.');
+ if (contextStack[contextStack.length - 1] !== XML_CONTEXT) {
+ throw new EVLToXMLConverterError('Unexpected XML end tag.');
}
- contextStack.pop(); // exit XML element name context
- return ABSTRACT_XML;
+ contextStack.pop();
+ return XML_TOKEN;
case XML_EMPTY_ELEMENT_TAG:
- return ABSTRACT_XML;
case XML_COMMENT:
- return ABSTRACT_XML;
- case EOF:
- if (contextStack[contextStack.length - 1] !== TOPLEVEL) {
- throw new ConverterError('Unexpected end-of-file.');
+ return XML_TOKEN;
+ case EOI:
+ if (contextStack.length !== 0) {
+ throw new EVLToXMLConverterError('Unexpected end-of-input.');
}
- contextStack.pop(); // exit top level context
- return ABSTRACT_EOF;
+ return EOI;
default:
- throw new CannotHappen('abstractRead');
+ throw new CannotHappen('sketchyRead');
}
}
-function updateContextStackForEVLObject(contextStack) {
- while (true) {
- switch (contextStack[contextStack.length - 1]) {
- case ABBREVIATION:
- contextStack.pop(); // exit abbreviation context
- break;
- case RTC1:
- contextStack.pop(); // exit rtc1 context
- contextStack.push(RTC2); // enter rtc2 context
- return;
- case RTC2:
- contextStack.pop(); // exit rtc2 context
- break;
- default:
- return;
- }
- }
-}
-
-function abstractReadEndOfLineComment(tokenizer) {
+function readEndOfLineComment(tokenizer) {
const whitespace = tokenizer.whitespace;
let lexeme = tokenizer.lexeme;
- const contextStack = [tokenizer.value]; // local stack
+ const contextStack = [];
while (true) {
tokenizer.nextToken();
- switch (tokenizer.type) {
+ switch (tokenizer.category) {
case XML_START_TAG:
lexeme += tokenizer.whitespace;
lexeme += tokenizer.lexeme;
- contextStack.push(tokenizer.value);
+ contextStack.push(XML_CONTEXT);
break;
case XML_END_TAG:
- if (contextStack[contextStack.length - 1] !== tokenizer.value) {
- throw new ConverterError('Unmatched XML tags.');
- }
lexeme += tokenizer.whitespace;
lexeme += tokenizer.lexeme;
- contextStack.pop();
if (contextStack.length === 0) {
- tokenizer.whitespace = whitespace; // whitespace before end-of-line comment
+ tokenizer.whitespace = whitespace; // run of whitespace before end-of-line comment
tokenizer.lexeme = lexeme; // end-of-line comment
return;
+ } else {
+ contextStack.pop();
+ break;
}
- break;
case XML_EMPTY_ELEMENT_TAG:
- lexeme += tokenizer.whitespace;
- lexeme += tokenizer.lexeme;
- break;
case XML_COMMENT:
lexeme += tokenizer.whitespace;
lexeme += tokenizer.lexeme;
break;
- case EOF:
- throw new ConverterError('Unexpected end-of-file.');
+ case EOI:
+ throw new EVLToXMLConverterError('Unexpected end-of-input.');
default:
- throw new CannotHappen('abstractReadEndOfLineComment');
+ throw new CannotHappen('readEndOfLineComment');
}
}
}
-function isXMLAbstractToken(abstractToken) {
- return abstractToken === ABSTRACT_XML;
+function isXMLToken(token) {
+ return token === XML_TOKEN;
}
-function isEVLAbstractToken(abstractToken) {
- return abstractToken === ABSTRACT_EVL || abstractToken === ABSTRACT_EOL_COMMENT;
+function isEVLToken(token) {
+ return token === EVL_TOKEN || token === EOL_COMMENT;
}
-function convertXML(previousAbstractToken, whitespace, abstractToken) {
+function convertXMLWhitespace(previousToken, whitespace, token) {
let xml = '';
- if (isXMLAbstractToken(previousAbstractToken) && isEVLAbstractToken(abstractToken)) {
+ if (isXMLToken(previousToken) && isEVLToken(token)) {
xml += whitespace;
xml += '<toplevelcode><blockcode>';
- } else if (isEVLAbstractToken(previousAbstractToken) && isEVLAbstractToken(abstractToken)) {
+ } else if (isEVLToken(previousToken) && isEVLToken(token)) {
if (countNewlines(whitespace) >= 2) {
xml += '</blockcode></toplevelcode>';
xml += whitespace;
} else {
xml += whitespace;
}
- } else if (isEVLAbstractToken(previousAbstractToken) && isXMLAbstractToken(abstractToken)) {
+ } else if (isEVLToken(previousToken) && isXMLToken(token)) {
xml += '</blockcode></toplevelcode>';
xml += whitespace;
} else {
return count;
}
-function convertEVL(previousAbstractToken, whitespace, abstractToken) {
+function convertEVLWhitespace(previousToken, whitespace, token) {
let xml = '';
- if (isEVLAbstractToken(previousAbstractToken) && isXMLAbstractToken(abstractToken)) {
+ if (isEVLToken(previousToken) && isXMLToken(token)) {
xml += '</blockcode><indentation style="margin-left: ';
xml += countSpacesAfterFirstNewline(whitespace);
xml += 'ch;"><blockcomment>';
xml += whitespace;
- } else if (isXMLAbstractToken(previousAbstractToken) && isEVLAbstractToken(abstractToken)) {
+ } else if (isXMLToken(previousToken) && isEVLToken(token)) {
xml += '</blockcomment></indentation><blockcode>';
xml += whitespace;
} else {
return count;
}
-/*******************/
-/* Syntax Analyzer */
-/*******************/
+/*****************/
+/* Form Analyzer */
+/*****************/
-function syntaxAnalyzerError(formName) {
- throw new SyntaxAnalyzerError(`Malformed ${formName} form.`);
+function formAnalyzerError(formName) {
+ throw new FormAnalyzerError(`Malformed ${formName} form.`);
}
-function checkCons(object, formName) {
- if (object instanceof EVLCons) {
+function checkVariable(object, formName) {
+ if (object instanceof EVLVariable) {
return object;
} else {
- syntaxAnalyzerError(formName);
+ formAnalyzerError(formName);
}
}
if (object instanceof EVLEmptyList) {
return object;
} else {
- syntaxAnalyzerError(formName);
+ formAnalyzerError(formName);
+ }
+}
+
+function checkCons(object, formName) {
+ if (object instanceof EVLCons) {
+ return object;
+ } else {
+ formAnalyzerError(formName);
}
}
if (list instanceof EVLCons) {
list = list.cdr;
} else {
- syntaxAnalyzerError(formName);
+ formAnalyzerError(formName);
}
}
return object;
if (object instanceof EVLVariable) {
return [[object], true];
} else {
- const variables = [];
- let variadic = false;
+ const parameters = [];
+ let rest = false;
let list = object
while (list !== EVLEmptyList.NIL) {
if (list instanceof EVLCons) {
if (list.car instanceof EVLVariable) {
- variables.push(list.car);
+ parameters.push(list.car);
} else {
- syntaxAnalyzerError(formName);
+ formAnalyzerError(formName);
}
if (list.cdr instanceof EVLVariable) {
- variables.push(list.cdr);
- variadic = true;
+ parameters.push(list.cdr);
+ rest = true;
break;
} else {
list = list.cdr;
}
} else {
- syntaxAnalyzerError(formName);
+ formAnalyzerError(formName);
}
}
- if (new Set(variables).size !== variables.length) {
- syntaxAnalyzerError(formName);
+ if (new Set(parameters).size !== parameters.length) {
+ formAnalyzerError(formName);
}
- return [variables, variadic];
- }
-}
-
-function checkVariable(object, formName) {
- if (object instanceof EVLVariable) {
- return object;
- } else {
- syntaxAnalyzerError(formName);
+ return [parameters, rest];
}
}
function analyzeQuote(form) {
let cons = form;
cons = checkCons(cons.cdr, 'quote');
- const object = cons.car;
+ const literal = cons.car;
checkEmptyList(cons.cdr, 'quote');
- return [object];
+ return [literal];
}
function analyzeProgn(form) {
function analyzeLambda(form) {
let cons = form;
cons = checkCons(cons.cdr, '_lambda');
- const [variables, variadic] = checkParameterList(cons.car, '_lambda');
+ const [parameters, rest] = checkParameterList(cons.car, '_lambda');
const forms = checkProperList(cons.cdr, '_lambda');
- return [variables, variadic, forms];
+ return [parameters, rest, forms];
}
function analyzeRef(form) {
return [tryForm];
}
-function analyzeApplication(mv, apply, form) {
+function analyzeCall(mv, apply, form) {
let cons = form;
if (mv || apply) {
- cons = checkCons(cons.cdr, 'application');
+ cons = checkCons(cons.cdr, 'call');
}
const operator = cons.car;
- const operands = checkProperList(cons.cdr, 'application');
+ const operands = checkProperList(cons.cdr, 'call');
return [operator, operands];
}
-/**********/
-/* Scopes */
-/**********/
+/*****************************/
+/* Scope-Extent Combinations */
+/*****************************/
-const LEX_SCOPE = 0; // lexical scope
-const DYN_SCOPE = 1; // dynamic scope
+const LEX_SCOPE = 0; // lexical scope and indefinite extent
+const DYN_SCOPE = 1; // indefinite scope and dynamic extent
/**************/
/* Namespaces */
if (value !== null) {
return value;
} else {
- throw new UnboundVariable(variable, 'VALUE');
+ throw new UnboundVariable(variable, 'value');
}
}
case FUN_NS: {
if (value !== null) {
return value;
} else {
- throw new UnboundVariable(variable, 'FUNCTION');
+ throw new UnboundVariable(variable, 'function');
}
}
default:
}
}
-/*********************/
-/* Local Environment */
-/*********************/
+/************************************/
+/* Lexical and Dynamic Environments */
+/************************************/
-class LocalEnv { // abstract class
+class DefiniteEnv { // abstract class
}
-class NullLocalEnv extends LocalEnv {
+class NullDefiniteEnv extends DefiniteEnv {
constructor() {
super();
}
}
}
-const nullLocalEnv = new NullLocalEnv();
+const nullDefiniteEnv = new NullDefiniteEnv();
-class Frame extends LocalEnv {
+class Frame extends DefiniteEnv {
constructor(namespace, variables, values, next) {
super();
this.namespace = namespace;
}
}
-/**********************************/
-/* Mapping Arguments to Variables */
-/**********************************/
+/*************************************/
+/* Pairing Parameters with Arguments */
+/*************************************/
class TooFewArguments extends EvaluatorError {
constructor() {
}
}
-class MalformedSpreadableArgumentList extends EvaluatorError {
+class MalformedSpreadableSequenceOfObjects extends EvaluatorError {
constructor() {
- super('Malformed spreadable argument list.');
- this.name = 'MalformedSpreadableArgumentList';
+ super('Malformed spreadable sequence of objects.');
+ this.name = 'MalformedSpreadableSequenceOfObjects';
}
}
-function mapPrimFunArgs(apply, args, arityMin, arityMax) {
+function pairPrimFunParameters(apply, args, arityMin, arityMax) {
if (!apply) {
- const nargs = args.length;
- if (nargs < arityMin) {
- throw new TooFewArguments();
- }
- if (arityMax !== null && nargs > arityMax) {
+ return pairPrimFunParametersNoApply(args, arityMin, arityMax);
+ } else {
+ return pairPrimFunParametersApply(args, arityMin, arityMax);
+ }
+}
+
+function pairPrimFunParametersNoApply(args, arityMin, arityMax) {
+ const nargs = args.length;
+ if (nargs < arityMin) {
+ throw new TooFewArguments();
+ }
+ if (arityMax !== null && nargs > arityMax) {
+ throw new TooManyArguments();
+ }
+ return args;
+}
+
+function pairPrimFunParametersApply(args, arityMin, arityMax) {
+ const nargs = args.length;
+ const spreadArgs = [];
+ let i = 0;
+ while (i < nargs - 1) {
+ if (arityMax === null || i < arityMax) {
+ spreadArgs.push(args[i]);
+ i++;
+ } else {
throw new TooManyArguments();
}
- return args;
- } else {
- const nargs = args.length;
- const spreadArgs = [];
- let i = 0;
- while (i < nargs - 1) {
+ }
+ if (nargs === 0 || !(args[nargs - 1] instanceof EVLList)) {
+ throw new MalformedSpreadableSequenceOfObjects();
+ }
+ let argList = args[nargs - 1];
+ while (argList !== EVLEmptyList.NIL) {
+ if (argList instanceof EVLCons) {
if (arityMax === null || i < arityMax) {
- spreadArgs.push(args[i]);
+ spreadArgs.push(argList.car);
i++;
} else {
throw new TooManyArguments();
}
+ argList = argList.cdr;
+ } else {
+ throw new MalformedSpreadableSequenceOfObjects();
}
- if (nargs === 0 || !(args[nargs - 1] instanceof EVLList)) {
- throw new MalformedSpreadableArgumentList();
- }
- let argList = args[nargs - 1];
- while (argList !== EVLEmptyList.NIL) {
- if (argList instanceof EVLCons) {
- if (arityMax === null || i < arityMax) {
- spreadArgs.push(argList.car);
- i++;
- } else {
- throw new TooManyArguments();
- }
- argList = argList.cdr;
- } else {
- throw new MalformedSpreadableArgumentList();
- }
- }
- if (i < arityMin) {
- throw new TooFewArguments();
- }
- return spreadArgs;
}
+ if (i < arityMin) {
+ throw new TooFewArguments();
+ }
+ return spreadArgs;
}
-function mapClosureArgs(apply, args, vars, variadic) {
+function pairClosureParameters(apply, args, parameters, rest) {
if (!apply) {
- if (!variadic) {
- return mapClosureArgsForFixedArityCall(args, vars);
+ if (!rest) {
+ return pairClosureParametersNoApplyNoRest(args, parameters);
} else {
- return mapClosureArgsForVariableArityCall(args, vars);
+ return pairClosureParametersNoApplyRest(args, parameters);
}
} else {
- if (!variadic) {
- return mapClosureArgsForFixedArityApply(args, vars);
+ if (!rest) {
+ return pairClosureParametersApplyNoRest(args, parameters);
} else {
- return mapClosureArgsForVariableArityApply(args, vars);
+ return pairClosureParametersApplyRest(args, parameters);
}
}
}
-function mapClosureArgsForFixedArityCall(args, vars) {
+function pairClosureParametersNoApplyNoRest(args, parameters) {
const nargs = args.length;
- const nvars = vars.length;
- if (nargs < nvars) {
+ const nparameters = parameters.length;
+ if (nargs < nparameters) {
throw new TooFewArguments();
}
- if (nargs > nvars) {
+ if (nargs > nparameters) {
throw new TooManyArguments();
}
return args;
}
-function mapClosureArgsForVariableArityCall(args, vars) {
+function pairClosureParametersNoApplyRest(args, parameters) {
const nargs = args.length;
- const nvars = vars.length;
- const values = new Array(nvars);
+ const nparameters = parameters.length;
+ const values = new Array(nparameters);
let list = EVLEmptyList.NIL;
let lastCons = null;
let i = 0;
while (i < nargs) {
- if (i < nvars - 1) {
+ if (i < nparameters - 1) {
values[i] = args[i];
i++;
} else {
i++;
}
}
- if (i < nvars - 1) {
+ if (i < nparameters - 1) {
throw new TooFewArguments();
}
- values[nvars - 1] = list;
+ values[nparameters - 1] = list;
return values;
}
-function mapClosureArgsForFixedArityApply(args, vars) {
+function pairClosureParametersApplyNoRest(args, parameters) {
const nargs = args.length;
- const nvars = vars.length;
- const values = new Array(nvars);
+ const nparameters = parameters.length;
+ const values = new Array(nparameters);
let i = 0;
while (i < nargs - 1) {
- if (i < nvars) {
+ if (i < nparameters) {
values[i] = args[i];
i++;
} else {
}
}
if (nargs === 0 || !(args[nargs - 1] instanceof EVLList)) {
- throw new MalformedSpreadableArgumentList();
+ throw new MalformedSpreadableSequenceOfObjects();
}
let argList = args[nargs - 1];
while (argList !== EVLEmptyList.NIL) {
if (argList instanceof EVLCons) {
- if (i < nvars) {
+ if (i < nparameters) {
values[i] = argList.car;
i++;
} else {
}
argList = argList.cdr;
} else {
- throw new MalformedSpreadableArgumentList();
+ throw new MalformedSpreadableSequenceOfObjects();
}
}
- if (i < nvars) {
+ if (i < nparameters) {
throw new TooFewArguments();
}
return values;
}
-function mapClosureArgsForVariableArityApply(args, vars) {
+function pairClosureParametersApplyRest(args, parameters) {
const nargs = args.length;
- const nvars = vars.length;
- const values = new Array(nvars);
+ const nparameters = parameters.length;
+ const values = new Array(nparameters);
let list = EVLEmptyList.NIL;
let lastCons = null;
let i = 0;
while (i < nargs - 1) {
- if (i < nvars - 1) {
+ if (i < nparameters - 1) {
values[i] = args[i];
i++;
} else {
}
}
if (nargs === 0 || !(args[nargs - 1] instanceof EVLList)) {
- throw new MalformedSpreadableArgumentList();
+ throw new MalformedSpreadableSequenceOfObjects();
}
let argList = args[nargs - 1];
while (argList !== EVLEmptyList.NIL) {
if (argList instanceof EVLCons) {
- if (i < nvars - 1) {
+ if (i < nparameters - 1) {
values[i] = argList.car;
i++;
} else {
}
argList = argList.cdr;
} else {
- throw new MalformedSpreadableArgumentList();
+ throw new MalformedSpreadableSequenceOfObjects();
}
}
- if (i < nvars - 1) {
+ if (i < nparameters - 1) {
throw new TooFewArguments();
}
- values[nvars - 1] = list;
+ values[nparameters - 1] = list;
return values;
}
}
function emptyListError() {
- throw new EvaluatorError('The empty list is not a form.');
+ throw new EvaluatorError('The empty list does not evaluate.');
}
function ifTestFormError() {
}
function forEachNotImplemented() {
- throw new EvaluatorError('_for-each is not implemented.');
+ throw new EvaluatorError('The _for-each-form is not implemented.');
}
function forEachFunctionFormError() {
throw new EvaluatorError('The list-form does not evaluate to a proper list.');
}
-function applicationOperatorFormError() {
+function callOperatorFormError() {
throw new EvaluatorError('The operator-form does not evaluate to a function.');
}
/*****************************/
function plainrecEval(form) {
- return plainrecEvalForm(form, nullLocalEnv, nullLocalEnv);
+ return plainrecEvalForm(form, nullDefiniteEnv, nullDefiniteEnv);
}
function plainrecEvalForm(form, lenv, denv) {
case _catchErrorsVariable:
return plainrecEvalCatchErrors(form, lenv, denv);
case applyVariable:
- return plainrecEvalApplication(false, true, form, lenv, denv);
+ return plainrecEvalCall(false, true, form, lenv, denv);
case multipleValueCallVariable:
- return plainrecEvalApplication(true, false, form, lenv, denv);
+ return plainrecEvalCall(true, false, form, lenv, denv);
case multipleValueApplyVariable:
- return plainrecEvalApplication(true, true, form, lenv, denv);
+ return plainrecEvalCall(true, true, form, lenv, denv);
default:
- return plainrecEvalApplication(false, false, form, lenv, denv);
+ return plainrecEvalCall(false, false, form, lenv, denv);
}
} else if (form instanceof EVLVariable) {
return lenv.ref(VAL_NS, form);
}
function plainrecEvalQuote(form, lenv, denv) {
- const [object] = analyzeQuote(form);
- return object;
+ const [literal] = analyzeQuote(form);
+ return literal;
}
function plainrecEvalProgn(form, lenv, denv) {
}
function plainrecEvalLambda(scope, namespace, macro, form, lenv, denv) {
- const [variables, variadic, forms] = analyzeLambda(form);
- return new EVLClosure(scope, namespace, macro, variables, variadic, forms, lenv);
+ const [parameters, rest, forms] = analyzeLambda(form);
+ return new EVLClosure(scope, namespace, macro, parameters, rest, forms, lenv);
}
function plainrecEvalRef(scope, namespace, form, lenv, denv) {
return EVLVoid.VOID;
}
-function plainrecEvalApplication(mv, apply, form, lenv, denv) {
- const [operator, operands] = analyzeApplication(mv, apply, form);
+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);
function plainrecInvokeFun(apply, macro, fn, args, lenv, denv) {
if (fn instanceof EVLPrimitiveFunction) {
- const values = mapPrimFunArgs(apply, args, fn.arityMin, fn.arityMax);
+ const values = pairPrimFunParameters(apply, args, fn.arityMin, fn.arityMax);
return fn.jsFunction(values);
} else if (fn instanceof EVLClosure) {
- const values = mapClosureArgs(apply, args, fn.variables, fn.variadic);
+ const values = pairClosureParameters(apply, args, fn.parameters, fn.rest);
switch (fn.scope) {
case LEX_SCOPE:
- const elenv = new Frame(fn.namespace, fn.variables, values, fn.lenv);
+ 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);
return plainrecEvalForms(fn.forms, elenv, denv);
}
case DYN_SCOPE:
- const edenv = new Frame(fn.namespace, fn.variables, values, denv);
+ const edenv = new Frame(fn.namespace, fn.parameters, values, denv);
return plainrecEvalForms(fn.forms, fn.lenv, edenv);
default:
throw new CannotHappen('plainrecInvokeFun');
}
} else {
- applicationOperatorFormError();
+ callOperatorFormError();
}
}
/****************************************/
function cpsEval(form) {
- return cpsEvalForm(form, nullLocalEnv, nullLocalEnv, cpsEndCont);
+ return cpsEvalForm(form, nullDefiniteEnv, nullDefiniteEnv, cpsEndCont);
}
function cpsEvalForm(form, lenv, denv, k) {
case _catchErrorsVariable:
return cpsEvalCatchErrors(form, lenv, denv, k);
case applyVariable:
- return cpsEvalApplication(false, true, form, lenv, denv, k);
+ return cpsEvalCall(false, true, form, lenv, denv, k);
case multipleValueCallVariable:
- return cpsEvalApplication(true, false, form, lenv, denv, k);
+ return cpsEvalCall(true, false, form, lenv, denv, k);
case multipleValueApplyVariable:
- return cpsEvalApplication(true, true, form, lenv, denv, k);
+ return cpsEvalCall(true, true, form, lenv, denv, k);
default:
- return cpsEvalApplication(false, false, form, lenv, denv, k);
+ return cpsEvalCall(false, false, form, lenv, denv, k);
}
} else if (form instanceof EVLVariable) {
return k(lenv.ref(VAL_NS, form));
const cpsEndCont = result => result;
function cpsEvalQuote(form, lenv, denv, k) {
- const [object] = analyzeQuote(form);
- return k(object);
+ const [literal] = analyzeQuote(form);
+ return k(literal);
}
function cpsEvalProgn(form, lenv, denv, k) {
}
function cpsEvalLambda(scope, namespace, macro, form, lenv, denv, k) {
- const [variables, variadic, forms] = analyzeLambda(form);
- return k(new EVLClosure(scope, namespace, macro, variables, variadic, forms, lenv));
+ const [parameters, rest, forms] = analyzeLambda(form);
+ return k(new EVLClosure(scope, namespace, macro, parameters, rest, forms, lenv));
}
function cpsEvalRef(scope, namespace, form, lenv, denv, k) {
return k(EVLVoid.VOID);
}
-function cpsEvalApplication(mv, apply, form, lenv, denv, k) {
- const [operator, operands] = analyzeApplication(mv, apply, form);
+function cpsEvalCall(mv, apply, form, lenv, denv, k) {
+ const [operator, operands] = analyzeCall(mv, apply, form);
return cpsEvalOperator(
operator, lenv, denv,
result => { // OperatorCont
function cpsInvokeFun(apply, macro, fn, args, lenv, denv, k) {
if (fn instanceof EVLPrimitiveFunction) {
- const values = mapPrimFunArgs(apply, args, fn.arityMin, fn.arityMax);
+ const values = pairPrimFunParameters(apply, args, fn.arityMin, fn.arityMax);
return k(fn.jsFunction(values));
} else if (fn instanceof EVLClosure) {
- const values = mapClosureArgs(apply, args, fn.variables, fn.variadic);
+ const values = pairClosureParameters(apply, args, fn.parameters, fn.rest);
switch (fn.scope) {
case LEX_SCOPE:
- const elenv = new Frame(fn.namespace, fn.variables, values, fn.lenv);
+ 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);
return cpsEvalForms(fn.forms, elenv, denv, k);
}
case DYN_SCOPE:
- const edenv = new Frame(fn.namespace, fn.variables, values, denv);
+ const edenv = new Frame(fn.namespace, fn.parameters, values, denv);
return cpsEvalForms(fn.forms, fn.lenv, edenv, k);
default:
throw new CannotHappen('cpsInvokeFun');
}
} else {
- applicationOperatorFormError();
+ callOperatorFormError();
}
}
/*********************************/
function oocpsEval(form) {
- return oocpsEvalForm(form, nullLocalEnv, nullLocalEnv, oocpsEndCont);
+ return oocpsEvalForm(form, nullDefiniteEnv, nullDefiniteEnv, oocpsEndCont);
}
function oocpsEvalForm(form, lenv, denv, k) {
case _catchErrorsVariable:
return oocpsEvalCatchErrors(form, lenv, denv, k);
case applyVariable:
- return oocpsEvalApplication(false, true, form, lenv, denv, k);
+ return oocpsEvalCall(false, true, form, lenv, denv, k);
case multipleValueCallVariable:
- return oocpsEvalApplication(true, false, form, lenv, denv, k);
+ return oocpsEvalCall(true, false, form, lenv, denv, k);
case multipleValueApplyVariable:
- return oocpsEvalApplication(true, true, form, lenv, denv, k);
+ return oocpsEvalCall(true, true, form, lenv, denv, k);
default:
- return oocpsEvalApplication(false, false, form, lenv, denv, k);
+ return oocpsEvalCall(false, false, form, lenv, denv, k);
}
} else if (form instanceof EVLVariable) {
return k.invoke(lenv.ref(VAL_NS, form));
const oocpsEndCont = new OOCPSEndCont();
function oocpsEvalQuote(form, lenv, denv, k) {
- const [object] = analyzeQuote(form);
- return k.invoke(object);
+ const [literal] = analyzeQuote(form);
+ return k.invoke(literal);
}
function oocpsEvalProgn(form, lenv, denv, k) {
}
function oocpsEvalLambda(scope, namespace, macro, form, lenv, denv, k) {
- const [variables, variadic, forms] = analyzeLambda(form);
- return k.invoke(new EVLClosure(scope, namespace, macro, variables, variadic, forms, lenv));
+ const [parameters, rest, forms] = analyzeLambda(form);
+ return k.invoke(new EVLClosure(scope, namespace, macro, parameters, rest, forms, lenv));
}
function oocpsEvalRef(scope, namespace, form, lenv, denv, k) {
return k.invoke(EVLVoid.VOID);
}
-function oocpsEvalApplication(mv, apply, form, lenv, denv, k) {
- const [operator, operands] = analyzeApplication(mv, apply, form);
+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)
function oocpsInvokeFun(apply, macro, fn, args, lenv, denv, k) {
if (fn instanceof EVLPrimitiveFunction) {
- const values = mapPrimFunArgs(apply, args, fn.arityMin, fn.arityMax);
+ const values = pairPrimFunParameters(apply, args, fn.arityMin, fn.arityMax);
return k.invoke(fn.jsFunction(values));
} else if (fn instanceof EVLClosure) {
- const values = mapClosureArgs(apply, args, fn.variables, fn.variadic);
+ const values = pairClosureParameters(apply, args, fn.parameters, fn.rest);
switch (fn.scope) {
case LEX_SCOPE:
- const elenv = new Frame(fn.namespace, fn.variables, values, fn.lenv);
+ 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);
return oocpsEvalForms(fn.forms, elenv, denv, k);
}
case DYN_SCOPE:
- const edenv = new Frame(fn.namespace, fn.variables, values, denv);
+ const edenv = new Frame(fn.namespace, fn.parameters, values, denv);
return oocpsEvalForms(fn.forms, fn.lenv, edenv, k);
default:
throw new CannotHappen('oocpsInvokeFun');
}
} else {
- applicationOperatorFormError();
+ callOperatorFormError();
}
}
function sboocpsEval(form) {
const kStack = new SBOOCPSControlStack();
kStack.push(sboocpsEndCont);
- return sboocpsEvalForm(form, nullLocalEnv, kStack);
+ return sboocpsEvalForm(form, nullDefiniteEnv, kStack);
}
class SBOOCPSControlStack {
case _catchErrorsVariable:
return sboocpsEvalCatchErrors(form, lenv, kStack);
case applyVariable:
- return sboocpsEvalApplication(false, true, form, lenv, kStack);
+ return sboocpsEvalCall(false, true, form, lenv, kStack);
case multipleValueCallVariable:
- return sboocpsEvalApplication(true, false, form, lenv, kStack);
+ return sboocpsEvalCall(true, false, form, lenv, kStack);
case multipleValueApplyVariable:
- return sboocpsEvalApplication(true, true, form, lenv, kStack);
+ return sboocpsEvalCall(true, true, form, lenv, kStack);
default:
- return sboocpsEvalApplication(false, false, form, lenv, kStack);
+ return sboocpsEvalCall(false, false, form, lenv, kStack);
}
} else if (form instanceof EVLVariable) {
return kStack.invokeCont(lenv.ref(VAL_NS, form));
const sboocpsEndCont = new SBOOCPSEndCont();
function sboocpsEvalQuote(form, lenv, kStack) {
- const [object] = analyzeQuote(form);
- return kStack.invokeCont(object);
+ const [literal] = analyzeQuote(form);
+ return kStack.invokeCont(literal);
}
function sboocpsEvalProgn(form, lenv, kStack) {
}
function sboocpsEvalLambda(scope, namespace, macro, form, lenv, kStack) {
- const [variables, variadic, forms] = analyzeLambda(form);
- return kStack.invokeCont(new EVLClosure(scope, namespace, macro, variables, variadic, forms, lenv));
+ const [parameters, rest, forms] = analyzeLambda(form);
+ return kStack.invokeCont(new EVLClosure(scope, namespace, macro, parameters, rest, forms, lenv));
}
function sboocpsEvalRef(scope, namespace, form, lenv, kStack) {
return kStack.invokeCont(EVLVoid.VOID);
}
-function sboocpsEvalApplication(mv, apply, form, lenv, kStack) {
- const [operator, operands] = analyzeApplication(mv, apply, form);
+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 sboocpsInvokeFun(apply, macro, fn, args, lenv, kStack) {
if (fn instanceof EVLPrimitiveFunction) {
- const values = mapPrimFunArgs(apply, args, fn.arityMin, fn.arityMax);
+ const values = pairPrimFunParameters(apply, args, fn.arityMin, fn.arityMax);
return kStack.invokeCont(fn.jsFunction(values));
} else if (fn instanceof EVLClosure) {
- const values = mapClosureArgs(apply, args, fn.variables, fn.variadic);
+ const values = pairClosureParameters(apply, args, fn.parameters, fn.rest);
switch (fn.scope) {
case LEX_SCOPE:
- const elenv = new Frame(fn.namespace, fn.variables, values, fn.lenv);
+ 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 sboocpsEvalForms(fn.forms, elenv, kStack);
}
case DYN_SCOPE:
- kStack.push(new Frame(fn.namespace, fn.variables, values, undefined));
+ kStack.push(new Frame(fn.namespace, fn.parameters, values, undefined));
return sboocpsEvalForms(fn.forms, fn.lenv, kStack);
default:
throw new CannotHappen('sboocpsInvokeFun');
}
} else {
- applicationOperatorFormError();
+ callOperatorFormError();
}
}
function trampolineEval(form) {
const kStack = new TrampolineControlStack();
kStack.push(trampolineEndCont);
- let bounce = new EvalReq(form, nullLocalEnv);
+ let bounce = new EvalReq(form, nullDefiniteEnv);
while (true) {
- if (signalArray[0] === 1) {
+ if (abortSignalArray !== null && abortSignalArray[0] === 1) {
throw new Aborted();
}
if (bounce instanceof EvalReq) {
case _catchErrorsVariable:
return trampolineEvalCatchErrors(form, lenv, kStack);
case applyVariable:
- return trampolineEvalApplication(false, true, form, lenv, kStack);
+ return trampolineEvalCall(false, true, form, lenv, kStack);
case multipleValueCallVariable:
- return trampolineEvalApplication(true, false, form, lenv, kStack);
+ return trampolineEvalCall(true, false, form, lenv, kStack);
case multipleValueApplyVariable:
- return trampolineEvalApplication(true, true, form, lenv, kStack);
+ return trampolineEvalCall(true, true, form, lenv, kStack);
default:
- return trampolineEvalApplication(false, false, form, lenv, kStack);
+ return trampolineEvalCall(false, false, form, lenv, kStack);
}
} else if (form instanceof EVLVariable) {
return lenv.ref(VAL_NS, form);
const trampolineEndCont = new TrampolineEndCont();
function trampolineEvalQuote(form, lenv, kStack) {
- const [object] = analyzeQuote(form);
- return object;
+ const [literal] = analyzeQuote(form);
+ return literal;
}
function trampolineEvalProgn(form, lenv, kStack) {
}
function trampolineEvalLambda(scope, namespace, macro, form, lenv, kStack) {
- const [variables, variadic, forms] = analyzeLambda(form);
- return new EVLClosure(scope, namespace, macro, variables, variadic, forms, lenv);
+ const [parameters, rest, forms] = analyzeLambda(form);
+ return new EVLClosure(scope, namespace, macro, parameters, rest, forms, lenv);
}
function trampolineEvalRef(scope, namespace, form, lenv, kStack) {
}
}
-function trampolineEvalApplication(mv, apply, form, lenv, kStack) {
- const [operator, operands] = analyzeApplication(mv, apply, form);
+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);
}
function trampolineInvokeFun(apply, macro, fn, args, lenv, kStack) {
if (fn instanceof EVLPrimitiveFunction) {
- const values = mapPrimFunArgs(apply, args, fn.arityMin, fn.arityMax);
+ const values = pairPrimFunParameters(apply, args, fn.arityMin, fn.arityMax);
return fn.jsFunction(values);
} else if (fn instanceof EVLClosure) {
- const values = mapClosureArgs(apply, args, fn.variables, fn.variadic);
+ const values = pairClosureParameters(apply, args, fn.parameters, fn.rest);
switch (fn.scope) {
case LEX_SCOPE:
- const elenv = new Frame(fn.namespace, fn.variables, values, fn.lenv);
+ 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.variables, values, undefined));
+ kStack.push(new Frame(fn.namespace, fn.parameters, values, undefined));
return trampolineEvalForms(fn.forms, fn.lenv, kStack);
default:
throw new CannotHappen('trampolineInvokeFun');
}
} else {
- applicationOperatorFormError();
+ callOperatorFormError();
}
}
function trampolineppEval(form, lenv = null) {
if (lenv === null) {
- form = trampolineppPreprocessForm(form, nullLocalEnv);
- lenv = nullLocalEnv;
+ form = trampolineppPreprocessForm(form, nullDefiniteEnv);
+ lenv = nullDefiniteEnv;
}
const kStack = new TrampolineppControlStack();
kStack.push(trampolineppEndCont);
let bounce = new EvalReq(form, lenv);
while (true) {
- if (signalArray[0] === 1) {
+ if (abortSignalArray !== null && abortSignalArray[0] === 1) {
throw new Aborted();
}
if (bounce instanceof EvalReq) {
case _catchErrorsVariable:
return trampolineppPreprocessCatchErrors(form, lenv);
case applyVariable:
- return trampolineppPreprocessApplication(false, true, form, lenv);
+ return trampolineppPreprocessCall(false, true, form, lenv);
case multipleValueCallVariable:
- return trampolineppPreprocessApplication(true, false, form, lenv);
+ return trampolineppPreprocessCall(true, false, form, lenv);
case multipleValueApplyVariable:
- return trampolineppPreprocessApplication(true, true, form, lenv);
+ return trampolineppPreprocessCall(true, true, form, lenv);
default:
- return trampolineppPreprocessApplication(false, false, form, lenv);
+ return trampolineppPreprocessCall(false, false, form, lenv);
}
} else if (form instanceof EVLVariable) {
return trampolineppPreprocessRef2(LEX_SCOPE, VAL_NS, form, lenv);
}
function trampolineppPreprocessQuote(form, lenv) {
- const [object] = analyzeQuote(form);
- return new TrampolineppQuote(object);
+ const [literal] = analyzeQuote(form);
+ return new TrampolineppQuote(literal);
}
class TrampolineppQuote extends TrampolineppForm {
- constructor(object) {
+ constructor(literal) {
super();
- this.object = object;
+ this.literal = literal;
}
eval(lenv, kStack) {
- const {object} = this;
- return object;
+ const {literal} = this;
+ return literal;
}
}
}
function trampolineppPreprocessLambda(scope, namespace, macro, form, lenv) {
- const [variables, variadic, forms] = analyzeLambda(form);
+ const [parameters, rest, forms] = analyzeLambda(form);
switch (scope) {
case LEX_SCOPE: {
- const elenv = new Frame(namespace, variables, new Array(variables.length).fill(null), lenv);
+ const elenv = new Frame(namespace, parameters, new Array(parameters.length).fill(null), lenv);
const preprocessedForms = trampolineppPreprocessForms(forms, elenv);
- return new TrampolineppLambda(scope, namespace, macro, variables, variadic, preprocessedForms);
+ return new TrampolineppLambda(scope, namespace, macro, parameters, rest, preprocessedForms);
}
case DYN_SCOPE: {
const preprocessedForms = trampolineppPreprocessForms(forms, lenv);
- return new TrampolineppLambda(scope, namespace, macro, variables, variadic, preprocessedForms);
+ return new TrampolineppLambda(scope, namespace, macro, parameters, rest, preprocessedForms);
}
default:
throw new CannotHappen('trampolineppPreprocessLambda');
}
class TrampolineppLambda extends TrampolineppForm {
- constructor(scope, namespace, macro, variables, variadic, forms) {
+ constructor(scope, namespace, macro, parameters, rest, forms) {
super();
this.scope = scope;
this.namespace = namespace;
this.macro = macro;
- this.variables = variables;
- this.variadic = variadic;
+ this.parameters = parameters;
+ this.rest = rest;
this.forms = forms;
}
eval(lenv, kStack) {
- const {scope, namespace, macro, variables, variadic, forms} = this;
- return new EVLClosure(scope, namespace, macro, variables, variadic, forms, lenv);
+ const {scope, namespace, macro, parameters, rest, forms} = this;
+ return new EVLClosure(scope, namespace, macro, parameters, rest, forms, lenv);
}
}
}
}
-function trampolineppPreprocessApplication(mv, apply, form, lenv) {
- const [operator, operands] = analyzeApplication(mv, apply, form);
+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 = mapClosureArgs(false, listToArray(operands), fn.variables, fn.variadic);
- const elenv = new Frame(fn.namespace, fn.variables, values, fn.lenv);
+ 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 TrampolineppApplication(mv, apply, preprocessedOperator, preprocessedOperands);
+ return new TrampolineppCall(mv, apply, preprocessedOperator, preprocessedOperands);
}
} else if (isMacroLet(operator, operands)) {
const preprocessedOperands = trampolineppPreprocessForms(operands, lenv);
- const [variables, variadic, forms] = analyzeLambda(operator);
- const values = listToArray(preprocessedOperands).map(preprocessedOperand => preprocessedOperand.eval(nullLocalEnv, null));
- const elenv = new Frame(FUN_NS, variables, values, 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, variables, variadic, preprocessedForms);
- return new TrampolineppApplication(mv, apply, preprocessedOperator, preprocessedOperands);
+ 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 TrampolineppApplication(mv, apply, preprocessedOperator, preprocessedOperands);
+ return new TrampolineppCall(mv, apply, preprocessedOperator, preprocessedOperands);
}
}
return true;
}
-class TrampolineppApplication extends TrampolineppForm {
+class TrampolineppCall extends TrampolineppForm {
constructor(mv, apply, operator, operands) {
super();
this.mv = mv;
function trampolineppInvokeFun(apply, fn, args, lenv, kStack) {
if (fn instanceof EVLPrimitiveFunction) {
- const values = mapPrimFunArgs(apply, args, fn.arityMin, fn.arityMax);
+ const values = pairPrimFunParameters(apply, args, fn.arityMin, fn.arityMax);
return fn.jsFunction(values);
} else if (fn instanceof EVLClosure) {
- const values = mapClosureArgs(apply, args, fn.variables, fn.variadic);
+ const values = pairClosureParameters(apply, args, fn.parameters, fn.rest);
switch (fn.scope) {
case LEX_SCOPE:
- const elenv = new Frame(fn.namespace, fn.variables, values, fn.lenv);
+ 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.variables, values, undefined));
+ kStack.push(new Frame(fn.namespace, fn.parameters, values, undefined));
return trampolineppEvalForms(fn.forms, fn.lenv, kStack);
default:
throw new CannotHappen('trampolineppInvokeFun');
}
} else {
- applicationOperatorFormError();
+ callOperatorFormError();
}
}
-/**********************************/
-/* Primitive Function Definer (1) */
-/**********************************/
+/**************************************/
+/* Primitive Function Definitions (1) */
+/**************************************/
const primitiveFunctions = new Map();
}
}
+// the only object of type void
EVLVoid.VOID = new EVLVoid();
function nullToVoid(x) {
class EVLBoolean extends EVLObject {
constructor(jsValue) {
super();
- this.jsValue = jsValue; // javascript boolean
+ this.jsValue = jsValue; // JavaScript boolean
}
toString() {
return this.jsValue ? '#t' : '#f';
}
}
+// the only object of type boolean representing true
EVLBoolean.TRUE = new EVLBoolean(true);
+// the only object of type boolean representing false
EVLBoolean.FALSE = new EVLBoolean(false);
function evlBoolean(jsBoolean) {
class EVLNumber extends EVLObject {
constructor(jsValue) {
super();
- this.jsValue = jsValue; // javascript number
+ this.jsValue = jsValue; // JavaScript number
}
eql(that) {
if (that instanceof EVLNumber) {
class EVLCharacter extends EVLObject {
constructor(jsValue) {
super();
- this.jsValue = jsValue; // javascript string of one UTF-16 code unit
+ this.jsValue = jsValue; // JavaScript string of one UTF-16 code unit
}
eql(that) {
if (that instanceof EVLCharacter) {
class EVLString extends EVLObject {
constructor(jsValue) {
super();
- this.jsValue = jsValue; // javascript string of zero or more UTF-16 code units
+ this.jsValue = jsValue; // JavaScript string
}
eql(that) {
if (that instanceof EVLString) {
class EVLSymbol extends EVLObject { // abstract class
constructor(name) {
super();
- this.name = name; // javascipt string of zero or more UTF-16 code units
+ this.name = name; // JavaScript string
}
}
class EVLVariable extends EVLSymbol {
constructor(name) {
super(name);
- this.value = null;
- this.function = null;
+ this.value = null; // EVLObject or null
+ this.function = null; // EVLObject or null
}
toString() {
return escapeCharacters(this.name, escapeProtoTokenCharacter);
const ifVariable = internVariable('if');
const _vlambdaVariable = internVariable('_vlambda');
const _mlambdaVariable = internVariable('_mlambda');
-const mlambdaVariable = internVariable('mlambda');
+const mlambdaVariable = internVariable('mlambda'); // mlet
const _flambdaVariable = internVariable('_flambda');
const _dlambdaVariable = internVariable('_dlambda');
const vrefVariable = internVariable('vref');
}
}
+// the only object of type empty-list
EVLEmptyList.NIL = new EVLEmptyList();
primitiveFunction('empty-list?', 1, 1, function(args) {
class EVLVector extends EVLObject {
constructor(elements) {
super();
- this.elements = elements; // javascript array of EVLObject or null elements
+ this.elements = elements; // JavaScript array of EVLObject's and/or null's
}
toString() {
let string = '';
super();
this.arityMin = arityMin;
this.arityMax = arityMax;
- this.jsFunction = jsFunction; // javascript function
+ this.jsFunction = jsFunction; // JavaScript function
}
toString() {
return '#<primitive-function>';
/**************/
class EVLClosure extends EVLFunction {
- constructor(scope, namespace, macro, variables, variadic, forms, lenv) {
+ constructor(scope, namespace, macro, parameters, rest, forms, lenv) {
super();
this.scope = scope;
this.namespace = namespace;
this.macro = macro;
- this.variables = variables;
- this.variadic = variadic;
+ this.parameters = parameters;
+ this.rest = rest;
this.forms = forms;
this.lenv = lenv;
}
return evlBoolean(args[0] instanceof EVLClosure);
});
-/*****************************/
-/* Other Primitive Functions */
-/*****************************/
+/*************************************/
+/* Miscellaneous Primitive Functions */
+/*************************************/
primitiveFunction('values', 0, null, function(args) {
return new EVLObjects(args);
return new EVLNumber(Date.now());
});
-/**********************************/
-/* Primitive Function Definer (2) */
-/**********************************/
+/**************************************/
+/* Primitive Function Definitions (2) */
+/**************************************/
for (const [name, [arityMin, arityMax, jsFunction]] of primitiveFunctions) {
GlobalEnv.set(FUN_NS, internVariable(name), new EVLPrimitiveFunction(arityMin, arityMax, jsFunction));
}
-/********/
-/* Node */
-/********/
+/****************************/
+/* Interface (Command Line) */
+/****************************/
-if (typeof onmessage === 'undefined') { // node
+const evaluatorOptions = [
+ '--plainrec',
+ '--cps',
+ '--oocps',
+ '--sboocps',
+ '--trampoline',
+ '--trampolinepp'
+];
+
+if (isRunningInsideNode) {
import('node:fs').then(fs => {
- signalArray = [0];
- selectedEvaluator = 'trampolinepp';
- GlobalEnv.set(VAL_NS, internVariable('*features*'), new EVLCons(internVariable(selectedEvaluator), EVLEmptyList.NIL));
const nargs = process.argv.length;
- let n = 2;
+ let n = 2; // skip 'node' and 'core.js'
+ selectedEvaluator = 'trampolinepp';
+ if (n < nargs && evaluatorOptions.includes(process.argv[n])) {
+ selectedEvaluator = process.argv[n++].substring(2);
+ }
+ initializeFeatureList([selectedEvaluator]);
while (n < nargs) {
const arg = process.argv[n++];
switch (arg) {
- case '-l':
+ case '-l': {
if (n === nargs) {
usage();
}
const fileContents = fs.readFileSync(file, 'utf8');
printToConsole(evaluateAllForms(fileContents));
break;
- case '-e':
+ }
+ case '-e': {
if (n === nargs) {
usage();
}
const form = process.argv[n++];
printToConsole(evaluateFirstForm(form));
break;
+ }
+ case '--convert': {
+ if (n === nargs) {
+ usage();
+ }
+ const file = process.argv[n++];
+ const fileContents = fs.readFileSync(file, 'utf8');
+ printToConsole(convertEVLToXML(fileContents));
+ break;
+ }
default:
usage();
}
}
function usage() {
- console.log('usage: -l <file> to load a file, -e <form> to evaluate a form');
+ console.log('usage:');
+ console.log('--plainrec: selects the plain recursive 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');
+ console.log('--trampoline: selects the trampoline evaluator');
+ console.log('--trampolinepp: selects the trampoline++ evaluator (DEFAULT)');
+ console.log('-l <file>: loads the EVL file');
+ console.log('-e <form>: evaluates the form');
+ console.log('--convert <file>: converts the EVL file to XML');
process.exit();
}
function printToConsole(response) {
switch (response.status) {
- case COMPLETED_NORMALLY:
+ case SUCCESS:
console.log(response.output);
break;
- case COMPLETED_ABNORMALLY:
+ case ERROR:
console.log(response.output);
process.exit();
}