Recursive-descent Optimizing Transpiler — a small custom programming language built as a learning project + portfolio piece. ROT is C++/Python-flavored: funct instead of def, cout/coutln instead of print, | instead of , for arg separators, this instead of self, // for comments, C-style braces. Since v2.0.0 the source is tokenized, parsed into a real AST, and executed by a tree-walking interpreter — no exec(), no compile-to-Python.
See rot/__init__.py for the current version and CHANGELOG.md for the full release history (newest first).
pip install -r requirements.txt
python -m rot examples/fizzbuzz.rot # run a program
python -m rot # REPL
python -m rot --no-run file.rot # validate without running
python -m rot --trace file.rot # show the lex / parse / interpret stagesThere's also a browser playground that runs ROT entirely client-side via Pyodide. See web/ for the Next.js site (landing page, docs, playground, paper PDF).
// recursive: factorial
funct fact(n) {
if (n <= 1) { return 1 }
return n * fact(n - 1)
}
coutln(fact(10)) // 3628800
// classes
class Counter {
init() { this.n = 0 }
tick() { this.n += 1 }
show() { coutln(f"count = {this.n}") }
}
c = Counter()
for i in range(3) { c.tick() }
c.show() // count = 3
// error handling with finally
try {
throw "boom"
} catch (e) {
coutln(f"caught: {e}")
} finally {
coutln("cleanup")
}
See examples/ for full programs (counter, factorial, fizzbuzz, functions, hello, multiple_prints, sum_list).
| Feature | Notes |
|---|---|
let keyword |
Opt-in fresh-local binding. Bare x = 1 chain-walks per the v2.10.0 closure-mutation design; let x = 1 always creates a fresh local. |
try / catch / finally |
Full error handling. finally runs through return/break/continue/throw. |
| Slicing | s[a:b:c] for strings and lists. Negative bounds wrap; reverse with [::-1]. |
| f-string format specs | f"{pi:.2f}", f"{n:>5}" — Python-compatible spec syntax. |
| rustc-style errors | Source line + caret + Python-ism hints (print → "did you mean 'cout'?"). |
| Immutable builtins | pi = 3.0 is rejected. Shadow locally with let pi = 3.0. |
| Info-leak hardening | No obj.__class__, no __bases__, no bytes returns from Python passthrough. |
| 35+ builtins | cout, coutln, str, num, len, range, read_file, write_file, sum, sorted, reversed, keys, values, items, chr, ord, seed, exit, and the rest. |
Three stages on the active pipeline:
- Lexer (rot/lexer.py) — hand-rolled, character-by-character. Produces
Token(lexeme, kind, line, col). Multi-character operators, string literals, and f-strings are single tokens. - Parser (rot/syntax.py) — recursive-descent with Pratt parsing for expression precedence. Produces
ast.Program; every AST node carriesline/colsource positions. - Interpreter (rot/interpreter.py) — walks the AST, maintains a chain-walking
Environment(with a frozen builtins layer at the root), dispatches each node. Runtime errors carry the source position threaded via a_locatedispatcher wrapper; the CLI renders them rustc-style.
The standalone Python-source emitter that lived in rot/emitter.py was removed in v2.23.0 once it had drifted too far from interpreter semantics. The tree-walking interpreter is the only reference implementation.
See ARCHITECTURE.md for the deep dive and paper/main.pdf for the design retrospective.
rot/ the language package (~5,700 LOC across lexer / syntax / interpreter / codegen / vm / builtins / repl)
tests/ 819 tests across per-layer, end-to-end, CLI, REPL, compiler, VM
examples/ 7 working .rot programs paired with .expected golden outputs
paper/ 10-page LaTeX design retrospective (compiled main.pdf included)
web/ Next.js 15 + Pyodide site: landing / docs / playground (~10,200 LOC)
ARCHITECTURE.md deep architecture doc
CHANGELOG.md per-release notes
~25,000 LOC total — ~14,100 Python (package + tests), ~10,200 TypeScript/CSS (site), plus build config, CI, and paper source.
python -m pytest tests/ # 819 passingCI runs on Python 3.9 / 3.10 / 3.11 / 3.12 via GitHub Actions (workflow).
The v2.x cut took the language from "regex transpiler" (v1.0.0) to a feature-complete tree-walking interpreter (v2.25.15) with let, finally, slicing, f-string format specs, rustc-style errors, and 819 tests.
- v2.27.x — bytecode VM. A stack-based VM with 38 opcodes (Milestone 2, shipped). The tree-walking interpreter remains the reference; bytecode is the fast path. Following Crafting Interpreters Part III as the rough guide.
- v3.0 (?) — native codegen. LLVM IR via
llvmlite.rot build hello.rotproduces a binary.
The original v1 (.rot → Python via exec()) is preserved in git history at v1.9.0 and earlier tags.