A complete, standalone programming language with native observer semantics, real concurrency, a 44-widget GUI toolkit, embedded database, tensor math, and a 52-module standard library (14 STEM) — all in a single zero-dependency C binary.
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 a ~620K minimal binary and installs it to ~/.local/bin/eigenscript.
Requires only gcc — no external dependencies.
Run ./install.sh full to also build the optional HTTP/DB/model binary
(eigenscript-full); that path requires PostgreSQL development headers.
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
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}"
Every value quietly remembers how it has been changing — and you don't pay for it until you ask. Six questions, in plain English:
signal is 10
signal is 14
signal is 15
what is signal # 15 — the value now
who is signal # "signal" — the name you gave it
when is signal # 3 — how many times it has been set
where is signal # ~0.34 — how much information it carries
why is signal # ~-0.016 — how fast that information is changing
(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
loop while not converged: # exits on its own once loss settles
loss is loss * 0.5
report of loss # "converged" (after 20 steps, loss ~ 0)
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. EigenScript's observer rests on a specific idea — a value locating itself from the inside, with no external goal — so the trajectory words don't always match a naive "smaller is better" reading. 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:
game.px is game.px + game.vx * DT
game.py is game.py + game.vy * DT
Inside the block, numeric assignments mutate the existing Value in
place (identity preserved) and the observer is skipped. Outside,
normal behavior resumes. ~22% faster on a mutation-heavy hot loop.
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.
EigenScript numbers are finite by construction. Operations that would create
NaN return 0; operations that would overflow to infinity saturate at
+/-1e308; domain-limited functions clamp their inputs where appropriate
(asin, acos) or return a boundary value (sqrt of -1 -> 0).
arena_mark of null # save allocation point
# ... compute gradients, intermediates ...
arena_reset of null # reclaim all transient allocations
Bounded computation for constrained environments.
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 |
44-widget 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 |
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]
See docs/STDLIB.md for the full library guide.
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 # 2,500+ checks (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 — 250+ builtin functions (199 core + ~60 extensions)
- 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)
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 (2,500+ checks)
make gfx # build with SDL2 graphics (UI toolkit, games)
make install # install to ~/.local/bin
make clean # remove build artifactsOr 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.
