From: Raphaƫl Van Dyck Date: Mon, 16 Feb 2026 13:25:43 +0000 (+0100) Subject: add implementation notes; revise user manual, tutorial, and reference manual X-Git-Url: http://evlambda.org/gitweb/gitweb.cgi?a=commitdiff_plain;h=d7071fd3f37ea2d5d0b1cdd9fbd3ab2f1289f002;p=evlambda.git add implementation notes; revise user manual, tutorial, and reference manual --- diff --git a/README b/README index bb057de..2719fbb 100644 --- a/README +++ b/README @@ -54,7 +54,29 @@ How to run the evaluator from the terminal ========================================== Execute the following command from : -node system-files/core.js { -l | -e
}* - -Example: +node system-files/core.js { --plainrec | --cps | --oocps | --sboocps | --trampoline | --trampoliepp }? { -l | -e }* + +--plainrec: selects the plain recursive evaluator +--cps: selects the continuation passing style evaluator +--oocps: selects the object-oriented CPS evaluator +--sboocps: selects the stack-based object-oriented CPS evaluator +--trampoline: selects the trampoline evaluator +--trampolinepp: selects the trampoline++ evaluator (DEFAULT) +-l : loads the EVL file +-e : evaluates the form + +Examples: node system-files/core.js -l system-files/mantle.evl -e '(test-loop 1000000)' +node system-files/core.js --plainrec -l system-files/mantle.evl -e '(test-loop 1000000)' + +How to run the EVL to XML converter from the terminal +===================================================== + +Execute the following command from : +node system-files/core.js --convert + +--convert : converts the EVL file to XML + +Examples: +node system-files/core.js --convert system-files/docgen-sample.evl +node system-files/core.js --convert system-files/docgen-sample.evl | xsltproc system-files/evl2html.xslt - diff --git a/src/evaluator.js b/src/evaluator.js index 50a739a..264d251 100644 --- a/src/evaluator.js +++ b/src/evaluator.js @@ -6,15 +6,15 @@ import { } from './utilities.js'; 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; export const evaluatorNames = new Map([ ['plainrec', 'Plain Recursive'], @@ -28,10 +28,10 @@ export const evaluatorNames = new Map([ let evaluator = null; let jobId = 0; const jobs = new Map(); -const signalBuffer = new SharedArrayBuffer(1); -const signalArray = new Uint8Array(signalBuffer); +const abortSignalBuffer = new SharedArrayBuffer(1); +const abortSignalArray = new Uint8Array(abortSignalBuffer); -signalArray[0] = 0; +abortSignalArray[0] = 0; // => {id, action, input} // <= {id, status, output} @@ -66,7 +66,7 @@ export function createEvaluator(jsFile, selectedEvaluator, evlFiles, callback) { callback(event.data); } } - sendRequest(INITIALIZE, {signalBuffer, selectedEvaluator, evlFiles}, callback); + sendRequest(INITIALIZE, {abortSignalBuffer, selectedEvaluator, evlFiles}, callback); } export function evaluateFirstForm(text, callback) { @@ -77,12 +77,12 @@ export function evaluateAllForms(text, callback) { sendRequest(EVALUATE_ALL_FORMS, text, callback); } -export function convertToHTML(text, xsltString, cssURL, jsURL, windowId, callback) { - sendRequest(CONVERT_TO_XML, text, response => { +export function convertEVLToHTML(text, xsltString, cssURL, jsURL, windowId, callback) { + sendRequest(CONVERT_EVL_TO_XML, text, response => { let result = null; try { switch (response.status) { - case COMPLETED_NORMALLY: + case SUCCESS: const parser = new DOMParser(); const processor = new XSLTProcessor(); const serializer = new XMLSerializer(); @@ -99,7 +99,7 @@ export function convertToHTML(text, xsltString, cssURL, jsURL, windowId, callbac //console.log(htmlString); result = htmlString; break; - case COMPLETED_ABNORMALLY: + case ERROR: result = errorPage(`ERROR: ${response.output}`, cssURL, jsURL, windowId); break; case ABORTED: @@ -137,13 +137,13 @@ export function formatForListener(response) { switch (response.status) { case FOUND_NO_FORM: return null; - case COMPLETED_NORMALLY: + case SUCCESS: let text = ''; for (const value of response.output) { text = text + value + '\n'; } return text; - case COMPLETED_ABNORMALLY: + case ERROR: return `ERROR: ${response.output}\n`; case ABORTED: return 'ABORTED\n'; @@ -156,7 +156,7 @@ export function formatForMinibuffer(response) { switch (response.status) { case FOUND_NO_FORM: return 'FOUND NO FORM'; - case COMPLETED_NORMALLY: + case SUCCESS: let text = ''; let first = true; for (const value of response.output) { @@ -168,7 +168,7 @@ export function formatForMinibuffer(response) { text = text + value.replaceAll('\n', '\u2424'); // SYMBOL FOR NEWLINE } return text; - case COMPLETED_ABNORMALLY: + case ERROR: return `ERROR: ${response.output}`; case ABORTED: return 'ABORTED'; @@ -178,5 +178,5 @@ export function formatForMinibuffer(response) { } export function abortEvaluation() { - signalArray[0] = 1; + abortSignalArray[0] = 1; } diff --git a/src/ide.jsx b/src/ide.jsx index affa9c0..30f874f 100644 --- a/src/ide.jsx +++ b/src/ide.jsx @@ -90,7 +90,7 @@ import { createEvaluator, evaluateFirstForm, evaluateAllForms, - convertToHTML, + convertEVLToHTML, formatForMinibuffer, abortEvaluation } from './evaluator.js'; @@ -227,7 +227,7 @@ class EVLBuffer extends FileBuffer { const jsURL = URL.createObjectURL(jsBlob); const state = this.transaction.state; const text = state.sliceDoc(); - convertToHTML(text, xslt, cssURL, jsURL, window.id, html => toggleHTMLModeCommand2(ide, window, html, cssURL, jsURL)); + convertEVLToHTML(text, xslt, cssURL, jsURL, window.id, html => toggleHTMLModeCommand2(ide, window, html, cssURL, jsURL)); } } @@ -1231,7 +1231,7 @@ init([ 'USER-MANUAL', 'TUTORIAL', 'REFERENCE-MANUAL', - //'IMPLEMENTATION-NOTES', + 'IMPLEMENTATION-NOTES', 'BIBLIOGRAPHY', 'LICENSE', 'all-caps.css', diff --git a/system-files/BIBLIOGRAPHY b/system-files/BIBLIOGRAPHY index cdeec8d..3fc13c0 100644 --- a/system-files/BIBLIOGRAPHY +++ b/system-files/BIBLIOGRAPHY @@ -4,6 +4,7 @@ + Bibliography diff --git a/system-files/IMPLEMENTATION-NOTES b/system-files/IMPLEMENTATION-NOTES index 785c083..52784d7 100644 --- a/system-files/IMPLEMENTATION-NOTES +++ b/system-files/IMPLEMENTATION-NOTES @@ -4,10 +4,436 @@ + Implementation Notes +
+ $\DeclareMathOperator{\bindingmathop}{binding}$ +
+

Implementation Notes

+

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

+

The JavaScript file implements five interpreter-based evaluators:

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

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

+

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

+

Here are quick links to the sections:

+
    +
  1. Global Variables
  2. +
  3. Interface (IDE)
  4. +
  5. Errors
  6. +
  7. Tokenizer
  8. +
  9. Reader
  10. +
  11. EVL to XML Converter
  12. +
  13. Form Analyzer
  14. +
  15. Scope-Extent Combinations
  16. +
  17. Namespaces
  18. +
  19. Global Environment
  20. +
  21. Lexical and Dynamic Environments
  22. +
  23. Pairing Parameters with Arguments
  24. +
  25. Generic Evaluator
  26. +
  27. Plain Recursive Evaluator
  28. +
  29. Continuation Passing Style Evaluator
  30. +
  31. Object-Oriented CPS Evaluator
  32. +
  33. Stack-Based Object-Oriented CPS Evaluator
  34. +
  35. Trampoline Evaluator
  36. +
  37. Trampoline++ Evaluator
  38. +
  39. Primitive Function Definitions (1)
  40. +
  41. Bounce
  42. +
  43. Evaluation Request
  44. +
  45. Result
  46. +
  47. EVLObjects
  48. +
  49. EVLObject
  50. +
  51. EVLVoid
  52. +
  53. EVLBoolean
  54. +
  55. EVLNumber
  56. +
  57. EVLCharacter
  58. +
  59. EVLString
  60. +
  61. EVLSymbol
  62. +
  63. EVLKeyword
  64. +
  65. EVLVariable
  66. +
  67. EVLList
  68. +
  69. EVLEmptyList
  70. +
  71. EVLCons
  72. +
  73. EVLVector
  74. +
  75. EVLFunction
  76. +
  77. EVLPrimitiveFunction
  78. +
  79. EVLClosure
  80. +
  81. Miscellaneous Primitive Functions
  82. +
  83. Primitive Function Definitions (2)
  84. +
  85. Interface (Command Line)
  86. +
+

Global Variables

+

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

+

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

+

The variable selectedEvaluator contains the name of the selected evaluator.

+

Interface (IDE)

+

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

+

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

+

The main thread and the web worker thread communicate through messages and shared arrays.

+

Messages

+

The request messages sent by the main thread to the web worker thread are objects containing the following properties:

+
    +
  • id (required): A unique integer id used to pair responses with requests.
  • +
  • action (required): An integer specifying the requested action.
  • +
  • input (optional): Some data.
  • +
+

The response messages sent by the web worker thread to the main thread are objects containing the following properties:

+
    +
  • id (required): A unique integer id used to pair responses with requests.
  • +
  • status (required): An integer specifying the status of the processing of the requested action.
  • +
  • output (optional): Some data.
  • +
+

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

+
    +
  • id: A unique integer id used to pair responses with requests.
  • +
  • status: The value of the constant ERROR.
  • +
  • output: A string describing the error.
  • +
+

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

+
    +
  • id: A unique integer id used to pair responses with requests.
  • +
  • status: The value of the constant ABORTED.
  • +
+

The following sections describe the messages currently implemented. The response messages ERROR and ABORTED are omitted from the descriptions. The property id is omitted from the descriptions.

+

INITIALIZE

+

This message is used to request the initialization of the web worker.

+

Request message:

+
    +
  • action: The value of the constant INITIALIZE.
  • +
  • input: An object containing the following properties: +
      +
    • abortSignalBuffer: The shared buffer under the shared array.
    • +
    • selectedEvaluator: The name of the selected evaluator.
    • +
    • evlFiles: The contents of some EVLambda source files to load.
    • +
    +
  • +
+

Response message when the processing of the requested action succeeds:

+
    +
  • status: The value of the constant SUCCESS.
  • +
  • output: An array containing the printable representations of the values of the last form of the last EVLambda source file.
  • +
+

EVALUATE_FIRST_FORM

+

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

+

Request message:

+
    +
  • action: The value of the constant EVALUATE_FIRST_FORM.
  • +
  • input: The input string.
  • +
+

Response message when the processing of the requested action fails because the input string does not contain any forms:

+
    +
  • status: The value of the constant FOUND_NO_FORM.
  • +
+

Response message when the processing of the requested action succeeds:

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

EVALUATE_ALL_FORMS

+

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

+

Request message:

+
    +
  • action: The value of the constant EVALUATE_ALL_FORMS.
  • +
  • input: The input string.
  • +
+

Response message when the processing of the requested action succeeds:

+
    +
  • status: The value of the constant SUCCESS.
  • +
  • output: An array containing the printable representations of the values of the last form contained in the input string.
  • +
+

CONVERT_EVL_TO_XML

+

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

+

Request message:

+
    +
  • action: The value of the constant CONVERT_EVL_TO_XML.
  • +
  • input: The input string.
  • +
+

Response message when the processing of the requested action succeeds:

+
    +
  • status: The value of the constant SUCCESS.
  • +
  • output: A string containing the result of the conversion.
  • +
+

Shared Arrays

+

abortSignalArray

+

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

+

Errors

+

This section defines some custom error types.

+

Tokenizer

+

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.

+

On each invocation, the tokenizer sets the following properties:

+
    +
  • whitespace: The run of whitespace preceding the lexeme associated with the next token.
  • +
  • lexeme: The lexeme associated with the next token.
  • +
  • category: The category of the next token.
  • +
  • value: The value of the next token or null if tokens of this category have no value.
  • +
+

Reader

+

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

+

The read-time conditionalization facility is integrated into the reader.

+

The central function is the function readObject, which returns one of the following:

+
    +
  • The next object.
  • +
  • The value of the constant DOT.
  • +
  • The value of the constant CLOSING_PARENTHESIS.
  • +
  • The value of the constant XML_END_TAG.
  • +
  • The value of the constant EOI.
  • +
+

The function readObject 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.

+

Most of the other functions invoke the function readObject and use a switch statement to decide what to do depending on the result of the invocation.

+

The main function is the function read, which invokes the function readObject and, depending on the result of the invocation, does one of the following:

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

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

+

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

+

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

+

EVL to XML Converter

+

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

+

The EVL to XML converter uses the following context-free grammar to model the input:

+ + + + + + + + + + + + + + + + + + + + + +
$\metavar{input}$$\Coloneq$beginning-of-input {$\metavar{top-level-whitespace}$ $\metavar{construct}$}* $\metavar{top-level-whitespace}$ end-of-input
$\metavar{construct}$$\Coloneq$$\metavar{xml-construct}$ | $\metavar{evl-construct}$
$\metavar{xml-construct}$$\Coloneq$xml-start-tag {$\metavar{xml-whitespace}$ $\metavar{construct}$}* $\metavar{xml-whitespace}$ xml-end-tag | xml-empty-element-tag | xml-comment
$\metavar{evl-construct}$$\Coloneq$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
+

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:

+
    +
  • New tokens have been added to represent the beginning and the end of the input.
  • +
  • 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.
  • +
  • Hash-string constructs are not decomposed into characters.
  • +
  • The grammar recognizes the tokens introducing abbreviations but it does not recognize abbreviations as units.
  • +
  • The grammar recognizes the tokens introducing read-time conditionals but it does not recognize read-time conditionals as units.
  • +
  • The grammar recognizes the dot but it does not distinguish between proper lists and dotted lists.
  • +
  • The value of a token is the lexeme associated with the token.
  • +
+

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

+
    +
  • Whether the run of whitespace occurs in top-level context, in XML context, or in EVL context.
  • +
  • Whether the token preceding the run of whitespace is beginning-of-input, an XML token, or an EVL token.
  • +
  • Whether the token following the run of whitespace is end-of-input, an XML token, or an EVL token.
  • +
+

The XML tokens are the following tokens: xml-start-tag, xml-end-tag, xml-empty-element-tag, and xml-comment.

+

The EVL tokens are the end-of-line and end-of-last-line comments (which are treated as a unit) and the following tokens: void, boolean, number, hash-string-construct, string, keyword, variable, quote, quasiquote, unquote, unquote-splicing, hash-plus, hash-minus, dot, left-parenthesis, hash-left-parenthesis, and right-parenthesis.

+

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.

+

Tags are never added before or after the runs of whitespace occurring in top-level context.

+

The runs of whitespace occurring in XML context are processed as follows:

+
+
$\metavar{xmltok}$$\metavar{ws}$$\metavar{evltok}$ is transformed into
+
$\metavar{xmltok}$$\metavar{ws}$<toplevelcode><blockcode>$\metavar{evltok}$
+
$\metavar{evltok}$$\metavar{ws}$$\metavar{evltok}$, where $\metavar{ws}$ contains at least one blank line, is transformed into
+
$\metavar{evltok}$</blockcode></toplevelcode>$\metavar{ws}$<toplevelcode><blockcode>$\metavar{evltok}$
+
$\metavar{evltok}$$\metavar{ws}$$\metavar{evltok}$, where $\metavar{ws}$ contains no blank lines, is transformed into
+
$\metavar{evltok}$$\metavar{ws}$$\metavar{evltok}$
+
$\metavar{evltok}$$\metavar{ws}$$\metavar{xmltok}$ is transformed into
+
$\metavar{evltok}$</toplevelcode></blockcode>$\metavar{ws}$$\metavar{xmltok}$
+
$\metavar{xmltok}$$\metavar{ws}$$\metavar{xmltok}$ is transformed into
+
$\metavar{xmltok}$$\metavar{ws}$$\metavar{xmltok}$
+
+

The runs of whitespace occurring in EVL context are processed as follows:

+
+
$\metavar{evltok}$$\metavar{ws}$$\metavar{xmltok}$ is transformed into
+
$\metavar{evltok}$</blockcode><indentation style="…"><blockcomment>$\metavar{ws}$$\metavar{xmltok}$
+
$\metavar{xmltok}$$\metavar{ws}$$\metavar{xmltok}$ is transformed into
+
$\metavar{xmltok}$$\metavar{ws}$$\metavar{xmltok}$
+
$\metavar{xmltok}$$\metavar{ws}$$\metavar{evltok}$ is transformed into
+
$\metavar{xmltok}$</blockcomment></indentation><blockcode>$\metavar{ws}$$\metavar{evltok}$
+
$\metavar{evltok}$$\metavar{ws}$$\metavar{evltok}$ is transformed into
+
$\metavar{evltok}$$\metavar{ws}$$\metavar{evltok}$
+
+

Form Analyzer

+

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

+

Scope-Extent Combinations

+

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

+
    +
  • LEX_SCOPE: lexical scope and indefinite extent (= lexical environment)
  • +
  • DYN_SCOPE: indefinite scope and dynamic extent (= dynamic environment)
  • +
+

Namespaces

+

The namespaces are identified using the members of the following enumeration:

+
    +
  • VAL_NS: value namespace
  • +
  • FUN_NS: function namespace
  • +
+

Global Environment

+

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

+

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

+

Lexical and Dynamic Environments

+

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

+
    +
  • Each lexical or dynamic environment consists of two sets of bindings: one for the value namespace and one for the function namespace.
  • +
  • Each lexical or dynamic environment is the result of extending zero or more times in sequence an empty environment.
  • +
  • Extending a lexical or dynamic environment is achieved by copying the two sets of bindings and then deleting and adding some bindings from/to the copy of one of the two sets.
  • +
+

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

+

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.

+

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:

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

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

+

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

+

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

+

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

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

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

+

Pairing Parameters with Arguments

+

The parameters are paired with the arguments by the following functions:

+ + + + + + + + +
FunctionFunction callRest parameter
pairPrimFunParametersNoApplyplain function call
multiple-value-call-form
N/A
pairPrimFunParametersApplyapply
multiple-value-apply-form
N/A
pairClosureParametersNoApplyNoRestplain function call
multiple-value-call-form
no
pairClosureParametersNoApplyRestplain function call
multiple-value-call-form
yes
pairClosureParametersApplyNoRestapply
multiple-value-apply-form
no
pairClosureParametersApplyRestapply
multiple-value-apply-form
yes
+

The function pairClosureParametersApplyRest reuses conses from the last element of the spreadable sequence of objects when possible. To illustrate, let us consider the following function calls:

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

Generic Evaluator

+

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

+

Plain Recursive Evaluator

+

Continuation Passing Style Evaluator

+

Object-Oriented CPS Evaluator

+

Stack-Based Object-Oriented CPS Evaluator

+

Trampoline Evaluator

+

Trampoline++ Evaluator

+

Primitive Function Definitions (1)

+

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

+

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

+

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

+

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

+

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

+

Bounce

+

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

+

Evaluation Request

+

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

+

Result

+

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

+

EVLObjects

+

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

+

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

+

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

+

EVLObject

+

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

+

The method primaryValue returns the single object.

+

The method allValues returns an array containing the single object.

+

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

+

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

+

EVLVoid

+

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

+

EVLBoolean

+

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

+

EVLNumber

+

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

+

EVLCharacter

+

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

+

EVLString

+

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

+

EVLSymbol

+

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

+

EVLKeyword

+

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

+

EVLVariable

+

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

+

EVLList

+

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

+

EVLEmptyList

+

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

+

EVLCons

+

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

+

EVLVector

+

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

+

EVLFunction

+

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

+

EVLPrimitiveFunction

+

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

+

EVLClosure

+

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

+

Miscellaneous Primitive Functions

+

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

+

Primitive Function Definitions (2)

+

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

+

Interface (Command Line)

+

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

+

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

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

The options and arguments have the following meanings:

+
    +
  • --plainrec: selects the plain recursive evaluator
  • +
  • --cps: selects the continuation passing style evaluator
  • +
  • --oocps: selects the object-oriented CPS evaluator
  • +
  • --sboocps: selects the stack-based object-oriented CPS evaluator
  • +
  • --trampoline: selects the trampoline evaluator
  • +
  • --trampolinepp: selects the trampoline++ evaluator (DEFAULT)
  • +
  • -l $\metavar{file}$: loads the EVL file
  • +
  • -e $\metavar{form}$: evaluates the form
  • +
+

The syntax of the command line to control the EVL to XML converter is as follows:

+
node core.js --convert $\metavar{file}$
+

The options and arguments have the following meanings:

+
    +
  • --convert $\metavar{file}$: converts the EVL file to XML
  • +
diff --git a/system-files/LICENSE b/system-files/LICENSE index e6942e6..118572b 100644 --- a/system-files/LICENSE +++ b/system-files/LICENSE @@ -4,6 +4,7 @@ + License diff --git a/system-files/REFERENCE-MANUAL b/system-files/REFERENCE-MANUAL index bed00c3..71ab8ce 100644 --- a/system-files/REFERENCE-MANUAL +++ b/system-files/REFERENCE-MANUAL @@ -4,6 +4,7 @@ + Reference Manual @@ -37,14 +38,14 @@

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

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

-

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

+

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

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

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

EVLambda has three levels of syntax:

  • The token level contains the rules used by the tokenizer to convert a sequence of characters into a sequence of tokens.
  • The object level contains the rules used by the parser to convert a sequence of tokens into a sequence of objects.
  • -
  • The form level contains the rules used by the syntax analyzer to classify forms.
  • +
  • The form level contains the rules used by the form analyzer to analyze forms.

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

EVLambda Source Files

@@ -206,7 +207,7 @@

References to named regular expressions can be used wherever regular expressions can be used. References to named regular expressions denoting classes of characters can also be used inside character classes. Circular definitions are not allowed.

Except for the parts referencing named regular expressions, regular expressions are typeset in a monospaced typeface. Spaces can be added freely outside literal strings and character classes without modifying the meaning of a regular expression.

Extended Backus-Naur Form (EBNF)

-

The parser and the syntax analyzer use a variant of the extended Backus-Naur form (EBNF) notation to define various context-free grammars.

+

The parser and the form analyzer use a variant of the extended Backus-Naur form (EBNF) notation to define various context-free grammars.

The following table summarizes the syntax of the variant of the EBNF notation used in this document:

@@ -644,7 +645,7 @@
  • Define a grammar $G$ whose sentences are also sentences of the pattern language.
  • Declare that the objects belonging to $S$ are the objects matching at least one sentence of $G$.
  • -

    The language specified by a grammar such as $G$ is called a template language. Template languages will be used to specify the forms recognized by the syntax analyzer, to specify template macro calls, to specify data structures, …

    +

    The language specified by a grammar such as $G$ is called a template language. Template languages will be used to specify the forms recognized by the form analyzer, to specify template macro calls, to specify data structures, …

    Here are the terminal symbols of the context-free grammar specifying the pattern language:

    • The names of the types: $\object$, $\void$, $\boolean$, $\number$, $\character$, $\string$, $symbol$, $\keyword$, $\variable$, $\list$, $\emptylist$, $\cons$, $\vector$, $\function$, $\primitivefunction$, and $\closure$.
    • @@ -793,8 +794,8 @@

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

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

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

      -

      Syntax Analyzer

      -

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

      +

      Form Analyzer

      +

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

    SyntaxMeaning
    @@ -1030,13 +1031,13 @@
    $\metavar{form}$

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

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

    -

    Classifying a form is the first step in evaluating a form. If the classification fails, then the evaluation fails.

    -

    Some syntax analyzers classify forms without checking that the components that must be forms are actually forms. Syntax analyzers associated with interpreters usually function this way. With this kind of syntax analyzer, the components that must be forms are classified if and when they are evaluated.

    +

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

    +

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

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

    -

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

    +

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

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

    Documentation Generator

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

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

    +

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

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

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

    diff --git a/system-files/TUTORIAL b/system-files/TUTORIAL index fab0c2e..b514db7 100644 --- a/system-files/TUTORIAL +++ b/system-files/TUTORIAL @@ -4,6 +4,7 @@ + Tutorial @@ -37,16 +38,16 @@
    > (not #t)
    #f

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

    > (and #t)
    #t

    > (and #f)
    #f

    > (and #t #t)
    #t

    > (and #t #f)
    #f

    > (and #f #t)
    #f

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

    > (or #t)
    #t

    > (or #f)
    #f

    > (or #t #t)
    #t

    > (or #t #f)
    #t

    > (or #f #t)
    #t

    > (or #f #f)
    #f

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

    -

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

    +

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

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

    • (and $\metavarn{test-form}{1}$ $\metavarn{test-form}{2}$ $\metavarn{test-form}{3}$) expands into
      (if $\metavarn{test-form}{1}$ (if $\metavarn{test-form}{2}$ (if $\metavarn{test-form}{3}$ #t #f) #f) #f)
    • @@ -62,7 +63,7 @@
      > (+)
      0

      > (+ 1)
      1

      > (+ 1 2)
      3

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

      > (- 1)
      -1

      > (- 0 1)
      -1

      > (- 0 1 2)
      -3

      > (- 0 1 2 3)
      -6
      @@ -72,7 +73,7 @@
      > (*)
      1

      > (* 2)
      2

      > (* 2 4)
      8

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

      > (/ 2)
      0.5

      > (/ 1 2)
      0.5

      > (/ 1 2 4)
      0.125

      > (/ 1 2 4 8)
      0.015625

      Comparison Operators

      @@ -206,12 +207,12 @@
    • The form (+ 1 2) is evaluated with respect to $[]$ and $[]$.
      • -
      • The form (+ 1 2) is classified as a plain function call.
      • +
      • The form (+ 1 2) is analyzed as a plain function call.
      • The variable + is treated as an abbreviation for (fref +).
      • The form (fref +) is evaluated with respect to $[]$ and $[]$.
        • -
        • The form (fref +) is classified as an fref-form.
        • +
        • The form (fref +) is analyzed as an fref-form.
        • The variable + is looked up.
      • @@ -219,14 +220,14 @@
      • The form 1 is evaluated with respect to $[]$ and $[]$.
        • -
        • The form 1 is classified as a self-evaluating object.
        • +
        • The form 1 is analyzed as a self-evaluating object.
      • The form 1 evaluates to the number 1.
      • The form 2 is evaluated with respect to $[]$ and $[]$.
        • -
        • The form 2 is classified as a self-evaluating object.
        • +
        • The form 2 is analyzed as a self-evaluating object.
      • The form 2 evaluates to the number 2.
      • @@ -238,12 +239,12 @@
      • The form (_+ x y) is evaluated with respect to $[\vbinding{x}{1},\vbinding{y}{2}]$ and $[]$.
        • -
        • The form (_+ x y) is classified as a plain function call.
        • +
        • The form (_+ x y) is analyzed as a plain function call.
        • The variable _+ is treated as an abbreviation for (fref _+).
        • The form (fref _+) is evaluated with respect to $[\vbinding{x}{1},\vbinding{y}{2}]$ and $[]$.
          • -
          • The form (fref _+) is classified as an fref-form.
          • +
          • The form (fref _+) is analyzed as an fref-form.
          • The variable _+ is looked up.
        • @@ -252,7 +253,7 @@
        • The form (vref x) is evaluated with respect to $[\vbinding{x}{1},\vbinding{y}{2}]$ and $[]$.
          • -
          • The form (vref x) is classified as a vref-form.
          • +
          • The form (vref x) is analyzed as a vref-form.
          • The variable x is looked up.
        • @@ -261,7 +262,7 @@
        • The form (vref y) is evaluated with respect to $[\vbinding{x}{1},\vbinding{y}{2}]$ and $[]$.
          • -
          • The form (vref y) is classified as a vref-form.
          • +
          • The form (vref y) is analyzed as a vref-form.
          • The variable y is looked up.
        • @@ -299,13 +300,13 @@
        • The form (simple-vlet x 1 (+ x 2)) is evaluated with respect to $[]$ and $[]$.
          • -
          • Because the variable simple-vlet names a macro according to the lookup rule used by fref, the form (simple-vlet x 1 (+ x 2)) is classified as a macro call.
          • +
          • Because the variable simple-vlet names a macro according to the lookup rule used by fref, the form (simple-vlet x 1 (+ x 2)) is analyzed as a macro call.
          • The global macro simple-vlet is invoked on the variable x, the number 1, and the list (+ x 2).
          • The global macro simple-vlet returns the list ((_vlambda (x) (+ x 2)) 1).
          • The form ((_vlambda (x) (+ x 2)) 1) is evaluated with respect to $[]$ and $[]$.
            • -
            • The form ((_vlambda (x) (+ x 2)) 1) is classified as a plain function call.
            • +
            • The form ((_vlambda (x) (+ x 2)) 1) is analyzed as a plain function call.
            • The form (_vlambda (x) (+ x 2)) is evaluated with respect to $[]$ and $[]$.
            • The form (_vlambda (x) (+ x 2)) evaluates to a closure recording the following two pieces of information: the _vlambda-form (_vlambda (x) (+ x 2)) and the lexical environment $[]$.
            • The form 1 is evaluated with respect to $[]$ and $[]$.
            • @@ -333,13 +334,13 @@
            • The form (simple-vlet x 1 (simple-vlet y 2 (+ x y))) is evaluated with respect to $[]$ and $[]$.
              • -
              • Because the variable simple-vlet names a macro according to the lookup rule used by fref, the form (simple-vlet x 1 (simple-vlet y 2 (+ x y))) is classified as a macro call.
              • +
              • Because the variable simple-vlet names a macro according to the lookup rule used by fref, the form (simple-vlet x 1 (simple-vlet y 2 (+ x y))) is analyzed as a macro call.
              • The global macro simple-vlet is invoked on the variable x, the number 1, and the list (simple-vlet y 2 (+ x y)).
              • The global macro simple-vlet returns the list ((_vlambda (x) (simple-vlet y 2 (+ x y))) 1).
              • The form ((_vlambda (x) (simple-vlet y 2 (+ x y))) 1) is evaluated with respect to $[]$ and $[]$.
                • -
                • The form ((_vlambda (x) (simple-vlet y 2 (+ x y))) 1) is classified as a plain function call.
                • +
                • The form ((_vlambda (x) (simple-vlet y 2 (+ x y))) 1) is analyzed as a plain function call.
                • The form (_vlambda (x) (simple-vlet y 2 (+ x y))) is evaluated with respect to $[]$ and $[]$.
                • The form (_vlambda (x) (simple-vlet y 2 (+ x y))) evaluates to a closure recording the following two pieces of information: the _vlambda-form (_vlambda (x) (simple-vlet y 2 (+ x y))) and the lexical environment $[]$.
                • The form 1 is evaluated with respect to $[]$ and $[]$.
                • @@ -351,13 +352,13 @@
                • The form (simple-vlet y 2 (+ x y)) is evaluated with respect to $[\vbinding{x}{1}]$ and $[]$.
                  • -
                  • Because the variable simple-vlet names a macro according to the lookup rule used by fref, the form (simple-vlet y 2 (+ x y)) is classified as a macro call.
                  • +
                  • Because the variable simple-vlet names a macro according to the lookup rule used by fref, the form (simple-vlet y 2 (+ x y)) is analyzed as a macro call.
                  • The global macro simple-vlet is invoked on the variable y, the number 2, and the list (+ x y).
                  • The global macro simple-vlet returns the list ((_vlambda (y) (+ x y)) 2).
                  • The form ((_vlambda (y) (+ x y)) 2) is evaluated with respect to $[\vbinding{x}{1}]$ and $[]$.
                    • -
                    • The form ((_vlambda (y) (+ x y)) 2) is classified as a plain function call.
                    • +
                    • The form ((_vlambda (y) (+ x y)) 2) is analyzed as a plain function call.
                    • The form (_vlambda (y) (+ x y)) is evaluated with respect to $[\vbinding{x}{1}]$ and $[]$.
                    • The form (_vlambda (y) (+ x y)) evaluates to a closure recording the following two pieces of information: the _vlambda-form (_vlambda (y) (+ x y)) and the lexical environment $[\vbinding{x}{1}]$.
                    • The form 2 is evaluated with respect to $[\vbinding{x}{1}]$ and $[]$.
                    • @@ -404,7 +405,7 @@
                    • The form (if (>= x 0) x (- x))) is evaluated with respect to $[\vbinding{x}{1}]$ and $[]$.
                      • -
                      • The form (if (>= x 0) x (- x))) is classified as an if-form.
                      • +
                      • The form (if (>= x 0) x (- x))) is analyzed as an if-form.
                      • The form (>= x 0) is evaluated with respect to $[\vbinding{x}{1}]$ and $[]$.
                        • @@ -439,7 +440,7 @@
                        • The form (if (>= x 0) x (- x))) is evaluated with respect to $[\vbinding{x}{-1}]$ and $[]$.
                          • -
                          • The form (if (>= x 0) x (- x))) is classified as an if-form.
                          • +
                          • The form (if (>= x 0) x (- x))) is analyzed as an if-form.
                          • The form (>= x 0) is evaluated with respect to $[\vbinding{x}{-1}]$ and $[]$.
                            • diff --git a/system-files/USER-MANUAL b/system-files/USER-MANUAL index 7df7704..fdae29d 100644 --- a/system-files/USER-MANUAL +++ b/system-files/USER-MANUAL @@ -4,6 +4,7 @@ + User Manual @@ -323,10 +324,10 @@
                              • The prompt is the greater-than sign printed at the beginning of a line to inform you that the listener buffer is waiting for a form to be typed in.
                              • If you press the Return or Enter key when (1) the cursor is not at the very end of the buffer or (2) the form is missing or incomplete, then a newline is simply inserted into the buffer and no evaluation takes place.
                              • -
                              • If the evaluation completes abruptly, then the reason is necessarily of type error and its payload (a message describing the error) is printed in place of the printable representations of the (nonexisting) resulting values.
                              • +
                              • If the evaluation completes abruptly, then the reason is necessarily of type error and its payload (an object of type string describing the error) is printed in place of the printable representations of the (nonexisting) resulting values.
                              • If the evaluation does not complete, then you must abort the evaluation or restart the evaluator in order to get a new prompt.
                              -

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

                              +

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

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

                              (car $\cons$)
                              @@ -475,8 +476,8 @@
                              • /system/USER-MANUAL: the user manual (this file)
                              • /system/TUTORIAL: the tutorial
                              • - - +
                              • /system/REFERENCE-MANUAL: the reference manual
                              • +
                              • /system/IMPLEMENTATION-NOTES: the implementation notes
                              • /system/BIBLIOGRAPHY: the bibliography
                              • /system/LICENSE: the license
                              • /system/all-caps.css: the CSS file styling the all-caps files
                              • @@ -485,7 +486,7 @@
                              • /system/evl2html.xslt: the XSLT file used to convert the EVLambda source files to HTML
                              • /system/evl2html.css: the CSS file styling the EVLambda source files converted to HTML
                              • /system/evl2html.js: the JavaScript file loaded by the EVLambda source files converted to HTML
                              • -
                              • /system/mantle.evl: the EVLambda source file implementing the nonprimitive data types, the nonprimitive functions, and the macros constituting the “mantle” of the EVLambda programming language
                              • +
                              • /system/mantle.evl: the EVLambda source file implementing the nonprimitive data types, the nonprimitive functions, the macros, etc., constituting the “mantle” of the EVLambda programming language
                              • Listener 1: the initial listener

                              Help Menu

                              diff --git a/system-files/all-caps.css b/system-files/all-caps.css index e63b786..818800b 100644 --- a/system-files/all-caps.css +++ b/system-files/all-caps.css @@ -7,6 +7,12 @@ html { line-height: 1.4; } +@media print { + html { + font-size: 12pt; + } +} + div.preamble { display: none; } diff --git a/system-files/core.js b/system-files/core.js index bda2c3a..1c1bcf4 100644 --- a/system-files/core.js +++ b/system-files/core.js @@ -1,25 +1,31 @@ // 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; @@ -33,9 +39,11 @@ if (typeof onmessage !== 'undefined') { // web worker 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}); @@ -47,23 +55,22 @@ function foundNoForm() { 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); @@ -73,7 +80,7 @@ function initialize(input) { try { object = read(tokenizer); } catch(exception) { - return completedAbnormally(exception); + return abortedOrError(exception); } if (object === null) { break; @@ -81,26 +88,28 @@ function initialize(input) { 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) { @@ -110,15 +119,17 @@ function evaluateFirstForm(text) { 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); @@ -127,7 +138,7 @@ function evaluateAllForms(text) { try { object = read(tokenizer); } catch(exception) { - return completedAbnormally(exception); + return abortedOrError(exception); } if (object === null) { break; @@ -135,24 +146,23 @@ function evaluateAllForms(text) { 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); } /**********/ @@ -187,17 +197,17 @@ class ReaderError extends Error { } } -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'; } } @@ -219,7 +229,7 @@ class TruncatedToken extends TokenizerError { } } -// token types +// token categories const QUOTE = 0; const QUASIQUOTE = 1; const UNQUOTE = 2; @@ -233,15 +243,16 @@ const HASH_MINUS = 9; 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]+)?$/; @@ -262,15 +273,17 @@ function isSurrogate(codeUnit) { 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) { @@ -281,7 +294,27 @@ function isNoncharacter(charOrCodePoint) { function isWhitespaceCharacter(charOrCodePoint) { // https://www.unicode.org/Public/UCD/latest/ucd/PropList.txt - // Pattern_White_Space + // Whitespace = + // 0009..000D .. + // 0020 SPACE + // 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 @@ -322,15 +355,15 @@ function isDecimalDigit(charOrCodePoint) { } 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.'); @@ -362,20 +395,20 @@ class Tokenizer { 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); } @@ -383,7 +416,7 @@ class Tokenizer { } } 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; @@ -401,39 +434,39 @@ class Tokenizer { 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); @@ -450,15 +483,15 @@ class Tokenizer { 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.'); @@ -473,7 +506,7 @@ function escapeCharacters(chars, escapeCharacter) { 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); @@ -518,6 +551,7 @@ function unicodeEscape(charOrCodePoint) { } function readEscapeSequence(tokenizer) { + // reads {xyz}, returns xyz let chars = ''; if (tokenizer.position === tokenizer.text.length) { throw new TruncatedToken('Truncated escape sequence.'); @@ -635,41 +669,41 @@ function readHashConstruct(tokenizer) { } 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); } @@ -735,7 +769,7 @@ function readXMLMarkup(tokenizer, consume) { 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.'); @@ -744,14 +778,14 @@ function readXMLMarkup(tokenizer, consume) { 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; } } @@ -842,10 +876,10 @@ class UnexpectedXMLEndTag extends ReaderError { } } -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'; } } @@ -858,7 +892,7 @@ function read(tokenizer) { throw new UnexpectedClosingParenthesis(); case XML_END_TAG: throw new UnexpectedXMLEndTag(); - case EOF: + case EOI: return null; default: return object; @@ -866,11 +900,11 @@ function read(tokenizer) { } 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: @@ -920,8 +954,8 @@ function readObject(tokenizer) { break; // skip case XML_COMMENT: break; // skip - case EOF: - return EOF; + case EOI: + return EOI; default: throw new CannotHappen('readObject'); } @@ -937,23 +971,24 @@ function readAbbreviation(tokenizer, variable) { 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: @@ -962,14 +997,14 @@ function readReadTimeConditionalFeatureExpression(tokenizer) { 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: @@ -978,8 +1013,8 @@ function readReadTimeConditionalObject(tokenizer) { throw new UnexpectedClosingParenthesis(); case XML_END_TAG: throw new UnexpectedXMLEndTag(); - case EOF: - throw new UnexpectedEndOfFile(); + case EOI: + throw new UnexpectedEndOfInput(); default: return object; } @@ -1004,6 +1039,21 @@ function evaluateFeatureExpression(featureExpression) { } } +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) { @@ -1072,8 +1122,8 @@ function readList(tokenizer) { 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) { @@ -1100,8 +1150,8 @@ function readDottedList(tokenizer, list, lastCons) { 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 @@ -1114,8 +1164,8 @@ function readDottedList(tokenizer, list, lastCons) { 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.'); } @@ -1132,8 +1182,8 @@ function readVector(tokenizer) { break loop; case XML_END_TAG: throw new UnexpectedXMLEndTag(); - case EOF: - throw new UnexpectedEndOfFile(); + case EOI: + throw new UnexpectedEndOfInput(); default: elements.push(object); break; @@ -1158,8 +1208,8 @@ function readXMLElement(tokenizer) { } 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) { @@ -1174,70 +1224,39 @@ function readXMLElement(tokenizer) { /* 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 EOF - // ^^^ ^^^ ^^^ - xml += tokenizer.whitespace; // whitespace is written as is - } else if ([ABBREVIATION, RTC1, RTC2, SEQUENCE].includes(context)) { - // ' - // ^^^ ^^^ ^^^ - // #+ - // ^^^ ^^^ ^^^ - // #+ - // ^^^ ^^^ ^^^ - // ( ) - // ^^^ ^^^ ^^^ - xml += convertEVL(previousAbstractToken, tokenizer.whitespace, abstractToken); // whitespace is converted by convertEVL - } else if (['chapter', 'section'].includes(context)) { - // - // ^^^ ^^^ ^^^ - //
                              - // ^^^ ^^^ ^^^ - xml += convertXML(previousAbstractToken, tokenizer.whitespace, abstractToken); // whitespace is converted by convertXML - } else { - // - // ^^^ ^^^ ^^^ - 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][2]([3]xxx[4])[5][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) { @@ -1251,147 +1270,111 @@ function xmlEscape(string) { }); } -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 += ''; - } else if (isEVLAbstractToken(previousAbstractToken) && isEVLAbstractToken(abstractToken)) { + } else if (isEVLToken(previousToken) && isEVLToken(token)) { if (countNewlines(whitespace) >= 2) { xml += ''; xml += whitespace; @@ -1399,7 +1382,7 @@ function convertXML(previousAbstractToken, whitespace, abstractToken) { } else { xml += whitespace; } - } else if (isEVLAbstractToken(previousAbstractToken) && isXMLAbstractToken(abstractToken)) { + } else if (isEVLToken(previousToken) && isXMLToken(token)) { xml += ''; xml += whitespace; } else { @@ -1418,14 +1401,14 @@ function countNewlines(string) { 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 += ''; xml += whitespace; - } else if (isXMLAbstractToken(previousAbstractToken) && isEVLAbstractToken(abstractToken)) { + } else if (isXMLToken(previousToken) && isEVLToken(token)) { xml += ''; xml += whitespace; } else { @@ -1453,19 +1436,19 @@ function countSpacesAfterFirstNewline(string) { 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); } } @@ -1473,7 +1456,15 @@ function checkEmptyList(object, 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); } } @@ -1483,7 +1474,7 @@ function checkProperList(object, formName) { if (list instanceof EVLCons) { list = list.cdr; } else { - syntaxAnalyzerError(formName); + formAnalyzerError(formName); } } return object; @@ -1493,48 +1484,40 @@ function checkParameterList(object, formName) { 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) { @@ -1558,9 +1541,9 @@ function analyzeIf(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) { @@ -1600,22 +1583,22 @@ function analyzeCatchErrors(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 */ @@ -1643,7 +1626,7 @@ class GlobalEnv { if (value !== null) { return value; } else { - throw new UnboundVariable(variable, 'VALUE'); + throw new UnboundVariable(variable, 'value'); } } case FUN_NS: { @@ -1651,7 +1634,7 @@ class GlobalEnv { if (value !== null) { return value; } else { - throw new UnboundVariable(variable, 'FUNCTION'); + throw new UnboundVariable(variable, 'function'); } } default: @@ -1685,14 +1668,14 @@ class GlobalEnv { } } -/*********************/ -/* Local Environment */ -/*********************/ +/************************************/ +/* Lexical and Dynamic Environments */ +/************************************/ -class LocalEnv { // abstract class +class DefiniteEnv { // abstract class } -class NullLocalEnv extends LocalEnv { +class NullDefiniteEnv extends DefiniteEnv { constructor() { super(); } @@ -1708,9 +1691,9 @@ class NullLocalEnv extends LocalEnv { } } -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; @@ -1751,9 +1734,9 @@ class Frame extends LocalEnv { } } -/**********************************/ -/* Mapping Arguments to Variables */ -/**********************************/ +/*************************************/ +/* Pairing Parameters with Arguments */ +/*************************************/ class TooFewArguments extends EvaluatorError { constructor() { @@ -1769,96 +1752,104 @@ class TooManyArguments extends EvaluatorError { } } -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 { @@ -1872,20 +1863,20 @@ function mapClosureArgsForVariableArityCall(args, vars) { 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 { @@ -1893,12 +1884,12 @@ function mapClosureArgsForFixedArityApply(args, vars) { } } 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 { @@ -1906,24 +1897,24 @@ function mapClosureArgsForFixedArityApply(args, vars) { } 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 { @@ -1938,12 +1929,12 @@ function mapClosureArgsForVariableArityApply(args, vars) { } } 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 { @@ -1956,13 +1947,13 @@ function mapClosureArgsForVariableArityApply(args, vars) { } 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; } @@ -1999,7 +1990,7 @@ function genericEval(form) { } function emptyListError() { - throw new EvaluatorError('The empty list is not a form.'); + throw new EvaluatorError('The empty list does not evaluate.'); } function ifTestFormError() { @@ -2007,7 +1998,7 @@ function ifTestFormError() { } function forEachNotImplemented() { - throw new EvaluatorError('_for-each is not implemented.'); + throw new EvaluatorError('The _for-each-form is not implemented.'); } function forEachFunctionFormError() { @@ -2018,7 +2009,7 @@ function forEachListFormError() { 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.'); } @@ -2027,7 +2018,7 @@ function applicationOperatorFormError() { /*****************************/ function plainrecEval(form) { - return plainrecEvalForm(form, nullLocalEnv, nullLocalEnv); + return plainrecEvalForm(form, nullDefiniteEnv, nullDefiniteEnv); } function plainrecEvalForm(form, lenv, denv) { @@ -2066,13 +2057,13 @@ 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); @@ -2082,8 +2073,8 @@ function plainrecEvalForm(form, lenv, denv) { } function plainrecEvalQuote(form, lenv, denv) { - const [object] = analyzeQuote(form); - return object; + const [literal] = analyzeQuote(form); + return literal; } function plainrecEvalProgn(form, lenv, denv) { @@ -2116,8 +2107,8 @@ function plainrecEvalIf(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) { @@ -2155,8 +2146,8 @@ function plainrecEvalCatchErrors(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); @@ -2192,13 +2183,13 @@ function plainrecEvalOperands(mv, macro, operands, args, 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); @@ -2206,13 +2197,13 @@ function plainrecInvokeFun(apply, macro, fn, args, 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(); } } @@ -2221,7 +2212,7 @@ function plainrecInvokeFun(apply, macro, fn, args, lenv, denv) { /****************************************/ function cpsEval(form) { - return cpsEvalForm(form, nullLocalEnv, nullLocalEnv, cpsEndCont); + return cpsEvalForm(form, nullDefiniteEnv, nullDefiniteEnv, cpsEndCont); } function cpsEvalForm(form, lenv, denv, k) { @@ -2260,13 +2251,13 @@ 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)); @@ -2278,8 +2269,8 @@ function cpsEvalForm(form, lenv, denv, k) { 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) { @@ -2321,8 +2312,8 @@ function cpsEvalIf(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) { @@ -2393,8 +2384,8 @@ function cpsEvalCatchErrors(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 @@ -2443,13 +2434,13 @@ function cpsEvalOperands(mv, macro, operands, args, lenv, denv, k) { 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); @@ -2457,13 +2448,13 @@ function cpsInvokeFun(apply, macro, fn, args, 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(); } } @@ -2472,7 +2463,7 @@ function cpsInvokeFun(apply, macro, fn, args, lenv, denv, k) { /*********************************/ function oocpsEval(form) { - return oocpsEvalForm(form, nullLocalEnv, nullLocalEnv, oocpsEndCont); + return oocpsEvalForm(form, nullDefiniteEnv, nullDefiniteEnv, oocpsEndCont); } function oocpsEvalForm(form, lenv, denv, k) { @@ -2511,13 +2502,13 @@ 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)); @@ -2546,8 +2537,8 @@ class OOCPSEndCont extends OOCPSCont { 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) { @@ -2608,8 +2599,8 @@ class OOCPSIfTestFormCont extends OOCPSCont { } 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) { @@ -2709,8 +2700,8 @@ function oocpsEvalCatchErrors(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) @@ -2794,13 +2785,13 @@ class OOCPSOperandsCont extends OOCPSCont { 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); @@ -2808,13 +2799,13 @@ function oocpsInvokeFun(apply, macro, fn, args, 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(); } } @@ -2825,7 +2816,7 @@ function oocpsInvokeFun(apply, macro, fn, args, lenv, denv, k) { function sboocpsEval(form) { const kStack = new SBOOCPSControlStack(); kStack.push(sboocpsEndCont); - return sboocpsEvalForm(form, nullLocalEnv, kStack); + return sboocpsEvalForm(form, nullDefiniteEnv, kStack); } class SBOOCPSControlStack { @@ -2913,13 +2904,13 @@ function sboocpsEvalForm(form, lenv, kStack) { 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)); @@ -2947,8 +2938,8 @@ class SBOOCPSEndCont extends SBOOCPSCont { 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) { @@ -3005,8 +2996,8 @@ class SBOOCPSIfTestFormCont extends SBOOCPSCont { } 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) { @@ -3104,8 +3095,8 @@ function sboocpsEvalCatchErrors(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); } @@ -3183,13 +3174,13 @@ class SBOOCPSOperandsCont extends SBOOCPSCont { 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(); @@ -3198,13 +3189,13 @@ function sboocpsInvokeFun(apply, macro, fn, args, lenv, kStack) { 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(); } } @@ -3215,9 +3206,9 @@ function sboocpsInvokeFun(apply, macro, fn, args, lenv, kStack) { 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) { @@ -3328,13 +3319,13 @@ function trampolineEvalForm(form, lenv, kStack) { 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); @@ -3359,8 +3350,8 @@ class TrampolineEndCont extends TrampolineCont { 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) { @@ -3417,8 +3408,8 @@ class TrampolineIfTestFormCont extends TrampolineCont { } 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) { @@ -3480,8 +3471,8 @@ class TrampolineCatchErrorsTryFormCont extends TrampolineCont { } } -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); } @@ -3559,25 +3550,25 @@ class TrampolineOperandsCont extends TrampolineCont { 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(); } } @@ -3598,14 +3589,14 @@ class TrampolineMacroCont extends TrampolineCont { 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) { @@ -3716,13 +3707,13 @@ function trampolineppPreprocessForm(form, lenv) { 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); @@ -3761,18 +3752,18 @@ class TrampolineppForm { // abstract class } 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; } } @@ -3858,16 +3849,16 @@ class TrampolineppIfTestFormCont extends TrampolineppCont { } 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'); @@ -3875,18 +3866,18 @@ function trampolineppPreprocessLambda(scope, namespace, macro, form, lenv) { } 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); } } @@ -4174,32 +4165,32 @@ class TrampolineppCatchErrorsTryFormCont extends TrampolineppCont { } } -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); } } @@ -4215,7 +4206,7 @@ function isMacroLet(operator, operands) { return true; } -class TrampolineppApplication extends TrampolineppForm { +class TrampolineppCall extends TrampolineppForm { constructor(mv, apply, operator, operands) { super(); this.mv = mv; @@ -4290,28 +4281,28 @@ class TrampolineppOperandsCont extends TrampolineppCont { 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(); @@ -4425,6 +4416,7 @@ class EVLVoid extends EVLObject { } } +// the only object of type void EVLVoid.VOID = new EVLVoid(); function nullToVoid(x) { @@ -4442,14 +4434,16 @@ primitiveFunction('void?', 1, 1, function(args) { 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) { @@ -4467,7 +4461,7 @@ primitiveFunction('boolean?', 1, 1, function(args) { class EVLNumber extends EVLObject { constructor(jsValue) { super(); - this.jsValue = jsValue; // javascript number + this.jsValue = jsValue; // JavaScript number } eql(that) { if (that instanceof EVLNumber) { @@ -4558,7 +4552,7 @@ primitiveFunction('>=', 2, 2, function(args) { 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) { @@ -4583,7 +4577,7 @@ primitiveFunction('character?', 1, 1, function(args) { 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) { @@ -4608,7 +4602,7 @@ primitiveFunction('string?', 1, 1, function(args) { 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 } } @@ -4655,8 +4649,8 @@ primitiveFunction('make-keyword', 1, 1, function(args) { 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); @@ -4684,7 +4678,7 @@ const prognVariable = internVariable('progn'); 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'); @@ -4794,6 +4788,7 @@ class EVLEmptyList extends EVLList { } } +// the only object of type empty-list EVLEmptyList.NIL = new EVLEmptyList(); primitiveFunction('empty-list?', 1, 1, function(args) { @@ -4847,7 +4842,7 @@ primitiveFunction('set-cdr!', 2, 2, 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 = ''; @@ -4893,7 +4888,7 @@ class EVLPrimitiveFunction extends EVLFunction { super(); this.arityMin = arityMin; this.arityMax = arityMax; - this.jsFunction = jsFunction; // javascript function + this.jsFunction = jsFunction; // JavaScript function } toString() { return '#'; @@ -4909,13 +4904,13 @@ primitiveFunction('primitive-function?', 1, 1, function(args) { /**************/ 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; } @@ -4928,9 +4923,9 @@ primitiveFunction('closure?', 1, 1, function(args) { return evlBoolean(args[0] instanceof EVLClosure); }); -/*****************************/ -/* Other Primitive Functions */ -/*****************************/ +/*************************************/ +/* Miscellaneous Primitive Functions */ +/*************************************/ primitiveFunction('values', 0, null, function(args) { return new EVLObjects(args); @@ -4945,29 +4940,40 @@ primitiveFunction('now', 0, 0, function(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(); } @@ -4975,13 +4981,24 @@ if (typeof onmessage === 'undefined') { // node 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(); } @@ -4990,16 +5007,25 @@ if (typeof onmessage === 'undefined') { // node } function usage() { - console.log('usage: -l to load a file, -e 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 : loads the EVL file'); + console.log('-e : evaluates the form'); + console.log('--convert : 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(); } diff --git a/system-files/docgen-sample.evl b/system-files/docgen-sample.evl new file mode 100644 index 0000000..dabe2f8 --- /dev/null +++ b/system-files/docgen-sample.evl @@ -0,0 +1,37 @@ + +Recursive Functions +...para... +...para... +
                              +Factorial Function +...para... +...para... +(fdef fact (n) + ...block... + ...block... + (if (= n 0) + 1 ...eol... + (* n (fact (- n 1))))) ...eoll... + +(test 1 (fact 0)) +(test 120 (fact 5)) +(test 3628800 (fact 10)) +
                              +
                              +Fibonacci Sequence +...para... +...para... +(fdef fib (n) + ...block... + ...block... + (if (= n 0) + 0 ...eol... + (if (= n 1) + 1 ...eol... + (+ (fib (- n 1)) (fib (- n 2)))))) ...eoll... + +(test 0 (fib 0)) +(test 5 (fib 5)) +(test 55 (fib 10)) +
                              +