Skip to main content

Tooling and generated output

WAST treats the generated module as a developer-facing artifact, not an opaque compiler byproduct.

Readable WAT output

A conforming implementation emits readable WebAssembly Text. The output includes comments that identify the WAST declaration, statement, or expression that caused an instruction sequence.

;; source: return value + value;
(return
(i32.add
(local.get $value)
(local.get $value)))

An implementation may also provide .wasm binary output. Optimization is optional and must be an explicit flag; it must preserve observable behavior and traps.

Command-line interface

The reference implementation, wastc, exposes three subcommands: build, check, and lsp. There are no other flags — no --emit, no verbosity control, no catalog selection, no color options, and no stdin input mode.

wastc build <INPUT> [-o <OUTPUT>] [--tail-calls]
wastc check <INPUT> [--tail-calls]
wastc lsp

Everything goes to stderr

wastc never writes to stdout. Success banners, diagnostics, and errors all go to stderr. wastc build x.wast > log.txt captures an empty file. Redirect with 2> instead.

Exit codes

CodeMeaning
0clean build (file written), clean check, or clean LSP shutdown
1any diagnostics, a read error, a write error, or an LSP error
2argument/usage error (missing subcommand, unknown flag) — from clap

There is no distinct code separating compile errors from I/O errors.

build

Compiles a module to WAT. If -o/--output is omitted, the output path is the input path with its final extension replaced by .wata.b.wast becomes a.b.wat, and an extensionless noext becomes noext.wat. Output lands next to the input, not in the working directory.

$ wastc build examples/double.wast
wastc: catalog wastc-core-gc-0 -> examples/double.wat

A failing module reports diagnostics and exits 1. No output file is written — a stale .wat from a previous successful build is left in place, not truncated. Check the exit code; don't infer success from the file existing.

I/O failures report as error: could not read '<path>': <reason> or error: could not write '<path>': <reason>.

check

Runs the same analysis as build but never writes a file — useful for editor integrations and CI steps that only need pass/fail plus diagnostics.

$ wastc check examples/double.wast
wastc: examples/double.wast -- no errors

check and build share one code path, so they always agree on whether a module is valid; a module that fails check also fails build, and vice versa. Diagnostics are printed one per line as <phase> error at <line>:<col>: <message>:

$ wastc check broken.wast
type error at 2:3: 'return;' is only valid for a function with result '()'

Only the earliest non-empty diagnostic phase is ever reported — see Diagnostics for what that hides and why.

--tail-calls

Accepted by both build and check. It lowers an eligible return of a direct call to return_call (and an indirect one to return_call_ref). It is an output mode only: it changes how a valid program lowers, never which programs are valid.

On check it is therefore a no-op for the pass/fail result, since check discards the WAT. Its help text is also blank — that entry is missing a doc comment, not a hidden feature.

lsp

Starts a Language Server Protocol server on stdio, for editor integration. It supports:

  • Diagnostics — published on didOpen/didChange/didClose, using the same phase-ordered diagnostics build/check report.
  • Hover — signatures and shapes for functions, globals, structs, arrays, function types, tables, memories, and tags, and the declared type of a parameter or let/var/for-loop local at the cursor's position.
  • Document symbols — an outline of every declaration in the module, not just functions and globals.
  • Completion — names and signatures for every declaration kind in the module, every parameter and local in scope at the cursor's position, and every opcode in the wasm. escape hatch's instruction catalog while typing one, with per-opcode documentation and a stack signature sourced from vendor/wasm-ops/data.
  • Go to definition — jumps from a use site to the declaration.
  • Document highlight — highlights every occurrence of the identifier under the cursor in the current file.
  • Find references and rename — for a parameter or local, scoped to exactly that binding (so a same-named local in another function or a sibling block isn't included or renamed); for a top-level declaration, every textual occurrence in the file.
  • Signature help — shows a function's signature and the active parameter while typing a call's arguments.
  • Semantic tokens — classifies every identifier occurrence as a function, parameter, variable, type, or field, so an editor can tell them apart even where the grammar alone can't (tree-sitter already handles keyword/string/comment highlighting; this covers what's left).

Diagnostics, hover, document symbols, completion, definition, references, and rename all keep working on the rest of a file even while one declaration has a syntax error — the compiler's lexer, parser, and resolver each retain their best-effort partial result instead of discarding the whole file over one incomplete statement. Only the declaration actively being edited (and anything textually swallowed by its unclosed braces) goes dark; everything else in the file stays fully usable. Signature help and document highlight are plain text-based operations and work even on a file that doesn't parse at all. Local/parameter completion, hover, references, and rename read directly off the parsed (not typechecked) function body, so they keep working on a function's parameters and already-declared locals even while a later statement in that same function has a type error. Semantic tokens re-tokenizes the raw text directly, so it degrades to "no token" for whatever it can't classify rather than going blank for the whole file.

{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"capabilities": {}}}

Point your editor's LSP client at wastc lsp (no flags); it communicates over stdin/stdout using standard Content-Length-framed JSON-RPC, like any other language server.

Black-box compiler tests

The repository test suite is designed for any compiler binary through an adapter contract. It does not link to a compiler implementation.

WAST_TEST_ADAPTER="/path/to/compiler-adapter" python3 tests/run.py

The adapter produces WAT, advertises optional binary/optimization support, and forwards diagnostics. The suite executes exported check() -> i32 functions with Wasmtime, checks compile failures, and structurally inspects WAT comments and lowering patterns.

Read the repository's tests/README.md for the adapter protocol and fixture format.

Tree-sitter

tree-sitter-wast/ provides syntax parsing, editor highlighting, scopes, symbols, and folds. It intentionally does not implement semantic resolution or type checking.

The Docusaurus site uses that same grammar and highlights.scm during its build. Every fenced wast block is parsed at build time and receives CSS classes from Tree-sitter captures instead of generic Prism tokenization.

cd tree-sitter-wast
npm install
npm test

The generated parser is committed. Update grammar.js, corpus tests, generated parser files, and queries together when the syntax evolves.

For normative output requirements, read Lowering and validation.