SPX is a small, space-exploration themed programming language implemented in Kotlin.
Source files use the .spx extension. The project is a complete language toolchain: lexer,
two parsers (a hand-written Pratt parser and an ANTLR reference grammar), a static type
checker, a macro system, a module system, a tree-walking interpreter and a bytecode
compiler + virtual machine, an advanced REPL, a watch/hot-reload mode and an HTML
documentation generator.
- Space-themed syntax — keywords like
probe,vessel,launch,transmit(see the reference below). - Two parsers — a custom Pratt (top-down operator precedence) parser used by default, and an ANTLR-generated grammar (
Spx.g4) used as a reference/oracle (--parser=generated). - Static type checking — types are validated before execution (
int,double,string,bool,void,List<T>,Promise<T>, nullableT?). - Two execution backends — a tree-walking interpreter (default) and a bytecode compiler with its own stack-based VM.
- OOP — classes (
vessel), interfaces (fleet), inheritance (derives), interface conformance (conforms), access modifiers and static members. - Macros —
@Warp,@Forge,@Buff,@Nerf(see Macros). - Async/await —
asyncfunctions /@Warp,Promise<T>, and thesync(...)built-in to await a job. - Module system —
uplink "name" from "path";. - Advanced REPL — syntax highlighting, history and auto-completion (JLine + Mordant).
- Watch mode — re-runs a file on save while keeping global state.
- Documentation generator — turns
scrolldoc comments into a browsable HTML site.
source (.spx)
│
▼ Lexer Source → Tokens
▼ Parser Tokens → AST (custom Pratt parser, or ANTLR)
▼ Type Checker static type validation
▼ Macro Expander @Warp / @Forge / @Buff / @Nerf → plain AST
▼
├─ Interpreter walks the AST directly (default backend)
└─ Compiler → VM AST → bytecode → stack VM (alternative backend)
Below is the mapping between standard programming concepts and the space-themed keywords.
| Concept | Keyword | Description |
|---|---|---|
| Function | probe |
Define a function (an autonomous operation) |
| Return | relay |
Relay a value back from a function |
| Variable | var |
Declare a variable |
| For Loop | orbit |
Iterate over a range or collection |
| While Loop | cycle |
Loop while a condition is true |
| Break | break |
Break out of a loop |
| Continue | continue |
Skip to the next loop iteration |
| Switch | route |
Route execution based on a value |
| Case | signal |
A case in a switch statement |
| Default | drift |
Default branch in a switch |
| Match | match |
Pattern matching expression/statement |
| Try | mission |
Start a try block (a risky mission) |
| Catch | abort |
Handle errors (abort handler) |
| Defer | cleanup |
Run a block at end of scope (post-mission cleanup) |
transmit |
Transmit a value to standard output | |
| Comment | log |
Single line comment (captain's log) |
| Dictionary | manifest |
Key-value store (cargo manifest) |
| Class | vessel |
Define a class (a spacecraft) |
| Interface | fleet |
Define an interface (shared protocol) |
| Implements | conforms |
Conform to an interface |
| Extends | derives |
Derive from a parent class |
| This | core |
Reference to the current instance |
| Super | flagship |
Reference to the parent class |
| New | launch |
Launch a new instance |
| Static | orbital |
Static member / static block (system-bound) |
| Public | broadcast |
Public access modifier |
| Private | encrypted |
Private access modifier |
| Protected | shielded |
Protected access modifier |
| Import | uplink |
Uplink (import) a module |
| From | from |
Module source |
| Async | async |
Declare an async function (or annotate with @Warp) |
| Await | sync(x) |
Synchronize on a Promise (built-in function) |
Arithmetic + - * / % ^ (^ is power), assignment =, increment/decrement ++ --,
comparison == != < <= > >=, logical && || ! (also the word forms and / or),
ternary cond ? a : b, and the pipe operator |> (passes the left value as the first
argument of the right-hand call).
| Function | Description |
|---|---|
transmit(x) |
Print x to standard output (keyword form). |
sync(job) |
Await an async job / Promise and return its result. |
sleep(ms) |
Pause for ms milliseconds (handy for async demos). |
clock() |
Current time in milliseconds. |
number(x) |
Convert a value to a number. |
log This is a single-line comment
scroll Documentation comment (picked up by the gendoc generator)
scroll @quest What this function does
scroll @requires name string the player's name
scroll @rewards a greeting message
log Hero vessel with a constructor and a probe
vessel Hero {
broadcast var name: string;
broadcast var health: int;
Hero(n: string, h: int): void {
core.name = n;
core.health = h;
}
broadcast probe greet(): void {
transmit("Hello, I am " + core.name);
}
}
probe main(): void {
var h: Hero = launch Hero("Aria", 100);
h.greet();
}
main();
More examples live in the examples/ directory.
Macros are expanded into plain AST before execution.
| Macro | Form | Effect |
|---|---|---|
@Warp |
on a probe |
Mark a function/method as async (returns a Promise). |
@Forge |
on a vessel |
Generate a constructor from the class fields. |
@Buff |
@Buff(stat, amount); |
Expands to stat = stat + amount;. |
@Nerf |
@Nerf(stat, amount); |
Expands to stat = stat - amount;. |
- JDK 21 (the Gradle toolchain targets JVM 21).
- No other setup needed — the Gradle wrapper (
./gradlew) handles dependencies, and ANTLR sources are generated automatically during the build.
# Compile and run the test suite
./gradlew build
# Run only the tests
./gradlew testThe application entry point is the tree-walking interpreter. During development you can run it through Gradle:
# Run a file
./gradlew run --args="examples/Game.spx"
# Start the interactive REPL (no file argument)
./gradlew run
# Use the ANTLR reference parser instead of the custom one
./gradlew run --args="examples/Game.spx --parser=generated"Re-runs the file every time you save, keeping the global environment between reloads:
./gradlew run --args="watch examples/Game.spx"SPX can turn scroll doc comments into a static HTML documentation site.
# Via Gradle
./gradlew run --args="gendoc examples/Player.spx examples/Game.spx --output=docs"
# Or via the helper script
./gendoc.sh examples/*.spx --output=docsOpen the result with open docs/index.html. Supported tags inside scroll comments:
@quest, @requires <name> <type> <desc>, @rewards, @example, @danger, @author, @version.
macOS / Linux:
./bin/kt-languageWindows:
bin\kt-language.batThis launches the advanced REPL with syntax highlighting, history and auto-completion.
macOS / Linux:
./bin/kt-language watch path/to/your/file.spxWindows:
bin\kt-language.bat watch path\to\your\file.spxsrc/main/antlr/Spx.g4 ANTLR reference grammar
src/main/kotlin/
Lexer.kt, Tokens.kt Tokenizer and keyword mapping
parser/ Custom Pratt parser (Parser, ExpressionParser, ClassParser, LBP)
TypeChecker.kt Static type checking
Macro*.kt, types/RpgMacros.kt Macro registry and default macros
ModuleSystem.kt uplink / from module loading
interpreter/ Tree-walking interpreter + Environment
compiler/ Bytecode, Compiler, VM, Serializer
docgen/ scroll doc-comment parser + HTML generator
repl/AdvancedRepl.kt Interactive REPL
Main.kt, MainCompiler.kt Entry points (interpreter / compiler backends)
examples/ Sample .spx programs
src/test/kotlin/ Test suite (lexer, parser, OOP, async, compiler, ...)
A companion VSCode extension (syntax highlighting, snippets, themes and icons for .spx)
lives in the separate SPX-extension project.
This project was built with assistance from LLMs. The key prompts used along the way are
documented in LLM.md.