Language Reference
This page is the language at a glance — every form Tulpar’s parser accepts, in one place. The Language Guide walks through each topic with examples; this is the spec-ish summary for when you already know what you’re looking for.
File structure
Section titled “File structure”A .tpr file is a sequence of top-level statements: imports, function
declarations, struct (type) declarations, and any other expression or
control-flow statement that runs immediately when the program starts.
There is no main() requirement — top-level statements execute in
source order.
import "wings"; // import (top-level only)
type Point { // struct declaration int x; int y;}
func origin(): Point { // function declaration Point p = { x: 0, y: 0 }; return p;}
print(origin()); // top-level expression — runs at startup| Type | Literal example | Notes |
|---|---|---|
int | 42, -1, 0xff | 64-bit signed. |
float | 3.14, 2.0, -0.5 | 64-bit IEEE 754. |
bool | true, false | |
str | "hi", t"x={n}" | UTF-8. t"..{expr}.." interpolates; + coerces. |
json | {"k": 1}, [1, 2, 3] | Tagged-union for objects/arrays/scalars at runtime. |
array<T> | [1, 2, 3] | T ∈ int / float / str / bool / json. |
void | — | Function return only — no values of type void. |
<TypeName> | Point p = { x: 1, y: 2 }; | User-declared via type. |
var (and its alias let, untyped) and null are also keywords — var
declares a binding the type-inferer fills in, null is the absence value
for json slots and unset variables.
String interpolation. t"total: {n} adet" is a t-string: each
{expr} is evaluated and stitched in. It always yields a str (even
t"{n}"); write a literal brace as \{ / \}. Plain + also works and
coerces — "n=" + 5 is "n=5", no toString() needed.
Variables
Section titled “Variables”int x = 10; // typed, with initializerstr name = "Hamza";bool flag; // typed, default-initialised (0 / "" / false / null)var n = 7; // type inferred from initializerlet count = 0; // synonym for `var`let and var are interchangeable — let is conventional for
“won’t change” but the language doesn’t enforce immutability.
Operators
Section titled “Operators”| Category | Operators |
|---|---|
| Arithmetic | +, -, *, /, %, unary - |
| Comparison | ==, !=, <, <=, >, >= |
| Logical | &&, ||, unary ! |
| Assignment | =, +=, -=, *=, /= |
| Increment | x++, x-- (statement form only — not an expression) |
| Subscript | arr[i], obj["key"], obj.key (sugar for ["key"]) |
| Call | f(args), mod.func(args) (mod-qualified import) |
Precedence (highest first):
- Postfix: call
(...), subscript[...], member., increment++/-- - Unary:
-,! *,/,%+,-<,<=,>,>===,!=&&||- Assignment:
=,+=, …
Control flow
Section titled “Control flow”if (cond) { ... }if (cond) { ... } else if (other) { ... } else { ... }
while (cond) { ... }
for (int i = 0; i < n; i = i + 1) { ... }// `i = i + 1` is also valid as `i++` or `i += 1`
// for-each — iterate an array, a string's chars, or range(n).// Parens are required; the loop variable is inferred (no type, no `var`).for (x in [10, 20, 30]) { print(x); }for (i in range(n)) { print(i); }
break; // exit innermost loopcontinue; // jump to next iterationreturn; // exit function (void)return expr; // exit function with value
// match — Rust-style; an *expression* (yields a value).str grade = match score { 90 => "A", // literal 1 | 2 | 3 => "low", // alternatives 10 .. 20 => "mid", // inclusive range _ => "other" // wildcard};// Also valid in statement position with block arms:match cmd { "go" => { run(); }, _ => { stop(); } }
// Destructuring — a bare identifier BINDS, a literal CONSTRAINS, `_`// ignores, `..rest` captures the array tail. Patterns nest arbitrarily.match arr { [] => "empty", [0, second] => "leading zero", // 0 constrains, second binds [head, ..tail] => "head + tail",};match user { // json object / struct fields {role: "admin", name} => "admin " + name, {name} => "user " + name,};match shape { // typed-struct variant patterns Circle{r: 0} => "point", Rect{w, h} => w * h,};match req { {user: {id}, path} => handle(id, path), _ => skip() }; // nestedmatch evaluates its subject once and takes the first matching arm.
Patterns are scalar literals (int / string / bool), |-alternatives,
lo .. hi ranges, and destructuring of arrays ([a, b],
[head, ..tail]), JSON objects / struct fields ({role: "admin", name}),
and typed-struct variants (Circle{r}) — which nest. _ is the catch-all.
Functions
Section titled “Functions”// Positional parameters; type before name.func add(int a, int b): int { return a + b;}
// Return type omitted = void (or json if you return one).func greet(str name) { print("Hello, " + name);}
// Recursion is supported (forward references resolved automatically).func fib(int n): int { if (n <= 1) { return n; } return fib(n - 1) + fib(n - 2);}Function-by-name dispatch (call("name")) looks up the symbol at
runtime; useful for routers / tables-of-handlers.
Lambdas & closures
Section titled “Lambdas & closures”// Lambda: `=>` arrow, expression or block body.var add = (int a, int b) => a + b; // add(2, 3) == 5var sq = (int x) => { return x * x; }; // sq(5) == 25
// Closures capture enclosing parameters/locals (heap env), so factories// and mutable counters work. Captures are independent per instance.func make_adder(int n) { return (int x) => n + x; }var add10 = make_adder(10); // add10(5) == 15
func make_counter() { int c = 0; return () => { c = c + 1; return c; };}var tick = make_counter(); // tick() -> 1, 2, 3, ...Closures are fully supported on the AOT path (default tulpar run / build).
Structs (type)
Section titled “Structs (type)”type Point { int x; int y;}
// Literal initialisation.Point p = { x: 3, y: 4 };print(p.x); // 3
// Pass by value; modify a copy.func translate(Point p, int dx, int dy): Point { Point q = { x: p.x + dx, y: p.y + dy }; return q;}Struct fields are accessed with . (sugar for ["field"]). == on
structs is currently field-by-field (json behaviour).
JSON / arrays
Section titled “JSON / arrays”json o = { "name": "Hamza", "age": 24, "tags": ["admin", "user"] };print(o["name"]); // "Hamza"print(o.tags[0]); // "admin"o["age"] = 25; // mutateo["new_field"] = true; // addpush(o.tags, "verified"); // helper builtins
// Arrays:array<int> nums = [1, 2, 3];push(nums, 4);int n = length(nums); // 4json is the runtime tagged-union — the same value can be an object,
array, string, number, or null.
Modules & imports
Section titled “Modules & imports”import "wings"; // brings every top-level func into scopeimport "wings" as w; // namespaced — call as `w.listen(...)` or `w__listen(...)`import "./local/util.tpr"; // path import (resolves relative to source)Resolution order: literal path → path.tpr →
tulpar_modules/<name>/<name>.tpr → tulpar_modules/<name>.tpr →
embedded stdlib. See Package Manager
for the lockfile + version specs.
Error handling
Section titled “Error handling”try { risky();} catch (e) { print("oh no: " + toString(e));} finally { cleanup();}
throw "error message";throw {"code": 500, "msg": "boom"};catch (e) binds the thrown value to e (a json) regardless of its
shape. finally always runs, even on uncaught throws.
Async / await
Section titled “Async / await”async func fetch(int id) { // returns a promise immediately await sleep_async(20); // non-blocking; other tasks run meanwhile return id * 2;}
var p = fetch(21); // queued, not run yetint r = await p; // drive it → 42
// gather: await many concurrently, results in argument ordervar all = await gather(fetch(1), fetch(2));async funcs are stackful coroutines on a single-threaded event loop;
calling one queues a task and returns a promise, await drives it. An
uncaught throw rejects the promise and await re-raises it, so ordinary
try / catch catches async errors. Up to 16 parameters. See the
Async / Await guide for the full model, including the
non-blocking HTTP client.
Comments
Section titled “Comments”// Single line — runs to EOL./* Block comment — does not nest. */Reserved keywords
Section titled “Reserved keywords”async await break catch continue doelse false finally for func ifimport in let match null returnthrow true try type var voidwhileType-name keywords (int, float, str, bool, json, array)
are also reserved when used as types but can appear inside identifiers
via underscore (int_value, array_size).
CLI cheat-sheet
Section titled “CLI cheat-sheet”| Command | Effect |
|---|---|
tulpar foo.tpr | AOT-compile + run, fall back to VM on AOT error. |
tulpar --vm foo.tpr | Bytecode VM — faster startup, slower steady-state. |
tulpar build foo.tpr [out] | Standalone native binary. |
tulpar --repl | Interactive prompt (VM-backed). |
tulpar fmt foo.tpr [-w] | Source formatter. |
tulpar pkg <subcmd> | Package manager — see Package Manager. |
tulpar --lsp | LSP server on stdio (used by editor extensions). |
tulpar typecheck foo.tpr | Standalone type-checker (also runs as a build pre-pass). |
tulpar update [--check] | Self-update from the official release. |
The full CLI reference, including environment variables, lives at CLI Reference.
Where this differs from the guide
Section titled “Where this differs from the guide”The Language Guide covers the same surface with
runnable examples and motivation; this page is the lookup table you
keep open in another tab. If something here looks wrong, the guide
pages and the source (src/parser/parser.cpp + src/lexer/lexer.cpp)
are the authoritative answer.