Skip to content
An illustration of a developer handing a flowchart to the saQut mascot, a smiling rounded-square character labeled "sqt", next to icons for memory, storage, and network.

saQut

Write a program, then watch the compiler think. saQut is a procedural language whose compiler hides nothing: every step from source to running code is a command you can run, read and feed to your own tools.

A typical compiler exposes only its source input and its final output; the stages in between are not observable. saQut exposes each stage as a separate command that produces a named, machine-readable result:

Terminal window
saqut tokens hello.sqt # token stream (JSON)
saqut ast hello.sqt # abstract syntax tree (JSON)
saqut symbols hello.sqt # symbol table (JSON)
saqut ir hello.sqt # 3-address IR
saqut run hello.sqt # compile & execute

The language is deliberately small and C-flavoured. It exists to give the inspectable pipeline something to compile.

  • Bytecode VM: the reference runtime. Your program is compiled to simple instructions and run by a loop inside saQut. It is the backend every other part is checked against: same input, same output, every time.
  • JIT compiler (--jit): translates the same instructions into native machine code while the program starts. Loops and arithmetic run much faster. It is experimental and must print exactly what the VM prints.
  • Garbage collector (GC): strings, arrays and structs are freed automatically when nothing can reach them any more, cycles included. You never write free; --gc-stats shows what was collected.
  • LSP (Language Server): the protocol editors use for “go to definition”, hover types, rename and live error squiggles. saqut lsp speaks it directly.
  • DAP (Debug Adapter): the protocol editors use for breakpoints and step-by-step execution. saqut dap lets you pause a program and inspect its variables.
  • Catchable runtime errors: division by zero, out-of-range index, null access and stack overflow raise an error you can catch, with a code, a message and a source location.
  • Modules and a standard library: split code across files with import / export; reach files, paths, text encoding, dates, the terminal and the process through fs, path, utf8, date, sys and friends.
  • Machine-readable everything: tokens, AST, symbols, IR and diagnostics come out as JSON or JSONL, so scripts and CI can read what the compiler saw.

  1. Write a file called hello.sqt:

    int main() {
    print("Hello, saQut!");
    return 0;
    }
  2. Run it (no class or boilerplate needed); execution starts at main:

    Terminal window
    saqut run hello.sqt
  3. Look inside at any stage you’re curious about:

    Terminal window
    saqut ast hello.sqt --json

The Getting Started guide covers building the compiler and writing longer programs.


Binary search over a sorted array. Each step halves the search range, so a 10,000-element array is decided in at most 14 comparisons.

int binarySearch(int[] values, int target) {
int low = 0;
int high = values.length() - 1;
while (low <= high) {
// (high - low) / 2 instead of (low + high) / 2:
// the second form can overflow int on a large array.
int mid = low + (high - low) / 2;
if (values[mid] == target) {
return mid;
}
if (values[mid] < target) {
low = mid + 1;
} else {
high = mid - 1;
}
}
return -1; // not found
}
int main() {
int[] sorted = [2, 5, 8, 12, 16, 23, 38, 56, 72, 91];
print(binarySearch(sorted, 23)); // 5
print(binarySearch(sorted, 7)); // -1
return 0;
}

Functions, arrays, control flow, and print(). No mandatory scaffolding. The same program produces the same output on the VM and on the JIT.


Glass box pipeline

Tokens, AST, symbol table, and IR are each a named, machine-readable output. Pipe saqut ast into your own tool, diff the optimized tree, or build an LSP from saqut symbols alone.

Procedural and explicit

C-family syntax. No OOP, no generics, no hidden type coercion. Integer and float mixing follows one stated rule. The compiler runs what you wrote.

Value and reference, no pointers

Primitives (int, float, bool, byte) copy. Compounds (string, struct, array) share by reference. There is no user-visible & or *.

Nullable is opt-in

A value is non-null unless you declare it T?. The type checker verifies that a non-null value cannot be null before you use it, so there is no null check at run time.

Deterministic VM

The bytecode VM is the reference backend: the same input produces the same output on every run. LLVM is intentionally not used. An experimental MIR JIT runs via –jit and is required to match the VM byte for byte; embedded-runtime AOT is planned next.

Catchable errors

try / catch / throw with struct-based errors. Out-of-bounds access, divide-by-zero, and null derefs raise a catchable error instead of terminating the program silently.



Source -> Tokenizer -> Parser -> AST -> Symbol Collector -> Type Checker
-> Optimizer (constant folding + DCE) -> IR Generator -> Bytecode VM -> output

Each arrow corresponds to a CLI command you can run and inspect.


October 3, 2026 · 1.0.1 · Many Threads, A Sharper Editor

Section titled “October 3, 2026 · 1.0.1 · Many Threads, A Sharper Editor”
  • 37542b1 | Threads arrive as a preview. thread { ... } starts a thread that runs at once and gives you a Thread you can join(), stop() and ask running(). Every thread has its own heap, its own garbage collector and its own copy of the globals, so threads never touch each other’s objects. The VM is the reference backend; the JIT is experimental.
  • 46f8c35 | New shared globals (int, float, bool, Pool, List) pass data between threads. Reads and writes are atomic, and so are +=, -=, ++ and --. A Pool is a bounded blocking queue, a List is an append-only list; anything put into them is copied deeply.
  • 46f8c35 | lock a, b; takes locks in a fixed order, so two locks can never deadlock each other, and releases them when the block ends. The compiler checks the rules (E014–E019) and warns about a non-atomic x = x + 1 on a shared variable (W008) and about blocking calls inside a lock (W009).
  • c5d2140 | Threads run on the VM and on the experimental JIT (0b8eded), with examples and a short guide in docs/threading-guide.md.
  • 5fecf75 | print and file writes are emitted in one piece under a lock, so lines from different threads do not tear.
  • 19b99aa | The debugger understands threads: it lists them, reports when they start and exit, pauses every thread at a breakpoint, lets you inspect a worker’s stack, and shows a Shared scope. A deadlock under the debugger is reported instead of hanging.
  • 6578af2 | Programs that never use threads pay about 0.10% for all of this, measured with hardware counters.
  • 72bd1ca | s[i] reads the i-th character of a string (zero-based, a Unicode code point) as a one-character string, on the VM and the JIT alike. Strings are immutable, so s[i] = ... is a compile error.
  • 72bd1ca | An array index must now be a non-null int, byte or longint. d["x"], d[1.5], d[true] and int? used to be accepted and did the wrong thing; indexing with an enum now needs as int.
  • 72bd1ca | The VM no longer cuts an array index to 32 bits: d[4294967297] used to read and write d[1], while the JIT correctly reported an out-of-range error. Both backends agree now.
  • 69913ef | 5 = 3;, 5 += 3; and 5 &= 3; used to compile. The left side of an assignment must now be a variable, a struct field or an array element (E027).
  • 8ae879d | int a = 1 ? 2 : 3;, 7 ? 5 and 5 ! 3 used to compile and print 0. They are now a clear syntax error (E901).
  • eb9937e | A variable named EOL no longer silently cuts the program short.
  • 835f42e | The catch-all E003 is split into precise codes. E003 now means only a type mismatch in an assignment, an argument or a return value; the rest moved to E020–E027 (literals, operators, nullable, as, switch, calls, method receivers, writable locations). Messages did not change. Codes that tools may match on are listed in docs/compiler-errors.md.
  • 46f6026 | A command now rejects options it does not accept (saqut tokens a.sqt --jit used to exit 0). The old file:, output: and ast: prefixes are gone; use a plain path.
  • 360bad2 | saqut ast loads the whole module graph, so programs with import no longer report a false E001/E007; it prints every unit.
  • 539885f | Completion is scope-aware and survives broken code: it understands a.b.c, a[i], f(), enum names and the real element type of a Pool/List, and falls back to the last good parse while you type.
  • 962785e | Project-wide index: find references and rename work across files, workspace/symbol is supported, and completion offers exports you have not imported yet and adds the import for you.
  • 3687e5c | Folding, symbol outline, greyed-out unused code, code actions, code lenses (“References: N”) and inlay hints for parameter names.
  • 28f42c8 | The first project index of a 200-file import chain went from 2.84 s to 38 ms.
  • 2ce7a11 | Error squiggles now cover the offending token instead of the whole line; rename covers struct types and fields; breakpoints move to the next runnable line, stepIn enters the call, and stop-on-entry starts at the first line of main.
  • a5f5b46 | The VS Code extension (1.0.1) highlights shared, thread, lock, unlock, wait, Pool, List and Thread, and ships thread snippets.
  • 835f42e | The command list, option table, diagnostic codes, keyword table, built-in method rules and opcode result types each now live in a single place instead of several hand-synced copies, so adding one is a one-file change.
  • 84495b8 | Release builds compile with zero warnings on GCC 14 and 16.

September 23, 2026 · 1.0.0 · Feedback MVP

Section titled “September 23, 2026 · 1.0.0 · Feedback MVP”
  • 34c22b5 | Programs that used to run with a silently wrong result now stop with a clear error: missing operands (5 +), missing ;, unknown escapes like "\x41", and the non-existent >>> operator.
  • 34c22b5 | Variables declared without a value start at a typed zero on every loop iteration, on the VM and the JIT alike.
  • 34c22b5 | % works on float and double; compound assignments like d *= k widen an int right-hand side correctly.
  • 34c22b5 | Endless recursion raises a catchable E_STACK_OVERFLOW instead of hanging or crashing; the limit is set with --max-call-depth=N.
  • 34c22b5 | Accessing a field of a null struct raises a catchable E_NULL.
  • 34c22b5 | Modules have their own namespace: private names no longer clash across files, import { x as y } works for source files, and a standard library import is only visible in the file that imports it.
  • 34c22b5 | A decimal literal without context is a double, as documented.
  • 34c22b5 | The CLI rejects unknown options and stray arguments instead of ignoring them; all runtime messages are in English.
  • 87068da | ** exponentiation, prefix ++x, and ++/-- on every numeric type, array element and struct field.
  • 100979f | byte arithmetic wraps within 8 bits; an explicit as byte cast still checks the range.
  • 1f4a119 | Optimization is on by default; --dont-optimize turns it off.
  • b33d569 | One garbage collector shared by the VM and the JIT.
  • 2862123 | Strings use a single garbage-collected representation on both backends.
  • 4ec9c2e | Runtime values shrank from 112 to 40 bytes.
  • 0a0847d | The JIT checks for errors only after instructions that can fail; pure arithmetic became up to 57x faster than the VM.
  • c54b157 | Array reads and writes in the JIT are direct memory accesses for every packed element type.
  • 41efa5e | New standard library modules: path, utf8, process, stdin/stdout/stderr, os and terminal.
  • 651b04f | readFile can read a slice of a file with seek and size.
  • b7aa4d5 | Imports can be renamed: import { readFile as load } from fs;.

August 24, 2026 · 0.9.4 · Errors Get Caught Now

Section titled “August 24, 2026 · 0.9.4 · Errors Get Caught Now”
  • a08c756 | try / catch / throw now work in the JIT backend with the same output and exit code as the VM, including catchable runtime errors like division by zero, failed casts, and out-of-range array indexing. (The planned setjmp/longjmp design turned out to be undefined behavior in a JIT; a per-instruction pending-error check is used instead, and functions without a try still pay zero cost.)
  • a68cad7 | Fixed a JIT garbage-collector bug: in recursive calls that allocate, a live object could be swept and reused while still in use. Shadow-stack frames now unwind properly per call.
  • 2095564 | The six test programs previously marked “JIT known broken” are now required to match the VM byte for byte; parity is enforced again in this area, with no exceptions.
  • 4568aac | The capability (permission) system has been removed (ADR-043): host calls are open by default, and file operations now work identically in the JIT.
  • 5aeec2b | String indexing and length() now count Unicode code points instead of bytes.
  • 1cea6bf | upper() and lower() now do real Unicode case mapping.
  • 4bc1b2b | File contents can be handled as byte[] end to end.
  • b2ed2f8 | 64-bit hex literals keep their exact bit pattern as longint instead of being mangled.
  • ec4174c | An uninitialized string now starts as "" instead of 0: local variables, struct fields, and globals alike.
  • b9f5c62 | A struct field with an array type now starts as an empty array, so record.items.push(x) works without assigning a value first.
  • 690025b | A module-level int?, string?, or longint? you never assigned now really reads as null. Before, x == null answered false because the slot silently held 0.
  • 2469bbf | Ordering comparisons (<, >, <=, >=) on a nullable value are now a compile error, same as arithmetic: pass the null gate first (if (x != null) { x < 26 }). Equality (==, !=) is untouched.
  • 74296d6 | Using a built-in method without calling it (s.length without parentheses) is now a compile error instead of silently producing 0.
  • dd8a493 | Statements at module scope (x = 5; outside any function) are now rejected with a clear error, as in C++; the top level only accepts declarations.
  • 030f4bd | Calling a function without its required arguments is now a compile error; the language server also survives that input gracefully.
  • 907d904 | The JIT lowers hot array accesses to direct memory operations and reuses compiled code, so array-heavy loops run faster.
  • 2645978 | The docs no longer advertise a ?: ternary operator the language never had.
  • 2d6395c | --help now reads like a standard manual page, and unimplemented commands were removed from the CLI entirely.
  • ee102a3 | Fifteen error fixtures that existed on paper but were never actually run are now wired into the test runner, so silent failure classes get caught.

August 1, 2026 · 0.9.2 · Learning to Measure Itself

Section titled “August 1, 2026 · 0.9.2 · Learning to Measure Itself”
  • c2266ad | An int or longint literal that does not fit the type is now caught at compile time instead of silently wrapping to a different value.
  • d85c7ee | decimal comparisons now agree with decimal arithmetic, so money math finally adds up.
  • fd7d082 | Self-referential structs like Node { Node? next; } now compile, and T? fields start out as null.
  • 7c8f678 | The JIT and the VM now give the same answer for decimal values and the same message for runtime errors.
  • 3261321 | The VM stopped doing debugger bookkeeping on every single instruction, so programs run about 1.9x faster.
  • f4a60c8 | The parser reads each token once instead of six times.
  • 1a096ee | Source positions got three times smaller, so long file paths no longer cost memory.
  • 76d2340 | bench now perturbs the program it measures by 1.4% instead of 13%, and type checking shows up as its own stage.
  • b576595 | Adds a harness that runs the same program through the AST, IR, VM, and JIT and reports where they disagree.

July 29, 2026 · 0.9.0 · Say What You Mean

Section titled “July 29, 2026 · 0.9.0 · Say What You Mean”
  • 77e9276 | The parser now reports a clear error for a missing closing ), ], or } in 25 different situations.
  • a01f46e | run, check, and ir now all return the same exit code for the same kind of error, no matter which one you use.
  • c7d1d94 | exec now reports parser errors instead of silently continuing.
  • fe90c0c | ast and symbols now report parser errors the same way exec and run do.
  • 74e01a5 | saqut ir now shows every instruction kind in its output.
  • c43e830 | When a function ends without an explicit return, saqut ir now shows that implicit return too.
  • b48e0cf | saqut ir now only prints color codes when the output goes to a real terminal, keeping piped or redirected output clean.
  • f7a32f9 | The compiler now correctly recognizes when every branch of a switch returns a value.
  • 54881c8 | Indexing a non-array value with [index] is now rejected at compile time.
  • 7f871b7 | Adds a differential test harness that automatically checks the VM and JIT agree on every result.
  • 2f31cb2 | Makes int32, longint64, float32, and double64 behave identically on the VM and the JIT.

July 16, 2026 · 0.8.0 🏆 · The Experimental JIT’s First Breath

Section titled “July 16, 2026 · 0.8.0 🏆 · The Experimental JIT’s First Breath”

🏆 saQut’s first official build happened here.

  • 2b53460 | Ships the first version of the experimental MIR JIT backend, enabled with the --jit flag.
  • dcc8a04 | Full programs can now run end-to-end on the JIT backend.
  • 62651cf | The JIT can now compile and run float values.
  • 24acdc7 | The JIT can now compile and run string values.
  • 748f992 | 32-bit int overflow now behaves identically on the VM and the JIT (defined, two’s-complement wraparound).
  • 574ce3f | Adds the saqut --version flag and a static release build.
  • 879a85e | saqut run --profile can now measure VM and JIT warmup and run time separately.

July 15, 2026 · 0.7.0 · The Language Opens Up To The Outside World

Section titled “July 15, 2026 · 0.7.0 · The Language Opens Up To The Outside World”
  • a785d2a | Adds FFI (foreign function interface) support: the ffi keyword lets the language declare and call functions provided by the host.
  • 2259bfb | Adds the fs module: read, write, check for, and remove files.
  • c3451f7 | Adds the sys module: generate random numbers, read environment variables, sleep, and read command-line arguments.
  • 76c9bc6 | Adds the date type, which stores a UTC timestamp in milliseconds.
  • 35790a2 | Adds the math::PI and math::E constants.
  • 5433cb3 | Adds the capability (permission) system for file, network, and system access. With no --allow option all capabilities are enabled; when present, --allow fs,net,sys acts as an explicit whitelist.
  • 1e0fbd3 | Adds caps::drop and caps::has: a running program can voluntarily give up permissions or check which ones it currently has.
  • 04d90b5 | When a variable inside a block shadows one with the same name from an outer scope, the compiler now resolves it to the correct one.

July 14, 2026 · 0.6.0 · Small, Sharp, And Deliberate

Section titled “July 14, 2026 · 0.6.0 · Small, Sharp, And Deliberate”
  • 7c5d90a | The garbage collector (GC) now runs automatically once memory use crosses a set threshold.
  • 587bb57 | Reworks how built-in functions are called: they can now be called with dot syntax (value.method()) and are grouped into namespaces.
  • 845588d | Adds the byte type: an 8-bit unsigned value, 0-255.

July 13, 2026 · 0.5.0 · Debugging, Step By Step

Section titled “July 13, 2026 · 0.5.0 · Debugging, Step By Step”
  • 025a667 | Rewrites the debugging protocol (DAP) correctly: editors can now step through a saQut program, pause it, and inspect variables.
  • e4abc6f | You can now actually pause a program while debugging it; it waits without freezing (non-blocking).
  • 75e3dd1 | Stepping through code one line at a time now covers every instruction and pauses on exactly the right line.
  • d26575b | Makes autocomplete in the editor more reliable.
  • 11af652 | You can now safely rename variables and functions from the editor, and see parameter hints while calling a function.
  • b9ca166 | When two modules import each other in a cycle, the compiler now reports it as an E_MODULE_CYCLE error.

July 11, 2026 · 0.4.0 · The Editor Gets A Window In

Section titled “July 11, 2026 · 0.4.0 · The Editor Gets A Window In”
  • ad45bd5 | Lays the groundwork for editor support: syntax highlighting (a TextMate grammar), a language server (LSP), and a debugging protocol (DAP) scaffold.
  • b66226b | The language server (LSP) can now compile the version of a file currently open in the editor, without needing it saved to disk.
  • f3a7853 | The language server now computes file positions (line/column) far more reliably, including across multi-file projects.
  • 934a903 | Adds pastel coloring to the AST, symbol table, and IR output.
  • 6651a91 | When a struct contains another struct as a field, all of its nested fields are now set up automatically as soon as the variable is declared.
  • d6dd530 | Adds the saqut bench command.
  • 585b8eb | Commits to a second execution backend: a MIR-based JIT and an embedded-runtime AOT compiler.

June 24, 2026 · 0.3.0 · The Language Learns To Split Itself

Section titled “June 24, 2026 · 0.3.0 · The Language Learns To Split Itself”
  • 8136773 | Adds enum type support.
  • 2af0bce | Adds the decimal type, fully supported by both the compiler and the VM.
  • 878faa3 | Adds struct arrays (Point[]) and multi-dimensional arrays.
  • b9a6176 | Adds a built-in method system: E::method(args) syntax.
  • 4a84e59 | Adds a module system (import/export, multi-file builds) and the exec command that goes with it.
  • 4a64812 | Tests the compiler against 37 real algorithms (sorting, searching, and more).

June 21, 2026 · 0.2.0 · Closing The Cracks

Section titled “June 21, 2026 · 0.2.0 · Closing The Cracks”
  • 060b4a0 | Adds a hint to every error message showing how to fix the problem.
  • 53823f1 | Rebuilds the garbage collector (GC): memory is now cleaned up the same way, every time, right when a function returns.
  • 50c2615 | Struct fields declared as nullable (T?) are now parsed and typed correctly.
  • d7872f7 | Translates every user-facing message from Turkish to English.
  • b8c2d95 | Constant folding now runs without crashing the compiler.
  • 3baa5e3 | A function that returns a struct now has its return type recognized correctly.
  • 8545ce4 | Bitwise operators (&, |, ^, <<, >>) are now fully compiled to IR, with expanded test coverage.

June 20, 2026 · 0.1.0 · A Language, Built From Zero

Section titled “June 20, 2026 · 0.1.0 · A Language, Built From Zero”
  • 2bf52f5 | Finishes the lexer/tokenizer, which turns source code into tokens. This is the compiler’s first stage.
  • 4839f37 | Completes the parser: expressions now build the correct tree based on operator precedence.
  • e488f29 | Runs fibonacci.sqt for the first time, through our own IR generator and bytecode VM.
  • d5a1b2c | Adds the type system and the engine that produces error and diagnostic messages.
  • c8ee926 | Adds the symbol table: resolves variable and function names, and tracks scopes and references.
  • 36ad5d0 | Adds the optimizer: computes constant expressions at compile time (constant folding) and removes code that can never run (dead-code elimination).
  • b02a7da | Adds string concatenation, try/catch/throw, and nullable flow analysis.
  • 7b2258e | Adds array support, backed by an object model the garbage collector (GC) can manage.
  • fab82ee | Adds bitwise operators and IR generation for global variables shared across a file.

0.1.0 → 0.2.0 · 0.2.0 → 0.3.0 · 0.3.0 → 0.4.0 · 0.4.0 → 0.5.0 · 0.5.0 → 0.6.0 · 0.6.0 → 0.7.0 · 0.7.0 → 0.8.0 · 0.8.0 → 0.9.0 · 0.9.0 → 0.9.4 · 0.9.4 → 1.0.0