# syntax-lang v0.2.0 — Lossless CST & builder
**The core, front-loaded.** v0.2.0 turns the scaffold into a working concrete
syntax tree: the [`Node`] / [`Element`] tree, the [`Builder`] a parser drives to
assemble it, and the iterative traversals that walk it. This was the hard part of
the roadmap, and the roadmap put it first.
## What is syntax-lang?
The lossless-CST layer of the `-lang` family — the substrate a formatter, a
language server, or a tree-sitter-style generator builds on. Unlike an abstract
syntax tree, a CST keeps everything: whitespace and comments ride in the tree as
ordinary leaf tokens, so the source can be reproduced from the tree byte for byte.
It owns no grammar: a language brings its own kind type, and syntax-lang supplies
the tree, the builder, and the walks.
## What's new in 0.2.0
### One kind, nodes and tokens
A language declares a single `enum` with both composite and lexical variants and
implements [`TokenKind`] on it. Trivia is marked, not dropped:
```rust
use syntax_lang::{Builder, Span, Token, TokenKind};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Kind { Root, Sum, Num, Plus, Space }
impl TokenKind for Kind {
fn is_trivia(&self) -> bool { matches!(self, Kind::Space) }
}
```
### `Builder` — three moves, no panic
A parser opens a node, pushes tokens, and closes the node. The outermost node
becomes the root:
```rust
# use syntax_lang::{Builder, Span, Token, TokenKind};
# #[derive(Clone, Copy, Debug, PartialEq, Eq)]
# enum Kind { Root, Sum, Num, Plus, Space }
# impl TokenKind for Kind { fn is_trivia(&self) -> bool { matches!(self, Kind::Space) } }
let mut b = Builder::new();
b.start_node(Kind::Root);
b.start_node(Kind::Sum);
b.token(Token::new(Kind::Num, Span::new(0, 1)));
b.token(Token::new(Kind::Space, Span::new(1, 2)));
b.token(Token::new(Kind::Plus, Span::new(2, 3)));
b.token(Token::new(Kind::Space, Span::new(3, 4)));
b.token(Token::new(Kind::Num, Span::new(4, 5)));
b.finish_node();
b.finish_node();
let root = b.finish().expect("balanced");
assert_eq!(root.text("1 + 2"), Some("1 + 2"));
```
Structural misuse — an unbalanced close, a token at the root, a second root — is
never a panic. The first fault is recorded and returned from `finish` as a
`BuildError`, so the builder can be driven on a parser's hot path without wrapping
every call in a `Result`.
### Lossless, zero-copy reconstruction
Tokens store spans, not copies of the text. `node.tokens()` yields every leaf in
source order (trivia included); concatenating their slices — or slicing the source
by `node.span()` — reproduces the source exactly. `node.text(source)` returns that
slice as a borrow, no allocation.
### Iterative everything
`Builder`, `tokens()`, `descendants()`, and `Drop` keep their work stack on the
heap. A 200,000-level tree — the kind adversarial input (a long run of open
delimiters) produces — is built, walked, and freed without overflowing the call
stack, so untrusted input cannot turn into a crash.
### Tests and benchmarks
`tests/cst.rs` holds the behavioural cases — losslessness, token order, trivia
filtering, every `BuildError`, the deep-chain build-walk-drop — and
`tests/properties.rs` cross-checks the invariants (losslessness, covering span,
gapless tiling, containment) against arbitrary generated tree shapes over a wide
`proptest` space. Benchmarks measure `build`, `tokens`, and `descendants` as the
tree grows.
## Breaking changes
**None** to released API: v0.1.0 had no public surface. The reserved no-op `serde`
feature is removed rather than carried unused toward the frozen 1.0 surface.
## Verification
```bash
cargo fmt --all -- --check
cargo clippy --all-targets -- -D warnings
cargo clippy --all-targets --all-features -- -D warnings
cargo clippy --no-default-features -- -D warnings
cargo test
cargo test --all-features
cargo build --no-default-features
cargo +1.85 build --all-features
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features
cargo audit
cargo deny check
```
All green on Windows and Linux (WSL2 Ubuntu). Counts at this tag: 19 unit + 10
behavioural + 6 property + 28 doctests.
## What's next
- **1.0.0 — API freeze.** The surface is stable and frozen until 2.0;
documentation, tests, and internal optimisation only.
## Installation
```toml
[dependencies]
syntax-lang = "0.2"
```
MSRV: Rust 1.85.
---
**Changelog:** [`CHANGELOG.md`](https://github.com/jamesgober/syntax-lang/blob/main/CHANGELOG.md#020---2026-07-01).