Last updated: 2026-09-26
PatLang Capabilities & Known Limitations
For new readers
PatLang is an experimental, self-hosted programming language (its own compiler is written in PatLang). This page is a status report rather than a tutorial: a plain list of what genuinely works today, sourced from actual verified behaviour rather than intention, sitting alongside an equally direct list of what doesn't work yet. A few terms recur below without much introduction — "the interpreter" and "compiled" refer to the two different ways a PatLang program can be run (see the compiler pipeline page for how they fit together); "fibers" and "the numeric tower" are specific features explained on their own pages, linked where they're mentioned. If you want an introduction to what PatLang is trying to do before reading what it currently can and can't, the Paradigms Guide is a gentler starting point than this list.
Language documentation tends to undersell gaps. This page does the opposite deliberately: a direct list of what PatLang currently cannot do, alongside what it genuinely can, sourced from code comments and verified parser/runtime behaviour rather than aspiration. Where a limitation is scoped future work rather than a permanent design decision, that's stated too.
What actually works, end to end
- Two independently-verified compilation paths (native Rust codegen, self-hosted
patc1.exe) plus an interpreter, with parity checks between interpreted, natively-compiled, and self-hosted-compiled output for language features as they're added. - A self-hosted compiler fixpoint-verified against its own source —
patc1.exerecompiling its own compiler source produces byte-identical Rust. - Nine working paradigms (see the Paradigms Guide), including genuine OS-thread parallelism, cooperative fibers, and predictive time-budgeted blocks (
budgeted(...)) — all three verified across interpreted, natively-compiled, and self-hosted-compiled execution paths; fibers/budgetedadditionally verified compiled to real threaded WebAssembly, running live in a browser via a hand-rolled WASI threads proposal implementation. - An exact numeric tower (Int → BigInt on overflow, Int/Int → exact Rational on uneven division, real → Complex on negative sqrt) rather than silent truncation or precision loss.
- An optional
classkeyword — single inheritance, method dispatch viasend, and composable traits (last-listed wins on a collision) — layered as sugar over the same object store every other OO usage already uses; see the Paradigms Guide. Deliberately thinner than a full OO type system: no open classes, nomethod_missing-style dynamic fallback, no multiple inheritance. - A runtime-extensible syntax mechanism (
syntax NAME { ... }) that genuinely works at runtime, not just at compile time.
The Stage 0 / Stage 1 dialect split — mostly closed, one gap remains
A mid-2026 audit found Stage 1 (self-hosted, what patc1.exe actually parses) had drifted narrower than Stage 0 (native) on several fronts that were all cosmetic or mechanical, not fundamental: block delimiters (Stage 1 was do...end-only), mutability (Stage 1 had no bare reassignment at all), and member-assignment (Stage 1 had no obj.prop = value). All three are now fixed — both stages accept both delimiter families for every block-shaped construct, both enforce the same let/let mut mutability rule, and both support member-assignment. Full detail is in the Grammar & Syntax compatibility table.
The declarative logic syntax has since been built, in both stages: rule Head(args) :- Body1, Body2. and goal NAME { ... } are real grammar productions, backed by the backward-chaining engine described below, and pursue and activate work as statements. case is now half of match/case pattern matching, which lowers to ordinary jumps and works in both stages (see the match demo). What remains is narrower: Stage 1's parser has no node shape for fact and query as standalone declarative statements, or for constrain, reasoning and relationship. Logic programming's call forms (fact(...), query(...)) work in both stages; the declarative-looking statement forms exist only as no-ops in Stage 0.
Logic engine: this section was stale — real rule-based inference has existed for a while
An earlier version of this page said query only supported direct fact lookup, with rule parsing but never actually consulted. That's no longer true, and hasn't been for some time: rule_add/solve do genuine backward-chaining resolution over declared rules, including recursive ones, with real unification (uppercase-first-letter logic variables) and a neq built-in comparison predicate. See Inductive Synthesis for the induction engine built entirely on top of this real resolver — including, most recently, two new generic search extensions (synth3_accumulator_search/synth4_toggle_search) that find accumulator-threading relations like "sum the numbers up to N" by search rather than by being told the answer. The goal-oriented demo page covers the separate action_add/plan GOAP planner this same fact substrate feeds. Lesson for this page itself: a capabilities page can go stale like any other kind of documentation — this section sat wrong long enough that a whole separate arc of work (see the synthesis/honesty journey page) built substantially on the "no rule inference" claim being false without anyone having corrected it here first.unification: matching terms by binding variables
Complex numbers: division and square root are asymmetric
sqrt promotes negative real inputs to Complex but explicitly rejects a Complex input with "sqrt: complex input not supported" — there's no complex square root. Complex modulo is likewise an explicit error, not silently coerced to something else. There also appear to be two separate numeric-operator implementations in the runtime (rust-runtime/src/ir/ops.rs and the numeric functions embedded in codegen.rs), with complex-division error messages that don't perfectly match between them — worth verifying against the actual execution path in use before relying on specific complex-division error text.
Concurrency: fibers work compiled-native AND compiled to threaded WASM now; neither primitive goes through the chunk system
parallel_map and the fiber functions (plus budgeted(...), built on fibers) work and are verified across interpreted, natively compiled (pat --patc), self-hosted compiled (patc1.exe), and compiled to threaded WASM — confirmed by a 200,000-iteration resumption test converging identically (17 rounds of pause/resume) across the first three, and by the fiber portfolio demo's real Fibonacci-generator transcript matching byte-for-byte between the native run and a real headless-browser run of the compiled WASM binary. Compiled-native fiber support was ported directly into codegen.rs's generated program text (mirrored into self_hosting/lib/runtime_rs.patlang), reusing the same "resolve and call a program function by name" mechanism parallel_map's compiled path had already established as working.
One thing remains genuinely true, not just historical: both primitives sit entirely outside HOST_CHUNK_TABLE, special-cased directly in the interpreter and codegen rather than following the same modular chunk pattern as the rest of the host surface — a real asymmetry, not an oversight to be quietly normalised in documentation.chunks: see the stdlib reference
WASM threading is real now, but on a second, opt-in target, not the default one. The ordinary wasm32-wasip1 target (still this project's default WASM build) has no std::thread support, so fiber_new/fiber_resume/fiber_yield/fiber_alive/budgeted(...) still return a clear runtime error there, unchanged. A new wasm32-wasip1-threads target, compiled with a nightly toolchain and -C target-feature=+atomics,+bulk-memory,+mutable-globals, genuinely runs them. The cfg gates in codegen.rs/runtime_rs.patlang now key off target_feature = "atomics" rather than target_arch = "wasm32", so real fiber support compiles in for any target with atomics (native or threaded-WASM) and stays cleanly stubbed for the ordinary non-threaded WASM build. Running it in a browser needed a hand-rolled implementation of the WASI threads proposal (real Workers + SharedArrayBuffer-backed shared memory, no wasm-bindgen). See the fiber demo page for a live "run in browser" button using it, and the paradigms guide for the one hard architectural constraint discovered building it (a fixed, generously-sized worker pool pre-warmed before execution starts, not literally-unbounded dynamic growth — see below).
The WASI/browser sandbox has no real filesystem, and only the opt-in threaded target has real threading
Portfolio demo pages that run PatLang in the browser via WebAssembly (the playground, the maze/flow-graph WASM drivers) run in a sandbox with no access to the visitor's disk, regardless of target. The shared browser shim simulates one preopened directory so write_file completes, and the in-memory vfs_* layer is available on every target. Real OS-thread-backed concurrency is available in the browser too: the fiber demo and the threading demo (parallel_map) each have a live button that runs the compiled threaded-WASM binary on real Workers. Both go through the same hand-rolled thread-spawn JS shim; parallel_map uses std::thread::scope where fibers use std::thread::spawn, and both were run under it. Both need cross-origin isolation on the page.
Self-hosting bootstrap: rustc has a limit on generated program size
Rebuilding patc1.exe from its own PatLang source, then having it recompile itself again (the fixpoint check), turned up two constraints worth knowing before touching the self-hosted compiler's own source:
rustc's optimiser has been observed to blow up to 30GB+ RAM (and, separately, to hang indefinitely burning CPU with no progress) compiling the self-hosted compiler's own ~1.6MB generated interpreter source at the default optimisation level. This is specific to that file's unusual size, not a general problem — ordinary, much smaller generated programs compile fine at the default level.rustc_buildnow accepts an optional opt-level override for this case; the bootstrap tooling passes0for its own build, confirmed to compile the same source correctly in seconds with no blowup.- Lower optimisation levels remove the stack-frame-shrinking effects that were keeping deep recursion within the OS-default thread stack. The self-hosted compiler's own lex/parse/lower/codegen pipeline is a deep recursive-descent interpreter running over its own source; compiled at
opt-level=0it could overflow the default main-thread stack. Generated native programs (and thepatCLI itself) now run their work on a spawned thread with an explicitly larger stack for this reason — WASM targets are unaffected (and unchanged), sincestd::threadsupport isn't guaranteed across WASI runtimes.
Neither of these affects ordinary, reasonably-sized PatLang programs — both are specific to the self-hosted compiler's own unusually large generated source, encountered only when rebuilding patc1.exe itself.
Structural gaps worth knowing
oounconditionally pulls inlogicas a compiled-program dependency, becausesend's relation-inference dispatch reads state the logic chunk declares — even for programs that never touch logic programming directly.- Dead-code elimination is whole-chunk, not per-function. Using one function from a chunk pulls the whole chunk's prelude text into the compiled output.
Value's variant set is an all-or-nothing choice per build. A program either compiles against the fast,Number(f64)-onlyValueprelude or the full numeric-towerValueprelude (Int/Float/BigInt/Rational/Complex) — there's no mixing the two within one compiled program.- Templates (
make a template called NAME { ... }) parse but their body is discarded. They're a pure no-op in Stage 0 today, not a working feature. - The Stage 0 IR/compare CLI pipeline (
--ir/--compareflags) has its own, narrower construct support than ordinarypat run— the CLI itself scans for unsupported high-level constructs (functions, facts/rules/goals, reasoning mode, object literals) up front and prints a direct warning rather than failing confusingly deep inside the pipeline. This is a limitation of those specific debugging flags, not of the interpreter or normal compiled path.
Native compilation without rustc, and what is still open
The default patc1.exe route still hands generated Rust text to rustc. The alternative patc1 in.patlang out.exe --x64 does not: it emits x64 assembly and turns it into a Windows executable with PatLang's own assembler and PE linker, with no rustc, nasm or gcc involved, and the compiler reproduces itself through three generations this way (see the compiler pipeline and Chapter 10). Three things are still open. The x64 backend produces Windows executables only, and it does not share every corner of the interpreter's behaviour. Floats carry no run-time type there, so a float passed across a function call is only handled where the compiler can see the call sites (GitHub issue 50 tracks the representation question), and numeric-fluent GOAP planning is not implemented. And the WASM target has no bytecode-emitting backend of its own: programs reach WebAssembly through the interpreter-in-generated-Rust route, so emit_wasm remains a reserved slot. A portable bootstrap for a fresh machine that has neither PatLang nor Rust installed is unscoped.
See also
- Grammar & Syntax — full detail on the dialect split.
- Standard Library & Host Function Reference — the chunk system these limitations sit inside.
- PatLang Real OS Threads and PatLang Fibers — the concurrency demos referenced above, including their WASM mocks.
- Journey Chapter 5: Teaching It to Find Its Own Answers, for Real — the arc that found this page's own stale logic-engine claim, alongside program synthesis and a Unicode lexer bug.