A compiler for the Teh Tarik programming language, written in Rust. It translates source programs into a custom intermediate representation (IR) and executes them via a built-in IR virtual machine.
Teh Tarik is a small, statically-typed language with a single integer type, functions, arrays, and structured control flow. The compiler pipeline is:
Source (.tt)
│
▼ Lexer — tokenizes the source text
│
▼ Parser — recursive-descent parser with simultaneous semantic analysis
│ and IR code generation (single-pass)
│
▼ IR — register-based intermediate representation (printed to stdout)
│
▼ IR Interpreter — executes the IR on a stack-based virtual machine
The compiler performs semantic analysis in the same pass as code generation, catching type errors, undefined identifiers, duplicate declarations, and misuse of control-flow statements before any IR is emitted.
- Integer arithmetic —
+,-,*,/,% - Comparison operators —
<,<=,>,>=,==,!= - Fixed-size integer arrays —
int[N] arr;with element access viaarr[i] - Functions — with typed parameters and return values;
mainis the entry point - Control flow —
whileloops,if/elsebranches,break,continue - Nested loops —
breakandcontinuetarget the innermost loop via a runtime loop stack - I/O —
print(expr)writes to stdout;read(var)reads an integer from stdin - Single-line comments — prefixed with
# - Semantic error detection:
- Undefined variables and functions
- Missing
mainfunction - Duplicate variable or function definitions
- Scalar/array type mismatches
- Invalid (non-positive) array sizes
breakorcontinueoutside a loop
Prerequisites: Rust toolchain (edition 2021).
cargo build --releaseRun a source file:
cargo run -- examples/add.ttSample input (examples/add.tt):
func main() {
int a;
int b;
int c;
a = 100;
b = 50;
c = a + b;
print(c);
}
Sample output:
Program Parsed Successfully.
--------------------------------------------
%func main()
%int a
%int b
%int c
%mov a, 100
%mov b, 50
%int _temp1
%add _temp1, a, b
%mov c, _temp1
%out c
%endfunc
--------------------------------------------
Valid IR. Executing Generated Bytecode...
150
Run successful. Exit code 0
The compiler prints the generated IR before execution. More examples — including arrays, functions, control flow, and intentional semantic errors — are in the examples/ directory.
src/
main.rs — lexer, recursive-descent parser, semantic analysis, IR code generator
interpreter.rs — IR lexer, IR parser, and virtual machine execution engine
examples/
add.tt — basic arithmetic
math.tt — all arithmetic operators
array.tt — array declaration and element access
function.tt — multiple functions with parameters and return values
if.tt — if / if-else branches
loop.tt — while loop
nested_loop.tt — nested while loops
break.tt — break statement
primes.tt — Sieve of Eratosthenes (arrays + nested loops + conditionals)
advanced.tt — multiple function calls including nested calls
error1.tt–error7.tt — programs with semantic errors (expected to fail with a message)
The only data type is a 32-bit signed integer (int). Arrays hold a fixed number of integers declared at compile time. Functions may take any number of int parameters and return a single int; main takes no parameters.
func add(int a, int b) {
return a + b;
}
func main() {
int[4] arr;
int result;
arr[0] = 10;
arr[1] = 20;
result = add(arr[0], arr[1]);
print(result); # prints 30
}
- Language: Rust (edition 2021), no external dependencies
- Parser style: handwritten recursive-descent, single-pass with inline code generation
- State management: per-function symbol table (
thread_local+RefCell) tracks declared identifiers and their kinds (scalar vs. array); a separate function registry enables forward-reference checking at call sites - IR: flat, register-based text format with labels and conditional/unconditional jumps for control flow
- VM: tree-walking interpreter over the parsed IR bytecode; maintains a call stack with per-frame variable and array storage
Built by Jake Wang and James Chang in March 2026. Refactored by Jake Wang in June 2026.