Stable language core · 0.1.0 Emacs-compatible syntax

100% Emacs Lisp core, 1+5+2 extensions, four-target reach.

Eliscript 0.1.0 combines a 100% Emacs Lisp-compatible default core with seven explicit extension packs (contracts, persistent data, pcase-superset destructuring, lexical modules, structured async, lossless JS interop, and multi-backend WASM/Native ABI).

Value model

EliscriptECMAScript / Emacs Compat
nilnull (only falsy value in eliscript-compat core)
js-false / js-null / js-undefinedexplicit high-fidelity JS tri-state sentinels
t / falsetrue / false
numbers and stringsEmacs integer division (/ 7 2) => 3 in compat core; IEEE-754 in ESM
evaluated keywordsinterned immutable Keyword values
vector expressions & persistent-vector32-way persistent immutable Vectors (plus mutable '[] in compat core)
list expressions & persistent-listpersistent linked Lists and Emacs cons cells
map and set literals & persistent-map / persistent-setpersistent HAMT Maps and Sets (plus AVL persistent-sorted-map / persistent-sorted-set)
#queue [...] / persistent-queuepersistent FIFO Queue
cl-defstruct-persistentimmutable structural records with Schema validation & JSON Schema round-trip
(js-array ...) / (js-object ...)explicit native JavaScript containers

In the 0.1.0 Emacs-compatible core (eliscript-compat), only nil is false—0, "", '[], and js-false are truthy, matching GNU Emacs Lisp 100%. In standalone .eli ESM modules, false, nil, and undefined are false while 0 and "" remain true.

Implemented forms & 1+5+2 extensions

defvar / defconst / defvar-once

Top-level dynamic/mutable, immutable, and hot-reload-preserving bindings.

defun / defn / defun-case

Named functions and explicit multi-arity argument-count dispatch (defun-case / lambda-case).

defun-bind / let-bind / let*-bind

Unified pcase-superset structural destructuring (:keys, :strs, :syms, :or, :as, &rest).

let-const / let*-const

Immutable local bindings that reject setq / setf mutation at expansion time.

declare (eliscript-*)

Zero-overhead eliscript-type, eliscript-pure, eliscript-pre, and eliscript-post contracts.

defportable

Worker entries with a statically checked immutable dependency closure.

defmulti / defmethod

Named multimethods and source-ordered method definitions over the portable multimethod library.

defprotocol / eliscript-defprotocol

Open protocols with exact-type, host-category, and default implementations.

lambda / fn / letfn

First-class anonymous and mutually recursive local functions.

let / let*

Parallel and sequential lexical bindings (plus dynamic defvar binding in compat core).

Compile-time & checked macros

(defmacro-checked once (form)
  `(let ((value$ ,form)) value$))

(defun load-once (value)
  (once (+ value 1)))

defmacro and cl-defmacro definitions are processed in source order and removed before emission. Backquote, comma, ,@, &optional, &rest, and &body are supported. defmacro-checked enforces a pure compile-time sandbox that statically rejects side-effecting IO, process, network, or file mutations during macro expansion.

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 & lexical defmodule

(import "eliscript/runtime/core/order" sort)
(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. Alongside file-level (module ...), the eliscript-module extension provides (defmodule name (:export ...) (:import ...) ...) with lexical-private internal bindings (inaccessible externally while exposed to exported functions) and defvar-once hot-reload state preservation. Package subpath imports under eliscript/* support both extensionless specifiers (such as "eliscript/runtime/core/order" or "eliscript/runtime/emacs-compat") and explicit .mjs specifiers across Node, Bun, and the browser compiler. The repository itself tracks zero .mjs source files in Git; all JavaScript modules are synthesized from .eli sources during bootstrap.

Persistent collections

ValueImplementation
ListSingly linked nodes with O(1) front operations and exact suffix sharing
Vector32-way bit-partitioned trie with a tail and O(log32 n) indexed updates
Map / SetHAMT with bitmap, dense array, collision nodes, and path copying
QueuePersistent front/rear Vectors with iterative million-value behavior
Sorted Map / SetStructurally 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, with structural reference-identity (===) short-circuiting for shared subtrees and compact O(1)-step persistent Vector Zipper navigation across 12,000+ levels without prefix array copying. 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 and runtime/core/sequence.eli modules are authored in Eliscript. They accept persistent, native, and externally extended reducible values through collection protocols, return persistent Vectors, add remove, and combine O(1) / O(log32 n) indexed fast-paths for first, last, and nth with unboxed early termination for find and circular array ring buffers with transient Vector builders for take-last and drop-last.

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 and stdlib/core/order.eli surfaces return value-semantic persistent Maps and persistent Vector groups, add frequencies, sort, and sort-by, and use transient HAMT and Vector builders to eliminate intermediate persistent path copying.

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.

100% Emacs Lisp core & 1+5+2 architecture

  • 100% Emacs Lisp default core (eliscript-compat): Preserves Lisp-2 dual function/value namespaces, nil-only falsiness, lexical and dynamic defvar scope, mutable native containers alongside 7 persistent collection types, integer division (/ 7 2) => 3, condition-case / define-error, and full defmacro / cl-defmacro / pcase compatibility.
  • 7 explicit extension packs (1+5+2): Activated by definition form without polluting standard Emacs Lisp — eliscript-contract, eliscript-data, eliscript-bind, eliscript-module, eliscript-async, eliscript-js, and eliscript-abi.
  • Four-target ABI execution: Functions annotated with (declare (eliscript-abi :numeric-f64 ...)) or (declare (eliscript-abi :memory-u32 ...)) execute with identical semantics across Emacs Lisp, JavaScript ESM, WASM, and ARM64 Native binaries.
  • Standalone .eli ESM modules: Keep lexical Lisp-1 resolution, immutable persistent literals [...] / {...} / #{...}, and explicit ECMAScript module boundaries.

See Emacs-Compatible Syntax Specification (0.1.0) for the complete 1+5+2 specification and 50-case Emacs/JS Differential Oracle.