Value model
| Eliscript | ECMAScript |
|---|---|
nil | null |
undefined | undefined |
t / false | true / false |
| numbers and strings | ECMAScript numbers and strings |
| evaluated keywords | interned immutable Keyword values |
| vector expressions | persistent immutable Vectors |
| list expressions and quoted lists | persistent linked Lists |
| map and set literals | persistent HAMT Maps and Sets |
#queue [...] | persistent FIFO Queue |
(js-array ...) / (js-object ...) | explicit native containers |
Truthiness is Lisp-like: only false, null, and undefined are false. Zero and empty strings remain true.
Implemented forms
defvar / defconstTop-level mutable and immutable bindings.
defun / defnNamed functions with single or argument-count-dispatched multi-arity bodies.
defportableWorker entries with a statically checked immutable dependency closure.
defmulti / defmethodNamed multimethods and source-ordered method definitions over the portable multimethod library.
defprotocol / extend-*Open protocols with exact-type, host-category, and default implementations over the canonical protocol library.
lambda / fnFirst-class anonymous functions with fixed and variadic multi-arity clauses.
letfnLexically scoped local functions with direct, mutual, multi-arity, portable, and async recursion.
let / let*Parallel and sequential lexical bindings.
if / when / unlessValue-producing conditional forms.
if-let / when-let / if-some / when-someSingle-evaluation conditional bindings with explicit truthy or nil-only matching.
condOrdered condition branches with a final value.
case / condpSingle-evaluation constant or predicate dispatch with lazy selected results.
-> / ->> / as->First-position, last-position, and named expression threading.
cond-> / cond->>Ordered test and step pairs over one accumulated value.
some-> / some->>Nil-short-circuiting pipelines that preserve false and undefined values.
whileLoop with Lisp truthiness and explicit mutation.
loop / recurStack-safe function and binding iteration with checked tail positions and simultaneous rebinding.
and / or / notShort-circuit operators that return operand values.
list / vector / hash-map / hash-setCanonical immutable persistent collection constructors.
[...] / {...} / #{...} / #queue [...]Persistent Vector, Map, Set, and Queue literals.
car / cdr / cons / nthProtocol-backed List and indexed collection operations.
nil? / undefined? / nullish?Strict null and undefined checks, plus an explicit combined nullish check. null remains a compatibility alias.
js-array / js-objectExplicit native JavaScript container construction.
get / putExplicit host property access, fallback, and assignment.
object-keys / object-has? / object-assocPortable own-key discovery, own-property checks, and shallow immutable association.
js-callMethod invocation that preserves its receiver.
newConstruct native JavaScript values.
funcall / applyCall first-class functions directly or from arrays.
js*A narrow, explicit raw JavaScript escape hatch.
Compile-time macros
(defmacro once (form)
`(let ((value$ ,form)) value$))
(defun load-once (value)
(once (+ value 1)))
defmacro definitions are processed in source order and removed before emission. Backquote, comma, &rest, and &body are supported. gensym and trailing-$ template symbols create deterministic private bindings, while ordinary template symbols deliberately resolve at the call site. Every expanded form is checked by the lexical analyzer.
Both compiler generations interpret the same deterministic macro subset. Unlisted host functions, editor state, files, environment variables, and processes are unavailable unless a future explicit compiler capability exposes them.
ECMAScript modules
(import "react" :default React useState)
(import "library" :as Library)
(import "side-effect-only")
(export Component helper)
(export-default Component)
One Eliscript source file emits one ESM file. The project builder recursively resolves root-contained relative source imports, preserves their directory tree, rewrites them to .mjs, emits Source Maps, and records deterministic graph identity. Package and JavaScript specifiers remain host-defined.
Persistent collections
| Value | Implementation |
|---|---|
| List | Singly linked nodes with O(1) front operations and exact suffix sharing |
| Vector | 32-way bit-partitioned trie with a tail and O(log32 n) indexed updates |
| Map / Set | HAMT with bitmap, dense array, collision nodes, and path copying |
| Queue | Persistent front/rear Vectors with iterative million-value behavior |
| Sorted Map / Set | Structurally shared AVL trees with custom comparators and range queries |
All collection families participate in value equality, hashing, metadata, canonical data text, generic lookup, reduction, construction, stack, and reversible traversal protocols where applicable. Records extend persistent Map behavior without surrendering their declared type identity.
Owner-token Transient Vector, Map, and Set builders support efficient isolated construction. persistent! completes the builder once; later mutation or reuse is rejected, and static compiler analysis prevents transient ownership from escaping trusted construction scopes.
Portable sequences
(import "../../stdlib/sequence.eli" map filter reduce range)
(map (lambda (value) (* value 2)) (range 1 5))
The standard library is written in Eliscript and includes persistent hash and AVL-sorted collections, protocols, replayable and memoized lazy sequences, pull transduction, immutable tree rewriting and zipper editing, text, numeric, Result, JSON, state, and host-interop modules without adding application-framework dependencies.
The maintained stdlib/core/seq.eli algorithms are also written in Eliscript. They accept persistent, native, and externally extended reducible values through collection protocols, return persistent Vectors, add remove, and use exact early termination for bounded transforms and searches. Adjacent Eliscript modules expose protocol, collection, transducer, and transient APIs while the optimized dispatch substrate remains in JavaScript.
Portable text
(import "../../stdlib/text.eli" contains? strip-prefix trim)
(trim (strip-prefix "#" "# article "))
Thirteen functions provide literal matching, end-exclusive slicing, prefix and suffix removal, ASCII-boundary trimming, blank checks, joining, and repetition. They are written in portable Eliscript without regular expressions or host string methods. Indices follow ECMAScript UTF-16 code units rather than locale-aware grapheme clusters.
Immutable objects
(import "../../stdlib/object.eli" assoc pick)
(import "../../stdlib/data.eli" index-by)
(pick (assoc (object :name "Eliscript") :runtime "JavaScript")
[:name :runtime])
Eleven object functions enumerate own keys, test own properties, associate without mutation, remove and merge keys, transform values, and select fields. The data module adds keyed lookup, grouping, and counting by importing object primitives through explicit import-portable edges. Graph-aware portable builds verify those targets and prune every generated module.
The runtime-backed stdlib/core/data.eli surface returns value-semantic persistent Maps and persistent Vector groups, adds frequencies, and uses transient builders for efficient final construction.
Recursion and constant stack
(defun sum-to (n total)
(if (= n 0)
total
(recur (1- n) (+ total n))))
recur is checked at compile time and lowers self recurrence or a lexical loop to an iterative transfer with constant JavaScript stack use. The standard-library trampoline runs returned zero-argument thunks and supports open or mutual recursion with a fixed stack.
Ordinary calls in tail position remain ordinary JavaScript calls. General self and mutual tail-call optimization is recorded as Draft specification 0180 and receives no current implementation credit.
Intentional differences
- Lexical scope is mandatory; dynamic scope is not inherited.
- Numbers follow ECMAScript behavior rather than Emacs integer semantics.
- Lists, Vectors, Maps, Sets, Queues, and Records are distinct persistent value categories.
falseis distinct fromnil.- Modules, objects, promises, and browser APIs are explicit language concerns.
Compatibility is a starting point, not a ceiling. Later versions may add more expressive forms when Emacs Lisp syntax or semantics would hold the language back.