Skip to content

Latest commit

 

History

History
205 lines (158 loc) · 5.68 KB

File metadata and controls

205 lines (158 loc) · 5.68 KB

AGENTS.md - Joker Codebase Guide

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.

Project Structure

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

Build Commands

# 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

Test Commands

# 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>

Test File Organization

  • Eval tests: tests/eval/*.joke - Unit tests using joker.test
  • Forked tests: tests/eval/*/ - Directories with input.joke, stdout.txt, stderr.txt, rc.txt
  • Linter tests: tests/linter/*/ - Directories with input.clj and expected output.txt
  • Formatter tests: tests/formatter/*/ - Input and expected output files

Code Style Guidelines

Imports

Organize imports in groups separated by blank lines:

  1. Standard library (alphabetically sorted)
  2. Third-party packages
  3. 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.

Naming Conventions

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

Type Patterns

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 - immutable

Error Handling

Pattern 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

File Naming

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

Generated Files

Files prefixed with a_ are auto-generated. Do not edit them directly:

  • core/a_*.go - Generated from core/data/*.joke
  • std/*/a_*.go - Generated from std/*.joke

To regenerate:

go generate ./...                    # Regenerate core
(cd std; ../joker generate-std.joke) # Regenerate std

Adding New Code

Adding a Core Namespace

  1. Create core/data/<namespace>.joke
  2. Add to CoreSourceFiles array in core/gen_code/gen_code.go
  3. Add tests in tests/eval/
  4. Run ./run.sh --build-only && ./all-tests.sh

Adding a Std Namespace

  1. Create std/<name>.joke with :go metadata
  2. mkdir -p std/<name>
  3. (cd std; ../joker generate-std.joke)
  4. Write supporting Go code in std/<name>/<name>_native.go
  5. Add import to main.go
  6. Add tests in tests/eval/
  7. Rebuild and test

Important Notes

  • Joker requires Go 1.24.0+
  • Build artifacts (a_*.go) are committed to the repo due to circular dependencies
  • The run.sh script handles the full build cycle including code generation
  • Use --build-only flag to skip running Joker after building
  • Core namespaces are listed in joker.core/*core-namespaces*