A small language with a long memory
EELisp is a compact Lisp where tables, records, forms and an agenda are part of the language rather than a library you bolt on. Define a table, insert a record, ask a question, get a table back. It runs anywhere Rust runs, and it fits in one binary.
;; A table is a form, not a schema file
(deftable books (title:string author:string year:number))
(insert books {:title "The Mythical Man-Month" :author "Brooks" :year 1975})
(insert books {:title "Thinking Forth" :author "Brodie" :year 1984})
(query books :where "year < ?" :params (list 1980) :order "year")
┌────┬────────────────────────┬────────┬──────┐
│ id │ title │ author │ year │
├────┼────────────────────────┼────────┼──────┤
│ 1 │ The Mythical Man-Month │ Brooks │ 1975 │
└────┴────────────────────────┴────────┴──────┘
Lexer, reader, environments and a tail-call-optimising evaluator, with macros and
quasiquote. Around 95 builtins, plus a prelude written in EELisp itself. Proper tail calls
mean loop/recur runs forever without growing the stack.
deftable, insert, query, update,
delete, on real SQLite. Field types, defaults, required fields and choices
are stored with the schema, so a table survives a reload intact. browse and
defform give you a grid and a form, not a printout.
Items with dates, priorities and notes; hierarchical categories; templates; views; and a rules engine whose conditions are ordinary EELisp, stored as source and evaluated against each item. Type in your own words and let the rules do the filing.
Most organisers make you file things. Lotus Agenda, in 1988, inverted it: you write in plain language, and rules decide where it belongs. EELisp keeps that, and because a rule's condition is just EELisp held as source and re-evaluated, the whole language is available inside it.
(defcategory work/calls)
(add "call Bob tomorrow !!") ; date and priority parsed out of the sentence
(defrule calls
:when (str-matches text "call|phone|ring")
:assign "work/calls")
(apply-rules) ; → 1
(items :category "work/calls")
Inside a condition the item is in scope — text, notes,
categories, props — along with get,
has-category, overdue? and match for regex captures.
EELisp is one Rust crate that builds a library and a CLI. You need a Rust toolchain:
git clone https://github.com/santacroce-tech/eelisp-rs
cd eelisp-rs
cargo build --release --bin eelisp
./target/release/eelisp # a REPL
./target/release/eelisp script.eelisp # run a file
./target/release/eelisp --serve # JSON-line RPC on stdin/stdout
Or use it inside an application: EEditor is a notes editor built on this engine, and the whole language is available from its REPL and from its keybindings.
Embedding it in your own Rust program takes about five lines — see embedding.
EELisp began as the scripting layer of a Mac editor written in Swift. This is a clean-break rewrite in Rust, done to fix the things that were wrong in the original — no tail calls, broken macro rest-parameters, no transactions, order-dependent behaviour — and to make the engine reusable rather than welded to one app.
The result is deliberately portable: no threads and no I/O in the library itself, no platform types in the value representation. One engine backs a desktop app, a phone app and a command line, and the same code is meant to reach WebAssembly.
MIT licensed. 74 tests. Source at github.com/santacroce-tech/eelisp-rs.