This project is a compiler for the Pluto programming language, written in Go. It compiles Pluto source files (.pt and .spt) into LLVM Intermediate Representation (IR), which is then compiled into native executables.
main.go: The command-line interface for the Pluto compiler. It scans for source files, manages the compilation process, and generates the final executables.go.mod: The Go module file, which defines the project's dependencies.test.py: A Python script used to run integration tests.requirements.txt: Lists the Python dependencies for the test runner.ast/: Defines the Abstract Syntax Tree (AST) for the Pluto language.lexer/: Handles tokenization of the Pluto source code, including support for indentation-based syntax.parser/: Parses the token stream from the lexer and builds the AST. It uses a recursive descent approach and supports operator precedence.compiler/: Performs type checking, symbol resolution, and generates LLVM IR from the AST. It includes a CFG-based type solver for type inference.runtime/: Contains a small C runtime library that is embedded in the binary and linked with the compiled Pluto code to provide low-level operations.tests/: Contains end-to-end integration tests, with.sptsource files and.expfiles for the expected output.pt.mod: The module declaration file, similar togo.mod.
- Go 1.26+
- LLVM 22 development libraries and tools (
llvm-config,clang) - Python 3.x (for build/test helpers)
- pip (for installing Python dependencies)
On macOS with Homebrew, you can install LLVM with brew install llvm and add it to your path. The path is /opt/homebrew/opt/llvm/bin (ARM) or /usr/local/opt/llvm/bin (Intel).
python3 build.py and python3 test.py derive the LLVM 22 byollvm CGO flags from llvm-config. Direct go build/go test can use eval "$(python3 scripts/llvm_env.py --shell)".
-
Install Python dependencies:
pip install -r requirements.txt
-
Build the compiler:
# Development build (version shows as "dev") python3 build.py # Production build with version from git tag (optional, used for releases) python3 build.py --release
-
Run the full test suite:
python3 test.py python3 test.py --leak-check
-
Run the Python test runner directly:
python3 test.py # Run all tests python3 test.py --keep # Keep build artifacts for debugging python3 test.py --leak-check # Run tests with memory leak detection
-
Run unit tests:
go test -race ./lexer ./parser ./compiler -
Run the compiler:
./pluto [directory] # Compiles .pt and .spt files in directory ./pluto -emit-ir [directory] # Also keeps linked pre-optimization script .ll files in the cache ./pluto -version # Show version information (or -v) ./pluto -clean # Clear cache for current version (or -c) PLUTO_TARGET_CPU=portable ./pluto . # Override host CPU tuning (defaults to native)
This will compile all
.ptand.sptfiles in the specified directory and generate executables in the same directory. -
Run specific integration tests:
python3 test.py tests/math
The compilation process consists of two main phases:
- Code Compilation: All
.ptfiles in the target directory are compiled into a single LLVM IR module. These files are intended for reusable functions and constants. - Script Compilation: For each
.sptfile, the compiler performs the following steps: a. Links the code module (from the.ptfiles) into the script's module. b. Generates LLVM IR for the script. c. Optimizes the IR and emits an object file in-process with LLVM. d. Links the object file with the C runtime to create a native executable.
- Module resolution: walks up to find
pt.mod; cache key based on module path. - Cache layout (versioned to isolate different compiler versions):
<PTCACHE>/<version>/runtime/<hash>/for compiled runtime objects- Default host CPU builds:
<PTCACHE>/<version>/<module-path>/{code,script} - Non-default
PLUTO_TARGET_CPUbuilds:<PTCACHE>/<version>/target_cpu-<setting>/<module-path>/{code,script}
The compiler uses a cache to store intermediate build artifacts (LLVM IR and object files) to speed up subsequent compilations.
- The cache location is determined by the
PTCACHEenvironment variable. - If
PTCACHEis not set, it defaults to:- macOS:
$HOME/Library/Caches/pluto - Linux:
$HOME/.cache/pluto - Windows:
%LocalAppData%\pluto
- macOS:
To clear the cache for the current version, run ./pluto -clean. To clear the entire cache manually, delete the appropriate directory.
- Quick smoke check:
./pluto tests/to see compile/link output. PTCACHEoverrides cache location; ensure PATH includes LLVM 22llvm-configandclang.PLUTO_TARGET_CPUoverrides host CPU tuning; set it toportableto disable the default-mcpu=native.- Use
pluto -cleanto clear cache for current version.
- Indentation: Use tabs for indentation across the repository; do not convert leading tabs to spaces. Preserve existing indentation when editing.
- Go files: Leading indentation MUST be tabs (this is gofmt's default). Run
gofmt -w(or enable format‑on‑save) before committing. It's fine for gofmt to leave spaces for alignment within a line; the rule applies to leading indentation only. - Go formatting:
go fmt ./...; basic checks:go vet ./.... - Packages: lowercase short names. Exports:
CamelCase. Tests:*_test.gowithTestXxxfunctions. - Avoid local helper closures in production and tests. Keep short logic inline; when extraction is worthwhile, define a package-level function or method and pass dependencies explicitly, even for a single caller. Use closures only when a callback API requires one or lexical capture is essential; test helpers that assert should call
t.Helper(). - Filenames: lowercase with underscores where needed (Go convention).
- Unit tests live under each package; run with
go test -raceas above. - E2E tests live in
tests/:- Inputs:
.spt(scripts) and optional.pt(shared code). - Expected output:
.exp(line-by-line, supportsre:regex prefixes).
- Inputs:
- Run:
python3 test.py [--keep].- Focused run:
python3 test.py tests/math. - Leak check run:
python3 test.py --leak-check [tests/math].
- Focused run:
- Leak tools by platform:
- Linux:
valgrind - macOS:
leaks
- Linux:
CI: GitHub Actions builds with Go 1.26, installs LLVM 22 + valgrind, and runs python3 test.py --leak-check on pushes/PRs.
- Commit style: Conventional Commits for the subject line (e.g.,
feat(parser): ...,refactor(compiler): ...). - Production-quality commit expectation for non-trivial changes:
- Add a short body describing what changed and the user-visible or behavioral impact.
- Include important context needed by future readers (constraints, tradeoffs, or risks) when not obvious from the diff.
- Reference issue/ticket IDs when applicable.
- Call out breaking changes or migration steps explicitly.
- Test/validation command details are optional in commit messages; put full verification details in the PR description when possible.
- PRs: include a clear description, linked issues, unit/E2E tests for changes, and sample before/after output where relevant.
When reviewing PRs or preparing code for review, check:
- Modularity & readability: Is each function focused on a single responsibility? Can a new reader follow the logic without excessive cross-referencing?
- Placement: Do changes belong in the functions, arguments, and structs they touch, or should logic be moved to a more natural home?
- Duplication: Is there code that duplicates existing patterns in the codebase? Extract shared logic into a helper or utility (e.g.,
ast.ExprChildrenfor tree-walking) rather than repeating type-switches or loop bodies. - Nesting & control flow: Can nested
if/forblocks be flattened using earlyreturn,continue, orbreak? Prefer guard clauses over deep indentation. - Naming: Are new identifiers clear, consistent with existing conventions, and free of ambiguity? Avoid mixing synonyms (e.g.,
tmpvstemp) for the same concept. - Edge cases: Are zero-length slices, nil maps, and boundary values handled? Does the code distinguish "absent" from "empty"?
- Resource cleanup: For compiler code specifically — are heap temporaries freed on all paths (true and false branches)? Are borrowed vs owned semantics respected?
- Keep changes minimal and focused; avoid reflowing or reindenting unrelated lines.
- Use tabs for indentation (preserve existing indentation style).
- NEVER add "🤖 Generated with..." footers to git commits.