Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
7f78100
feat: compile intrinsics and rest params
RegiByte May 6, 2026
e52fc6d
feat: compile symbol var references
RegiByte May 6, 2026
982bbaa
feat: started VM work, phase 0
RegiByte May 6, 2026
21b7511
feat: add call op to the vm
RegiByte May 8, 2026
a7a0b83
feat: add collection literals
RegiByte May 8, 2026
8e05692
feat: add equivalence tests for VM/Interpreter
RegiByte May 9, 2026
af9477e
feat: add let* bytecode compilation
RegiByte May 11, 2026
bdd829a
feat: refactor vm state and remove DestructurePattern type
RegiByte May 11, 2026
72b7f7f
feat: compile loop/recur to bytecode
RegiByte May 11, 2026
b776ccc
feat: add arithmetic intrinstics to bytecode compiler
RegiByte May 11, 2026
6d6441d
feat: cleanup pass
RegiByte May 11, 2026
d99f65b
feat: add bytecode-to-bytecode function calls with call frames
RegiByte May 12, 2026
2cc5415
feat: harden stack traces
RegiByte May 12, 2026
7c71bbc
feat: add function level recur bytecode op
RegiByte May 12, 2026
d975844
feat: add nested noncapturing fn through bytecode vm
RegiByte May 12, 2026
9af256c
feat: implemented uplvalues for bytecode vm
RegiByte May 12, 2026
a089b40
feat: implemented rest params with upvalue slot
RegiByte May 12, 2026
bca1dcf
feat: cleanup vm tests
RegiByte May 12, 2026
d9b6141
feat: cleanup vm tests part 2
RegiByte May 12, 2026
542c269
feat: add vm callees
RegiByte May 12, 2026
1e92b83
feat: ifn hub and vm callees
RegiByte May 13, 2026
7da02e4
feat: aadd throw vm op
RegiByte May 13, 2026
5dd27a3
feat: add try/catch to vm frames
RegiByte May 13, 2026
8f06c74
feat: add finnaly support for bytecode vm
RegiByte May 13, 2026
ac253b5
feat: add proper binding compilation
RegiByte May 13, 2026
544e253
feat: predicate catch closure bytecode
RegiByte May 13, 2026
9b24179
feat: support fn* self reference
RegiByte May 14, 2026
6619d2e
feat: compile fn* self reference in tail position as recur
RegiByte May 14, 2026
6e31c84
feat: vm quote and destructure support
RegiByte May 15, 2026
e2a63e4
feat: lazy seq demoted to macro
RegiByte May 15, 2026
c77e002
feat: add top level readiness spec
RegiByte May 15, 2026
af7a48c
feat: add support for top level fns in vm path
RegiByte May 15, 2026
71100e0
feat: add support for letfn in compiler vm
RegiByte May 16, 2026
3fe61e3
feat: vm def parity
RegiByte May 16, 2026
4423f59
feat: vm defmacro parity tests
RegiByte May 16, 2026
eac3383
feat: enable oportunistic vm by default
RegiByte May 16, 2026
3bb44ce
fix: experiments
RegiByte May 16, 2026
299c392
feat: add js interop to vm support
RegiByte May 17, 2026
128e231
feat: update presets
RegiByte May 17, 2026
3af7502
feat: added time measurement utilities
RegiByte May 17, 2026
acc36a1
feat: added vm introspection functions
RegiByte May 17, 2026
2b41201
feat: added top level bytecode caching
RegiByte May 17, 2026
e5ec9a7
feat: improve function namings
RegiByte May 18, 2026
3026f4f
feat: add experimental psds
RegiByte May 18, 2026
bb719d6
feat: adding parts of the jank test suite for semantic validation
RegiByte May 19, 2026
5190361
feat: add more parity tests from jank suite
RegiByte May 20, 2026
2d396a9
feat: add more parity tests with jank suite
RegiByte May 22, 2026
f27cf25
feat: lazy seq tests
RegiByte May 24, 2026
5873592
feat: prepare to remove js closure compiler
RegiByte May 25, 2026
dba5605
feat: removed js closure compiler. It will not be forgotten.
RegiByte May 25, 2026
f45defd
feat: prepare for tests migration
RegiByte May 25, 2026
310ebdf
fix: clone value implementation / session isolation
RegiByte May 26, 2026
bb13db4
fix: sequence lazy realization
RegiByte May 28, 2026
0108cdc
feat: analyzer v0 first pass
RegiByte May 30, 2026
5f1a94a
feat: analyzer v0 second pass
RegiByte May 30, 2026
e310263
feat: analyzer slot reconciliation
RegiByte May 30, 2026
88f7b48
feat: ir-driven bytecode compiler
RegiByte May 30, 2026
21cf5ae
feat: enable ir-driven compiler in main path
RegiByte May 31, 2026
9b21e01
fix: ir macro positions
RegiByte May 31, 2026
5a43496
feat: harden ir analysis guards
RegiByte May 31, 2026
740d763
feat: add ir guard for malformed set
RegiByte May 31, 2026
2fd10f4
feat: add ir guard for let* loop* and letfn*
RegiByte Jun 1, 2026
e271f39
feat: add ir guards for fn* and defmacro
RegiByte Jun 1, 2026
271ed4e
feat: analyzer recur strictness
RegiByte Jun 2, 2026
f1531fe
feat: use ast-parsed arities info on ir-compiler
RegiByte Jun 4, 2026
e06ac07
feat: add package loader phase
RegiByte Jun 5, 2026
7b395de
feat: reject misplaced ns forms in REPL eval, leading ns switches
RegiByte Jun 7, 2026
8a2c2d5
feat: cleanup loadfile implementation
RegiByte Jun 7, 2026
a9fe155
feat: graph-aware Vite loader + unresolvable-dep fail-fast
RegiByte Jun 7, 2026
e4466dd
feat: add vector tries psd
RegiByte Jun 21, 2026
2b62dbf
fix: cljam mcp startup
RegiByte Jun 21, 2026
fb4a2e6
feat: add benchmark suite
RegiByte Jun 21, 2026
2d76a69
feat: add module probe experiment
RegiByte Jun 21, 2026
1bc09b9
fix: lazy seqs and range realization
RegiByte Jun 23, 2026
43c0c5d
chore: update bench
RegiByte Jun 23, 2026
0052b5a
feat: make indexed seqs better
RegiByte Jul 5, 2026
4d5f2e9
feat: add ir-driven tree walker
RegiByte Jul 5, 2026
6764d51
feat: add async tree walker
RegiByte Jul 6, 2026
8e0fe5e
feat: phase 4 ns analyzer op, direct the-var resolution, neutral pend…
RegiByte Jul 6, 2026
690ad2d
test: phase 4/S2 probes to direct assertions, differential flips to w…
RegiByte Jul 6, 2026
9aa8ff9
feat: phase 4/S3 async is a lexical boundary (F8)
RegiByte Jul 6, 2026
76f9e7a
feat: phase 4/S4a mode retaxonomy — 'ast' dies, 'off' = walker default
RegiByte Jul 6, 2026
07e151f
feat: phase 4/S4b the kill — form walker, async-evaluator, fn-body se…
RegiByte Jul 6, 2026
dd22218
feat: phase 4/S5 legacy vm/compiler.ts deleted — introspection + comp…
RegiByte Jul 6, 2026
6bd7446
docs: session 352 — S6 bench scoreboard under the new engine taxonomy
RegiByte Jul 6, 2026
4dea871
fix: variadic fn-recur binds the rest slot AS-IS (JVM parity)
RegiByte Jul 7, 2026
4460d16
fix: RB-005 — runtime reader errors are catchable Clojure errors
RegiByte Jul 7, 2026
085dc85
docs: RB-008 — READMEs describe the real architecture
RegiByte Jul 7, 2026
02571d4
feat: use vm for function bodies by default
RegiByte Aug 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,7 @@ packages/cljam/src/bin/version.ts
CLAUDE.md

## Experiments
experiments/mcp-workspace/
experiments/mcp-workspace/

# External repos to reference
.project-references
60 changes: 53 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,19 @@ Or even better, contribute to the project, open an issue or a pull request and I

## What it is

Cljam is an **interpreter**. Source code is read, macro-expanded, and evaluated at runtime. There is no compilation step and no bytecodethe evaluator walks the AST directly.
Cljam is an **interpreter** with a real compiler front-end. Source code is read, macro-expanded, and **analyzed**: a resolver pass turns every form into a resolved ASTlexical slots, closure capture sets, tail positions — and rejects malformed forms with precise, positioned errors before anything runs.

It is designed to be embedded. The core session API is a plain TypeScript object: create a session, inject host functions, evaluate strings. The CLI and nREPL server are thin wrappers around the same session.
Evaluation is a tree walk over that resolved AST. A bytecode VM shares the same front-end: function bodies can additionally be compiled to bytecode and run on the VM, and a differential test harness keeps both engines in exact semantic agreement across the whole test suite. There is no Clojure → JavaScript file output; both engines are internal to the runtime.

An incremental compiler is built in — hot-path forms compile to native closures at definition time, giving a meaningful speed-up over pure tree-walking. There is no Clojure → JavaScript file output today; the compiler is an internal optimization, not a code generator.
It is designed to be embedded. The core session API is a plain TypeScript object: create a session, inject host functions, evaluate strings. The CLI and nREPL server are thin wrappers around the same session.

***

## Features

### Language

* Immutable collections (no structural sharing): vectors, maps, sets, lists
* Immutable persistent collections with structural sharing: vectors (32-way trie), maps (HAMT), sets, lists, lazy sequences
* Namespaces with `ns`, `require`, `refer`, `alias`
* Multi-arity and variadic functions
* Sequential and associative destructuring, including nested patterns, `:keys`, `:syms`, `:strs`, qualified keys, and `& {:keys [...]}` kwargs
Expand All @@ -43,7 +43,7 @@ An incremental compiler is built in — hot-path forms compile to native closure

### Standard Library

`clojure.core` and `clojure.string` are implemented in Clojure itself and loaded at session startup. This means the standard library is readable, forkable, and patchable without touching TypeScript.
`clojure.core`, `clojure.string`, `clojure.edn`, `clojure.math`, and `clojure.test` are implemented in Clojure itself and loaded at session startup. This means the standard library is readable, forkable, and patchable without touching TypeScript.

### Error Handling

Expand Down Expand Up @@ -87,6 +87,52 @@ An incremental compiler is built in — hot-path forms compile to native closure
{:msg (ex-message e) :data (ex-data e)}))
```

### Async

`(async body)` evaluates its body asynchronously and immediately returns a **pending value** — cljam's promise. Inside an `async` block, `@` (`deref`) on a pending value awaits it, exactly like `await`:

```clojure
(async
(let [user @(fetch-user 42)] ; awaits
(str "hello, " (:name user)))) ; => pending of "hello, ..."
```

```js
(async () => {
const user = await fetchUser(42) // awaits
return `hello, ${user.name}` // => Promise of "hello, ..."
})()
```

**Async is a lexical boundary, and closures never inherit it** — the JavaScript model. `@` awaits only in code written literally inside the `(async ...)` form. A `fn` defined inside an async block has a sync body: `@` there is a sync deref and throws a teaching error, the same way `await` inside a plain callback is a syntax error in JS:

```clojure
(async (mapv (fn [x] @(fetch x)) xs)) ; ✗ fn body is sync — throws
```

```js
async () => xs.map((x) => await fetch(x)) // ✗ SyntaxError in JS
```

Give each callback its own async context instead — a collection of pendings, like an array of Promises — and gather with `all`:

```clojure
(async
(let [ps (mapv (fn [x] (async @(fetch x))) xs)] ; each call returns a pending
@(all ps))) ; like Promise.all
```

Pendings also compose without an `async` block, and `deref` takes the JVM's 3-arg timeout form:

```clojure
(then p (fn [x] (* x 10))) ; like p.then(...)
(catch* p (fn [e] :recovered)) ; like p.catch(...) — e is the error value
(deref p 100 :timed-out) ; JVM parity: timeout-ms + timeout-val
(pending? p) ; predicate
```

`try`/`catch`/`finally` work inside `async` bodies across await points, and a rejected pending awaited with `@` throws the same catchable value sync code would see. At the top level, the REPL and `session.evaluateAsync` resolve a returned pending before printing.

### nREPL Server

Full TCP nREPL server with bencode transport. Supports `eval`, `load-file`, `complete`, `clone`, `close`, `describe`, and `interrupt`. Namespace switching after `load-file` is handled automatically.
Expand Down Expand Up @@ -126,7 +172,7 @@ Cljam is semantically close to Clojure but runs on a JavaScript host. The follow
| Java interop (`.method`, `new Foo`, `java.lang.*`) | Not available |
| `deftype` | Not available — `defrecord` covers most use cases |
| `gen-class` | Not available |
| `future`, `promise`, `agent`, `ref`, STM | Not available — use `atom` |
| `future`, `agent`, `ref`, STM | Not available — use `atom` for state, `(async ...)` + pending values for concurrency |
| `Long`, `BigDecimal`, ratio literals (`1/3`) | Numbers are JS floats |
| Class-based `catch` (`catch Exception e`) | Predicate-based catch only |
| `import`, Java class hierarchy | Not available |
Expand Down Expand Up @@ -261,7 +307,7 @@ Full bencode op coverage: symbol info, docstring lookup, source location, cross-

### Compiler

An incremental compiler covering all hot-path forms is already built in. The long-term goal is a self-hosting compiler: the compiler written in cljam and compiled with itself.
The analyzer → AST walker → bytecode VM pipeline is in place; the walker and the VM are kept in exact agreement by a differential harness. The long-term goal is a self-hosting compiler: the compiler written in cljam and compiled with itself.

## License

Expand Down
Loading