Frontend Pipeline

The tswift frontend is a pure-Rust Swift compiler front-end that turns Swift source into a stable, runtime-facing typed AST. There is no C code, no LLVM, no unsafe, and no Swift toolchain involved.


Stage 1 — Lexer (tswift-lexer)

The lexer converts raw Swift source into a stream of tokens. It handles:

String interpolation is particularly tricky: the lexer emits a token stream that the parser then re-enters for the interpolated expression, allowing arbitrary nesting.


Stage 2 — AST (tswift-ast)

The AST crate defines the node types shared by all pipeline stages. Every node carries:

After semantic analysis, nodes also carry:


Stage 3 — Parser (tswift-parser)

A recursive-descent parser that consumes the token stream and produces an untyped AST. Highlights:

    
flowchart TD
  tokens["Token stream"]
  decls["Declarations\n(func, var, class, …)"]
  exprs["Expressions\n(binary, call, closure, …)"]
  stmts["Statements\n(if, for, switch, guard, …)"]
  pats["Patterns\n(case, tuple, optional, …)"]
  raw["Untyped AST"]

  tokens --> decls & exprs & stmts & pats --> raw
  style raw fill:#1e1e27,stroke:#a855f7,color:#eeeef5

  
Parser produces an untyped CST → AST walk

Stage 4 — Semantic Analysis (tswift-sema)

The semantic analysis pass resolves types and names:

The output is a typed AST where each expression node carries its resolved SwiftType.


Stage 5 — Runtime-facing AST (tswift-frontend)

tswift-frontend is the only seam between the frontend and the runtime. It:

  1. Drives the full pipeline (lexer → parser → sema) behind a single Analysis::analyze(source) entry point.
  2. Exposes the result as Analysis / Node / NodeKind — where NodeKind is tswift_ast::NodeKind. The frontend and the runtime share one node vocabulary, not two.

A Node is a thin cursor straight over the parse AST — there is no separate lowered tree or arena copy. The payloads the runtime reads (modifier bitmasks, numeric literal values, the #-directive name) are decoded on the fly. Because the seam is just a cursor, the runtime never depends on frontend internals, and the parse AST stays the single source of truth.

Historically the runtime was written against the decommissioned msf C frontend’s tree shape, so this crate carried a structural compat lowerer that reproduced it. That layer has been removed: the runtime now consumes the clean parse AST directly.


Why no unsafe?

The entire frontend stack is #![forbid(unsafe_code)]. Rust’s ownership model gives us memory safety during parsing for free — no garbage collector, no arena lifetime tricks, just owned Vec and Box trees.


Explore further