The geastack TypeScript-to-C++ compiler.
It is a clean-room rewrite. The design goal is the second constraint below: a statically typed value that reaches a dynamic carrier is a defect. The earlier implementation could not be driven to zero of them, because boxing was load-bearing in its emitter and removing it anywhere broke it somewhere else. Starting from a tree where the carrier algebra is closed and fail-closed from the first commit is what makes "no boxing" a property rather than an aspiration.
See docs/ARCHITECTURE.md for the pipeline, the layering
and the constraints, and CLAUDE.md for the code conventions.
identity/ canonical IDs
semantics/ the target-neutral frontend: regions, structural types,
operations, edges, coverage. The frontend/backend boundary.
representation/ physical carriers, the sealed plan, and fail-closed guards
conversion/ the closed dynamic-conversion capability algebra
preflight/ whole-pipeline capability census and certificate
ir/ typed IR: verified operations and SSA values
targets/cpp/ emission; consumes typed IR only
diagnostics/ authority-component sweep, deterministic ordering
Nothing after semantics/ may import typescript or touch a TypeScript AST.
That boundary is what stops two layers from answering the same question
differently.
- No source-shaped authority. No decision may depend on a file name,
path, source offset, .pos/.end/getStart()compared to a literal, source text, declaration or local spelling, statement layout, or generated C++ text. Identity is a checker symbol/declaration, a sealed semantic ID, or a versioned protocol identity.
- No boxing. A value with a static type never gets the dynamic carrier.
Representation.dynamicis admissible only for the four declaredDynamicReasonvalues insrc/representation/model.ts.
- Fail closed. A guard firing is always correct; the defect is upstream.
Never relax a guard, never downgrade an unresolved, never default when a fact is missing.unresolvedis lattice bottom, not an answer.
scripts/architecture.mjs enforces the layering over src/**/*.ts: file and
directory size maximums, and zero-tolerance checks for RegExp literals under
src/targets/cpp/, source-position literal comparisons, typescript imports
outside src/semantics/, source-shaped discriminants, unclassified
Authority/Admission/Proof contracts, class declarations under
src/targets/, and any. It collects every violation before failing, so one
run reports everything wrong rather than one thing at a time.
node dist/cli.js test/fixtures/spread.tsruns the whole pipeline and prints the complete diagnostic set — never a top-N —
exiting non-zero when no capability certificate was minted. Add --preflight for
the full obligation census as canonical NDJSON.
node dist/cli.js coverage app/index.ts --plugin ../node-compat/plugin/v2.mjsanswers the porting question the diagnostics do not: which lines of my file are the problem, and what kind of problem is each? It joins every layer's outcome by source position and prints one row per statement with a stable code, a status and a one-line reason, then a summary and a by-code count. A carrier that BOXED is a row too -- the program compiles and runs slower than it says, and no other view places that on a line. Exit 0 iff a certificate was minted and nothing refused at lowering or emission.
The trailing digits index fixed tables in src/cli-coverage.ts, never a hash
of a message, so rewording a diagnostic keeps its code; x999 means the table
lacks a row. Options: --project/--no-project, --plugin, --plugin-option,
--json (rows + summary as one object), --no-derived (roots only),
--no-boxed (refusals only).
node scripts/probe-carriers.mjs test/fixtures/language.tsprints the carrier and C++ spelling selected for every declaration, and counts unresolved and boxed carriers. It is the view of the structural mapper and carrier selection, which nothing else exercises directly.
node dist/cli.js compile app/index.ts --out-dir build/generatedis the command the gea build pipeline drives (build-gea-vite-geatsc.mjs
passes it through --geatsc-bin). It writes the C++ into --out-dir together
with gea_runtime.h, generated_support.hpp, whatever the installed hosts
require beside the unit, geatsc-sources.txt -- the list of .cpp files the
build compiles -- and, for the per-file layout, geatsc-header.txt naming the
shared header the build should precompile. Options:
compile-module-graph <gea-module-graph.json> --entry <file> is the same
command over vite's module graph instead of files on disk, and is the path every
real application build takes; it accepts the same options.
Apache-2.0 (see LICENSE), the compiler and the runtime it combines into
generated programs alike. Programs you compile with geatsc are yours; their
license is decided only by what they link. The GeaStack framework and the
desktop, mobile and web targets are Apache-2.0 as well; the embedded board
support (targets, @geastack/chips) is GPL-3.0-only and needs a commercial
license for closed-source firmware. Contact contact@geastack.com for commercial terms.