TASKS: Implement elf-lang
in <lang>
This document guides an LLM agent to implement elf-lang
in idiomatic, modular, and readable <lang>, progressing through stages 1-5. Each stage builds on the previous one. Keep all earlier stages green as you advance.
Details
Language: <lang> Requirements: <requirements>
Workflow overview
- Author (first action): verify the author. If unset, set a festive elf name using the tool.
- Check:
tools/bin/santa-journal -d ./impl/<directory> author
- If not set, set:
tools/bin/santa-journal -d ./impl/<directory> author set "<christmas-themed elf name>"
- Check:
- Developer tooling setup (Dockerized toolchain) (if missing): if your implementation directory does not have developer tooling set up, create the initial project structure under
./impl/<directory>
with Dockerized toolchain and CLI (see Developer tooling setup section below). Verify images build before proceeding. - Review the journal before starting: run
tools/bin/santa-journal -d ./impl/<directory> entries
to align with context and decisions. - Keep the journal updated: after meaningful progress and before ending the session, update progress via
tools/bin/santa-journal -d ./impl/<directory> progress
and add notes withtools/bin/santa-journal -d ./impl/<directory> entry
. - Read the full spec: open
specs/LANG.md
to understand the complete language. - Read the current stage brief: open
tests/stage-*/README.md
for the stage you are on. - Study examples: review
tests/stage-*/*.santat
to see exact expected behavior and outputs. - Implement incrementally: code the minimum needed to pass the current failing tests for your stage.
- Run tests frequently to steer development using the Makefile in your implementation directory:
- All tests for a stage:
make -C ./impl/<directory> test-stage-N
- Single test file:
make -C ./impl/<directory> test-file FILE=tests/stage-N/<testname>
- All tests for a stage:
- Advancement rule: You may proceed to the next stage only when all tests in the current stage and all prior stages pass.
All invocations MUST use Docker via the standardized Makefile.
Required CLI contract (must not change)
- Run program:
<bin> <file>
- Print AST:
<bin> ast <file>
- Print tokens:
<bin> tokens <file>
- Exit code: not prescribed; tests validate stdout content only
- Streams: results on stdout, diagnostics on stdout
- File encoding: UTF-8; treat input as LF newlines (
\n
) - Source files: elf-lang programs use the
.santa
extension; pass a.santa
file path to the CLI..santat
files are used by thesanta-test
runner only and are not valid CLI inputs. - Determinism/formatting: no banners, prompts, or ANSI color; exact spacing; LF newlines; stable key order; stable ordering across runs; no timing or extra logs
Repository boundaries and tests (strict)
- Allowed: read and review
tests/**/*.santat
to understand the spec. - Allowed: add temporary
.santat
test files under your own./impl/<directory>/tmp/*
solely for local debugging.- These temporary files must be removed before you finish; they are not part of the shared suite.
- Forbidden: any changes outside
./impl/<directory>/**
.- Do not edit files in
./tests/**
,./tools/**
,./README.md
, or any other repository path outside your implementation directory. - Do not modify the shared tests or the test runner in any way.
- Do not edit files in
Additional notes:
- Use
specs/LANG.md
and the.santat
tests as your source of truth. - Ensure Docker images exist and are used for all invocations:
local/santa-<directory>:build
andlocal/santa-<directory>:cli
. Always build and run them via the standardized Makefile targets. - For any ad-hoc/example debug runs you create locally, always wrap the command with
timeout 5
to avoid hangs. Example:timeout 5 make -C ./impl/<directory> run ARGS=./impl/<directory>/tmp/example.santa
Project layout for your implementation
Create your implementation under /impl/<directory>
:
./impl/<directory>/cli
— the CLI entrypoint your:cli
image invokes (optional for host use; tests use Docker)- Any source files/modules as needed (you choose structure)
./impl/<directory>/.gitignore
— language-specific ignores for the chosen <lang>, MUST include/impl/<directory>/tmp
directory
Notes:
- Your
Dockerfile.cli
must setENTRYPOINT
to the CLI. You may keep a localcli
script/binary for convenience, but the test runner will always use the Docker image.
Developer tooling setup (Dockerized toolchain)
If ./impl/<directory>
lacks a Dockerized toolchain, create the following before you start coding:
-
./impl/<directory>/Dockerfile.build
— full toolchain image (non-root,WORKDIR=/work
). -
./impl/<directory>/Dockerfile.cli
— minimal runtime image withARG BUILDER_IMAGE=local/santa-<directory>:build
and anENTRYPOINT
invoking the CLI. -
An initial CLI entrypoint at
./impl/<directory>/cli
that the:cli
image runs. -
Use the existing per-implementation Makefile targets (scoped to
./impl/<directory>
):build-image
: builds/tagslocal/santa-<directory>:build
cli-image
(depends onbuild-image
): buildslocal/santa-<directory>:cli
using--build-arg BUILDER_IMAGE=local/santa-<directory>:build
shell
: interactive shell in the builder imageexec
: run a single command in the builder image (e.g.,make exec CMD="go test ./..."
)run
: run the CLI image with args (e.g.,ARGS="tokens tests/stage-1/01_basic_tokens.santat"
)print-uri
: printdocker://
URI for santa-testtest
,test-stage-1..5
,test-file
: run conformance tests via santa-test
Then build and validate using Make:
make -C ./impl/<directory> build-image
make -C ./impl/<directory> cli-image
make -C ./impl/<directory> test-stage-1
Test integration: use the provided Makefile targets which invoke santa-test with the dockerized CLI.
Acceptance:
make -C ./impl/<directory> build-image
produceslocal/santa-<directory>:build
locally.make -C ./impl/<directory> cli-image
produceslocal/santa-<directory>:cli
.make -C ./impl/<directory> test-stage-1
runs successfully (substitute any stage or file target as needed).make -C ./impl/<directory> shell
provides a development shell with the toolchain without modifying the host environment.
Reference patterns (abridged examples; adapt per language):
- Rust
Dockerfile.build
usesrust:<version>
with non-rootdev
user,WORKDIR=/work
, and cache mounts for Cargo registry/git;Dockerfile.cli
usesARG BUILDER_IMAGE=local/santa-rust:build
, builds in the builder stage, and copies the release binary intogcr.io/distroless/cc-debian12
with anENTRYPOINT
to the binary. - Go
Dockerfile.build
usesgolang:<version>
with non-rootdev
user,WORKDIR=/work
,GOPATH
configured;Dockerfile.cli
builds with cache mounts forgo mod
andgo-build
caches and copies the static binary intogcr.io/distroless/base-debian12
. - Python
Dockerfile.build
includes full tooling (pip/uv/poetry);Dockerfile.cli
installs only runtime deps (e.g.,requirements.txt
). If build artifacts are needed, setARG BUILDER_IMAGE=local/santa-python:build
and copy from the builder stage.
How to run tests
From the repository root, always use the per-impl Makefile:
# Build the CLI Docker image for your implementation first
make -C ./impl/<directory> cli-image
# Run tests using the Dockerized CLI
make -C ./impl/<directory> test-stage-1
make -C ./impl/<directory> test-stage-2
make -C ./impl/<directory> test-stage-3
make -C ./impl/<directory> test-stage-4
make -C ./impl/<directory> test-stage-5
# Run an individual test file
make -C ./impl/<directory> test-file FILE=tests/stage-1/01_basic_tokens.santat
Stage overviews and success criteria
Use specs/LANG.md
for authoritative definitions and each tests/stage-N/README.md
for stage scope.
Before stage 3, the run program mode may be incomplete and tests will only invoke the tokens
and ast
subcommands. When advancing stages, do not regress behavior or formatting for earlier stages.
Stage 1 — Lexing
- Scope: tokens for literals, operators/symbols, keywords/identifiers, numeric underscores.
- Outputs:
tokens
subcommand prints JSON Lines with exacttype
andvalue
slices in stable order. - Pass condition: all
tests/stage-1/**
pass.
Stage 2 — Parsing
- Scope: parse literals, lets, infix ops, lists/sets/dicts, if-expr, function literals/calls, threads/composition (AST-only).
- Outputs:
ast
subcommand prints a single JSON document for the program with pretty-printed JSON (2-space indentation) and ordered keys. - Pass condition: all
tests/stage-1/**
andtests/stage-2/**
pass.
Stage 3 — Basic evaluation
- Scope: runtime with
puts
/CLI integration, arithmetic, variables, errors, string escapes, numeric underscores, precedence, unicode literals. - Outputs: running
<bin> <file>
prints program output exactly; errors go to stdout and exit non-zero. - Pass condition: all
tests/stage-1..3/**
pass with no regressions.
Stage 4 — Collections & indexing
- Scope: list/set/dict operations, string ops (size, indexing), indexing edge cases, mixed-type collection behaviors. Printed order for collections is deterministic: Dictionaries print in ascending order by key; Sets print in ascending order by value.
- Outputs: evaluation semantics must match tests precisely, including error messages and indexing rules.
- Pass condition: all
tests/stage-1..4/**
pass with no regressions.
Stage 5 — Higher-order & composition
- Scope: recursion,
map
/filter
/fold
, arity handling, complex nested expressions. - Outputs: correct evaluation results and exact error messages for arity/type issues.
- Pass condition: all
tests/stage-1..5/**
pass with no regressions.
Output formats (strict)
Tokens (JSON Lines; one object per line):
{"type":"INT","value":"123"}
{"type":"DEC","value":"456.789"}
{"type":"STR","value":"\"hello\""}
{"type":"TRUE","value":"true"}
{"type":"FALSE","value":"false"}
{"type":"NIL","value":"nil"}
- Required keys:
type
(token kind),value
(exact slice) - Tokens must be minified JSON Lines: no spaces, exactly one JSON object per line, exactly as shown above
- Determinism: stable ordering and whitespace; LF newlines; no ANSI color; no extra logs
AST (single JSON document; abridged example):
{
"statements": [
{
"type": "Expression",
"value": {
"name": { "name": "x", "type": "Identifier" },
"type": "Let",
"value": { "type": "Integer", "value": "42" }
}
}
],
"type": "Program"
}
AST output must be pretty-printed JSON with ordered keys, 2-space indentation, and no trailing commas
Program output: exact stdout of running <file>
with no extra logs.
Completion criteria
- All tests pass via the Makefile:
make -C ./impl/<directory> test
- CLI contract is respected and stable across runs.
- Output formats and error messages match expectations exactly. Exact error strings live in
specs/LANG.md
; mismatches (even punctuation or casing) will fail tests. local/santa-<directory>:build
andlocal/santa-<directory>:cli
images build successfully;:cli
ENTRYPOINT invokes the CLI.- Code is idiomatic, readable, and maintainable in <lang>.
Agent handoff and progress tracking
- Purpose: enable multiple agents to continue work seamlessly, knowing exactly what was completed and what remains.
- Location (required): keep continuity data inside your implementation directory only, managed via
tools/bin/santa-journal -d ./impl/<directory>
.- Do not modify files outside
./impl/<directory>/**
.
- Do not modify files outside
Required update cadence (MUST):
- At session start: review entries with
tools/bin/santa-journal -d ./impl/<directory> entries
and align your plan; then set Current focus via a newtools/bin/santa-journal -d ./impl/<directory> entry
. - Immediately after setting the author: add a kickoff entry noting the chosen implementation directory, initial focus, and the exact command to resume (e.g.,
make -C ./impl/<directory> cli-image
). - After completing Developer tooling setup (Dockerized toolchain): add an entry noting images were built and the CLI is runnable, including the exact commands used (e.g.,
make -C ./impl/<directory> build-image
,cli-image
, and a samplerun
/test-stage-1
). - Before starting work on a stage: set its progress to in-progress:
tools/bin/santa-journal -d ./impl/<directory> progress <stage-*> set in-progress
- When all tests pass for a stage and you are moving on: mark it complete:
tools/bin/santa-journal -d ./impl/<directory> progress <stage-*> set complete
- During a stage: after meaningful progress (e.g., a group of tests turns green), add an explanatory
tools/bin/santa-journal -d ./impl/<directory> entry
. Do not change stage status unless transitioning state (e.g., to complete). - Before ending your session: add a
tools/bin/santa-journal -d ./impl/<directory> entry
with current status and next steps. - Only update stage status when transitioning (not-started → in-progress, in-progress → complete):
tools/bin/santa-journal -d ./impl/<directory> progress <stage-*> set <status>
.
Session end checklist (do these before you stop):
- Confirm which stages/tests pass and which fail; reflect in
tools/bin/santa-journal -d ./impl/<directory> progress
. - Note current focus, immediate next steps, and any blockers as a
tools/bin/santa-journal -d ./impl/<directory> entry
. - List important design decisions and file paths touched in the entry.
- Include the exact command(s) to resume testing in the entry.