Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ The `.` anchor adapts to what it's anchoring:
| Pattern | Behavior |
| ----------- | ------------------------------------------- |
| `(a) . (b)` | Skip trivia, no named nodes between |
| `"x" . (b)` | Strictnothing between (anonymous involved) |
| `(a) . "x"` | Strictnothing between (anonymous involved) |
| `"x" . (b)` | Strictnothing between (anonymous involved) |
| `(a) . "x"` | Strictnothing between (anonymous involved) |

Rule: anchor is as strict as its strictest operand.

Expand Down
2 changes: 1 addition & 1 deletion crates/plotnik-core/src/interner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use std::collections::HashMap;
/// A lightweight handle to an interned string.
///
/// Comparing two symbols is O(1). Symbols are ordered by insertion order,
/// not lexicographicallyuse `Interner::resolve` if you need string ordering.
/// not lexicographicallyuse `Interner::resolve` if you need string ordering.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct Symbol(u32);

Expand Down
2 changes: 1 addition & 1 deletion crates/plotnik-lib/src/analyze/type_check/unify.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Unification logic for alternation branches.
//!
//! Handles merging TypeFlow from different branches of untagged alternations.
//! Tagged alternations don't unifythey produce Enum types directly.
//! Tagged alternations don't unifythey produce Enum types directly.

use std::collections::BTreeMap;

Expand Down
2 changes: 1 addition & 1 deletion crates/plotnik-lib/src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
//!
//! # Recovery Strategy
//!
//! The parser is resilientit always produces a tree. Recovery follows these rules:
//! The parser is resilientit always produces a tree. Recovery follows these rules:
//!
//! 1. Unknown tokens get wrapped in `SyntaxKind::Error` nodes and consumed
//! 2. Missing expected tokens emit a diagnostic but don't consume (parent may handle)
Expand Down
2 changes: 1 addition & 1 deletion docs/binary-format/04-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ This section defines the type system metadata used for code generation and runti
| TypeScript | Binding-provided object with `startPosition`, `text`, etc. |
| JSON | Unique node identifier (e.g., `"node:42"` or path-based) |

The handle provides access to node metadata (kind, span, text) without copying the source. Lifetime management is platform-specificRust enforces it statically, bindings may use reference counting or arena allocation.
The handle provides access to node metadata (kind, span, text) without copying the source. Lifetime management is platform-specificRust enforces it statically, bindings may use reference counting or arena allocation.

**TypeKind (u8)**: Discriminator for `TypeDef`.

Expand Down
2 changes: 1 addition & 1 deletion docs/binary-format/06-transitions.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ The compiler places effects based on semantic requirements: scope openers often

### 4.3. Epsilon Transitions

A Match instruction with `nav == Epsilon` is an **epsilon transition**it succeeds unconditionally without cursor movement or node checking. The VM skips navigation and node matching entirely, only executing effects and proceeding to successors. This enables:
A Match instruction with `nav == Epsilon` is an **epsilon transition**it succeeds unconditionally without cursor movement or node checking. The VM skips navigation and node matching entirely, only executing effects and proceeding to successors. This enables:

- **Branching at EOF**: `(a)?` must succeed when no node exists to match.
- **Pure control flow**: Decision points for quantifiers.
Expand Down
2 changes: 1 addition & 1 deletion docs/binary-format/07-dump-format.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ They require all three conditions:
- `node_field == None` (no field constraint)

A step with `nav == Stay` but with a type constraint (e.g., `(identifier)`) is NOT
epsilonit matches at the current cursor position.
epsilonit matches at the current cursor position.

**Capture effect consolidation**: Scalar capture effects (`Node`, `Text`, `Set`) are
placed directly on match instructions rather than in separate epsilon steps. Structural
Expand Down
6 changes: 3 additions & 3 deletions docs/binary-format/08-trace-format.md
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ Assignment:
16 ◀ (Assignment) ◼
```

First branch (Literal) matches immediatelycheckpoint at 28 is never used.
First branch (Literal) matches immediatelycheckpoint at 28 is never used.

---

Expand Down Expand Up @@ -323,7 +323,7 @@ Expression:
○ string hello
```

Both branches fail. No more checkpointsquery does not match. The CLI exits with code 1.
Both branches fail. No more checkpointsquery does not match. The CLI exits with code 1.

---

Expand Down Expand Up @@ -421,7 +421,7 @@ Assignment:
○ number 42
```

Type check fails at rootno navigation occurs. The CLI exits with code 1.
Type check fails at rootno navigation occurs. The CLI exits with code 1.

---

Expand Down
30 changes: 15 additions & 15 deletions docs/lang-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Plotnik is a pattern-matching language for tree-sitter syntax trees. It extends [tree-sitter's query syntax](https://tree-sitter.github.io/tree-sitter/using-parsers/queries/1-syntax.html) with named expressions, recursion, and static type inference.

Predicates (`#eq?`, `#match?`) and directives (`#set!`) are intentionally unsupportedfiltering logic belongs in your host language.
Predicates (`#eq?`, `#match?`) and directives (`#set!`) are intentionally unsupportedfiltering logic belongs in your host language.

---

Expand Down Expand Up @@ -51,13 +51,13 @@ Matches `a.b` even if there's a comment like `a /* x */ .b` (trivia skipped), bu
(call_expression (identifier) @fn . "(") ; no trivia between name and paren
```

When any side of the anchor is an anonymous node (literal token), the match is exactno trivia allowed.
When any side of the anchor is an anonymous node (literal token), the match is exactno trivia allowed.

**Rule**: The anchor is as strict as its strictest operand. Anonymous nodes demand precision; named nodes tolerate trivia.

### Partial Matching

Node patterns are openunmentioned children are ignored:
Node patterns are openunmentioned children are ignored:

```
(binary_expression left: (identifier) @left)
Expand All @@ -78,7 +78,7 @@ Sequences `{...}` advance through siblings in order, skipping non-matching nodes
)
```

Fields participate in sequential matchingthey're not independent lookups.
Fields participate in sequential matchingthey're not independent lookups.

---

Expand Down Expand Up @@ -206,11 +206,11 @@ The pattern is 4 levels deep, but the output is flat. You're extracting specific
→ { methods: { method: Node, name: Node }[] }
```

This prevents association losseach struct is a distinct object, not parallel arrays that lose per-iteration grouping. See [Type System: Strict Dimensionality](type-system.md#1-strict-dimensionality).
This prevents association losseach struct is a distinct object, not parallel arrays that lose per-iteration grouping. See [Type System: Strict Dimensionality](type-system.md#1-strict-dimensionality).

### The Node Type

Default capture typea reference to a tree-sitter node:
Default capture typea reference to a tree-sitter node:

```
interface Node {
Expand Down Expand Up @@ -297,7 +297,7 @@ Use cases:
Rules:

- `@_` and `@_name` match like regular captures but produce no output
- Named suppressive captures (`@_foo`) are equivalent to `@_`the name is documentation only
- Named suppressive captures (`@_foo`) are equivalent to `@_`the name is documentation only
- Type annotations are not allowed on suppressive captures
- Nesting works: `@_outer` containing `@_inner` correctly suppresses both

Expand Down Expand Up @@ -351,7 +351,7 @@ Match named nodes (non-terminals and named terminals) by type:
(binary_expression (identifier) (number))
```

Children can be partialthis matches any `binary_expression` with at least one `string_literal` child:
Children can be partialthis matches any `binary_expression` with at least one `string_literal` child:

```
(binary_expression (string_literal))
Expand Down Expand Up @@ -490,7 +490,7 @@ decorator: (decorator)* @decorators ; repeats the whole field
value: [A: (x) B: (y)] @kind ; captures the field (containing the alternation)
```

This allows repeating fields (useful for things like decorators in JavaScript). The capture still correctly produces the value's typefor alternations, you get the tagged union, not a raw node.
This allows repeating fields (useful for things like decorators in JavaScript). The capture still correctly produces the value's typefor alternations, you get the tagged union, not a raw node.

### Negated Fields

Expand All @@ -502,7 +502,7 @@ Assert a field is absent with `-`:
-type_parameters)
```

Negated fields don't affect the output typethey're purely structural constraints:
Negated fields don't affect the output typethey're purely structural constraints:

```typescript
{
Expand Down Expand Up @@ -532,7 +532,7 @@ Output types:
{ decorators: [Node, ...Node[]] }
```

The `+` quantifier always produces non-empty arraysno opt-out.
The `+` quantifier always produces non-empty arraysno opt-out.

Plotnik also supports non-greedy variants: `*?`, `+?`, `??`

Expand Down Expand Up @@ -745,7 +745,7 @@ interface Target {

## Anchors

The anchor `.` constrains sibling positions. Anchors don't affect typesthey're structural constraints.
The anchor `.` constrains sibling positions. Anchors don't affect typesthey're structural constraints.

### Anchor Strictness

Expand All @@ -758,7 +758,7 @@ Anchor behavior depends on the node types being anchored:
| `(a) . "x"` | Disallowed | Disallowed |
| `"x" . "y"` | Disallowed | Disallowed |

When anchoring named nodes, trivia (comments, whitespace) is skipped but no other named nodes may appear between. When any operand is an anonymous node (literal token), the anchor enforces exact adjacencynothing in between.
When anchoring named nodes, trivia (comments, whitespace) is skipped but no other named nodes may appear between. When any operand is an anonymous node (literal token), the anchor enforces exact adjacencynothing in between.

### Position Anchors

Expand Down Expand Up @@ -792,7 +792,7 @@ Here, no trivia is allowed between the function name and the opening parenthesis

### Output Types

Anchors are structural constraints onlythey don't affect output types:
Anchors are structural constraints onlythey don't affect output types:

```typescript
{ first: Node }
Expand Down Expand Up @@ -835,7 +835,7 @@ The rules:

- **Boundary anchors** (at start/end of sequence) need a parent named node to provide first/last child or adjacent sibling semantics
- **Interior anchors** (between items in a sequence) are always valid because both sides are explicitly defined
- **Alternations** cannot contain anchors directlyanchors must be inside a branch expression
- **Alternations** cannot contain anchors directlyanchors must be inside a branch expression

---

Expand Down
12 changes: 6 additions & 6 deletions docs/runtime-engine.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Executes compiled query graphs against Tree-sitter syntax trees. See [06-transit

```rust
struct VM<'t> {
cursor: TreeCursor<'t>, // Never resetpreserves descendant_index for O(1) backtrack
cursor: TreeCursor<'t>, // Never resetpreserves descendant_index for O(1) backtrack
ip: StepId, // Current step index
frames: Vec<Frame>, // Call stack
effects: EffectLog<'t>, // Side-effect log
Expand Down Expand Up @@ -43,7 +43,7 @@ Fetch step at `ip` → dispatch by `type_id` → execute → update `ip`.

### Epsilon Transitions

A `Match8` or `Match16–64` with `node_type: None`, `node_field: None`, and `nav: Stay` is an **epsilon transition**it succeeds unconditionally without cursor interaction. This enables pure control-flow decisions (branching for quantifiers) even when the cursor is exhausted (EOF).
A `Match8` or `Match16–64` with `node_type: None`, `node_field: None`, and `nav: Stay` is an **epsilon transition**it succeeds unconditionally without cursor interaction. This enables pure control-flow decisions (branching for quantifiers) even when the cursor is exhausted (EOF).

Common patterns:

Expand Down Expand Up @@ -97,7 +97,7 @@ struct Frame {
}
```

"Pop" just moves `current`frames remain for checkpoint restoration.
"Pop" just moves `current`frames remain for checkpoint restoration.

### Pruning

Expand All @@ -110,7 +110,7 @@ arena.truncate(high_water + 1)

Bounds arena to O(max_checkpoint_depth + current_call_depth).

**O(1) Invariant**: The checkpoint stack maintains `max_frame_ref`the highest `frame_index` referenced by any active checkpoint.
**O(1) Invariant**: The checkpoint stack maintains `max_frame_ref`the highest `frame_index` referenced by any active checkpoint.

| Operation | Invariant Update | Complexity |
| --------- | ---------------------------------------------------- | -------------- |
Expand Down Expand Up @@ -185,7 +185,7 @@ The `Node` and `Text` variants carry the actual `tree_sitter::Node` so the mater

### Bytecode vs Runtime Effects

**Bytecode** (`EffectOp` in `bytecode/effects.rs`): Compact 2-byte encoding with 6-bit opcode + 10-bit payload. No embedded datathe `Node` opcode signals "capture `matched_node`" but doesn't carry it.
**Bytecode** (`EffectOp` in `bytecode/effects.rs`): Compact 2-byte encoding with 6-bit opcode + 10-bit payload. No embedded datathe `Node` opcode signals "capture `matched_node`" but doesn't carry it.

**Runtime** (`RuntimeEffect`): The VM interprets bytecode effects and produces runtime effects with embedded data. When the VM executes a bytecode `Node` effect, it emits `RuntimeEffect::Node(matched_node)`.

Expand All @@ -204,4 +204,4 @@ Exhaustion returns `RuntimeError`, not panic.

## Trivia Handling

Per-language trivia list used for `*Skip` navigation. A node is never skipped if it matches the current target`(comment)` still matches comments.
Per-language trivia list used for `*Skip` navigation. A node is never skipped if it matches the current target`(comment)` still matches comments.
12 changes: 6 additions & 6 deletions docs/tree-navigation.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ struct Checkpoint {
}
```

**Critical constraint**: The cursor must be created at the tree root and never call `reset()`. The `descendant_index` is relative to the cursor's root`reset(node)` would invalidate all checkpoints.
**Critical constraint**: The cursor must be created at the tree root and never call `reset()`. The `descendant_index` is relative to the cursor's root`reset(node)` would invalidate all checkpoints.

### Why TreeCursor

Expand All @@ -35,10 +35,10 @@ struct Checkpoint {
| `descendant_index()` | O(1) | — |
| `goto_descendant()` | O(depth) | — |

The `Node` API's `next_sibling()` is O(siblings)unacceptable for repeated backtracking. TreeCursor provides O(1) sibling traversal and 4-byte checkpoints via `descendant_index`.
The `Node` API's `next_sibling()` is O(siblings)unacceptable for repeated backtracking. TreeCursor provides O(1) sibling traversal and 4-byte checkpoints via `descendant_index`.

- Checkpoint save: O(1)
- Checkpoint restore: O(depth)cold path only
- Checkpoint restore: O(depth)cold path only

## Nav Encoding

Expand Down Expand Up @@ -88,7 +88,7 @@ Each mode defines what happens when a match fails:

| Mode | Constraint |
| ----------------- | --------------------------------------------- |
| `Up(n)` | Nonejust ascend n levels |
| `Up(n)` | Nonejust ascend n levels |
| `UpSkipTrivia(n)` | Must be at last non-trivia child, then ascend |
| `UpExact(n)` | Must be at last child, then ascend |

Expand Down Expand Up @@ -185,7 +185,7 @@ Using dump format from [07-dump-format.md](binary-format/07-dump-format.md):
05 *↑³ ◼
```

Multi-level `Up(n)` coalesces ascent when no intermediate anchors exist. Not yet implementedcurrently emits individual `Up(1)` steps.
Multi-level `Up(n)` coalesces ascent when no intermediate anchors exist. Not yet implementedcurrently emits individual `Up(1)` steps.

**Mixed anchors**: `(a (b) . (c) .)`

Expand Down Expand Up @@ -249,7 +249,7 @@ for &fid in pattern.neg_fields {
}
```

Both constraints participate in the skip policya mismatch triggers retry (for `*`), fail-if-non-trivia (for `~`), or immediate fail (for `.`).
Both constraints participate in the skip policya mismatch triggers retry (for `*`), fail-if-non-trivia (for `~`), or immediate fail (for `.`).

## Call Navigation

Expand Down
10 changes: 5 additions & 5 deletions docs/type-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Two principles guide the type system:

1. **Flat structure**: Captures bubble up to the nearest scope boundary.

2. **Strict dimensionality**: Quantifiers (`*`, `+`) containing captures require a struct capture. The alternativeparallel arraysloses per-iteration association between `a[i]` and `b[i]`.
2. **Strict dimensionality**: Quantifiers (`*`, `+`) containing captures require a struct capture. The alternativeparallel arraysloses per-iteration association between `a[i]` and `b[i]`.

### Why Transparent Scoping

Expand All @@ -24,7 +24,7 @@ Extracting a pattern into a definition shouldn't change output:
// Extracted
Func = (function name: (identifier) @name)
(Func)
→ { name: Node } // Same shape@name bubbles through
→ { name: Node } // Same shape@name bubbles through
```

If definitions created implicit boundaries, extraction would wrap output in a new struct, breaking downstream types.
Expand Down Expand Up @@ -105,7 +105,7 @@ The strict rule forces you to think about structure upfront.

### Optional Bubbling

The `?` quantifier does **not** add dimensionalityit produces at most one value, not a list. Therefore, optional groups without captures are allowed:
The `?` quantifier does **not** add dimensionalityit produces at most one value, not a list. Therefore, optional groups without captures are allowed:

```
{ (decorator) @dec }?
Expand Down Expand Up @@ -215,7 +215,7 @@ Within each struct, inner quantifiers apply to fields:
→ { items: { decs: Node[], fn: Node }[] }
```

Each struct has its own `decs` arrayno cross-struct mixing.
Each struct has its own `decs` arrayno cross-struct mixing.

## 5. Type Unification in Alternations

Expand Down Expand Up @@ -257,7 +257,7 @@ When a quantified capture appears in some branches but not others, the missing b
] // x: Node[]
```

Untagged alternations are "I don't care which branch matched"so distinguishing "branch didn't match" from "matched zero times" is irrelevant. The empty array is easier to consume downstream.
Untagged alternations are "I don't care which branch matched"so distinguishing "branch didn't match" from "matched zero times" is irrelevant. The empty array is easier to consume downstream.

When types start to conflict, use tagged alternations:

Expand Down