Skip to content

Repository files navigation

R.O.T

Beta Website

tests version python changelog

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).

Try it

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 stages

There'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).

Taste

// 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).

Highlights

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.

How it works

Three stages on the active pipeline:

  1. 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.
  2. Parser (rot/syntax.py) — recursive-descent with Pratt parsing for expression precedence. Produces ast.Program; every AST node carries line/col source positions.
  3. 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 _locate dispatcher 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.

Repo layout

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.

Tests

python -m pytest tests/                  # 819 passing

CI runs on Python 3.9 / 3.10 / 3.11 / 3.12 via GitHub Actions (workflow).

Roadmap

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.rot produces a binary.

The original v1 (.rot → Python via exec()) is preserved in git history at v1.9.0 and earlier tags.

About

ROT is a custom built programming language. Every step it takes — tokenize, parse, execute, compile to bytecode — happens in front of you.

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages