cljgo

module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 16, 2026 License: MIT

README

cljgo

CI Release Go Reference Go Clojure clojure-test-suite License

muthuishere.github.io/cljgo — docs, examples, and the live status board.

Clojure hosted on Go: a compiler (written in Go) that AOT-emits plain Go source — the ClojureScript model with Go as the JavaScript — plus a tree-walk evaluator that is the REPL and the macro engine.

Install

go install github.com/muthuishere/cljgo/cmd/cljgo@latest

Or grab a prebuilt binary for your platform from the latest release (macOS/Linux/Windows, amd64 + arm64).

cljgo repl, cljgo run and Go interop work from the binary alone, with no Go toolchain installed. cljgo build additionally needs the Go toolchain. From v0.2.0, that is the whole story: a release binary pins the published runtime module in the generated go.mod (require github.com/muthuishere/cljgo v<version>, ADR 0028), and the first build fetches it from the Go module proxy once per machine (~1 MB, a few seconds).

v0.1.0 binaries predate that and still need a checkout of this repo — their generated module replaces the runtime to a local source tree, so point CLJGO_SRC at your clone or run inside it:

git clone https://github.com/muthuishere/cljgo && export CLJGO_SRC=$PWD/cljgo
$ cljgo --version
cljgo CLI version 0.1.0 (Go 1.26.3, Clojure 1.12.5)

$ cljgo repl
cljgo 0.1.0 (Go 1.26.3, Clojure 1.12.5)
user=> (clojure-version)
"1.12.5"

Priorities

  1. Universal interop — any Go module importable and callable with zero bindings; the Go ecosystem is the standard library. C via cgo modules and purego FFI.
  2. Full REPL-driven development — live re-def, defmacro at the prompt, nREPL for CIDER/Calva.
  3. Faithful Clojure principles — persistent data structures, macros, seqs, vars.
  4. High performance in both modes — a feature, not an option.
  5. cgo builds are first-classCGO_ENABLED=1 projects are supported, not tolerated.

Status

Working REPL and native compiler. The same source runs interpreted at the prompt and AOT-compiles to a static Go binary — byte-identical output on both paths (a dual-harness conformance suite enforces this on every commit; a REPL↔binary divergence is a release blocker).

Against the jank clojure-test-suite: 217/242 files passing (89.7%), 90.4% of non-skipped, with 240/242 vars resolved (99.2%). Run cljgo suite to reproduce. Early, moving fast.

Milestone State What landed
M0 REPL: reader (full syntax-quote), loop*/recur, dynamic vars, namespaces
M1 Macroexpansion, defmacro at the prompt, embedded core.clj, clojure.test
M2 cljgo build → native binary, <10 ms startup, fixed-arity calling convention
M3-v0 Zero-ceremony Go interop, both modesrequire-go, package fns/consts, (T,error)[v err], ! unwrap-or-throw
M3.1/3.2 Members: (.Method r …), (.-Field r), (set! (.-Field r) v), ctors (pkg/T. {…}), (go/new T)
M4-v0 Concurrency: (chan)/(chan n), (>! c v)/(<! c), (close! c), (go …) over real goroutines — no CPS rewrite
Result/Option ok/err/just/none + unwrap/and-then/map-ok + let?, #cljgo/ok literals (ADR 0014)
Diagnostics cljgo check --json structured records, cljgo explain <code> (ADR 0015)
nREPL cljgo nrepl — babashka's 13-op surface, per-session *ns*/*1/*out* streaming, .nrepl-port, doc (ADR 0031)
nREPL cljgo nrepl — Calva/CIDER connect; 13-op surface, sessions on goroutine-keyed bindings (ADR 0031)
Next with-precision, C FFI (purego), alts!/timeout, generics, AOT core.clj (binary size), persistent-collection aliasing fix
Try it
;; hello.clj
(require-go '[strings])
(require-go '[strconv])

(println (strings/ToUpper "hello from go's stdlib"))
(println "Atoi! ->" (strconv/Atoi! "42"))   ; unwraps, or throws
(println "Atoi  ->" (strconv/Atoi "oops"))   ; => [0 <error>], errors-as-values
$ cljgo run hello.clj        # interpreted
$ cljgo build hello.clj      # -> ./hello, a static native binary
$ ./hello                    # byte-identical output

The Go ecosystem is the standard library: (require-go '[net/http :as http]) and call it — no bindings, no wrappers, the Go toolchain is the classpath.

Editor REPL: cljgo nrepl, then connect Calva ("Connect to a running REPL") or CIDER (cider-connect-clj) to the printed port — .nrepl-port makes it auto-discoverable.

Development

Authority chain: docs/adr/ (decisions) › design/00-architecture.md (contracts + M0–M5 roadmap) › design/01–07 (component internals) › openspec/ (active change proposals). Process for non-trivial work: ADR → OpenSpec propose/design → apply.

Gates before every commit:

go build ./... && go vet ./... && gofmt -l pkg cmd conformance && go test ./...
pkg/lang     runtime (persistent data structures, vendored from Glojure)
pkg/reader   pkg/ast   pkg/analyzer   pkg/eval   pkg/repl   pkg/emit
cmd/cljgo    CLI (repl · nrepl · run · build · version)
core/        core.clj — Clojure-in-Clojure
conformance/ dual-harness tests (eval + compiled), oracle-cited vs JVM Clojure
design/      architecture + component design docs
docs/adr/    decision log        openspec/   spec-driven change proposals

Toolchain: Go 1.26.

Credits

cljgo stands on work by people who solved the hard parts first.

  • Clojure — Rich Hickey and contributors. The language, and cljgo's specification: every semantic behavior in conformance/ is verified against real JVM Clojure as the oracle, and the Java source (LispReader.java, Compiler.java, PersistentVector.java, PersistentHashMap.java) is the reference the reader, analyzer and data structures were built from.
  • Glojure — the runtime under pkg/lang is a hard fork of Glojure's persistent data structures, seqs, symbols, keywords and vars (v0.6.8). Roughly 17k lines that would have taken months to write from scratch. It stays EPL-1.0; our surgery on it is logged in pkg/lang/PROVENANCE.md.
  • Elvish — the persistent vector in pkg/lang/internal/persistent/vector is a port from the Elvish shell.
  • cljs2go — Håkan Råberg's 2015 Clojure→Go experiment. Read as reference for the emitter's per-op emission strategy and AFn machinery; proof the reader→analyzer→emitter split works with Go as a target. No code taken.
  • let-go — reference for treating Go channels and goroutines as first-class Clojure concurrency rather than reimplementing core.async's CPS transform. No code taken.
  • ClojureScript — the model this project follows: a compiler that emits host source, with the AST "op" vocabulary cljgo's analyzer keeps.

License

  • cljgo's own code — MIT (see LICENSE).
  • pkg/lang/ — Eclipse Public License 1.0, as vendored from Glojure. The MIT grant does not extend to it.

NOTICE has the full breakdown of which license covers what.

Directories

Path Synopsis
cmd
cljgo command
Diagnostics CLI surface (ADR 0015): `cljgo check` and `cljgo explain` expose the structured pkg/diag data model so editors, CI, and LLM agents consume compiler output as data instead of parsing prose.
Diagnostics CLI surface (ADR 0015): `cljgo check` and `cljgo explain` expose the structured pkg/diag data model so editors, CI, and LLM agents consume compiler output as data instead of parsing prose.
Package core embeds the bootstrap clojure.core source (design/00 §6, M1).
Package core embeds the bootstrap clojure.core source (design/00 §6, M1).
docs
diagnostics
Package diagnostics embeds the diagnostic explain pages and the append-only registry lock snapshot so pkg/diag can serve Explain(code) from the binary (ADR 0015; design.md D2).
Package diagnostics embeds the diagnostic explain pages and the append-only registry lock snapshot so pkg/diag can serve Explain(code) from the binary (ADR 0015; design.md D2).
pkg
analyzer
Package analyzer turns read forms into pkg/ast nodes (design/03 §2–§5).
Package analyzer turns read forms into pkg/ast nodes (design/03 §2–§5).
ast
Package ast defines the AST produced by the analyzer (design/03 §1, design/00 §4.1): one uniform *Node with an integer Op tag and a typed per-op payload struct in Sub.
Package ast defines the AST produced by the analyzer (design/03 §1, design/00 §4.1): one uniform *Node with an integer Op tag and a typed per-op payload struct in Sub.
build
Package build is the Zig-style build system (ADR 0021, design/08 §1).
Package build is the Zig-style build system (ADR 0021, design/08 §1).
diag
Package diag implements the structured-diagnostics data model of ADR 0015 (docs/adr/0015-structured-diagnostics-introspection.md) as settled in openspec/changes/structured-diagnostics/design.md D1/D2: the Diagnostic value, the append-only banded error-code registry, Explain pages, and a best-effort adapter from today's error values.
Package diag implements the structured-diagnostics data model of ADR 0015 (docs/adr/0015-structured-diagnostics-introspection.md) as settled in openspec/changes/structured-diagnostics/design.md D1/D2: the Diagnostic value, the append-only banded error-code registry, Explain pages, and a best-effort adapter from today's error values.
emit
Package emit is the Go source emitter (design/04, design/00 §4, ADR 0001): it consumes the analyzer's AST — never re-analyzing, never holding private special-form knowledge (ADR 0002) — and writes plain Go statements into one generated main package, gated through go/format.Source.
Package emit is the Go source emitter (design/04, design/00 §4, ADR 0001): it consumes the analyzer's AST — never re-analyzing, never holding private special-form knowledge (ADR 0002) — and writes plain Go statements into one generated main package, gated through go/format.Source.
emit/rt
Package rt is the runtime bootstrap for cljgo-emitted binaries.
Package rt is the runtime bootstrap for cljgo-emitted binaries.
eval
Package eval is the tree-walk evaluator (design/03 §6, design/00 §4.2).
Package eval is the tree-walk evaluator (design/03 §6, design/00 §4.2).
lang/internal/goid
Package goid returns the current goroutine's ID.
Package goid returns the current goroutine's ID.
lang/internal/identity/pkga
Package pkga simulates one separately-compiled emitted package that hoists keyword literals to package-level vars, exactly as the cljgo emitter will (design doc 00 §4.4).
Package pkga simulates one separately-compiled emitted package that hoists keyword literals to package-level vars, exactly as the cljgo emitter will (design doc 00 §4.4).
lang/internal/identity/pkgb
Package pkgb simulates a second, independently-compiled emitted package interning the same keyword literals as pkga.
Package pkgb simulates a second, independently-compiled emitted package interning the same keyword literals as pkga.
lang/internal/persistent/vector
Package vector implements persistent vector.
Package vector implements persistent vector.
lang/internal/seq
Package seq provides a definition of an internal Seq interface for use across packages.
Package seq provides a definition of an internal Seq interface for use across packages.
nrepl
Bencode codec for nREPL's wire format (ADR 0031: own the codec — everything nREPL puts on the wire in ~150 lines, zero external deps).
Bencode codec for nREPL's wire format (ADR 0031: own the codec — everything nREPL puts on the wire in ~150 lines, zero external deps).
reader
Package reader implements the Clojure reader: UTF-8 text in, pkg/lang persistent data structures out, with :file/:line/:column/ :end-line/:end-column metadata on every IObj form.
Package reader implements the Clojure reader: UTF-8 text in, pkg/lang persistent data structures out, with :file/:line/:column/ :end-line/:end-column metadata on every IObj form.
repl
Package repl is the REPL driver of design/03-analyzer-eval.md §7b: a loop of Read (pkg/reader) → Analyze+Eval (pkg/eval) → bind *1 *2 *3 (and *e on error) → print via pr-str, over an injected reader/writer pair.
Package repl is the REPL driver of design/03-analyzer-eval.md §7b: a loop of Read (pkg/reader) → Analyze+Eval (pkg/eval) → bind *1 *2 *3 (and *e on error) → print via pr-str, over an injected reader/writer pair.
version
Package version is the single source of truth for cljgo's version, the Go toolchain hosting it, and the Clojure language level it targets.
Package version is the single source of truth for cljgo's version, the Go toolchain hosting it, and the Clojure language level it targets.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL