This document provides guidance for AI coding agents working in the Joker codebase. Joker is a small Clojure interpreter, linter, and formatter written in Go.
core/ # Core interpreter (parser, evaluator, data types)
core/data/ # Core Joker libraries (.joke files: core.joke, test.joke, repl.joke)
core/gen_code/ # Code generation for building Joker
core/gen_go/ # Go code generation helpers
std/ # Standard library wrappers (.joke definitions + Go implementations)
std/*/ # Go implementations for each std namespace
tests/ # Test suites (eval, linter, formatter, flags)
docs/ # Documentation generation
# Full build (recommended) - cleans, generates, vets, builds, regenerates std
./run.sh --build-only
# Quick build - generates, vets, builds
./build.sh
# Manual build steps
go generate ./... # Generate code from .joke files
go vet ./... # Static analysis
go build # Compile
# Build with debugging tools
go build -tags go_spew # Enables joker.core/go-spew debugging function# Run all tests
./all-tests.sh
# Individual test suites
./eval-tests.sh # Core evaluation tests
./linter-tests.sh # Linter functionality tests
./formatter-tests.sh # Formatter tests
./flag-tests.sh # Command-line flag tests
# Run a single test file
./joker tests/run-eval-tests.joke tests/eval/<test-name>.joke
# Example:
./joker tests/run-eval-tests.joke tests/eval/core.joke
# Lint Go code for shadowed variables
./shadow.sh
# Lint Clojure/Joker files
./joker --lint <file.clj>
# Format Clojure/Joker files
./joker --format <file.clj>- Eval tests:
tests/eval/*.joke- Unit tests using joker.test - Forked tests:
tests/eval/*/- Directories withinput.joke,stdout.txt,stderr.txt,rc.txt - Linter tests:
tests/linter/*/- Directories withinput.cljand expectedoutput.txt - Formatter tests:
tests/formatter/*/- Input and expected output files
Organize imports in groups separated by blank lines:
- Standard library (alphabetically sorted)
- Third-party packages
- Local project imports
import (
"fmt"
"strings"
"github.com/pkg/profile"
. "github.com/candid82/joker/core"
_ "github.com/candid82/joker/std/string"
)Note: The core package often uses dot imports (.). Std packages use blank identifier imports (_) for side-effect registration.
| Type | Convention | Example |
|---|---|---|
| Exported types | PascalCase | Symbol, ReplContext |
| Unexported types | camelCase | pos, internalInfo |
| Exported functions | PascalCase | MakeSymbol, NewReplContext |
| Unexported functions | camelCase | processFile, escapeRune |
| Variables | camelCase | dataRead, posStack |
| Constants (global) | SCREAMING_SNAKE_CASE | EOF, VERSION |
| Constants (enums) | PascalCase | READ, PARSE, EVAL |
| Proc functions | camelCase with context | procMeta, procWithMeta |
Small, focused interfaces:
type Equality interface {
Equals(interface{}) bool
}Struct embedding for composition:
type Symbol struct {
InfoHolder // Position info
MetaHolder // Metadata
ns *string
name *string
hash uint32
}Method receivers - pointer for mutable/large, value for immutable/small:
func (a *Atom) Deref() Object { ... } // Pointer - mutable
func (c Char) ToString(escape bool) string { ... } // Value - immutablePattern 1: Panic with custom error types (most common in core)
func CheckArity(args []Object, min int, max int) {
if n := len(args); n < min || n > max {
PanicArityMinMax(n, min, max)
}
}Pattern 2: PanicOnErr helper
f, err := filepath.Abs(filename)
PanicOnErr(err)Pattern 3: EnsureObjectIs assertions*
str := EnsureObjectIsString(obj, "pattern")
num := EnsureArgIsNumber(args, 0)Pattern 4: Deferred recovery with type switching on ParseError, EvalError, Error
| Pattern | Purpose |
|---|---|
a_*.go |
Generated files (do not edit manually) |
*_native.go |
Hand-written Go implementations for std |
*_slow_init.go |
Slow-startup initialization code |
*_fast_init.go |
Fast-startup initialization code |
*_plan9.go, *_windows.go |
Platform-specific code |
Files prefixed with a_ are auto-generated. Do not edit them directly:
core/a_*.go- Generated from core/data/*.jokestd/*/a_*.go- Generated from std/*.joke
To regenerate:
go generate ./... # Regenerate core
(cd std; ../joker generate-std.joke) # Regenerate std- Create
core/data/<namespace>.joke - Add to
CoreSourceFilesarray incore/gen_code/gen_code.go - Add tests in
tests/eval/ - Run
./run.sh --build-only && ./all-tests.sh
- Create
std/<name>.jokewith:gometadata mkdir -p std/<name>(cd std; ../joker generate-std.joke)- Write supporting Go code in
std/<name>/<name>_native.go - Add import to
main.go - Add tests in
tests/eval/ - Rebuild and test
- Joker requires Go 1.24.0+
- Build artifacts (
a_*.go) are committed to the repo due to circular dependencies - The
run.shscript handles the full build cycle including code generation - Use
--build-onlyflag to skip running Joker after building - Core namespaces are listed in
joker.core/*core-namespaces*