Skip to content

About

A standalone programming language with native observer semantics, closures, dictionaries, error handling, and a self-interpreter — all in a single zero-dependency C binary

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Latest commit

 

History

1,500 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

EigenScript

CI Release License: MIT Stars OpenSSF Best Practices CodSpeed CodeQL OpenSSF Scorecard Open in GitHub Codespaces

EigenScript

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.

Try it in your browser

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.

Install

git clone https://github.com/InauguralSystems/EigenScript.git
cd EigenScript
./install.sh

This 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/eigenscript

Docker (linux/amd64):

docker run --rm -v "$PWD:/work" ghcr.io/inauguralsystems/eigenscript:latest program.eigs

Image tags: :latest, :X.Y.Z, :X.Y, :X for releases; :edge tracks main.

Run

eigenscript program.eigs    # run a script
eigenscript                 # interactive REPL (history, arrow keys, tab completion)
eigenscript --version

The Language

# 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.

Ask Your Code

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 improving and the rest actually mean. For numbers the trajectory words read the value's own motion (#861): converged is the standard stopping criterion — steps settled under a tolerance — and improving means 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 (what converged requires, how it differs from equilibrium, the N-step window) are in docs/PREDICATES.md.

Don't Ask — unobserved

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.

Tensor Math

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 Memory

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.

Standard Library

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.

Packages

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 lockfile

Package 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).

Embedding

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.

Examples

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 controller

Test Suite

cd tests
./run_all_tests.sh    # the full suite (minimal build; full build adds HTTP/DB/model suites)

Writing your own tests

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.

Documentation

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 --api prints 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 --step tape-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.

Stability

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.

Contributing

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.

Editor Support

  • VS Code: editors/vscode/ — syntax highlighting, f-string interpolation, bracket/indent rules, and a bundled client that auto-launches the eigenlsp language server (./install.sh puts it on your PATH). Symlink it into ~/.vscode/extensions/ or package with vsce.
  • Vim / Neovim: editors/vim/ — copy into ~/.vim/ or ~/.config/nvim/.
  • Any LSP client: make lsp builds src/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, .eigs files render with Python highlighting (closest match) until a Linguist grammar is upstreamed.

Build from Source

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 artifacts

The 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.

Develop in a container

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.

About

A standalone programming language with native observer semantics, closures, dictionaries, error handling, and a self-interpreter — all in a single zero-dependency C binary

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages