The goal of the EVLambda project is to write from scratch a custom Lisp dialect and to use the resulting programming language to explore various science topics. Along the way, the programming language will be fine-tuned and complemented by libraries also written from scratch whenever possible.
Please note that providing a stable programming language useful outside of the project is a non-goal. Backward compatibility will not be a priority and the decision to include a feature or an optimization will be based primarily on its usefulness to the project.
This section provides an overview of the programming language. Some of the concepts introduced in this section will be illustrated in the section Listener Buffers. An introduction to writing programs can be found in the tutorial. A detailed account of the programming language can be found in the reference manual.
The programming language, which is called EVLambda like the project, is heavily inspired by the programming languages Scheme and Common Lisp. The bibliography contains many references covering and/or using those two programming languages.
In EVLambda, there is no difference of nature between code and data. Both code and data are represented by objects and it is the context of its occurrence that determines if an object must be treated as code or as data. The word “object” is used here in the broad sense of data structure without any reference to object-oriented programming.
As we will see later in this section, objects are patterns of bits inside the computer's memory. To facilitate communicating about objects, reading and writing code and data, etc., objects have associated sequences of characters that can be used to represent them.
Most objects have at least one readable representation. A readable representation of an object is a sequence of characters that can be used to represent the object in input operations. The reader is the component of the programming language responsible for converting a readable representation into the corresponding object.
All objects have exactly one printable representation. The printable representation of an object is a sequence of characters that can be used to represent the object in output operations. When an object has exactly one readable representation, its printable representation is identical to its readable representation. When an object has more than one readable representation, its printable representation is identical to one of its readable representations chosen to be the standard way to represent the object. When an object has no readable representations, its printable representation is a sequence of characters revealing its type. The printer is the component of the programming language responsible for converting an object into its printable representation.
Objects are organized into classes called types and types are organized into a hierarchy of types. The type at the top of the hierarchy is called the root type. The types at the bottom of the hierarchy are called the leaf types. Any object belongs to exactly one leaf type. If type $A$ is located below type $B$ in the hierarchy, then type $A$ is called a subtype of type $B$ and any object that belongs to type $A$ also belongs to type $B$. The term “data type” is sometimes used in place of the term “type” even though a (data) type is a class of objects and an object can be a piece of code or a piece of data.
Here is a tree-view representation of the hierarchy of types:
object
|-void
|-boolean
|-number
|-character
|-string
|-symbol
| |-keyword
| |-variable
|-list
| |-empty-list
| |-cons
|-vector
|-function
| |-primitive-function
| |-closure
Here is a brief description of the leaf types (readable and printable representations have a gray background):
voidvoid. Its readable representation is #v and its purpose is to represent missing or undefined objects.booleanboolean represent truth values. There are exactly two objects of type boolean: #t (representing true) and #f (representing false). An object of type boolean is often simply called a boolean when there is no risk of confusion.numbernumber represent mathematical numbers: 123, -123, 123.456, -123.456, … The representation of a mathematical number by an object of type number can be exact or approximate. An object of type number is often simply called a number when there is no risk of confusion.charactercharacter represent Unicode characters: #"a", #"b", #"c", #"你", #"好", … Most of the characters used in the world have a corresponding Unicode character. An object of type character is often simply called a character when there is no risk of confusion.stringstring represents indexed sequences of Unicode characters: "abc", "你好", … An object of type string is often simply called a string when there is no risk of confusion.keywordkeyword can, among other uses, represent named values: :red, :green, :blue, … An object of type keyword is often simply called a keyword when there is no risk of confusion.variablevariable are commonly used to name objects and other kinds of entities. For example, the names of the types are objects of type variable: object, void, … An object of type variable is often simply called a variable when there is no risk of confusion.empty-listempty-list. Its readable representation is () and its purpose is to represent empty lists of objects. The single object of type empty-list is often simply called the empty list when there is no risk of confusion.conscons are ordered pairs of objects. The first/second element of an object of type cons is called its car/cdr. An object of type cons is often simply called a cons when there is no risk of confusion. Conses are the building blocks of many data structures. In particular, conses can be chained together to represent nonempty lists of objects. A nonempty list of objects is represented by a cons whose car is the first element of the list and whose cdr is the sublist of the list obtained by omitting its first element. For example, the list (1 2 3) is represented by a chain of three conses: a first cons whose car is the number 1 and whose cdr is the second cons, a second cons whose car is the number 2 and whose cdr is the third cons, and a third cons whose car is the number 3 and whose cdr is the empty list.vectorvector represent indexed sequences of objects: #(), #(1 2 3), … An object of type vector is often simply called a vector when there is no risk of confusion.primitive-functionprimitive-function are input/output mappings implemented in a programming language other than EVLambda. Objects of type primitive-function have no readable representations and their printable representation is #<primitive-function>. An object of type primitive-function is often simply called a primitive function when there is no risk of confusion.closureclosure are input/output mappings implemented in EVLambda. Some objects of type closure are tagged as being macros. Macros are code to code mappings used to create new language constructs. Objects of type closure have no readable representations and their printable representation is #<closure>. An object of type closure is often simply called a closure when there is no risk of confusion.Each symbol has an associated sequence of (Unicode) characters called the name of the symbol. The readable representation of a keyword is its name preceded by a colon. The readable representation of a variable is its name. The reader always converts the same readable representation of a symbol into the same symbol. To that end, the reader maintains two mappings from names to symbols called packages: one package mapping the names of the previously encountered keywords to the corresponding keywords and one package mapping the names of the previously encountered variables to the corresponding variables. Adding a symbol to its package is called interning the symbol.
The printable representation of a list consists of a left parenthesis followed by the printable representations of the elements of the list separated by a single space followed by a right parenthesis. The printable representation of a list is just one of its infinitely many readable representations. In particular, other readable representations can be obtained by using arbitrary sequences of at least one whitespace character instead of single spaces to separate the elements of the list.
Although a macro is technically a function (because a macro is an object of type closure and the type closure is a subtype of the type function), the word “function” is often used to denote specifically a function other than a macro (that is, a primitive function or a closure not tagged as being a macro).
Variables name objects through the use of namespaces, bindings, environments, and lookup rules:
function and another object that is used by default in all other contexts.function and bindings labeled with the namespace name “value” are used in all other contexts. Although the constraint is not enforced by the programming language, the value of a binding labeled with the namespace name “function” should be of type function.A variable that is associated with an object through a binding belonging to namespace $X$ of environment $Y$ is said to be bound to the object in namespace $X$ of environment $Y$. A variable that is not associated with an object through a binding belonging to namespace $X$ of environment $Y$ is said to be unbound in namespace $X$ of environment $Y$.
Objects, bindings, and environments are represented by nonoverlapping patterns of bits located inside a region of the computer's memory called the heap. Each object, binding, or environment is uniquely identified by the address of the pattern of bits that represents it. Like objects, bindings, and environments, addresses are also represented by patterns of bits. A reference to an object, binding, or environment is an instance of the pattern of bits that represents the address of the pattern of bits that represents the object, binding, or environment. By abuse of language, we often confuse a reference to an object, binding, or environment with the object, binding, or environment being referenced.
The pattern of bits representing an object has two parts: one part specifying the type of the object and one part specifying which member of the type the object is.
An object, binding, or environment references another object, binding, or environment by embedding into its representation a reference to that other object, binding, or environment:
The references embedded into the representation of an object, binding, or environment can be thought of as occupying memory locations denoted by the object, binding, or environment. For example, a cons can be thought of as denoting two memory locations: one containing a reference to the car of the cons and one containing a reference to the cdr of the cons. By abuse of language, we often say that a memory location contains an object, binding, or environment when in reality the memory location contains a reference to the object, binding, or environment.
Multiple objects, bindings, and/or environments can reference a common object, binding, or environment, leading to the sharing of the common object, binding, or environment. An object, binding, or environment can reference itself directly ($X\rightarrow X$) or indirectly ($X\rightarrow Y\rightarrow\cdots\rightarrow X$), leading to the existence of a cycle.
Objects of type void, boolean, keyword, symbol, and empty-list have the following uniqueness properties:
void.boolean representing true.boolean representing false.keyword with the same name. (This property is enforced by the reader.)variable with the same name. (This property is enforced by the reader.)empty-list.Objects of type integer, character, and string do not have similar uniqueness properties:
number representing the same mathematical number.character representing the same Unicode character.string representing the same indexed sequence of Unicode characters.Objects of type void, boolean, number, character, string, keyword, symbol, empty-list, primitive-function, and closure are immutable and cannot be altered. Objects of type cons and vector, bindings, and environments are mutable and can be altered in the following ways:
As explained above, what replacing an object by another object really means is replacing a reference to an object by a reference to another object.
The life cycle of an object, binding, or environment consists of the following events: a creation (which consists of an allocation followed by an initialization) followed by any number of alterations followed by a destruction (which consists of a deallocation). Alterations are possible only if the object, binding, or environment is mutable.
The destruction of an object, binding, or environment occurs automatically if and when the object, binding, or environment becomes unreachable. The rules used to determine if an object, binding, or environment is reachable are as follows (the concepts of global environment and control stack will be introduced later in this section):
The garbage collector is the component of the programming language responsible for the automatic destruction of unreachable objects, bindings, and environments.
Objects treated as code are called forms. A form is executed by being submitted to a component of the programming language called the evaluator. (The form is said to be evaluated.) The evaluation of a form has three possible outcomes:
The primary value of a form whose evaluation has completed normally is defined as follows: If the result consists of one or more objects, then the primary value of the form is the first object. Otherwise, the primary value of the form is #v.
The abrupt completion of an evaluation has an associated reason, which has a type and carries a payload. An abrupt completion caused by an error has an associated reason of type error, which carries a payload consisting of two strings: the category of the error and a human-readable description of the error. An abrupt completion caused by a nonlocal exit has an associated reason of type nonlocal-exit, which carries a payload consisting of a variable (the exit tag identifying the exit point of the nonlocal exit) and a sequence of zero or more objects (the values to propagate to the exit point of the nonlocal exit).
Forms submitted to the evaluator through a listener buffer, through the Evaluate Form command, or through the Load Buffer command are called top-level forms. A consequence of the evaluation rules stated later in this section is that the evaluation of a top-level form usually entails the evaluation of other non-top-level forms.
Execution of EVLambda code is achieved through interpretation or compilation.
An interpreter for a language $X$ is a program capable of directly executing code written in language $X$. Language $X$ is called the source language of the interpreter. A compiler for a language $X$ is a program capable of translating code written in language $X$ into code written in a language $Y$. Language $X$ is called the source language of the compiler and language $Y$ is called the target language of the compiler. Code handed to an interpreter or compiler is called source code. A file containing source code is called a source file. Code produced by a compiler is called compiled code. A file containing compiled code is called a compiled file.
An interpreter-based EVLambda evaluator executes EVLambda code by submitting the EVLambda code to its embedded EVLambda interpreter. A compiler-based EVLambda evaluator executes EVLambda code by first submitting the EVLambda code to its embedded EVLambda compiler and then arranging for the compiled code to be executed. Interpretation of EVLambda code and execution of compiled code occur at a time called run time. Compilation of EVLambda code occurs at a time called compile time.
EVLambda source files contain not only EVLambda source code but also documentation in XML format. Documentation is ignored by interpreters and compilers but can be converted to HTML, together with the EVLambda source code, by a component of the programming language called the documentation generator.
Each evaluation is done with respect to three environments: a global environment, a lexical environment, and a dynamic environment. The reference manual will introduce the concepts of scope and extent. The different environments draw their names from the scope and extent of their bindings:
The fact that a global/lexical/dynamic environment contains bindings with such scope and such extent is a direct consequence of the evaluation rules stated later in this section. It is thus not necessary to know the concepts of scope and extent to start writing programs in EVLambda. Knowing the evaluation rules should be enough.
Evaluations are all done with respect to the same global environment. That environment, referred to as “the global environment”, is created when the evaluator starts and continues to exist until the evaluator stops. The global environment of an evaluator that has just started contains a set of predefined bindings, most of which providing access to a primitive function. As forms are evaluated, the global environment can change in the following ways:
Evaluations are not all done with respect to the same lexical and dynamic environments. The lexical environment and the dynamic environments with respect to which a form is evaluated are referred to as “the current lexical environment” and “the current dynamic environment”, respectively.
Top-level forms are evaluated with respect to an initial lexical environment and an initial dynamic environment that are both empty.
A consequence of the evaluation rules stated later in this section is that non-top-level forms are evaluated with respect to (1) a lexical environment that is either the initial empty lexical environment or the result of extending, once or multiple times in sequence, the initial empty lexical environment and (2) a dynamic environment that is either the initial empty dynamic environment or the result of extending, once or multiple times in sequence, the initial empty dynamic environment.
Let $\env$ be an environment, $\ns$ be a namespace of the environment, $n$ be a nonnegative integer, $\var_1,\ldots,\var_n$ be a sequence of $n$ distinct variables, and $\obj_1,\ldots,\obj_n$ be a sequence of $n$ objects. The environment extending the environment $\env$ to bind, in the namespace $\ns$, the variable $\var_i$ to the object $\obj_i$ (for all $i$ from $1$ to $n$) is the environment obtained as follows:
A binding deleted from the environment in step 2 is said to be shadowed by the binding for the same variable added to the environment in step 3. The extended environment is nonempty unless the environment being extended is empty and $n$ is equal to zero.
Two additional consequences of the evaluation rules stated later in this section are that (1) the function namespace of a dynamic environment is always empty and (2) bindings are never added to or deleted from a lexical or dynamic environment after the environment has been created (that is, after the three steps mentioned above).
Together, the global environment, the current lexical environment, and the current dynamic environment can contain up to five bindings for any given variable:
Three pairs of operations are provided to get and set the values of the aforementioned bindings. Each pair uses a specific lookup rule to select one of the bindings. In each pair, one operation is used to get the value of the selected binding (the operation fails if the lookup rule fails to select a binding) and one operation is used to set the value of the selected binding (a new binding is added to the global environment if the lookup rule fails to select a binding). The lookup rules used by the operations are stated below. The operations themselves are detailed later in this section.
The operations vref (getter) and vset! (setter) use the following lookup rule:
The operations fref (getter) and fset! (setter) use the following lookup rule:
The operations dref (getter) and dset! (setter) use the following lookup rule:
A consequence of the lookup rules is that a binding in the value/function namespace of the current lexical/dynamic environment will effectively shadow a binding for the same variable in the value/function namespace of the global environment.
A global variable is a binding between a variable and an object (of any type) in the value namespace of the global environment, or the name or value of such binding. A global function is a binding between a variable and a function other than a macro in the function namespace of the global environment, or the name or value of such binding. A global macro is a binding between a variable and a macro in the function namespace of the global environment, or the name or value of such binding. Global variables, global functions, and global macros are created and altered using language constructs called global definitions.
A local variable is a binding between a variable and an object (of any type) in the value namespace of a lexical environment, or the name or value of such binding. A local function is a binding between a variable and a function other than a macro in the function namespace of a lexical environment, or the name or value of such binding. A local macro is a binding between a variable and a macro in the function namespace of a lexical environment, or the name or value of such binding. A dynamic variable is a binding between a variable and an object (of any type) in the value namespace of a dynamic environment, or the name or value of such binding. Local variables, local functions, local macros, and dynamic variables are created (but not altered) using language constructs called binding constructs.
When a form is submitted to the evaluator, the evaluator analyzes the form to determine how to evaluate it. If the form is the empty list, then the evaluation completes abruptly for a reason of type error. Otherwise, if the form is neither a variable nor a cons, then the result of the evaluation is the form itself. (Objects that are neither the empty list, nor a variable, nor a cons are said to be self-evaluating.) Otherwise, if the form is a variable $\variable$, then the variable is treated as an abbreviation for either (vref $\variable$) or (fref $\variable$), depending on the context of its occurrence. Otherwise, the form is necessarily a cons and the evaluation completes abruptly for a reason of type error unless the form matches one of the following patterns:
(quote $\metavar{literal}$)(progn $\metavar{serial-forms}$)(if $\metavar{test-form}$ $\metavar{then-form}$ $\metavar{else-form}$)(_for-each $\metavar{function-form}$ $\metavar{list-form}$)(_vlambda $\metavar{parameter-list}$ $\metavar{body}$)(_mlambda $\metavar{parameter-list}$ $\metavar{body}$)(_flambda $\metavar{parameter-list}$ $\metavar{body}$)(_dlambda $\metavar{parameter-list}$ $\metavar{body}$)(vref $\metavar{variable}$)(vset! $\metavar{variable}$ $\metavar{value-form}$)(fref $\metavar{variable}$)(fset! $\metavar{variable}$ $\metavar{value-form}$)(dref $\metavar{variable}$)(dset! $\metavar{variable}$ $\metavar{value-form}$)(block $\metavar{block-name}$ $\metavar{serial-forms}$)(return-from $\metavar{block-name}$ $\metavar{values-form}$)(catch $\metavar{exit-tag-form}$ $\metavar{serial-forms}$)(throw $\metavar{exit-tag-form}$ $\metavar{values-form}$)(_handler-bind $\metavar{handler-form}$ $\metavar{serial-forms}$)(unwind-protect $\metavar{protected-form}$ $\metavar{cleanup-forms}$)(apply $\metavar{operator-form}$ $\metavar{operand-forms}$)(multiple-value-call $\metavar{operator-form}$ $\metavar{operand-forms}$)(multiple-value-apply $\metavar{operator-form}$ $\metavar{operand-forms}$)($\metavar{macro-operator}$ $\metavar{macro-operands}$)($\metavar{operator-form}$ $\metavar{operand-forms}$)Names enclosed in angle brackets have the following meanings:
frefBy way of example, a form matches the first pattern if and only if it is a list of two elements whose first element is the variable quote.
The patterns are tried in sequence and the first matching pattern wins.
A form matching one of the first twenty-three patterns is called a special form and the variables quote, progn, if, _for-each, _vlambda, _mlambda, _flambda, _dlambda, vref, vset!, fref, fset!, dref, dset!, block, return-from, catch, throw, _handler-bind, unwind-protect, apply, multiple-value-call, and multiple-value-apply are called special operators.
Special forms matching patterns 5 (_vlambda), 6 (_mlambda), 7 (_flambda), and 8 (_dlambda) are called lambda abstractions. Lambda abstractions are the fundamental binding constructs from which all other binding constructs are built. Forms consisting of a variable and special forms matching patterns 9 (vref), 11 (fref), and 13 (dref) are called variable references. Special forms matching patterns 10 (vset!), 12 (fset!), and 14 (dset!) are called variable assignments. Forms matching pattern 24 are called macro calls. Forms matching pattern 25 are called plain function calls.
The rule regarding forms consisting of a variable is the following: If a variable $\variable$ occurs in operator position, then the variable is treated as an abbreviation for (fref $\variable$) and the function namespace is used. Otherwise, the variable is treated as an abbreviation for (vref $\variable$) and the value namespace is used. For example, the plain function call (f x) would be treated as an abbreviation for ((fref f) (vref x)). The full forms (fref $\variable$) and (vref $\variable$) can always be used to force the use of a specific namespace.
As we will see below, the evaluation of a form entails the evaluation of some or all of the direct components of the form that are identified as forms by the form's pattern. When a direct subform is evaluated, the following rules apply:
block, catch, _handler-bind, and unwind-protect, which are described in the reference manual.Special forms, macro calls, and plain function calls are evaluated as follows:
(quote $\metavar{literal}$)quote form evaluates to the unevaluated literal. Using a quote form, any object can be treated as data. For any object $\object$, (quote $\object$) can be abbreviated to '$\object$.(progn $\metavar{serial-forms}$)progn form evaluates to the values of the last serial form. Otherwise, the progn form evaluates to #v.(if $\metavar{test-form}$ $\metavar{then-form}$ $\metavar{else-form}$)if form completes abruptly for a reason of type error. If $\mlvar{test}$ is the boolean #t, then the then form is evaluated (but not the else form) and the if form evaluates to the values of the then form. If $\mlvar{test}$ is the boolean #f, then the else form is evaluated (but not the then form) and the if form evaluates to the values of the else form.(_for-each $\metavar{function-form}$ $\metavar{list-form}$)(_vlambda $\metavar{parameter-list}$ $\metavar{body}$)(_mlambda $\metavar{parameter-list}$ $\metavar{body}$)(_flambda $\metavar{parameter-list}$ $\metavar{body}$)(_dlambda $\metavar{parameter-list}$ $\metavar{body}$)_mlambda form is tagged as being a macro. Note that the body of a lambda abstraction is not considered to be a direct subform of the lambda abstraction as the evaluation of a lambda abstraction does not entail the evaluation of its body.(vref $\metavar{variable}$)vref form evaluates to the value of that binding. Otherwise, if there exists a binding for the variable in the value namespace of the global environment, then the vref form evaluates to the value of that binding. Otherwise, the evaluation of the vref form completes abruptly for a reason of type error.(vset! $\metavar{variable}$ $\metavar{value-form}$)vset form evaluates to $\mlvar{value}$.(fref $\metavar{variable}$)fref form evaluates to the value of that binding. Otherwise, if there exists a binding for the variable in the function namespace of the global environment, then the fref form evaluates to the value of that binding. Otherwise, the evaluation of the fref form completes abruptly for a reason of type error.(fset! $\metavar{variable}$ $\metavar{value-form}$)fset form evaluates to $\mlvar{value}$.(dref $\metavar{variable}$)dref form evaluates to the value of that binding. Otherwise, if there exists a binding for the variable in the value namespace of the global environment, then the dref form evaluates to the value of that binding. Otherwise, the evaluation of the dref form completes abruptly for a reason of type error.(dset! $\metavar{variable}$ $\metavar{value-form}$)dset form evaluates to $\mlvar{value}$.(block $\metavar{block-name}$ $\metavar{serial-forms}$)(return-from $\metavar{block-name}$ $\metavar{values-form}$)(catch $\metavar{exit-tag-form}$ $\metavar{serial-forms}$)(throw $\metavar{exit-tag-form}$ $\metavar{values-form}$)(_handler-bind $\metavar{handler-form}$ $\metavar{serial-forms}$)(unwind-protect $\metavar{protected-form}$ $\metavar{cleanup-forms}$)(apply $\metavar{operator-form}$ $\metavar{operand-forms}$)(multiple-value-call $\metavar{operator-form}$ $\metavar{operand-forms}$)(multiple-value-apply $\metavar{operator-form}$ $\metavar{operand-forms}$)($\metavar{macro-operator}$ $\metavar{macro-operands}$)fref. That macro is invoked on the unevaluated macro operands. If the invocation completes abruptly for any reason, then the evaluation of the macro call also completes abruptly for the same reason. Otherwise, if the invocation does not complete, then the evaluation of the macro call does not complete either. Otherwise, the primary value of the invocation, which is called the expansion of the macro call, is evaluated with respect to the current lexical and dynamic environments. If the evaluation of the expansion completes abruptly for any reason, then the evaluation of the macro call also completes abruptly for the same reason. Otherwise, if the evaluation of the expansion does not complete, then the evaluation of the macro call does not complete either. Otherwise, the macro call evaluates to the values of the expansion.($\metavar{operator-form}$ $\metavar{operand-forms}$)error. Otherwise, the operand forms are evaluated in sequence from left to right and $\mlvar{operator}$ is invoked on the primary values of the operand forms. If the invocation completes abruptly for any reason, then the evaluation of the plain function call also completes abruptly for the same reason. Otherwise, if the invocation does not complete, then the evaluation of the plain function call does not complete either. Otherwise, the plain function call evaluates to the values of the invocation.Invoking a function is what causes the function to compute the output corresponding to an input. The input of an invocation consists of zero or more objects—object references actually—called the arguments of the invocation. (The function is said to be invoked on the arguments and the arguments are said to be passed to the function.) The invocation of a function has three possible outcomes:
The primary value of an invocation that has completed normally is defined as follows: If the output consists of one or more objects, then the primary value of the invocation is the first object. Otherwise, the primary value of the invocation is #v.
The abrupt completion of an invocation has an associated reason, which has a type and carries a payload. The types and payloads for invocations are the same as for evaluations.
The verbs “accept” and “return” are often used to describe the input/output mapping implemented by a function. For example, one could describe a function by saying that the function accepts two numbers and returns the sum of their squares.
A primitive function is invoked as follows:
Let us assume that, as is the case with all the evaluators currently available, the primitive function is implemented by a JavaScript function accepting (an encoding of) the arguments of an invocation of the primitive function and returning (an encoding of) the values of the invocation of the primitive function. The JavaScript function is invoked on the arguments. If the invocation of the JavaScript function completes abruptly for any reason, then the invocation of the primitive function also completes abruptly for the same reason. Otherwise, if the invocation of the JavaScript function does not complete, then the invocation of the primitive function does not complete either. Otherwise, the primitive function returns the values returned by the JavaScript function.
A closure is invoked as follows:
If the number of variables in the parameter list of the lambda abstraction recorded by the closure and the number of arguments are different, then the invocation completes abruptly for a reason of type
error. (As we will see in the reference manual, it is actually possible to create closures accepting a variable number of arguments.) Otherwise, let $\var_1,\ldots,\var_n$ be the variables composing the parameter list of the lambda abstraction recorded by the closure, $\arg_1,\ldots,\arg_n$ be the arguments, and $\lexenv$ and $\dynenv$ be the following environments:
- If the closure results from the evaluation of a
_vlambdaform or an_mlambdaform, then $\lexenv$ is the environment extending the lexical environment recorded by the closure to bind, in the value namespace, the variable $\var_i$ to the argument $\arg_i$ (for all $i$ from $1$ to $n$) and $\dynenv$ is the current dynamic environment.- If the closure results from the evaluation of an
_flambdaform, then $\lexenv$ is the environment extending the lexical environment recorded by the closure to bind, in the function namespace, the variable $\var_i$ to the argument $\arg_i$ (for all $i$ from $1$ to $n$) and $\dynenv$ is the current dynamic environment.- If the closure results from the evaluation of a
_dlambdaform, then $\lexenv$ is the lexical environment recorded by the closure and $\dynenv$ is the environment extending the current dynamic environment to bind, in the value namespace, the variable $\var_i$ to the argument $\arg_i$ (for all $i$ from $1$ to $n$).The objects composing the body of the lambda abstraction recorded by the closure are evaluated with respect to $\lexenv$ and $\dynenv$ as if they were the serial forms of a
prognform. (The objects composing the body are said to belong to an implicitprognform.) If the evaluation of theprognform completes abruptly for any reason, then the invocation also completes abruptly for the same reason. Otherwise, if the evaluation of theprognform does not complete, then the invocation does not complete either. Otherwise, the closure returns the values of theprognform.
As we will see in the reference manual, the evaluation of a top-level form cannot complete abruptly for a reason of type nonlocal-exit. The three possible outcomes of the evaluation of a top-level form are thus the following:
error.The special operator _for-each has an underscore at the beginning of its name because it is a hack only needed and implemented by some of the interpreters. The special operators _vlambda, _mlambda, _flambda, and _dlambda have an underscore at the beginning of their names to distinguish them from the similarly named macros vlambda, mlambda, flambda, and dlambda. The purpose of those macros is to facilitate the creation of closures accepting a variable number of arguments. The special operator _handler-bind has an underscore at the beginning of its name to distinguish it from the similarly named macro handler-bind.
The evaluator uses a data structure called a control stack to coordinate its activities. Each time a top-level form is submitted to the evaluator, a new control stack is created that will be used throughout the evaluation of the top-level form. The same control stack is used to evaluate the top-level form and all the non-top-level forms whose evaluations are entailed by the evaluation of the top-level form.
An evaluation/invocation that completes normally produces a result/output consisting of zero or more objects. The production of that result/output, which only occurs if the evaluation/invocation completes normally, is the primary effect of the evaluation/invocation. In addition to or in place of its primary effect, an evaluation/invocation can also have secondary effects called side effects. Examples of side effects are:
A consequence of the existence of side effects is that the repeated evaluations of the same form or the repeated invocations of the same function on the same arguments do not necessarily have the same outcome and, if they complete normally, do not necessarily produce the same result/output.
It is customary for a special operator, function, or macro that can alter objects, bindings, and/or environments to have a name ending with an exclamation mark. This explains the exclamation mark in the names of the special operators vset!, fset!, and dset!.
The integrated development environment (IDE) is a web application that can run either from the EVLambda web server (online mode) or from a web server running on the user's machine (offline mode). The code running in the web browser is exactly the same in both modes but the behavior of the IDE is slightly different because the backends have different capabilities.
The IDE's graphical user interface consists of a menu bar at the top left, an info bar at the top right, a minibuffer at the bottom, and a set of windows in the main area. Each window consists of a contents area and a status bar. At any given time, a window displays the contents of a buffer, of which there are two types: the file buffers and the listener buffers. A given buffer can be displayed in any number of windows (including zero) and any buffer can be displayed in any window. This organization around buffers and windows is borrowed from the Emacs text editor.
A file buffer is a buffer whose contents reflects the contents of a file. The contents of the buffer is read from the file through open and revert operations and written into the file through save operations. When a window displays the contents of a file buffer, its status bar displays the name of the file, followed by a star when the current contents of the buffer differs from the contents that was last read from or written into the file.
When the IDE starts, it automatically opens a predefined set of files. In online mode, the files are located in a directory on the machine hosting the EVLambda web server. In offline mode, the files are located in the directory <EVLAMBDA_HOME>/system-files on the user's machine.
A listener buffer is a buffer that allows the user to evaluate forms interactively. When a window displays the contents of a listener buffer, its status bar displays the name of the buffer. Currently, the IDE has exactly one listener buffer whose name is “Listener 1”.
At any given time, there is exactly one selected window and, by extension, exactly one selected buffer. The status bar of the selected window is darker than the status bar of the nonselected windows. A window becomes the selected window when it receives the focus, which happens for instance when it receives a click event.
Currently, the only function of the minibuffer is to display messages and evaluation results.
A window can display the contents of a file buffer in one of two modes: raw mode or HTML mode. In raw mode, the contents of the buffer is displayed in the CodeMirror text editor. In HTML mode, the contents of the buffer, or some HTML contents derived from the contents of the buffer, is displayed in an HTML viewer.
The all-caps files (USER-MANUAL, …), which are actually HTML files, can be displayed in raw mode or HTML mode. The EVLambda source files (extension .evl), which contain a mix of EVLambda source code and documentation in XML format, can be displayed in raw mode or HTML mode. The other types of files are always displayed in raw mode.
A listener buffer allows the user to evaluate forms interactively. To evaluate a form in a listener buffer, the user types in a readable representation of the form after the prompt and presses the Return or Enter key when the cursor is at the very end of the buffer. In response, the form is evaluated, the printable representations of the resulting values are printed separated by a newline, and a new prompt is printed, allowing the user to evaluate another form. This sequence of operations is called a read-eval-print loop (REPL).
Notes:
error and a message combining the category and the description carried by the reason is printed in place of the printable representations of the (nonexisting) resulting values.Some of the concepts introduced in the section Programming Language will now be illustrated by providing a commented transcript of a sequence of evaluations conducted in a listener buffer. If you want to reproduce the evaluations, be sure to start with a fresh trampoline++ evaluator. To get a fresh trampoline++ evaluator, restart the evaluator (using the Restart Evaluator… command from the Eval menu) with Trampoline++ selected.
The global functions used in the evaluations are listed below. For each function, a template function call, an indication of the outcome of the invocation, and a description of the function's behavior are provided. The variable in operator position is the name of the function (i.e., the variable bound to the function in the function namespace of the global environment). The arguments named after a type must be of that type, otherwise the invocation completes abruptly for a reason of type error.
(car $\cons$) ⇒ $\object$(cdr $\cons$) ⇒ $\object$(list $\object_1\ldots\object_n$) ⇒ $\list$(+ $\number_1\ldots\number_n$) ⇒ $\number$(* $\number_1\ldots\number_n$) ⇒ $\number$(values $\object_1\ldots\object_n$) ⇒ $\object_1,\ldots,\object_n$The global macros used in the evaluations are listed below. For each macro, a template macro call and a description of the macro's behavior are provided. The variable in operator position is the name of the macro (i.e., the variable bound to the macro in the function namespace of the global environment).
(vdef $\metavar{variable}$ $\metavar{value-form}$)(fdef $\metavar{variable}$ $\metavar{parameter-list}$ $\metavar{body}$)_vlambda form (_vlambda $\metavar{parameter-list}$ $\metavar{body}$). The macro call evaluates to the variable.(loop $\metavar{serial-forms}$)(loop) endlessly does nothing. The evaluation of the macro call normally does not complete but there are ways to exit an infinite loop.For any object $\object$, '$\object$ is an abbreviation for (quote $\object$).
Here is the commented transcript, where each gray box contains a form and its values. The character ⏎ marks the places where the Return or Enter key should be pressed.
> (+ 1 2)⏎
3
The evaluation produces a result consisting of the sum of the two numbers 1 and 2. Because numbers are self-evaluating, quoting the numbers is not necessary.
> (+ '1 '2)⏎
3
The evaluation produces the same result if the numbers are quoted. Quoting self-evaluating objects is unidiomatic, though.
> (car '(1 2 3))⏎
1
The evaluation produces a result consisting of the first element of the list (1 2 3). Because lists are not self-evaluating, quoting the list is necessary.
> (car (1 2 3))⏎
EvaluatorError: form-type-error:
The operator form does not evaluate to a function.
The evaluation completes abruptly for a reason of type error if the list is not quoted. The explanation for this behavior is as follows: The evaluator treats the list (1 2 3) as a plain function call and the operator form, the number 1, does not evaluate to a function.
> (cdr '(1 2 3))⏎
(2 3)
The evaluation produces a result consisting of the sublist of the list (1 2 3) obtained by omitting its first element.
> (car (cdr '(1 2 3)))⏎
2
The evaluation produces a result consisting of the second element of the list (1 2 3).
> (cdr (cdr '(1 2 3)))⏎
(3)
The evaluation produces a result consisting of the sublist of the list (1 2 3) obtained by omitting its first two elements.
> (car (cdr (cdr '(1 2 3))))⏎
3
The evaluation produces a result consisting of the third element of the list (1 2 3).
> (cdr (cdr (cdr '(1 2 3))))⏎
()
The evaluation produces a result consisting of the sublist of the list (1 2 3) obtained by omitting its first three elements.
> (car (cdr (cdr (cdr '(1 2 3)))))⏎
EvaluatorError: argument-type-error:
The 1st argument is not of type EVLCons.
The evaluation completes abruptly for a reason of type error because the empty list is not a cons.
> (disk-area 2)⏎
EvaluatorError: unbound-variable:
The variable 'disk-area' is unbound in the function namespace.
The evaluation completes abruptly for a reason of type error because the global function disk-area is undefined.
> (fdef disk-area (r) (* 3.14 r r))⏎
disk-area
The evaluation produces a result consisting of the variable disk-area. More importantly, the evaluation has the side effect of defining the global function disk-area. In its intended usage, the function accepts the radius of a disk and returns the area of the disk computed using 3.14 as the value of pi. When the function is invoked, its body is evaluated with respect to a lexical environment binding, in the value namespace, the variable r to the argument of the invocation (that is, the radius of the disk).
> (disk-area 2)⏎
12.56
The evaluation produces the expected result.
> (fdef disk-area (r) (* 3.1416 r r))⏎
disk-area
The evaluation has the side effect of redefining the global function disk-area to compute the area of the disk using 3.1416 as the value of pi.
> (disk-area 2)⏎
12.5664
The evaluation produces the expected result.
> (fdef disk-area (r) (* *pi* r r))⏎
disk-area
The evaluation has the side effect of redefining the global function disk-area to compute the area of the disk using the value of the global variable *pi* as the value of pi. It is customary for a global or dynamic variable to have a name starting and ending with an asterisk.
> (disk-area 2)⏎
EvaluatorError: unbound-variable:
The variable '*pi*' is unbound in the value namespace.
The evaluation completes abruptly for a reason of type error because the global variable *pi* is undefined.
> (vdef *pi* 3.141593)⏎
*pi*
The evaluation produces a result consisting of the variable *pi*. More importantly, the evaluation has the side effect of defining the global variable *pi*.
> *pi*⏎
3.141593
The global variable *pi* has the value 3.141593.
> (disk-area 2)⏎
12.566372
The evaluation produces the expected result.
> (vdef *pi* 3.14159265)⏎
*pi*
The evaluation has the side effect of redefining the global variable *pi*.
> *pi*⏎
3.14159265
The global variable *pi* has the value 3.14159265.
> (disk-area 2)⏎
12.5663706
The evaluation produces the expected result.
> (values)⏎
The evaluation produces a result consisting of zero values.
> (values 1)⏎
1
The evaluation produces a result consisting of one value: 1.
> 1⏎
1
Producing a result consisting of one value is the default behavior so using values in this case is unnecessary and unidiomatic.
> (values 1 2)⏎
1
2
The evaluation produces a result consisting of two values: 1 and 2.
> (list (values) (values 1) 1 (values 1 2))⏎
(#v 1 1 1)
The primary values of the forms (values), (values 1), 1, and (values 1 2) are #v, 1, 1, and 1, respectively.
> (loop)⏎
ABORTED
The evaluation is caught in an infinite loop. The Abort Evaluation command from the Eval menu is one way to stop the evaluation and get a new prompt.
> (disk-area 2)⏎
12.5663706
Aborting an evaluation has no effect on the global definitions.
> (loop)⏎
TERMINATED
The evaluation is caught in an infinite loop. The Restart Evaluator… command from the Eval menu is another way to stop the evaluation and get a new prompt.
> (disk-area 2)⏎
EvaluatorError: unbound-variable:
The variable 'disk-area' is unbound in the function namespace.
Restarting the evaluator erases all global definitions.
The command writes the contents of the selected file buffer into its associated file.
The command is not available in online mode.
The command reverts the contents of the selected file buffer to the contents of its associated file.
The command toggles the selected window between raw and HTML modes.
The command is only available when the selected window displays the contents of an all-caps file or an EVLambda source file.
The command clears the selected listener buffer.
All contents before the last prompt is deleted.
The command evaluates a top-level form contained inside the selected file buffer.
The command is only available when the selected window displays the contents of an EVLambda source file.
A top-level form is a form that is not contained inside another form.
The top-level form to evaluate is selected as follows:
If the evaluation of the top-level form completes normally, then the printable representations of the resulting values are printed in the minibuffer, separated by a comma. If the evaluation of the top-level form completes abruptly, then the reason for the abrupt completion is necessarily of type error and a message combining the category and the description carried by the reason is printed in the minibuffer. If the evaluation of the top-level form does not complete, then no new evaluation is possible until the evaluation is aborted or the evaluator is restarted.
The command evaluates the top-level forms contained inside the selected file buffer.
The command is only available when the selected window displays the contents of an EVLambda source file.
The top-level forms contained inside the selected file buffer are evaluated as if they were part of a progn form.
If the evaluation of the progn form completes normally, then the printable representations of the resulting values are printed in the minibuffer, separated by a comma. If the evaluation of the progn form completes abruptly, then the reason for the abrupt completion is necessarily of type error and a message combining the category and the description carried by the reason is printed in the minibuffer. If the evaluation of the progn form does not complete, then no new evaluation is possible until the evaluation is aborted or the evaluator is restarted.
The command aborts the current evaluation.
The command terminates the current evaluator and starts a new one.
Warning: All global definitions are lost.
The following interpreter-based evaluators are available:
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 command selects one of the nonselected windows.
The command is not available when the selected window is maximized.
The command toggles the selected window between unmaximized and maximized states.
The buffer menu allows the user to select the buffer displayed in the selected window.
The buffer menu contains the following entries:
/system/USER-MANUAL: the user manual (this file)/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/core.js: the JavaScript file implementing the interpreters, the primitive data types, the primitive functions, etc., constituting the “core” of the EVLambda programming language/system/mantle.evl: the EVLambda source file implementing the nonprimitive data types, the nonprimitive functions, the macros, etc., constituting the “mantle” of the EVLambda programming language/system/evl2html.xslt: the XSLT file used to convert the EVLambda source files to HTML/system/common.css: the CSS file styling the all-caps files and the EVLambda source files converted to HTML/system/common.js: the JavaScript file loaded by the all-caps files and the EVLambda source files converted to HTMLListener 1: the initial listenerThe help menu allows the user to quickly navigate to various parts of the EVLambda website.
The help menu contains the following entries:
The info bar displays the name of the current evaluator.
| Linux | Windows | macOS | Command |
|---|---|---|---|
| ArrowLeft | ArrowLeft | ArrowLeft | cursorCharLeft |
| Shift-ArrowLeft | Shift-ArrowLeft | Shift-ArrowLeft | selectCharLeft |
| Ctrl-ArrowLeft | Ctrl-ArrowLeft | Alt-ArrowLeft | cursorGroupLeft |
| Ctrl-Shift-ArrowLeft | Ctrl-Shift-ArrowLeft | Alt-Shift-ArrowLeft | selectGroupLeft |
| N/A | N/A | Cmd-ArrowLeft | cursorLineBoundaryLeft |
| N/A | N/A | Cmd-Shift-ArrowLeft | selectLineBoundaryLeft |
| ArrowRight | ArrowRight | ArrowRight | cursorCharRight |
| Shift-ArrowRight | Shift-ArrowRight | Shift-ArrowRight | selectCharRight |
| Ctrl-ArrowRight | Ctrl-ArrowRight | Alt-ArrowRight | cursorGroupRight |
| Ctrl-Shift-ArrowRight | Ctrl-Shift-ArrowRight | Alt-Shift-ArrowRight | selectGroupRight |
| N/A | N/A | Cmd-ArrowRight | cursorLineBoundaryRight |
| N/A | N/A | Cmd-Shift-ArrowRight | selectLineBoundaryRight |
| ArrowUp | ArrowUp | ArrowUp | cursorLineUp |
| Shift-ArrowUp | Shift-ArrowUp | Shift-ArrowUp | selectLineUp |
| N/A | N/A | Cmd-ArrowUp | cursorDocStart |
| N/A | N/A | Cmd-Shift-ArrowUp | selectDocStart |
| N/A | N/A | Ctrl-ArrowUp | cursorPageUp |
| N/A | N/A | Ctrl-Shift-ArrowUp | selectPageUp |
| ArrowDown | ArrowDown | ArrowDown | cursorLineDown |
| Shift-ArrowDown | Shift-ArrowDown | Shift-ArrowDown | selectLineDown |
| N/A | N/A | Cmd-ArrowDown | cursorDocEnd |
| N/A | N/A | Cmd-Shift-ArrowDown | selectDocEnd |
| N/A | N/A | Ctrl-ArrowDown | cursorPageDown |
| N/A | N/A | Ctrl-Shift-ArrowDown | selectPageDown |
| PageUp | PageUp | PageUp | cursorPageUp |
| Shift-PageUp | Shift-PageUp | Shift-PageUp | selectPageUp |
| PageDown | PageDown | PageDown | cursorPageDown |
| Shift-PageDown | Shift-PageDown | Shift-PageDown | selectPageDown |
| Home | Home | Home | cursorLineBoundaryBackward |
| Shift-Home | Shift-Home | Shift-Home | selectLineBoundaryBackward |
| Ctrl-Home | Ctrl-Home | Cmd-Home | cursorDocStart |
| Ctrl-Shift-Home | Ctrl-Shift-Home | Cmd-Shift-Home | selectDocStart |
| End | End | End | cursorLineBoundaryForward |
| Shift-End | Shift-End | Shift-End | selectLineBoundaryForward |
| Ctrl-End | Ctrl-End | Cmd-End | cursorDocEnd |
| Ctrl-Shift-End | Ctrl-Shift-End | Cmd-Shift-End | selectDocEnd |
| Enter | Enter | Enter | insertNewlineAndIndent |
| Ctrl-a | Ctrl-a | Cmd-a | selectAll |
| Backspace | Backspace | Backspace | deleteCharBackward |
| Shift-Backspace | Shift-Backspace | Shift-Backspace | deleteCharBackward |
| Delete | Delete | Delete | deleteCharForward |
| Ctrl-Backspace | Ctrl-Backspace | Alt-Backspace | deleteGroupBackward |
| Ctrl-Delete | Ctrl-Delete | Alt-Delete | deleteGroupForward |
| N/A | N/A | Cmd-Backspace | deleteLineBoundaryBackward |
| N/A | N/A | Cmd-Delete | deleteLineBoundaryForward |
| Alt-ArrowLeft | Alt-ArrowLeft | Ctrl-ArrowLeft | cursorSyntaxLeft |
| Alt-Shift-ArrowLeft | Alt-Shift-ArrowLeft | Ctrl-Shift-ArrowLeft | selectSyntaxLeft |
| Alt-ArrowRight | Alt-ArrowRight | Ctrl-ArrowRight | cursorSyntaxRight |
| Alt-Shift-ArrowRight | Alt-Shift-ArrowRight | Ctrl-Shift-ArrowRight | selectSyntaxRight |
| Alt-ArrowUp | Alt-ArrowUp | Alt-ArrowUp | moveLineUp |
| Alt-Shift-ArrowUp | Alt-Shift-ArrowUp | Alt-Shift-ArrowUp | copyLineUp |
| Alt-ArrowDown | Alt-ArrowDown | Alt-ArrowDown | moveLineDown |
| Alt-Shift-ArrowDown | Alt-Shift-ArrowDown | Alt-Shift-ArrowDown | copyLineDown |
| Escape | Escape | Escape | simplifySelection |
| Ctrl-Enter | Ctrl-Enter | Cmd-Enter | insertBlankLine |
| Alt-l | Alt-l | Ctrl-l | selectLine |
| Ctrl-i | Ctrl-i | Cmd-i | selectParentSyntax |
| Ctrl-[ | Ctrl-[ | Cmd-[ | indentLess |
| Ctrl-] | Ctrl-] | Cmd-] | indentMore |
| Ctrl-Alt-\ | Ctrl-Alt-\ | Cmd-Alt-\ | indentSelection |
| Ctrl-Shift-k | Ctrl-Shift-k | Cmd-Shift-k | deleteLine |
| Ctrl-Shift-\ | Ctrl-Shift-\ | Cmd-Shift-\ | cursorMatchingBracket |
| Ctrl-/ | Ctrl-/ | Cmd-/ | toggleComment |
| Alt-Shift-a | Alt-Shift-a | Alt-Shift-a | toggleBlockComment |
| N/A | N/A | Ctrl-b | cursorCharLeft |
| N/A | N/A | Ctrl-Shift-b | selectCharLeft |
| N/A | N/A | Ctrl-f | cursorCharRight |
| N/A | N/A | Ctrl-Shift-f | selectCharRight |
| N/A | N/A | Ctrl-p | cursorLineUp |
| N/A | N/A | Ctrl-Shift-p | selectLineUp |
| N/A | N/A | Ctrl-n | cursorLineDown |
| N/A | N/A | Ctrl-Shift-n | selectLineDown |
| N/A | N/A | Ctrl-a | cursorLineStart |
| N/A | N/A | Ctrl-Shift-a | selectLineStart |
| N/A | N/A | Ctrl-e | cursorLineEnd |
| N/A | N/A | Ctrl-Shift-e | selectLineEnd |
| N/A | N/A | Ctrl-d | deleteCharForward |
| N/A | N/A | Ctrl-h | deleteCharBackward |
| N/A | N/A | Ctrl-k | deleteToLineEnd |
| N/A | N/A | Ctrl-Alt-h | deleteGroupBackward |
| N/A | N/A | Ctrl-o | splitLine |
| N/A | N/A | Ctrl-t | transposeChars |
| N/A | N/A | Ctrl-v | cursorPageDown |
| Ctrl-z | Ctrl-z | Cmd-z | undo |
| Ctrl-y | Ctrl-y | Cmd-Shift-z | redo |
| Ctrl-Shift-z | N/A | N/A | redo |
| Tab | Tab | Tab | indentSelection |
| Ctrl-f | Ctrl-f | Cmd-f | openSearchPanel |
| F3 | F3 | F3 | findNext |
| Shift-F3 | Shift-F3 | Shift-F3 | findPrevious |
| Ctrl-g | Ctrl-g | Cmd-g | findNext |
| Ctrl-Shift-g | Ctrl-Shift-g | Cmd-Shift-g | findPrevious |
| Escape | Escape | Escape | closeSearchPanel |
| Ctrl-Shift-l | Ctrl-Shift-l | Cmd-Shift-l | selectSelectionMatches |
| Alt-g | Alt-g | Alt-g | gotoLine |
| Ctrl-d | Ctrl-d | Cmd-d | selectNextOccurrence |
| Ctrl-s | Ctrl-s | Cmd-s | Save Buffer |
| Ctrl-Alt-h | Ctrl-Alt-h | Ctrl-Cmd-h | Toggle HTML Mode |
| Ctrl-Alt-e | Ctrl-Alt-e | Ctrl-Cmd-e | Evaluate Form |
| Ctrl-Alt-l | Ctrl-Alt-l | Ctrl-Cmd-l | Load Buffer |
| Ctrl-Alt-o | Ctrl-Alt-o | Ctrl-Cmd-o | Select Other Window |
| Ctrl-Alt-m | Ctrl-Alt-m | Ctrl-Cmd-m | Toggle Maximized State |