Element 0 Language Reference¶
Element 0 is a small Lisp dialect based on R7RS-small, implemented by Elz, a compiler and bytecode virtual machine written in Zig. This document describes the language as implemented. For the standard semantics of R7RS procedures, see the R7RS-small report; this reference lists what Element 0 provides and details only behavior that is specific to it. The Procedures page lists every built-in procedure by name. Deviations from the standard are collected in the last section.
Lexical Syntax¶
Element 0 reads the full R7RS-small lexical syntax.
- Comments come in three forms. A line comment runs from
;to the end of the line,#| ... |#is a nestable block comment, and#;comments out the following datum. - Booleans are
#t,#f,#true, and#false. - Integers have no size limit. They can carry a radix prefix (
#x10,#o17,#b101,#d16) and an exactness prefix (#e,#i) in either order;#e1.5reads as3/2.1/3is an exact rational.+inf.0,-inf.0, and+nan.0are inexact reals.3+4iis a complex number in rectangular notation; an exact zero imaginary part, as in3+0i, denotes a real number. Number syntax is strict, soinf,nan,1_000, and0x10are symbols, not numbers. - Characters are written
#\a, by name (#\space,#\newline,#\tab,#\return,#\alarm,#\backspace,#\delete,#\escape,#\null), or by code point (#\x41). A character literal may be any single UTF-8 character. - Strings support the escapes
\n,\t,\r,\a,\b,\0,\\,\",\|, and\xHH...;for a code point in hex. A backslash before a line break continues the string on the next line with the surrounding whitespace removed. - Symbols with unusual characters are written between pipes, as in
|two words|, with the same escapes as strings. #(1 2 3)is a vector literal and#u8(1 2 3)is a bytevector literal.'x,`x,,x, and,@xabbreviatequote,quasiquote,unquote, andunquote-splicing.- Datum labels
#n=and#n#write and read shared and cyclic structure. - The
#!fold-caseand#!no-fold-casedirectives switch case folding of symbols for the rest of the datum being read.
Special Forms¶
The core forms follow R7RS-small.
- Definitions:
define,define-values,define-syntax,define-record-type,define-library, and the Elz-specificdefine-macro. - Binding:
let,let*,letrec, namedlet,let-values,let*-values,let-syntax,letrec-syntax, andparameterize. - Conditionals:
if,cond(including=>clauses),case(including=>clauses),when,unless,and,or, andcond-expand. - Sequencing and iteration:
beginanddo. - Quoting:
quoteandquasiquotewithunquoteandunquote-splicing. - Assignment:
set!. - Lazy evaluation:
delayanddelay-force. - Procedures:
lambdawith fixed, dotted-rest, and all-variadic parameter lists, andcase-lambda. - Exceptions:
guard, plus the Elz-specifictry/catchdescribed below. - Continuations:
resetandshift, described below. - Compile-time inclusion:
includeandinclude-cisplice the forms of the named files in place;include-cifolds symbols to lower case. - Macros can signal misuse with
syntax-error.
A lexically bound name shadows a special form of the same name, so
(let ((if list)) (if 1 2 3)) calls the variable. A definition inside a body
likewise shadows a macro of the same name.
Definitions may appear at the start of any body, including inside begin
forms and as the output of macros such as define-values and
define-record-type. A define in expression position (inside when, an
if branch, or a do result) is a syntax error.
Macros¶
define-syntax with syntax-rules implements hygienic pattern macros with
the full R7RS feature set. Patterns support nested and mid-list ellipsis,
dotted tails, the _ wildcard, literal identifiers, and a custom ellipsis
identifier. Templates support consecutive ellipses for splicing and the
(... <template>) escape.
Hygiene works by renaming. Every identifier a template introduces gets a
fresh alias that remembers where the macro was defined, and the compiler
resolves the alias from that definition scope. A let at the use site cannot
capture a template's temporary, a use-site binding of list or if does not
change what a template's list or if means, and a template that refers to
a local variable of the function the macro was defined in reaches that
variable even from a nested scope that shadows the name.
Quoted data in a template is never renamed. A (quote x) form, the datum
lists of case, vector literals, and the parts of a quasiquotation that are
not unquoted appear in the output exactly as written.
define-macro defines a procedural (non-hygienic) macro. The body receives
the unevaluated argument forms and returns a replacement form.
Records¶
define-record-type follows R7RS.
Record types are distinct even when their names collide. Reading a field through the wrong type's accessor raises an error.
Standard Procedures¶
The R7RS-small procedure set is implemented in full. The list below groups it by section, and procedures behave as the report specifies unless noted in the deviations section.
- Equivalence:
eq?,eqv?,equal?. - Numbers: the arithmetic and comparison operators (n-ary, chained), the
numeric predicates including
exact-integer?,finite?,infinite?, andnan?, rounding and division families (floor/,truncate/,quotient,remainder,modulo,gcd,lcm), transcendental functions,expt,exact-integer-sqrt,square,abs,min,max, exactness conversion (exact,inexact), rational accessors (numerator,denominator,rationalize), complex constructors and accessors (make-rectangular,make-polar,real-part,imag-part,magnitude,angle), and radix awarenumber->stringandstring->number. - Booleans:
not,boolean?,boolean=?. - Pairs and lists: the constructors and accessors,
set-car!,set-cdr!, the fullcaarthroughcddddrfamily,list,length,append,reverse,list-ref,list-set!,list-tail,list-copy,make-list,memq,memv,member,assq,assv,assoc(the last two of each accept an optional comparator),mapandfor-each(stopping at the shortest list),filter,fold-left, andfold-right. - Symbols:
symbol?,symbol=?,symbol->string,string->symbol,gensym. - Characters: the predicates, the case-sensitive and case-insensitive
comparison chains, classification (
char-alphabetic?and friends),char-upcase,char-downcase,char-foldcase,digit-value,char->integer, andinteger->char. - Strings: construction, access, mutation, the comparison chains,
substring,string-append,string-copy,string-copy!,string-fill!,string->list,list->string,string-map,string-for-each,string-upcase,string-downcase,string-foldcase,string-split, and conversions to and from vectors and UTF-8 bytevectors. Strings are UTF-8 and indexes count characters. Procedures withstartandendarguments accept them as the report specifies. - Vectors: construction, access, mutation,
vector-copy,vector-copy!,vector-append,vector-fill!,vector-map,vector-for-each, and conversions to and from lists and strings. - Bytevectors: the full section 6.9 set, including
bytevector-copy!with overlapping-range support,utf8->string, andstring->utf8. - Control:
procedure?,apply,values,call-with-values,dynamic-wind,force,make-promise,promise?, andevalwithenvironmentandinteraction-environment. - Ports and I/O: textual and binary ports over files, strings, and
bytevectors, the
readfamily with optional port arguments, thewritefamily (writelabels cyclic data with datum labels,write-sharedlabels all shared structure,write-simplelabels nothing),display,newline,write-string,write-char, the u8 procedures, port predicates and open checks,close-port,call-with-port, thecurrent-input-port,current-output-port, andcurrent-error-portaccessors, thewith-...-fileandcall-with-...-fileconveniences, andload. - System interface:
command-line,exit,emergency-exit,get-environment-variable,get-environment-variables,features,current-second,current-jiffy, andjiffies-per-second.
Exceptions¶
The R7RS exception system is implemented in full.
raiseraises any object.raise-continuableallows the installed handler's return value to become the value of the raise expression.with-exception-handlerinstalls a handler for the dynamic extent of a thunk. The handler runs with itself uninstalled. A handler returning from a non-continuable raise is itself an error.errorraises an error object carrying a message and irritants, examined witherror-object?,error-object-message, anderror-object-irritants.read-error?andfile-error?test the error kind.- Errors detected by the runtime (a bad argument, an unbound variable, a
failed file open, a malformed datum) are also error objects when caught
by
guardortry. Their message is the runtime's description, their irritants are empty, and file and read failures answerfile-error?andread-error?. guarddispatches a raised object over cond-style clauses and reraises when no clause matches.
Elz also provides try/catch, a lower-level form that predates guard:
The catch body receives the raised object, or an error object for internal
runtime errors. guard is implemented over try/catch and is the
recommended form.
Continuations¶
Element 0 deliberately does not provide full first-class continuations.
call-with-current-continuation and call/cc are aliases for
call-with-escape-continuation (call/ec); the continuation they capture
is one-shot and upward-only, valid only during the dynamic extent of the
call. This supports early exit and exception-like control flow but not
generators or re-entry. Full call/cc cannot capture the Zig frames that
sit between Elz frames when embedded code calls back into the interpreter,
so this restriction is permanent; it matches the choices of other practical
embedded languages.
Composable control flow is provided by delimited continuations instead.
(reset body ...)installs a prompt and evaluates the body.(shift k body ...)captures the computation between the call and the nearest enclosing prompt as the procedurek, unwinds to the prompt, and evaluates the body. Invokingkreinstates the captured computation under a fresh prompt. Continuations are multi-shot and remain valid after their prompt has returned.
Prompts do not cross native frames. A shift whose nearest reset is on
the other side of a Zig primitive (for example, inside a map callback)
raises an error rather than capturing across the boundary. for-each is
written in Element 0, so a shift inside it works.
A local variable that a closure captured inside the captured segment stays
shared. After k resumes the segment, the closure and the resumed code see
the same variable, and a value assigned in one invocation of k is visible
in the next.
dynamic-wind before and after thunks do not re-fire when a captured
segment is reinstated.
Libraries and Modules¶
define-library defines a named library with export, import, begin,
include, and include-ci clauses.
(define-library (my utils)
(export twice)
(import (scheme base))
(begin
(define (twice x) (* 2 x))))
(import (my utils))
(twice 21) ; => 42
(import (name ...)) binds a registered library's exports. Names beginning
with scheme or elz resolve to the built-in global environment. The
library body is evaluated in the global environment, matching how file
modules load; only the declared exports are bound by import, but internal
definitions are not hidden from the global namespace.
Elz's original module system is also available. (import "path.elz") is an
expression returning a module object whose exports are read with
module-ref.
Elz Extensions¶
These features are not part of R7RS-small.
- Hash maps:
make-hash-map,hash-map?,hash-map-set!,hash-map-ref,hash-map-contains?,hash-map-remove!, andhash-map-count. Keys are strings or symbols; a string and a symbol with the same characters are the same key. - Regular expressions:
regex-match?,regex-search,regex-replace, andregex-split, backed by a small NFA engine. Patterns support literals,.,*,+,?, bracket character classes,^,$, and backslash escapes of literal characters. Groups, alternation, counted repetition, and shorthand classes such as\dare not supported.regex-replacetakes the pattern, the replacement, then the input string. - JSON:
json-serializeandjson-deserialize. - Formatting:
formatwith the~a,~s,~%, and~~directives, andvalue->string. - Files and OS:
file-exists?,delete-file,rename-file,current-directory,directory-list,getenv, andsleep-ms. - Time:
current-time,current-time-ms, andtime->components. - List utilities:
take,drop,first,rest,head,tail,nth,indexof,last-element,last-pair,any?,every?,quicksort, andatom?.
Running and Embedding¶
The elz-repl binary starts an interactive session, or runs a file given
as its argument. Uncaught runtime errors report the error code, a message, the
source location as At: file:line, the failing top-level form, and a
backtrace of the calls that led to the failure.
Elz embeds into Zig applications through the Interpreter API in
src/lib.zig; see the Embedding Guide. SandboxFlags
restricts I/O and other capabilities and can impose a wall-clock time
limit, and an instruction budget bounds every evaluation.
Fixed limits keep hostile input from exhausting the native stack and are
reported as errors. Expressions nest at most 1000 levels in the compiler and
2048 levels in the reader, a JSON document nests at most 512 levels, the VM
holds at most 65536 call frames, and callbacks from primitives (map,
apply, call/cc, guard, and the like) nest at most 600 levels deep.
Deviations from R7RS-small¶
The vendored Chibi Scheme conformance suite passes 976 of 977 checks; run it
with make test-conformance. The known gaps follow.
- Exact integers have arbitrary size, but rationals use 64-bit components.
A rational whose numerator or denominator would not fit, or a rational
combined with an integer that does not fit, raises
Overflow. call/cccaptures escape-only continuations, as described above. This is the one failing conformance check, since adynamic-windcannot be re-entered through a continuation.shiftdoes not capture across a native primitive such asmap.dynamic-windbefore and after thunks do not re-fire when a captured segment is reinstated.define-libraryevaluates its body in the global environment; private definitions leak into the global namespace even thoughimportbinds only the declared exports.string-ci=?and the other case-insensitive comparisons compare full case foldings, butchar-upcaseand friends use the simple one-to-one mappings, as the report specifies. No locale-specific or final-sigma handling is applied.- Mutating a quoted literal is allowed rather than an error.
exact-integer-sqrt,exact, andinexactare complete, butrationalizeandnumeratoron inexact numbers work within the 64-bit rational range only.