cljgo

module
v0.1.0 Latest Latest
Warning

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

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

README

cljgo

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

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) — no Go toolchain needed to run cljgo, only to cljgo build (the Go compiler is the backend).

$ 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: 102/242 files passing (42.1%), 46.8% of non-skipped, with 218/242 vars resolved (90.1%). 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)
Next Third-party modules via deps.edn, alts!/timeout/select, C FFI (purego), generics, self-hosted core.clj
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.

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 · 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/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.
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