A complete, standalone programming language with native observer semantics,
OS-thread concurrency (spawn/channel/thread_join), a cooperative task
layer whose numeric task IDs are opaque generation-checked handles, and a
memory model documented in docs/CONCURRENCY.md —
values copy through a channel, including mutable buffers and text builders,
while closure environments and resource handles stay shared by reference,
a GUI toolkit, embedded database, tensor math,
and a standard library with STEM modules — all in a single zero-dependency C binary.
exit of N is state-wide even when a spawned worker calls it: VM threads stop,
blocked concurrency calls wake, and the process exits with status N after
worker teardown. Native I/O must return before teardown completes. Embedded
evaluations have separate stop scopes (see docs/EMBEDDING.md).
vm_run_bytecode raises a catchable value error naming a rejected chunk descriptor; a valid program may still return null. sandbox_run reports descriptor rejection in its structured {ok: false, error: ...} result. Its optional fourth limit, max_work, bounds cumulative bytecode instructions across function calls and callback re-entry (default 10,000,000), independently of loop and allocation limits. This meters VM work, not elapsed time: a blocking native callback must return before the sandbox can stop.
Sandbox execution does not update shared temporal history: assignment values, names, counts and observer snapshots stay outside that history even when recording is armed. Ordinary tape assignment records still emit. Host and trusted descriptor history recording resumes normally outside the sandbox; sandbox temporal reads remain refused.
inauguralsystems.github.io/EigenScript/playground — WASM build of the interpreter; no install, runs entirely client-side. JIT/networking/subprocess are compiled out, everything else is the same runtime that ships in the CLI.
git clone https://github.com/InauguralSystems/EigenScript.git
cd EigenScript
./install.shThis builds the hosted release (including lazily loaded graphics) and installs it to ~/.local/bin/eigenscript.
Requires only gcc — no external dependencies.
Run ./install.sh server to also build the HTTP + raw-TCP + model profile
(eigenscript-server), or ./install.sh server-db to add PostgreSQL; the
latter path requires PostgreSQL development headers. full remains a
compatibility spelling for server-db.
In VM/native-JIT evaluation, an unresolved omitted HTTP, network, database,
or model builtin name raises a catchable value error at its first reference,
naming the unavailable capability and required profile. This occurs before
call arguments are evaluated. Local, captured and host bindings take precedence,
including a binding to null; other unknown names retain undefined_name errors.
--api, lint and token-vocabulary discovery describe the language surface,
not callable availability. Direct host global lookup returns actual absence.
Direct AOT adoption, capability imports and host grants remain separate work.
Homebrew (macOS + Linux):
brew install InauguralSystems/eigenscript/eigenscriptDocker (linux/amd64):
docker run --rm -v "$PWD:/work" ghcr.io/inauguralsystems/eigenscript:latest program.eigsImage tags: :latest, :X.Y.Z, :X.Y, :X for releases; :edge tracks main.
eigenscript program.eigs # run a script
eigenscript # interactive REPL (history, arrow keys, tab completion)
eigenscript --version# Variables
x is 42
name is "hello"
# Functions with named parameters (comma-separated; `add(a b)` is a parse error)
define add(a, b) as:
return a + b
print of (add of [3, 4])
# String interpolation
print of f"Hello {name}, x is {x}"
# Loops
for i in range of 10:
print of i
# Lists
items is [1, 2, 3, 4, 5]
print of (len of items)
# Conditionals
if x > 0:
print of "positive"
# Dictionaries
person is {"name": "Alice", "age": 30}
print of person.name
# Closures
define make_adder(n) as:
define inner(x) as:
return x + n
return inner
add5 is make_adder of 5
print of (add5 of 10) # 15
# Error handling
try:
result is risky_operation of data
catch e:
print of f"Error: {e}"
7
Hello hello, x is 42
0
1
2
3
4
5
6
7
8
9
5
positive
Alice
15
Error: {"kind": "undefined_name", "message": "undefined variable 'risky_operation'", "line": 41}
Comparisons, not and predicates return true/false, a bool. A bool is
not a number: true + 1 and (x > 0) == 1 raise, so test a bool directly
(if x > 0:). See docs/SPEC.md.
Every value quietly remembers how it has been changing — and you don't pay for it until you ask. Six questions, in plain English:
At the REPL, where an interrogative displays its answer:
$ eigenscript
eigs> signal is 10
eigs> signal is 14
eigs> signal is 15
eigs> what is signal => 15 — the value now
eigs> who is signal => signal — the name you gave it
eigs> when is signal => 3 — how many times it has been set
eigs> where is signal => 0.337… — how much information it carries
eigs> why is signal => -0.016… — how fast that information is changing
In a script an interrogative is an expression, so use it where a value
goes — print of (what is signal), if (when is signal) > 2:. A bare
what is signal as a statement is refused (E004) wherever its answer
would be discarded — which is everywhere except the last statement of
its block, the one position nothing pops. Don't lean on that position:
--lint flags the bare form (W019) in both:
signal is 10
signal is 14
signal is 15
print of (what is signal)
print of (when is signal)
15
3
(how is the sixth interrogative; see the observer guide for its current
semantics.)
The runtime also classifies each value's trajectory, so a value can tell you when it has settled instead of you writing epsilon checks by hand:
loss is 50.0
steps is 0
loop while not converged: # exits on its own once loss settles
loss is loss * 0.5
unobserved:
steps is steps + 1 # unobserved, so the bare predicate keeps reading loss
print of (report of loss)
print of steps
print of loss
converged
35
1.4551915228366852e-09
reading is 5.0
reading is 2.0
reading is 5.0
reading is 2.0
reading is 5.0
reading is 2.0
report of reading # "oscillating" (needs a few periods to classify)
Trajectory states: converged, stable, equilibrium, oscillating,
improving, diverging — handy for convergence detection, instability
alerts, or debugging without writing logging code.
The everyday payoff before any theory: loop while not converged deletes the
epsilon / remembered-previous / max-iteration boilerplate every numeric loop in
Python or JS hand-rolls — see
docs/COMPARISON.md → Convergence loops.
What
improvingand the rest actually mean. For numbers the trajectory words read the value's own motion (#861):convergedis the standard stopping criterion — steps settled under a tolerance — andimprovingmeans the steps are contracting toward a limit. For non-numeric values the observer's entropy reading applies — a value locating itself from the inside, with no external goal. The full model, including the resolution knob (set_observer_thresholds), is in docs/OBSERVER.md; the precise predicate semantics (whatconvergedrequires, how it differs fromequilibrium, the N-step window) are in docs/PREDICATES.md.
The observer runs on every assignment so interrogations are always cheap. When you know a hot region won't be interrogated, opt out:
unobserved:
loop while i < n:
acc is acc + weights[i] * xs[i]
i is i + 1
Inside the block, assignments to plain variables skip the observer's
entropy walk and mutate the existing Value in place. Outside, normal
behavior resumes. Measured 2.7x on a 2M-iteration accumulator loop
(834ms → 307ms, n=5 medians); iLambdaAi saw ~22% end-to-end on an
18-hour training run. A scalar assignment inside the block still drops its
O(1) sample into the value window (#1049), so the verdicts report and the
predicates give a numeric binding are the same with the block as without
it — only the entropy channel (why/how, the dH window) is elided.
The block only helps plain variables — x is .... A dict field or
list element (d.k is ..., xs[i] is ...) is never observed in the
first place, and is already mutated in place, so wrapping one buys
nothing; --lint flags a block with no plain-variable assignments as
W020. Note the depth is global rather than lexical: a function called
inside the block runs unobserved too.
Because the depth is dynamic, an interrogation inside the block — or
inside anything it calls — has no trajectory to classify. Rather than
answer false forever, an observer predicate raises inside an
unobserved: block (#871):
Error line 4: converged: the observer is off inside an 'unobserved:'
block, so this predicate has no trajectory to classify — the block's
depth is dynamic, so it also covers functions called from inside it
That is what keeps the annotation a performance knob: it cannot
silently change an answer. Before it raised, wrapping a call in
unobserved: made a settle loop return -1 instead of 22, and a bare
loop while not converged inside one never terminated at all — the
predicate could not become true, and the stall backstop that would have
ended the loop is gated on the same depth.
w is random_normal of [8, 32, 0.1]
h is matmul of [input, w]
h is leaky_relu of h
probs is softmax of h
Builtins: matmul, add, subtract, multiply, divide, softmax,
log_softmax, relu, leaky_relu, zeros, random_normal, shape,
numerical_grad, sgd_update, tensor_save, tensor_load.
Binary tensor files use a shared 10,000,000-element cap. tensor_load and
tensor_save raise catchable limit errors above it; stream_open requires
an integral count from 1 through that cap, and build_corpus includes file
separators in its capped token count. These limits also raise under
EIGS_STRICT=0; see BUILTINS.md for the I/O contracts.
Each of them takes a nested list, a flat list, or a flat numeric buffer,
and returns a buffer when every tensor operand was one. zeros of n returns a
buffer (zeros of [rows, cols] still returns the nested list) — numeric work
wants the flat container, and that is the name it reaches for.
EigenScript numbers are finite by construction. Operations that would overflow
to infinity saturate at +/-1e308. Strict mode is the default: an
out-of-domain call (sqrt of -1), a NaN result or a wrong-typed builtin
argument (abs of "x") raises a catchable error. Run with EIGS_STRICT=0 to
get the finite stand-ins instead: NaN becomes 0, sqrt of -1 is 0, and
asin/acos clamp their inputs. The same rule applies when a raw tensor-kernel
result is read from a buffer; one stored element never changes meaning with
the operator that consumes it. Structural buffer equality and scalar reductions
normalize each input by this rule too, as do mixed buffer/list materialization
and numeric byte or sample conversion. A raised read stops subsequent work.
str_from_bytes and the inflate/deflate codecs (including their zlib_* forms) reject nonnumeric list elements with a builtin-named
type_mismatch error by default; EIGS_STRICT=0 retains numeric-zero
substitution. str_from_bytes stops at numeric NUL, without inspecting later
elements; byte codecs consume the complete list.
Fixed- or optional-shape builtin argument lists reject surplus outer elements in strict mode with a catchable type_mismatch error naming the builtin and maximum width. The check precedes the call's mutation, I/O and tape effects. EIGS_STRICT=0 keeps each builtin's legacy result, including existing null/error stand-ins. Scalar overloads, lists used as data and genuinely variadic arguments keep their documented meaning.
The optional model extension raises a catchable value error when
eigen_generate or eigen_eval_loss receives a prompt longer than the loaded
model's max_seq_len. Training applies the same limit to the combined input
and output lengths; callers must choose their context window explicitly.
arena_mark of null # save allocation point
# ... compute gradients, intermediates ...
arena_reset of null # reclaim all transient allocations
Bounded computation for constrained environments. Values that escape the scope are safe: anything stored into a binding or a container that outlives the window is promoted to the heap at the store (#873) — the arena reclaims only the unstored intermediates.
Arena-backed lists are promoted iteratively when they escape into longer-lived
storage. Repeated references to the same source list share one promoted list.
A promotion may contain at most 100,000 distinct arena-backed lists; exceeding
it raises a catchable limit error, or sandbox inside a sandbox. Promotion
copies and bookkeeping count toward an active sandbox allocation budget.
Pure EigenScript libraries under lib/:
| Module | Description |
|---|---|
lib/math.eigs |
abs, max_val, min_val, clamp, lerp, dot |
lib/list.eigs |
map, filter, reduce, reverse, zip, flatten |
lib/string.eigs |
join, repeat, pad_left |
lib/text_builder.eigs |
text_builder_new, text_builder_append, text_builder_append_line, text_builder_to_string |
lib/int_vector.eigs |
int_vector_new, int_vector_filled, int_vector_from_list, int_vector_copy |
lib/observer.eigs |
is_converged, is_stable, entropy_of, track_regimes, snapshot |
lib/tensor.eigs |
xavier_init, he_init, linear, mse_loss, cross_entropy_loss, accuracy |
lib/io.eigs |
read_lines, write_lines, read_csv, write_csv, slurp |
lib/json.eigs |
json_get, json_has, json_merge, json_from_pairs, json_pretty |
lib/test.eigs |
assert_eq, assert_near, assert_true, test_summary |
lib/format.eigs |
fmt_num, fmt_percent, fmt_bar, fmt_table, fmt_padded |
lib/sort.eigs |
sort_asc, sort_desc, sort_by, sorted_indices, unique |
lib/map.eigs |
map_new, map_get, map_set, map_has, map_keys, map_merge |
lib/functional.eigs |
chain, apply_all, complement, when, iterate, times |
lib/args.eigs |
parse_args, get_flag, get_opt, get_positional, require_opt |
lib/datetime.eigs |
now, today, timestamp, elapsed, timer_start, sleep_ms |
lib/config.eigs |
load_env_file, load_ini, config_get, env_or, config_section |
lib/set.eigs |
set_from, union, intersect, difference, is_subset, set_equal |
lib/log.eigs |
log_debug, log_info, log_warn, log_error, log_level |
lib/validate.eigs |
is_number, is_email, in_range, is_one_of, validate_all |
lib/http.eigs |
http_get, http_post_json, route_get, parse_query, json_response |
lib/queue.eigs |
enqueue, dequeue, push, pop, pq_push, pq_pop |
lib/state.eigs |
sm_new, sm_add_transition, sm_send, sm_state, sm_history |
lib/template.eigs |
render, render_file, render_each, fill |
lib/sanitize.eigs |
sanitize_text, is_garble, clean_response, check_openai |
lib/auth.eigs |
auth_login, auth_check, auth_logout, require_auth |
lib/data.eigs |
df_from_csv, df_select, df_where, df_sort_by, df_join, df_group_by |
lib/stats.eigs |
mean, median, std_dev, variance, histogram, correlation, describe |
lib/concurrent.eigs |
future, await_all, parallel_map, parallel_each, worker_pool |
lib/sync.eigs |
lock_new, lock_acquire, lock_release, with_lock |
lib/store.eigs |
open, put, get, find, upsert, bulk_put, to_dataframe |
lib/ui.eigs |
GUI toolkit (buttons, sliders, tables, charts, trees, etc.) |
lib/physics.eigs |
Kinematics, forces, waves, thermodynamics, EM, optics, relativity, quantum |
lib/chemistry.eigs |
Periodic table, molecular weight, stoichiometry, gas laws, pH, Gibbs |
lib/biology.eigs |
Population dynamics, genetics, DNA/RNA/codons, enzyme kinetics, ecology |
lib/engineering.eigs |
Unit conversion, DFT/signal processing, PID, structural, electrical |
lib/earth_science.eigs |
Atmosphere, seismology, oceanography, astronomy, climate |
lib/linalg.eigs |
Matrices, vectors, determinant, inverse, linear solve, eigenvalues |
lib/calculus.eigs |
Derivatives, integrals, root finding, ODEs (Euler, RK4), Taylor series |
lib/probability.eigs |
Binomial, Poisson, normal, exponential distributions, Bayesian inference |
lib/optimize.eigs |
Observer-aware gradient descent, simulated annealing, genetic algorithm |
lib/simulation.eigs |
Observer-aware spring-mass, Lotka-Volterra, heat equation, equilibrium detection |
lib/numerics.eigs |
Jacobi/Gauss-Seidel solvers, power iteration — observer convergence |
lib/experiment.eigs |
Measurement stability, entropy spike detection, regime classification |
lib/geometry.eigs |
Points, vectors, triangles, polygons, convex hull, circles, transforms |
lib/lab.eigs |
Experiment management, data collection with observer feedback, CSV export |
lib/audio.eigs |
play_note, note_freq, play_chord, drum synthesis |
lib/checksum.eigs |
CRC-32, Adler-32, byte sums over strings or buffers |
lib/bcd.eigs |
Packed BCD codec (RTC registers, DAA), loud on invalid nibbles |
lib/harness.eigs |
Count-and-continue test scaffolding with grep-able pass markers |
lib/observer_slots.eigs |
Named observer slots for watching dynamic collections |
lib/eigen.eigs |
Meta-circular interpreter — full language parity, debug hooks |
lib/complex.eigs |
Complex numbers as [re, im] — arithmetic, polar form, polynomial roots |
lib/autograd.eigs |
Reverse-mode autograd (Wengert tape) over the f64 tensor builtins |
lib/contract.eigs |
Trajectory contracts — require/ensure over observer verdicts |
lib/invariant.eigs |
Runtime invariant declarations checked inside a program |
lib/test_runner.eigs |
Multi-file suite runner — per-file and total tallies |
lib/supervise.eigs |
Observer-native supervision: crash-restart plus wedged-worker detection |
lib/utf8.eigs |
UTF-8 codepoint semantics over byte strings |
lib/pkg.eigs |
--pkg runtime half (eigs.json, SHA-pinned lockfiles) |
The table names importable modules. The lib/ui_*.eigs files are
fragments of lib/ui.eigs, composed by it rather than imported directly,
so they have no row of their own. Inspect lib/ for the current module set.
lib/eigen.eigs snapshots the host values used by its tokenizer, parser, evaluator, import helpers, and fresh meta environments when it loads (#1386). Later host builtin rebinding does not change those dependencies, including entropy's log/divide calls. For load_file, the snapshots use values visible during initialization; loaded code still shares the host scope. String conversion still uses the pristine reserved f-string bridge. Explicit custom environments, debug hooks, and rebinding the interpreter's own helper names remain caller-controlled; captured caller-defined functions retain their own binding behavior.
An imported module's builtin names are the builtins: rebinding len or str
in your program changes them for your code, never inside a module you import
(SPEC, Modules, #1388).
load_file of "lib/list.eigs"
define double as: # functions take one argument, n
return n * 2
doubled is map of [[1, 2, 3], double] # [2, 4, 6]
File loading is relative to the containing file, then the eigs_modules walk,
then the nearest eigs.json project root, then stdlib locations; absolute paths
are used as-is. There is no process cwd search. The REPL (including piped input)
and the embed API without a file path use their working directory as the base.
Add eigs.json at the root
when subdirectory files use root-relative paths. See the exact
shared import/load_file resolution chain.
See docs/STDLIB.md for the full library guide — start at
its "Finding Things" index ("I need to..." → module) so you reach for
stats.median or data.df_group_by instead of hand-rolling it.
Third-party packages are git repos consumable via the built-in package tool:
eigenscript --pkg add alice/vecmath https://github.com/alice/eigs-vecmath v1.2.0
eigenscript --pkg install # reproduce eigs_modules/ from the lockfile
eigenscript --pkg verify # re-hash trees against the lockfilePackage identifiers are <owner>/<name> from the start — bare names
are reserved (the tool rejects them) so the namespace can't get
fragmented by a popularity spike. The disk layout and the
import <name> form stay flat (the leaf), so the example above is
import vecmath in user code.
--pkg add clones the repo into eigs_modules/<name>/, records the
resolved commit in eigs.lock.json, and the consumer can then
import <name>. No code from the dep runs at install time — only
the import does. Force-pushed tags can't sneak a different tree
past --pkg install (the lockfile pins a commit SHA, and verify
catches working-tree tampering).
- eigs-package-template — forkable starting point for a new package
- awesome-eigenscript — curated index of community packages (a list, not a registry)
- docs/PACKAGE_DESIGN.md — design intent; CONTRIBUTING.md — naming + semver guidance for publishers
EigenScript can be embedded in a C/C++ host. The public API is in
src/eigs_embed.h — opaque handles for the interpreter and per-thread
context, REPL-style source eval, error retrieval, ref-counted value
handles, and FFI registration for calling host functions from script:
#include "eigs_embed.h"
static EigsValue *host_add(EigsValue *arg) {
EigsValue *a = eigs_value_list_get(arg, 0);
EigsValue *b = eigs_value_list_get(arg, 1);
double sum = eigs_value_as_num(a) + eigs_value_as_num(b);
eigs_value_release(a);
eigs_value_release(b);
return eigs_value_new_num(sum);
}
int main(void) {
EigsState *st = eigs_open();
eigs_register_function("host_add", host_add);
EigsValue *r = eigs_eval_string("host_add of [3, 4]");
printf("%g\n", eigs_value_as_num(r)); /* 7 */
eigs_value_release(r);
eigs_close(st);
}The runtime is multi-state: a single process can host multiple EigsState
instances concurrently, each with its own global env, JIT cache, module
cache, and observer thresholds. The contract test in src/embed_smoke.c
(run with make embed-smoke) is the worked example.
Ordered as a learning path:
eigenscript examples/hello.eigs # printing
eigenscript examples/basics.eigs # variables, functions, loops, strings
eigenscript examples/fizzbuzz.eigs # conditionals and modular arithmetic
eigenscript examples/fibonacci.eigs # recursion
eigenscript examples/sort.eigs # algorithms with list mutation
eigenscript examples/json_config.eigs # JSON data processing
eigenscript examples/stdlib_demo.eigs # standard library (map, filter, reduce)
eigenscript examples/data_pipeline.eigs # combining libraries for real work
eigenscript examples/observer.eigs # observer semantics (entropy, dH)
eigenscript examples/tensors.eigs # tensor math, gradients, SGD
# STEM simulations
eigenscript examples/stem/orbital_mechanics.eigs # Kepler orbits via RK4
eigenscript examples/stem/climate_model.eigs # energy balance, CO2 sensitivity
eigenscript examples/stem/genetic_drift.eigs # Wright-Fisher population genetics
eigenscript examples/stem/signal_analysis.eigs # DFT frequency detection
eigenscript examples/stem/greenhouse_controller.eigs # closed-loop STEM controllercd tests
./run_all_tests.sh # the full suite (minimal build; full build adds HTTP/DB/model suites)For your own projects, the --test runner is the convention. Write
test_*.eigs files that use the assertion library and end with
test_summary:
load_file of "lib/test.eigs"
assert_eq of [2 + 2, 4, "arith"]
assert_true of [1 < 2, "ordering"]
test_summary of null
eigenscript --test # run every test_*.eigs in ./tests (or cwd)
eigenscript --test path/to/dir # run a specific directory
eigenscript --test a.eigs b.eigs # run specific files
eigenscript --test --json # machine-readable results (for CI)Each file runs in its own interpreter process (isolated assertion state). A file passes iff it exits 0; the runner aggregates the per-file pass/fail counts and exits nonzero if any file failed.
Full map: docs/README.md. Highlights:
- docs/SPEC.md — canonical, executable spec: every construct with a runnable example and exact output, verified by the test suite on every commit (the spec cannot drift from the implementation)
- docs/llms.txt — the whole language in one file for an
LLM: syntax, the
of/spread rule, the outward-scope model, observer idioms, the trap list, and a generate-then-validate loop. Hand it to a model (or read it yourself) before generating.eigs - docs/COMPARISON.md — EigenScript next to Python/JS/Rust/Lisp, with a porting checklist (also suite-verified)
- docs/PERFORMANCE.md — benchmarks as a regression-gated fact: wall-clock medians for humans, deterministic instruction counts for CI
- docs/SYNTAX.md — tutorial-style language guide
- docs/GRAMMAR.md — formal EBNF grammar
- docs/LANGUAGE_CONTRACT.md — edge-case promises
- docs/BUILTINS.md — builtin functions;
eigenscript --apiprints the live index - docs/STDLIB.md — standard library guide
- docs/DIAGNOSTICS.md — error format and exit codes
- docs/TRACE.md — execution trace, deterministic replay, temporal interrogatives
- docs/DEBUGGING.md — the debugging loop and the
--steptape-stepper (time travel + trajectories) - docs/ARCHITECTURE.md — lexer → parser → bytecode VM → JIT internals
- docs/EMBEDDING.md — C embedding API reference (
eigs_embed.h) - examples/errors/ — programs that fail on purpose, each with its expected error message (suite-verified)
Embedded trace replay uses stable host-provided stream keys and parent-local
spawn occurrences to match workers. Tape associations carry state grouping;
event order does not choose replay identity. Hosts explicitly advance replay
sessions between evaluations; unread sibling outcomes prevent advance. See docs/TRACE.md
and docs/EMBEDDING.md for the binding and version contract.
Replay also refuses side-effecting boundaries the tape cannot reconstruct:
in particular, mktemp raises before creating a temporary file.
EigenScript is pre-1.0. The compatibility surface is exactly what
docs/SPEC.md documents: every construct there carries a
runnable example whose output CI verifies on every commit, so a change
in documented behavior cannot land without rewriting the spec in the
same PR. Within the 0.x series, patch releases (0.x.y) never change
documented behavior; minor releases (0.x) may, and any such break
is listed in CHANGELOG.md under the release that made
it. Anything not in the spec — observer internals, bytecode layout,
JIT behavior, trace-tape format, extension builtins (http/model/
db/gfx) — may change in any release. At 1.0 the documented surface
freezes and semantic versioning applies from then on.
EigenScript is solo-maintained today and unpaid. See CONTRIBUTING.md for the build/test gates and how to file PRs, and GOVERNANCE.md for how decisions get made and how contributors can earn commit access over time.
- VS Code:
editors/vscode/— syntax highlighting, f-string interpolation, bracket/indent rules, and a bundled client that auto-launches theeigenlsplanguage server (./install.shputs it on yourPATH). Symlink it into~/.vscode/extensions/or package withvsce. - Vim / Neovim:
editors/vim/— copy into~/.vim/or~/.config/nvim/. - Any LSP client:
make lspbuildssrc/eigenlsp(lint + syntax diagnostics, completion, hover, go-to-definition, find-references, document & workspace symbols, formatting, rename, code actions, and semantic tokens over stdio). - On github.com,
.eigsfiles render with Python highlighting (closest match) until a Linguist grammar is upstreamed.
make # build
make test # build and run the full suite
make server # build with HTTP + raw TCP sockets + model builtins
make server-db # server profile plus PostgreSQL
make zlib # release profile plus DEFLATE codecs (links libz)
make install # install to ~/.local/bin
make clean # remove build artifactsThe tools/consumer_acceptance.sh helper takes the release binary as its
positional input, with separate server and database-capable binaries when
needed. See Consumer acceptance wave
for its profile options, compatibility aliases and recorded binary identities.
Or use the shell scripts directly: ./build.sh and ./install.sh.
The minimal binary is a single C program with no runtime dependencies.
A devcontainer provides the full toolchain (gcc, clang,
libpq-dev, a PostgreSQL sidecar for the ext_db tests) with one click —
Code ▸ Codespaces ▸ Create, or the badge above. It pins linux/amd64 so
the x86-64 JIT is exercised, not silently skipped. The same image backs CI's
Linux legs (ci.yml runs them container:-side), so a green build in a
Codespace and a green CI run can't drift.
Launching a Codespace bills to your account's free monthly hours — you can open this org repo from a personal account without org Codespaces billing.
