panproto_parse/emit_pretty.rs
1#![allow(
2 clippy::module_name_repetitions,
3 clippy::too_many_lines,
4 clippy::too_many_arguments,
5 clippy::map_unwrap_or,
6 clippy::option_if_let_else,
7 clippy::elidable_lifetime_names,
8 clippy::items_after_statements,
9 clippy::needless_pass_by_value,
10 clippy::single_match_else,
11 clippy::manual_let_else,
12 clippy::match_same_arms,
13 clippy::missing_const_for_fn,
14 clippy::single_char_pattern,
15 clippy::naive_bytecount,
16 clippy::expect_used,
17 clippy::redundant_pub_crate,
18 clippy::used_underscore_binding,
19 clippy::redundant_field_names,
20 clippy::struct_field_names,
21 clippy::redundant_else,
22 clippy::similar_names
23)]
24
25//! De-novo source emission from a by-construction schema.
26//!
27//! [`AstParser::emit`] reconstructs source from byte-position fragments
28//! that the parser stored on the schema during `parse`. That works for
29//! edit pipelines (`parse → transform → emit`) but fails for schemas
30//! built by hand (`SchemaBuilder` with no parse history): they carry
31//! no `start-byte`, no `interstitial-N`, no `literal-value`, and the
32//! reconstructor returns `Err(EmitFailed { reason: "schema has no
33//! text fragments" })`.
34//!
35//! This module renders such schemas to source bytes by walking
36//! tree-sitter's `grammar.json` production rules. For each schema
37//! vertex of kind `K`, the walker looks up `K`'s production in the
38//! grammar and emits its body in order:
39//!
40//! - `STRING` nodes contribute literal token bytes directly.
41//! - `SYMBOL` and `FIELD` nodes recurse into the schema's children,
42//! matching by edge kind (which is the tree-sitter field name).
43//! - `SEQ` emits its members in order.
44//! - `CHOICE` picks the alternative whose head `SYMBOL` matches an
45//! actual child kind, or whose terminals appear in the rendered
46//! prefix; falls back to the first non-`BLANK` alternative when no
47//! alternative matches.
48//! - `REPEAT` and `REPEAT1` emit their content once per matching
49//! child edge in declared order.
50//! - `OPTIONAL` emits its content iff a corresponding child edge or
51//! constraint is populated.
52//! - `PATTERN` is a regex placeholder for variable-text terminals
53//! (identifiers, numbers, quoted strings). The walker emits a
54//! `literal-value` constraint when present and otherwise falls
55//! back to a placeholder derived from the regex shape.
56//! - `BLANK`, `TOKEN`, `IMMEDIATE_TOKEN`, `ALIAS`, `PREC*` are
57//! handled transparently (the inner content is emitted; the
58//! wrapper is dropped).
59//!
60//! Whitespace and indentation come from a `FormatPolicy` applied
61//! during emission. The default policy inserts a single space between
62//! adjacent tokens, a newline after `;` / `}` / `{`, and tracks an
63//! indent counter on `{` / `}` boundaries.
64//!
65//! Output is *syntactically valid* for any grammar that ships
66//! `grammar.json`. Idiomatic formatting (rustfmt-style spacing rules,
67//! per-language conventions) is a polish layer that lives outside
68//! this module.
69
70use std::collections::BTreeMap;
71
72use panproto_schema::{Edge, Schema};
73use serde::Deserialize;
74
75use crate::error::ParseError;
76
77// ═══════════════════════════════════════════════════════════════════
78// Grammar JSON model
79// ═══════════════════════════════════════════════════════════════════
80
81/// A single tree-sitter production rule.
82///
83/// Mirrors the shape emitted by `tree-sitter generate`: every node has
84/// a `type` discriminator that selects a structural variant. The
85/// untyped subset (`PATTERN`, `STRING`, `SYMBOL`, `BLANK`) handles
86/// terminals; the structural subset (`SEQ`, `CHOICE`, `REPEAT`,
87/// `REPEAT1`, `OPTIONAL`, `FIELD`, `ALIAS`, `TOKEN`,
88/// `IMMEDIATE_TOKEN`, `PREC*`) builds composite productions.
89#[derive(Debug, Clone, Deserialize)]
90#[serde(tag = "type")]
91#[non_exhaustive]
92pub enum Production {
93 /// Concatenation of productions.
94 #[serde(rename = "SEQ")]
95 Seq {
96 /// Ordered members; each is emitted in turn.
97 members: Vec<Self>,
98 },
99 /// Alternation between productions.
100 #[serde(rename = "CHOICE")]
101 Choice {
102 /// Alternatives; the walker picks one based on the schema's
103 /// children and constraints.
104 members: Vec<Self>,
105 },
106 /// Zero-or-more repetition.
107 #[serde(rename = "REPEAT")]
108 Repeat {
109 /// The repeated body.
110 content: Box<Self>,
111 },
112 /// One-or-more repetition.
113 #[serde(rename = "REPEAT1")]
114 Repeat1 {
115 /// The repeated body.
116 content: Box<Self>,
117 },
118 /// Optional inclusion (zero or one).
119 ///
120 /// Tree-sitter usually emits `OPTIONAL` as `CHOICE { content,
121 /// BLANK }`, but recent generator versions also emit explicit
122 /// `OPTIONAL` nodes; both shapes are accepted.
123 #[serde(rename = "OPTIONAL")]
124 Optional {
125 /// The optional body.
126 content: Box<Self>,
127 },
128 /// Reference to another rule by name.
129 #[serde(rename = "SYMBOL")]
130 Symbol {
131 /// Name of the referenced rule (matches a vertex kind on the
132 /// schema side).
133 name: String,
134 },
135 /// Literal token bytes.
136 #[serde(rename = "STRING")]
137 String {
138 /// The literal token. Emitted verbatim.
139 value: String,
140 },
141 /// Regex-matched terminal.
142 ///
143 /// At parse time this matches arbitrary bytes; at emit time the
144 /// walker substitutes a `literal-value` constraint when present
145 /// and falls back to a placeholder otherwise.
146 #[serde(rename = "PATTERN")]
147 Pattern {
148 /// The original regex.
149 value: String,
150 },
151 /// The empty production. Emits nothing.
152 #[serde(rename = "BLANK")]
153 Blank,
154 /// Named field over a content production.
155 ///
156 /// The field `name` matches an edge kind on the schema side; the
157 /// walker resolves the corresponding child vertex and recurses
158 /// into `content` with that child as context.
159 #[serde(rename = "FIELD")]
160 Field {
161 /// Field name (matches edge kind).
162 name: String,
163 /// The contents of the field.
164 content: Box<Self>,
165 },
166 /// An aliased production.
167 ///
168 /// `value` records the parser-visible kind; the walker emits
169 /// `content` and ignores the alias rename.
170 #[serde(rename = "ALIAS")]
171 Alias {
172 /// The aliased content.
173 content: Box<Self>,
174 /// Whether the alias is a named node.
175 #[serde(default)]
176 named: bool,
177 /// The alias's surface name.
178 #[serde(default)]
179 value: String,
180 },
181 /// A token wrapper.
182 ///
183 /// Tree-sitter uses `TOKEN` to mark a sub-rule as a single
184 /// lexical token; the walker emits the inner content unchanged.
185 #[serde(rename = "TOKEN")]
186 Token {
187 /// The wrapped content.
188 content: Box<Self>,
189 },
190 /// An immediate-token wrapper (no preceding whitespace).
191 ///
192 /// Treated like [`Production::Token`] for emit purposes.
193 #[serde(rename = "IMMEDIATE_TOKEN")]
194 ImmediateToken {
195 /// The wrapped content.
196 content: Box<Self>,
197 },
198 /// Precedence wrapper.
199 #[serde(rename = "PREC")]
200 Prec {
201 /// Precedence value (numeric or string). Ignored at emit time.
202 #[allow(dead_code)]
203 value: serde_json::Value,
204 /// The wrapped content.
205 content: Box<Self>,
206 },
207 /// Left-associative precedence wrapper.
208 #[serde(rename = "PREC_LEFT")]
209 PrecLeft {
210 /// Precedence value. Ignored at emit time.
211 #[allow(dead_code)]
212 value: serde_json::Value,
213 /// The wrapped content.
214 content: Box<Self>,
215 },
216 /// Right-associative precedence wrapper.
217 #[serde(rename = "PREC_RIGHT")]
218 PrecRight {
219 /// Precedence value. Ignored at emit time.
220 #[allow(dead_code)]
221 value: serde_json::Value,
222 /// The wrapped content.
223 content: Box<Self>,
224 },
225 /// Dynamic precedence wrapper.
226 #[serde(rename = "PREC_DYNAMIC")]
227 PrecDynamic {
228 /// Precedence value. Ignored at emit time.
229 #[allow(dead_code)]
230 value: serde_json::Value,
231 /// The wrapped content.
232 content: Box<Self>,
233 },
234 /// Reserved-word wrapper (tree-sitter ≥ 0.25).
235 ///
236 /// Tree-sitter's `RESERVED` rule marks an inner production as a
237 /// reserved-word context: the parser excludes the listed identifiers
238 /// from being treated as the inner symbol. The `context_name`
239 /// metadata names the reserved-word set; the emitter does not need
240 /// it (we are walking schema → bytes, not enforcing reserved-word
241 /// constraints), so we emit the inner content unchanged, the same
242 /// way [`Production::Token`] and [`Production::ImmediateToken`] do.
243 #[serde(rename = "RESERVED")]
244 Reserved {
245 /// The wrapped content.
246 content: Box<Self>,
247 /// Name of the reserved-word context. Ignored at emit time.
248 #[allow(dead_code)]
249 #[serde(default)]
250 context_name: String,
251 },
252}
253
254/// A grammar's production-rule table, deserialized from `grammar.json`.
255///
256/// Only the fields the emitter consumes are decoded; precedences,
257/// conflicts, externals, and other parser-only metadata are ignored.
258#[derive(Debug, Clone, Deserialize)]
259#[non_exhaustive]
260pub struct Grammar {
261 /// Grammar name (e.g. `"rust"`, `"typescript"`).
262 #[allow(dead_code)]
263 pub name: String,
264 /// Map from rule name (a vertex kind on the schema side) to
265 /// production. Entries are kept in lexical order so iteration
266 /// is deterministic.
267 pub rules: BTreeMap<String, Production>,
268 /// Supertypes declared in the grammar's `supertypes` field. A
269 /// supertype is a rule whose body is a `CHOICE` of `SYMBOL`
270 /// references; tree-sitter parsers report a node's kind as one
271 /// of the subtypes (e.g. `identifier`, `typed_parameter`) rather
272 /// than the supertype name (`parameter`), so the emitter needs to
273 /// know that a child kind in a subtype set should match the
274 /// supertype name when a SYMBOL references it.
275 #[serde(default, deserialize_with = "deserialize_supertypes")]
276 pub supertypes: std::collections::HashSet<String>,
277 /// Tree-sitter `extras` rules: the named symbols (typically comments)
278 /// that tree-sitter skips at parse time but records as children of the
279 /// surrounding vertex. They appear nowhere in the production grammar,
280 /// so the rule walker cannot reconcile them against the cursor — the
281 /// emit pass therefore drains them as a side channel: at vertex entry
282 /// and between REPEAT iterations any leading extras-kind edges are
283 /// consumed and emitted directly. The set is populated at
284 /// `Grammar::from_bytes` by collecting every `SYMBOL { name }` and
285 /// named `ALIAS { value, named: true }` under the top-level `extras`
286 /// array. Pattern-only extras (e.g. `\s` whitespace) are not vertex
287 /// kinds and are excluded.
288 #[serde(default, deserialize_with = "deserialize_extras")]
289 pub extras: std::collections::HashSet<String>,
290 /// Precomputed subtyping closure: `subtypes[symbol_name]` is the
291 /// set of vertex kinds that satisfy a SYMBOL `symbol_name`
292 /// reference on the schema side.
293 ///
294 /// Built once at [`Grammar::from_bytes`] time by walking each
295 /// hidden rule (`_`-prefixed), declared supertype, and named
296 /// `ALIAS { value: K, ... }` production to its leaf SYMBOLs and
297 /// recording the closure. This replaces the prior heuristic
298 /// `kind_satisfies_symbol` that walked the rule body on every
299 /// query: lookups are now O(1) and the relation is exactly the
300 /// transitive closure of "is reachable via hidden / supertype /
301 /// alias dispatch", with no over-expansion through non-hidden
302 /// non-supertype rule references.
303 #[serde(skip)]
304 pub subtypes: std::collections::HashMap<String, std::collections::HashSet<String>>,
305 /// Precomputed Yield sets: `yield_sets[rule_name]` is the set of
306 /// concrete vertex kinds that can appear as the **first named
307 /// child** when that rule's production is taken.
308 ///
309 /// Defined inductively:
310 /// - `Yield(SYMBOL S)` where S is hidden/supertype = `Yield(rules[S])`
311 /// - `Yield(SYMBOL S)` where S is concrete = `{S}`
312 /// - `Yield(SEQ [M1, ...])` = `Yield(M1)` (only first member)
313 /// - `Yield(CHOICE [M1, ..., Mn])` = `⋃ Yield(Mi)`
314 /// - `Yield(OPTIONAL { c })` = `Yield(c) ∪ {ε}`
315 /// - `Yield(BLANK)` = `{ε}`
316 /// - Wrappers (PREC*, TOKEN, FIELD, REPEAT, etc.) = `Yield(content)`
317 /// - `Yield(STRING)` = `Yield(PATTERN)` = `∅`
318 /// - `Yield(ALIAS { value: V, named: true })` = `{V}`
319 ///
320 /// Epsilon is represented as the empty string `""`.
321 #[serde(skip)]
322 pub yield_sets: std::collections::HashMap<String, std::collections::HashSet<String>>,
323 /// Child kinds allowed per parent kind, derived from node-types.json.
324 /// Maps parent kind to the set of ALL named child kinds that tree-sitter's
325 /// parser can produce for that parent (from both `children.types` and
326 /// `fields.*.types`). Used by `augment_subtypes_from_node_types` to
327 /// close the grammar/parser divergence gap.
328 #[serde(skip)]
329 pub node_type_children: std::collections::HashMap<String, std::collections::HashSet<String>>,
330 /// Anonymous ALIAS values for external scanner tokens. Maps external
331 /// symbol name (e.g. `_ternary_qmark`) to the ALIAS value string
332 /// (e.g. `"?"`). Built by scanning grammar.json rule bodies for
333 /// `ALIAS { content: SYMBOL S, named: false, value: V }` where S
334 /// has no grammar rule.
335 #[serde(skip)]
336 pub external_alias_map: std::collections::HashMap<String, String>,
337 /// Rules whose `{`/`}` STRING tokens are inline delimiters (e.g.
338 /// string interpolation) rather than block scopes. Identified
339 /// structurally: a rule whose SEQ contains `{` and `}` but no
340 /// REPEAT/REPEAT1 between them.
341 #[serde(skip)]
342 pub inline_brace_rules: std::collections::HashSet<String>,
343}
344
345fn deserialize_supertypes<'de, D>(
346 deserializer: D,
347) -> Result<std::collections::HashSet<String>, D::Error>
348where
349 D: serde::Deserializer<'de>,
350{
351 let entries: Vec<serde_json::Value> = Vec::deserialize(deserializer)?;
352 let mut out = std::collections::HashSet::new();
353 for entry in entries {
354 match entry {
355 serde_json::Value::String(s) => {
356 out.insert(s);
357 }
358 serde_json::Value::Object(map) => {
359 if let Some(serde_json::Value::String(name)) = map.get("name") {
360 out.insert(name.clone());
361 }
362 }
363 _ => {}
364 }
365 }
366 Ok(out)
367}
368
369fn deserialize_extras<'de, D>(
370 deserializer: D,
371) -> Result<std::collections::HashSet<String>, D::Error>
372where
373 D: serde::Deserializer<'de>,
374{
375 let entries: Vec<serde_json::Value> = Vec::deserialize(deserializer)?;
376 let mut out = std::collections::HashSet::new();
377 for entry in entries {
378 if let serde_json::Value::Object(map) = entry {
379 let ty = map.get("type").and_then(serde_json::Value::as_str);
380 match ty {
381 // SYMBOL { name: K } — the extras rule is a named symbol
382 // (typically `line_comment` / `block_comment`). The kind
383 // K appears as a real child vertex on the schema side.
384 Some("SYMBOL") => {
385 if let Some(serde_json::Value::String(name)) = map.get("name") {
386 out.insert(name.clone());
387 }
388 }
389 // ALIAS { content, value: V, named: true } — the extras
390 // rule renames its content; V is the kind on the schema.
391 Some("ALIAS") => {
392 let named = map
393 .get("named")
394 .and_then(serde_json::Value::as_bool)
395 .unwrap_or(false);
396 if named {
397 if let Some(serde_json::Value::String(value)) = map.get("value") {
398 out.insert(value.clone());
399 }
400 }
401 }
402 // PATTERN / STRING / TOKEN entries describe inter-token
403 // whitespace and have no vertex-side representation.
404 _ => {}
405 }
406 }
407 }
408 Ok(out)
409}
410
411impl Grammar {
412 /// Parse a grammar's `grammar.json` bytes.
413 ///
414 /// Builds the subtyping closure as part of construction so every
415 /// downstream lookup is O(1). The closure is the least relation
416 /// containing `(K, K)` for every rule key `K` and closed under:
417 ///
418 /// - hidden-rule expansion: if `S` is hidden and a SYMBOL `S` may
419 /// reach SYMBOL `K`, then `K ⊑ S`.
420 /// - supertype expansion: if `S` is in the grammar's supertypes
421 /// block and `K` is one of `S`'s alternatives, then `K ⊑ S`.
422 /// - alias renaming: if a rule body contains
423 /// `ALIAS { content: SYMBOL R, value: A, named: true }` where
424 /// `R` reaches kind `K` (or `K = R` when no further hop), then
425 /// `A ⊑ R` and `K ⊑ A`.
426 ///
427 /// # Errors
428 ///
429 /// Returns [`ParseError::EmitFailed`] when the bytes are not a
430 /// valid `grammar.json` document.
431 pub fn from_bytes(protocol: &str, bytes: &[u8]) -> Result<Self, ParseError> {
432 Self::from_bytes_with_node_types(protocol, bytes, None)
433 }
434
435 /// Parse a grammar from both `grammar.json` and optionally
436 /// `node-types.json` bytes.
437 ///
438 /// # Errors
439 ///
440 /// Returns [`ParseError::EmitFailed`] when `grammar_bytes` is
441 /// not a valid `grammar.json` document.
442 pub fn from_bytes_with_node_types(
443 protocol: &str,
444 grammar_bytes: &[u8],
445 node_types_bytes: Option<&[u8]>,
446 ) -> Result<Self, ParseError> {
447 let mut grammar: Self =
448 serde_json::from_slice(grammar_bytes).map_err(|e| ParseError::EmitFailed {
449 protocol: protocol.to_owned(),
450 reason: format!("grammar.json deserialization failed: {e}"),
451 })?;
452 grammar.subtypes = compute_subtype_closure(&grammar);
453 if let Some(nt_bytes) = node_types_bytes {
454 grammar.node_type_children = build_node_type_children(nt_bytes);
455 augment_subtypes_from_node_types(&mut grammar);
456 }
457 grammar.external_alias_map = build_external_alias_map(&grammar);
458 grammar.inline_brace_rules = identify_inline_brace_rules(&grammar);
459 grammar.yield_sets = compute_yield_sets(&grammar);
460 Ok(grammar)
461 }
462}
463
464/// Compute the subtyping relation as a forward-indexed map from a
465/// SYMBOL name to the set of vertex kinds that satisfy that SYMBOL.
466fn compute_subtype_closure(
467 grammar: &Grammar,
468) -> std::collections::HashMap<String, std::collections::HashSet<String>> {
469 use std::collections::{HashMap, HashSet};
470 // Edges of the "kind X satisfies SYMBOL Y" relation. `K ⊑ Y` is
471 // recorded whenever Y is reached by walking the grammar's
472 // ALIAS / hidden-rule / supertype dispatch from a position where
473 // K is the actual vertex kind.
474 let mut subtypes: HashMap<String, HashSet<String>> = HashMap::new();
475 for name in grammar.rules.keys() {
476 subtypes
477 .entry(name.clone())
478 .or_default()
479 .insert(name.clone());
480 }
481
482 // First pass: collect the immediate "satisfies" edges from each
483 // expandable rule (hidden, supertype) to the kinds reachable by
484 // walking its body, plus alias edges.
485 fn walk<'g>(
486 grammar: &'g Grammar,
487 production: &'g Production,
488 visited: &mut HashSet<&'g str>,
489 out: &mut HashSet<String>,
490 ) {
491 match production {
492 Production::Symbol { name } => {
493 // Direct subtype.
494 out.insert(name.clone());
495 // Continue expansion through hidden / supertype rules
496 // so the closure traverses pass-through dispatch.
497 let expand = name.starts_with('_') || grammar.supertypes.contains(name.as_str());
498 if expand && visited.insert(name.as_str()) {
499 if let Some(rule) = grammar.rules.get(name) {
500 walk(grammar, rule, visited, out);
501 }
502 }
503 }
504 Production::Choice { members } | Production::Seq { members } => {
505 for m in members {
506 walk(grammar, m, visited, out);
507 }
508 }
509 Production::Alias {
510 content,
511 named,
512 value,
513 } => {
514 if *named && !value.is_empty() {
515 out.insert(value.clone());
516 }
517 walk(grammar, content, visited, out);
518 }
519 Production::Repeat { content }
520 | Production::Repeat1 { content }
521 | Production::Optional { content }
522 | Production::Field { content, .. }
523 | Production::Token { content }
524 | Production::ImmediateToken { content }
525 | Production::Prec { content, .. }
526 | Production::PrecLeft { content, .. }
527 | Production::PrecRight { content, .. }
528 | Production::PrecDynamic { content, .. }
529 | Production::Reserved { content, .. } => {
530 walk(grammar, content, visited, out);
531 }
532 _ => {}
533 }
534 }
535
536 for (name, rule) in &grammar.rules {
537 let expand = name.starts_with('_') || grammar.supertypes.contains(name.as_str());
538 if !expand {
539 continue;
540 }
541 let mut visited: HashSet<&str> = HashSet::new();
542 visited.insert(name.as_str());
543 let mut reachable: HashSet<String> = HashSet::new();
544 walk(grammar, rule, &mut visited, &mut reachable);
545 for kind in &reachable {
546 subtypes
547 .entry(kind.clone())
548 .or_default()
549 .insert(name.clone());
550 }
551 }
552
553 // Aliases: scan every rule body for ALIAS { content, value }
554 // declarations. The kinds reachable from `content` satisfy
555 // `value`, AND (by construction) `value` satisfies the
556 // surrounding rule. Walking the ENTIRE grammar once captures
557 // every alias site, irrespective of which rule introduces it.
558 fn collect_aliases<'g>(production: &'g Production, out: &mut Vec<(String, &'g Production)>) {
559 match production {
560 Production::Alias {
561 content,
562 named,
563 value,
564 } => {
565 if *named && !value.is_empty() {
566 out.push((value.clone(), content.as_ref()));
567 }
568 collect_aliases(content, out);
569 }
570 Production::Choice { members } | Production::Seq { members } => {
571 for m in members {
572 collect_aliases(m, out);
573 }
574 }
575 Production::Repeat { content }
576 | Production::Repeat1 { content }
577 | Production::Optional { content }
578 | Production::Field { content, .. }
579 | Production::Token { content }
580 | Production::ImmediateToken { content }
581 | Production::Prec { content, .. }
582 | Production::PrecLeft { content, .. }
583 | Production::PrecRight { content, .. }
584 | Production::PrecDynamic { content, .. }
585 | Production::Reserved { content, .. } => {
586 collect_aliases(content, out);
587 }
588 _ => {}
589 }
590 }
591 let mut aliases: Vec<(String, &Production)> = Vec::new();
592 for rule in grammar.rules.values() {
593 collect_aliases(rule, &mut aliases);
594 }
595 for (alias_value, content) in aliases {
596 let mut visited: HashSet<&str> = HashSet::new();
597 let mut reachable: HashSet<String> = HashSet::new();
598 walk(grammar, content, &mut visited, &mut reachable);
599 // Aliased value satisfies itself and is satisfied by every
600 // kind its content can reach.
601 subtypes
602 .entry(alias_value.clone())
603 .or_default()
604 .insert(alias_value.clone());
605 for kind in reachable {
606 subtypes
607 .entry(kind)
608 .or_default()
609 .insert(alias_value.clone());
610 }
611 }
612
613 // Transitive close: `K ⊑ A` and `A ⊑ B` implies `K ⊑ B`. Iterate
614 // a few rounds; the relation is small so a quick fixed-point
615 // suffices in practice.
616 for _ in 0..8 {
617 let snapshot = subtypes.clone();
618 let mut changed = false;
619 for (kind, supers) in &snapshot {
620 let extra: HashSet<String> = supers
621 .iter()
622 .flat_map(|s| snapshot.get(s).cloned().unwrap_or_default())
623 .collect();
624 let entry = subtypes.entry(kind.clone()).or_default();
625 for s in extra {
626 if entry.insert(s) {
627 changed = true;
628 }
629 }
630 }
631 if !changed {
632 break;
633 }
634 }
635
636 subtypes
637}
638
639/// Compute the Yield set for every rule in the grammar.
640///
641/// `Yield(P)` is the set of concrete vertex kinds that can appear as
642/// the first named child when production P is taken. See the
643/// `Grammar::yield_sets` doc comment for the inductive definition.
644fn compute_yield_sets(
645 grammar: &Grammar,
646) -> std::collections::HashMap<String, std::collections::HashSet<String>> {
647 let mut cache: std::collections::HashMap<String, std::collections::HashSet<String>> =
648 std::collections::HashMap::new();
649 for (name, rule) in &grammar.rules {
650 if cache.contains_key(name) {
651 continue;
652 }
653 let mut visited = std::collections::HashSet::new();
654 let ys = yield_of_production(grammar, rule, &mut visited, &mut cache);
655 cache.insert(name.clone(), ys);
656 }
657 cache
658}
659
660/// Compute the Yield set of an arbitrary production node.
661///
662/// Uses `cache` (the partially-built `yield_sets` map) as
663/// memoization. `visited` tracks the current recursion path to
664/// detect cycles through hidden/supertype rules; a cycle returns ∅
665/// (a cycle that never passes through a concrete named symbol
666/// cannot produce a first child).
667fn yield_of_production(
668 grammar: &Grammar,
669 production: &Production,
670 visited: &mut std::collections::HashSet<String>,
671 cache: &mut std::collections::HashMap<String, std::collections::HashSet<String>>,
672) -> std::collections::HashSet<String> {
673 match production {
674 Production::Symbol { name } => {
675 if let Some(cached) = cache.get(name) {
676 return cached.clone();
677 }
678 let expand = name.starts_with('_') || grammar.supertypes.contains(name.as_str());
679 if expand {
680 if !visited.insert(name.clone()) {
681 return std::collections::HashSet::new();
682 }
683 let result = if let Some(rule) = grammar.rules.get(name) {
684 yield_of_production(grammar, rule, visited, cache)
685 } else {
686 std::collections::HashSet::new()
687 };
688 visited.remove(name);
689 cache.insert(name.clone(), result.clone());
690 result
691 } else {
692 let mut set = std::collections::HashSet::new();
693 set.insert(name.clone());
694 set
695 }
696 }
697 Production::Alias {
698 content,
699 named,
700 value,
701 } => {
702 if *named && !value.is_empty() {
703 let mut set = std::collections::HashSet::new();
704 set.insert(value.clone());
705 set
706 } else {
707 yield_of_production(grammar, content, visited, cache)
708 }
709 }
710 Production::Seq { members } => {
711 if members.is_empty() {
712 let mut set = std::collections::HashSet::new();
713 set.insert(String::new());
714 set
715 } else {
716 // Walk the SEQ members left-to-right, returning the
717 // Yield of the first member that can produce a named
718 // child. STRING and PATTERN yield ∅ (anonymous
719 // tokens); skip them to reach the first named-child-
720 // producing position. This handles hidden rules like
721 // `_initializer = SEQ ["=", FIELD { value, ... }]`
722 // where the leading "=" is a STRING.
723 for m in members {
724 let ys = yield_of_production(grammar, m, visited, cache);
725 if !ys.is_empty() {
726 return ys;
727 }
728 }
729 std::collections::HashSet::new()
730 }
731 }
732 Production::Choice { members } => {
733 let mut union = std::collections::HashSet::new();
734 for m in members {
735 union.extend(yield_of_production(grammar, m, visited, cache));
736 }
737 union
738 }
739 Production::Optional { content } => {
740 let mut set = yield_of_production(grammar, content, visited, cache);
741 set.insert(String::new());
742 set
743 }
744 Production::Blank => {
745 let mut set = std::collections::HashSet::new();
746 set.insert(String::new());
747 set
748 }
749 Production::String { .. } | Production::Pattern { .. } => std::collections::HashSet::new(),
750 Production::Repeat { content }
751 | Production::Repeat1 { content }
752 | Production::Field { content, .. }
753 | Production::Token { content }
754 | Production::ImmediateToken { content }
755 | Production::Prec { content, .. }
756 | Production::PrecLeft { content, .. }
757 | Production::PrecRight { content, .. }
758 | Production::PrecDynamic { content, .. }
759 | Production::Reserved { content, .. } => {
760 yield_of_production(grammar, content, visited, cache)
761 }
762 }
763}
764
765// ═══════════════════════════════════════════════════════════════════
766// node-types.json integration
767// ═══════════════════════════════════════════════════════════════════
768
769/// Parse node-types.json and build a map from parent kind to the set
770/// of all named child kinds the parser can produce for that parent.
771fn build_node_type_children(
772 nt_bytes: &[u8],
773) -> std::collections::HashMap<String, std::collections::HashSet<String>> {
774 let node_types: Vec<crate::theory_extract::NodeType> = match serde_json::from_slice(nt_bytes) {
775 Ok(v) => v,
776 Err(_) => return std::collections::HashMap::new(),
777 };
778 let mut map: std::collections::HashMap<String, std::collections::HashSet<String>> =
779 std::collections::HashMap::new();
780 for entry in &node_types {
781 if !entry.named {
782 continue;
783 }
784 let mut child_kinds = std::collections::HashSet::new();
785 for field_value in entry.fields.values() {
786 if let Some(types) = field_value.get("types").and_then(|t| t.as_array()) {
787 for t in types {
788 if let (Some(name), Some(true)) = (
789 t.get("type").and_then(|n| n.as_str()),
790 t.get("named").and_then(serde_json::Value::as_bool),
791 ) {
792 child_kinds.insert(name.to_owned());
793 }
794 }
795 }
796 }
797 if let Some(ref children) = entry.children {
798 for t in &children.types {
799 if t.named {
800 child_kinds.insert(t.node_type.clone());
801 }
802 }
803 }
804 if !child_kinds.is_empty() {
805 map.insert(entry.node_type.clone(), child_kinds);
806 }
807 }
808 map
809}
810
811/// Augment `grammar.subtypes` with child-kind data from node-types.json.
812///
813/// For each parent kind P with node-type children, for each SYMBOL S
814/// referenced in P's grammar rule, for each child kind C in
815/// `node_type_children[P]`: if C does not already satisfy S, record
816/// C satisfies S. This closes the grammar/parser divergence where
817/// tree-sitter's parser produces child kinds not reachable from
818/// grammar.json's production rules.
819fn augment_subtypes_from_node_types(grammar: &mut Grammar) {
820 let pairs: Vec<(String, String)> = grammar
821 .node_type_children
822 .iter()
823 .flat_map(|(parent_kind, allowed_children)| {
824 let symbols: Vec<&str> = grammar
825 .rules
826 .get(parent_kind)
827 .map(|rule| referenced_symbols(rule))
828 .unwrap_or_default();
829 let mut out = Vec::new();
830 for sym_name in &symbols {
831 for child_kind in allowed_children {
832 if !kind_satisfies_symbol(grammar, Some(child_kind), sym_name) {
833 out.push((child_kind.clone(), (*sym_name).to_owned()));
834 }
835 }
836 }
837 out
838 })
839 .collect();
840 for (child_kind, sym_name) in pairs {
841 grammar
842 .subtypes
843 .entry(child_kind)
844 .or_default()
845 .insert(sym_name);
846 }
847}
848
849/// Build a map from external scanner symbol names to their anonymous
850/// ALIAS values by walking every rule body in the grammar.
851fn build_external_alias_map(grammar: &Grammar) -> std::collections::HashMap<String, String> {
852 let mut map = std::collections::HashMap::new();
853 fn walk(
854 grammar: &Grammar,
855 prod: &Production,
856 map: &mut std::collections::HashMap<String, String>,
857 ) {
858 match prod {
859 Production::Alias {
860 content,
861 named,
862 value,
863 } => {
864 if !*named && !value.is_empty() {
865 if let Production::Symbol { name } = content.as_ref() {
866 if name.starts_with('_') && !grammar.rules.contains_key(name) {
867 map.entry(name.clone()).or_insert_with(|| value.clone());
868 }
869 }
870 }
871 walk(grammar, content, map);
872 }
873 Production::Choice { members } | Production::Seq { members } => {
874 for m in members {
875 walk(grammar, m, map);
876 }
877 }
878 Production::Repeat { content }
879 | Production::Repeat1 { content }
880 | Production::Optional { content }
881 | Production::Field { content, .. }
882 | Production::Token { content }
883 | Production::ImmediateToken { content }
884 | Production::Prec { content, .. }
885 | Production::PrecLeft { content, .. }
886 | Production::PrecRight { content, .. }
887 | Production::PrecDynamic { content, .. }
888 | Production::Reserved { content, .. } => walk(grammar, content, map),
889 _ => {}
890 }
891 }
892 for rule in grammar.rules.values() {
893 walk(grammar, rule, &mut map);
894 }
895 map
896}
897
898/// Identify rules whose `{`/`}` tokens are inline delimiters (e.g.
899/// interpolation) rather than block scopes. A rule is inline-brace
900/// iff its production SEQ contains both an opening brace token and
901/// `}`, and the members between them contain no REPEAT/REPEAT1
902/// (which would indicate a statement-list block).
903fn identify_inline_brace_rules(grammar: &Grammar) -> std::collections::HashSet<String> {
904 fn is_inline_brace_body(prod: &Production) -> bool {
905 match prod {
906 Production::Seq { members } => {
907 let open_idx = members.iter().position(|m| match m {
908 Production::String { value } => value.contains('{'),
909 _ => false,
910 });
911 let close_idx = members
912 .iter()
913 .rposition(|m| matches!(m, Production::String { value } if value == "}"));
914 if let (Some(open), Some(close)) = (open_idx, close_idx) {
915 if open < close {
916 let between = &members[open + 1..close];
917 return !has_repeat(between);
918 }
919 }
920 false
921 }
922 Production::Prec { content, .. }
923 | Production::PrecLeft { content, .. }
924 | Production::PrecRight { content, .. }
925 | Production::PrecDynamic { content, .. }
926 | Production::Token { content }
927 | Production::ImmediateToken { content }
928 | Production::Reserved { content, .. } => is_inline_brace_body(content),
929 _ => false,
930 }
931 }
932 fn has_repeat(members: &[Production]) -> bool {
933 members.iter().any(|m| match m {
934 Production::Repeat { .. } | Production::Repeat1 { .. } => true,
935 Production::Prec { content, .. }
936 | Production::PrecLeft { content, .. }
937 | Production::PrecRight { content, .. }
938 | Production::PrecDynamic { content, .. } => {
939 matches!(
940 content.as_ref(),
941 Production::Repeat { .. } | Production::Repeat1 { .. }
942 )
943 }
944 _ => false,
945 })
946 }
947 let mut result = std::collections::HashSet::new();
948 for (name, rule) in &grammar.rules {
949 if is_inline_brace_body(rule) {
950 result.insert(name.clone());
951 }
952 }
953 result
954}
955
956// ═══════════════════════════════════════════════════════════════════
957// Format policy
958// ═══════════════════════════════════════════════════════════════════
959
960/// Whitespace and indentation policy applied during emission.
961///
962/// The default policy inserts a single space between adjacent tokens,
963/// a newline after `;` / `}` / `{`, and tracks indent on `{` / `}`
964/// boundaries. Per-language overrides (idiomatic indent width,
965/// trailing-comma rules, blank-line conventions) can ride alongside
966/// this struct in a follow-up branch; today's defaults aim only for
967/// syntactic validity.
968#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
969pub struct FormatPolicy {
970 /// Number of spaces per indent level.
971 pub indent_width: usize,
972 /// Separator inserted between adjacent terminals that the lexer
973 /// would otherwise glue together (word ↔ word, operator ↔ operator).
974 /// Default is a single space.
975 pub separator: String,
976 /// Newline byte sequence emitted after `line_break_after` tokens
977 /// and at end-of-output. Default is `"\n"`.
978 pub newline: String,
979 /// Tokens after which the walker breaks to a new line.
980 pub line_break_after: Vec<String>,
981 /// Tokens that increase indent on emission.
982 pub indent_open: Vec<String>,
983 /// Tokens that decrease indent on emission.
984 pub indent_close: Vec<String>,
985}
986
987impl Default for FormatPolicy {
988 fn default() -> Self {
989 Self {
990 indent_width: 2,
991 separator: " ".to_owned(),
992 newline: "\n".to_owned(),
993 line_break_after: vec![";".into(), "{".into(), "}".into()],
994 indent_open: vec!["{".into()],
995 indent_close: vec!["}".into()],
996 }
997 }
998}
999
1000// ═══════════════════════════════════════════════════════════════════
1001// Emitter
1002// ═══════════════════════════════════════════════════════════════════
1003
1004/// Emit a by-construction schema to source bytes.
1005///
1006/// `protocol` is the grammar / language name (used in error messages
1007/// and to label the entry point).
1008///
1009/// The walker treats `schema.entries` as the ordered list of root
1010/// vertices, falling back to a deterministic by-id ordering when
1011/// `entries` is empty. Each root is emitted using the production
1012/// associated with its kind in `grammar.rules`.
1013///
1014/// # Errors
1015///
1016/// Returns [`ParseError::EmitFailed`] when:
1017///
1018/// - the schema has no vertices
1019/// - a root vertex's kind is not a grammar rule
1020/// - a `SYMBOL` reference points at a kind with no rule and no schema
1021/// child to resolve it to
1022/// - a required `FIELD` has no corresponding edge in the schema
1023pub fn emit_pretty(
1024 protocol: &str,
1025 schema: &Schema,
1026 grammar: &Grammar,
1027 policy: &FormatPolicy,
1028) -> Result<Vec<u8>, ParseError> {
1029 let roots = collect_roots(schema);
1030 if roots.is_empty() {
1031 return Err(ParseError::EmitFailed {
1032 protocol: protocol.to_owned(),
1033 reason: "schema has no entry vertices".to_owned(),
1034 });
1035 }
1036
1037 let mut out = Output::new(policy);
1038 for (i, root) in roots.iter().enumerate() {
1039 if i > 0 {
1040 out.newline();
1041 }
1042 emit_vertex(protocol, schema, grammar, root, &mut out)?;
1043 }
1044 Ok(out.finish())
1045}
1046
1047fn collect_roots(schema: &Schema) -> Vec<&panproto_gat::Name> {
1048 if !schema.entries.is_empty() {
1049 return schema
1050 .entries
1051 .iter()
1052 .filter(|name| schema.vertices.contains_key(*name))
1053 .collect();
1054 }
1055
1056 // Fallback: every vertex that is not the target of any structural edge
1057 // (sorted by id for determinism).
1058 let mut targets: std::collections::HashSet<&panproto_gat::Name> =
1059 std::collections::HashSet::new();
1060 for edge in schema.edges.keys() {
1061 targets.insert(&edge.tgt);
1062 }
1063 let mut roots: Vec<&panproto_gat::Name> = schema
1064 .vertices
1065 .keys()
1066 .filter(|name| !targets.contains(name))
1067 .collect();
1068 roots.sort();
1069 roots
1070}
1071
1072fn emit_vertex(
1073 protocol: &str,
1074 schema: &Schema,
1075 grammar: &Grammar,
1076 vertex_id: &panproto_gat::Name,
1077 out: &mut Output<'_>,
1078) -> Result<(), ParseError> {
1079 let vertex = schema
1080 .vertices
1081 .get(vertex_id)
1082 .ok_or_else(|| ParseError::EmitFailed {
1083 protocol: protocol.to_owned(),
1084 reason: format!("vertex '{vertex_id}' not found"),
1085 })?;
1086
1087 // Leaf shortcut: a vertex carrying a `literal-value` constraint
1088 // and no outgoing structural edges is a terminal token. Emit the
1089 // captured value directly. This handles identifiers, numeric
1090 // literals, and string literals that the parser stored as
1091 // `literal-value` even on by-construction schemas.
1092 if let Some(literal) = literal_value(schema, vertex_id) {
1093 if children_for(schema, vertex_id).is_empty() {
1094 out.token(literal);
1095 return Ok(());
1096 }
1097 }
1098
1099 let kind = vertex.kind.as_ref();
1100 let edges = children_for(schema, vertex_id);
1101 if let Some(rule) = grammar.rules.get(kind) {
1102 let old_suppress = out.suppress_brace_indent;
1103 if grammar.inline_brace_rules.contains(kind) {
1104 out.suppress_brace_indent = true;
1105 }
1106 let mut cursor = ChildCursor::new(&edges);
1107 emit_production(protocol, schema, grammar, vertex_id, rule, &mut cursor, out)?;
1108 // Drain any extras left after the rule walk completed; tree-sitter
1109 // may record trailing comments as children of the surrounding
1110 // vertex (i.e. after the last structural child the rule matched).
1111 drain_extras(protocol, schema, grammar, &mut cursor, out)?;
1112 out.suppress_brace_indent = old_suppress;
1113 return Ok(());
1114 }
1115
1116 // No rule for this kind. The parser produced it via an ALIAS
1117 // (tree-sitter's `alias($.something, $.actual_kind)`) or via an
1118 // external scanner (e.g. YAML's `document` root). Fall back to
1119 // walking the children directly so the inner content survives;
1120 // surrounding tokens — whose only source is the missing rule —
1121 // are necessarily absent.
1122 for edge in &edges {
1123 emit_vertex(protocol, schema, grammar, &edge.tgt, out)?;
1124 }
1125 Ok(())
1126}
1127
1128/// Linear cursor over a vertex's outgoing edges, used to thread
1129/// children through a production rule without double-consuming them.
1130struct ChildCursor<'a> {
1131 edges: &'a [&'a Edge],
1132 consumed: Vec<bool>,
1133}
1134
1135impl<'a> ChildCursor<'a> {
1136 fn new(edges: &'a [&'a Edge]) -> Self {
1137 Self {
1138 edges,
1139 consumed: vec![false; edges.len()],
1140 }
1141 }
1142
1143 /// Take the next unconsumed edge whose kind equals `field_name`.
1144 fn take_field(&mut self, field_name: &str) -> Option<&'a Edge> {
1145 for (i, edge) in self.edges.iter().enumerate() {
1146 if !self.consumed[i] && edge.kind.as_ref() == field_name {
1147 self.consumed[i] = true;
1148 return Some(edge);
1149 }
1150 }
1151 None
1152 }
1153
1154 /// Whether any unconsumed edge satisfies `predicate`. Used by the
1155 /// unit tests; the live emit path went through `has_matching` on
1156 /// each alternative until cursor-driven dispatch was rewritten to
1157 /// pick the first-unconsumed-edge's kind directly.
1158 #[cfg(test)]
1159 fn has_matching(&self, predicate: impl Fn(&Edge) -> bool) -> bool {
1160 self.edges
1161 .iter()
1162 .enumerate()
1163 .any(|(i, edge)| !self.consumed[i] && predicate(edge))
1164 }
1165
1166 /// Take the next unconsumed edge whose target vertex satisfies
1167 /// `predicate`. Returns the edge and the underlying production
1168 /// resolution path is the caller's job.
1169 fn take_matching(&mut self, predicate: impl Fn(&Edge) -> bool) -> Option<&'a Edge> {
1170 for (i, edge) in self.edges.iter().enumerate() {
1171 if !self.consumed[i] && predicate(edge) {
1172 self.consumed[i] = true;
1173 return Some(edge);
1174 }
1175 }
1176 None
1177 }
1178}
1179
1180thread_local! {
1181 static EMIT_DEPTH: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
1182 /// Set of `(vertex_id, rule_name)` pairs that are currently being
1183 /// walked by the recursion. A SYMBOL that resolves to a rule
1184 /// already on this stack closes a μ-binder cycle: in the
1185 /// coinductive reading, the rule walk at any vertex is the least
1186 /// fixed point of `body[μ X . body / X]`, which unfolds at most
1187 /// once, with the second visit returning the empty sequence (the
1188 /// unit of the free token monoid). Examples that trigger this:
1189 /// YAML's `stream` ⊃ `_b_blk_*` mutually-recursive chain, Rust's
1190 /// `_expression` ⊃ `binary_expression` ⊃ `_expression`.
1191 static EMIT_MU_FRAMES: std::cell::RefCell<std::collections::HashSet<(String, String)>> =
1192 std::cell::RefCell::new(std::collections::HashSet::new());
1193 /// The name of the FIELD whose body the walker is currently inside,
1194 /// or `None` at top level. Lets a SYMBOL nested arbitrarily deep
1195 /// in the field's content (under SEQ, CHOICE, REPEAT, OPTIONAL)
1196 /// consume from the *outer* cursor by edge-kind rather than from
1197 /// the child's own cursor by symbol-match. Without this, shapes
1198 /// like `field('args', commaSep1($.X))` — which expands to
1199 /// `FIELD(SEQ(SYMBOL X, REPEAT(SEQ(',', SYMBOL X))))` — emit only
1200 /// the first matched edge: the FIELD handler consumed one edge,
1201 /// the inner REPEAT searched the consumed child's cursor (which
1202 /// has no more sibling field edges), and the REPEAT broke after
1203 /// one iteration. Setting the context here so the inner SYMBOL
1204 /// pulls successive field-named edges from the outer cursor
1205 /// recovers every matched edge across arbitrary nesting.
1206 static EMIT_FIELD_CONTEXT: std::cell::RefCell<Option<String>> =
1207 const { std::cell::RefCell::new(None) };
1208}
1209
1210/// RAII guard that restores the prior `EMIT_FIELD_CONTEXT` value on drop.
1211struct FieldContextGuard(Option<String>);
1212
1213impl Drop for FieldContextGuard {
1214 fn drop(&mut self) {
1215 EMIT_FIELD_CONTEXT.with(|f| *f.borrow_mut() = self.0.take());
1216 }
1217}
1218
1219fn push_field_context(name: &str) -> FieldContextGuard {
1220 let prev = EMIT_FIELD_CONTEXT.with(|f| f.borrow_mut().replace(name.to_owned()));
1221 FieldContextGuard(prev)
1222}
1223
1224/// Clear the field context for the duration of a child-context walk.
1225/// The child's own production has its own FIELDs that set their own
1226/// context; the outer field hint must not leak into them.
1227fn clear_field_context() -> FieldContextGuard {
1228 let prev = EMIT_FIELD_CONTEXT.with(|f| f.borrow_mut().take());
1229 FieldContextGuard(prev)
1230}
1231
1232fn current_field_context() -> Option<String> {
1233 EMIT_FIELD_CONTEXT.with(|f| f.borrow().clone())
1234}
1235
1236/// Walk a rule at a vertex inside a μ-binder. The wrapping frame is
1237/// pushed before recursion and popped after, so any SYMBOL inside
1238/// `rule` that re-enters the same `(vertex_id, rule_name)` pair
1239/// returns the empty sequence (μ X . body unfolds once).
1240fn walk_in_mu_frame(
1241 protocol: &str,
1242 schema: &Schema,
1243 grammar: &Grammar,
1244 vertex_id: &panproto_gat::Name,
1245 rule_name: &str,
1246 rule: &Production,
1247 cursor: &mut ChildCursor<'_>,
1248 out: &mut Output<'_>,
1249) -> Result<(), ParseError> {
1250 let key = (vertex_id.to_string(), rule_name.to_owned());
1251 let inserted = EMIT_MU_FRAMES.with(|frames| frames.borrow_mut().insert(key.clone()));
1252 if !inserted {
1253 // We are already walking this rule at this vertex deeper in
1254 // the call stack. The coinductive μ-fixed-point reading
1255 // returns the empty sequence here; the surrounding
1256 // production resumes after the SYMBOL.
1257 return Ok(());
1258 }
1259 let result = emit_production(protocol, schema, grammar, vertex_id, rule, cursor, out);
1260 EMIT_MU_FRAMES.with(|frames| {
1261 frames.borrow_mut().remove(&key);
1262 });
1263 result
1264}
1265
1266fn emit_production(
1267 protocol: &str,
1268 schema: &Schema,
1269 grammar: &Grammar,
1270 vertex_id: &panproto_gat::Name,
1271 production: &Production,
1272 cursor: &mut ChildCursor<'_>,
1273 out: &mut Output<'_>,
1274) -> Result<(), ParseError> {
1275 let depth = EMIT_DEPTH.with(|d| {
1276 let v = d.get() + 1;
1277 d.set(v);
1278 v
1279 });
1280 if depth > 500 {
1281 EMIT_DEPTH.with(|d| d.set(d.get() - 1));
1282 return Err(ParseError::EmitFailed {
1283 protocol: protocol.to_owned(),
1284 reason: format!(
1285 "emit_production recursion >500 (likely a cyclic grammar; \
1286 vertex='{vertex_id}')"
1287 ),
1288 });
1289 }
1290 drain_extras(protocol, schema, grammar, cursor, out)?;
1291 let result = emit_production_inner(
1292 protocol, schema, grammar, vertex_id, production, cursor, out,
1293 );
1294 EMIT_DEPTH.with(|d| d.set(d.get() - 1));
1295 result
1296}
1297
1298/// Consume and emit every leading edge on `cursor` whose target kind
1299/// is in `grammar.extras` (typically `line_comment` / `block_comment`).
1300/// Extras live outside the production grammar — tree-sitter skips them
1301/// at parse time and records them as children of the surrounding
1302/// vertex — so the rule walker cannot reconcile them against the
1303/// cursor. Draining them as a side channel preserves their content in
1304/// the output without confusing the structural matchers.
1305fn drain_extras(
1306 protocol: &str,
1307 schema: &Schema,
1308 grammar: &Grammar,
1309 cursor: &mut ChildCursor<'_>,
1310 out: &mut Output<'_>,
1311) -> Result<(), ParseError> {
1312 if grammar.extras.is_empty() {
1313 return Ok(());
1314 }
1315 loop {
1316 let next_extra: Option<usize> = cursor
1317 .edges
1318 .iter()
1319 .enumerate()
1320 .find(|(i, _)| !cursor.consumed[*i])
1321 .and_then(|(i, edge)| {
1322 let kind = schema.vertices.get(&edge.tgt).map(|v| v.kind.as_ref())?;
1323 if grammar.extras.contains(kind) {
1324 Some(i)
1325 } else {
1326 None
1327 }
1328 });
1329 let Some(idx) = next_extra else {
1330 return Ok(());
1331 };
1332 cursor.consumed[idx] = true;
1333 let target = &cursor.edges[idx].tgt;
1334 emit_vertex(protocol, schema, grammar, target, out)?;
1335 }
1336}
1337
1338fn emit_production_inner(
1339 protocol: &str,
1340 schema: &Schema,
1341 grammar: &Grammar,
1342 vertex_id: &panproto_gat::Name,
1343 production: &Production,
1344 cursor: &mut ChildCursor<'_>,
1345 out: &mut Output<'_>,
1346) -> Result<(), ParseError> {
1347 match production {
1348 Production::String { value } => {
1349 out.token(value);
1350 Ok(())
1351 }
1352 Production::Pattern { value } => {
1353 if let Some(literal) = literal_value(schema, vertex_id) {
1354 out.token(literal);
1355 } else if is_newline_like_pattern(value) {
1356 // Patterns like `\r?\n`, `\n`, `\r\n` are the structural
1357 // newline tokens grammars use to separate top-level
1358 // statements (csound's `_new_line`, ABC's line-end, etc.).
1359 // Emitting them through the placeholder fallback rendered
1360 // the bare `_` sentinel between siblings; route them to
1361 // the layout pass's line-break instead so the output
1362 // re-parses.
1363 out.newline();
1364 } else if is_whitespace_only_pattern(value) {
1365 // `\s+`, `[ \t]+` and friends are interstitial whitespace
1366 // tokens. Emit nothing: the layout pass inserts the
1367 // policy separator between adjacent Lits if needed.
1368 } else {
1369 out.token(&placeholder_for_pattern(value));
1370 }
1371 Ok(())
1372 }
1373 Production::Blank => Ok(()),
1374 Production::Symbol { name } => {
1375 // Inside a FIELD body, a SYMBOL consumes by field-name on
1376 // the outer cursor rather than searching by symbol-match.
1377 // This covers the simple `FIELD(SYMBOL X)` case as well as
1378 // every nesting under FIELD that contains SYMBOLs (SEQ,
1379 // CHOICE, REPEAT, OPTIONAL, ALIAS). Without the override,
1380 // shapes like `field('args', commaSep1($.X))` consume one
1381 // field edge in the FIELD handler and then the REPEAT
1382 // inside SEQ searches the consumed child's cursor — where
1383 // no sibling field edges sit — and breaks after one
1384 // iteration.
1385 if let Some(field) = current_field_context() {
1386 if let Some(edge) = cursor.take_field(&field) {
1387 return emit_in_child_context(
1388 protocol, schema, grammar, &edge.tgt, production, out,
1389 );
1390 }
1391 // No matching field-named edge left on the outer
1392 // cursor. Surface nothing; the surrounding REPEAT /
1393 // OPTIONAL / CHOICE backtracks the literal tokens it
1394 // emitted on this iteration when it sees no progress.
1395 return Ok(());
1396 }
1397 if name.starts_with('_') {
1398 // Hidden rule: not a vertex kind on the schema side.
1399 // Inline-expand the rule body so its children take
1400 // edges from the current cursor, instead of trying to
1401 // take a single child edge that "satisfies" the
1402 // hidden rule and discarding the rest of the body
1403 // (which would drop tokens like `=` and the trailing
1404 // value SYMBOL inside e.g. TOML's `_inline_pair`).
1405 //
1406 // Wrapped in a μ-frame so a hidden rule that
1407 // references its own kind cyclically (or another
1408 // hidden rule that closes the cycle) unfolds once
1409 // and then collapses to the empty sequence at the
1410 // second visit, rather than blowing the stack.
1411 if let Some(rule) = grammar.rules.get(name) {
1412 walk_in_mu_frame(
1413 protocol, schema, grammar, vertex_id, name, rule, cursor, out,
1414 )
1415 } else {
1416 // External hidden rule (declared in the
1417 // grammar's `externals` block, scanned by C code,
1418 // not listed in `rules`). Heuristic fallback by
1419 // name:
1420 //
1421 // - `_indent` / `*_indent`: open an indent block.
1422 // Indent-based grammars (Python, YAML, qvr)
1423 // declare an `_indent` external scanner before
1424 // the body of a block-bodied declaration; the
1425 // emitted output is unparseable without the
1426 // corresponding indentation jump.
1427 // - `_dedent` / `*_dedent`: close the matching
1428 // indent block.
1429 // - `_newline` / `*_line_ending` / `*_or_eof`:
1430 // universally newline-or-empty; emitting a
1431 // single newline is the right default for
1432 // grammars like TOML whose `pair` SEQ trails
1433 // into `_line_ending_or_eof`.
1434 //
1435 // Check the precomputed alias map first: if this
1436 // external token appears as the content of an
1437 // anonymous ALIAS elsewhere in the grammar, emit
1438 // the alias value as the token text.
1439 if let Some(alias_value) = grammar.external_alias_map.get(name) {
1440 out.token(alias_value);
1441 return Ok(());
1442 }
1443 if name == "_indent" || name.ends_with("_indent") {
1444 out.indent_open();
1445 } else if name == "_dedent" || name.ends_with("_dedent") {
1446 out.indent_close();
1447 } else if name.contains("line_ending")
1448 || name.contains("newline")
1449 || name.ends_with("_or_eof")
1450 {
1451 out.newline();
1452 } else if name.contains("semicolon") {
1453 out.token(";");
1454 }
1455 Ok(())
1456 }
1457 } else if let Some(edge) = { take_symbol_match(grammar, schema, cursor, name) } {
1458 // For supertype / hidden-rule dispatch the child's
1459 // own kind names the actual production to walk
1460 // (`child.kind` IS the subtype). For ALIAS the
1461 // dependent-optic context is carried by the
1462 // surrounding `Production::Alias` branch, which calls
1463 // `emit_aliased_child` directly; we don't reach here
1464 // for that case. So walking `grammar.rules[child.kind]`
1465 // via `emit_vertex` is correct: the dependent-optic
1466 // path is preserved at every site where it actually
1467 // diverges from `child.kind`.
1468 emit_vertex(protocol, schema, grammar, &edge.tgt, out)
1469 } else if vertex_id_kind(schema, vertex_id) == Some(name.as_str()) {
1470 let rule = grammar
1471 .rules
1472 .get(name)
1473 .ok_or_else(|| ParseError::EmitFailed {
1474 protocol: protocol.to_owned(),
1475 reason: format!("no production for SYMBOL '{name}'"),
1476 })?;
1477 // Self-reference (`X = ... SYMBOL X ...`): wrap in a
1478 // μ-frame so re-entry collapses to the empty sequence.
1479 walk_in_mu_frame(
1480 protocol, schema, grammar, vertex_id, name, rule, cursor, out,
1481 )
1482 } else {
1483 // Named rule with no matching child: emit nothing and
1484 // let the surrounding CHOICE / OPTIONAL / REPEAT
1485 // resolve the absence.
1486 Ok(())
1487 }
1488 }
1489 Production::Seq { members } => {
1490 for member in members {
1491 emit_production(protocol, schema, grammar, vertex_id, member, cursor, out)?;
1492 }
1493 Ok(())
1494 }
1495 Production::Choice { members } => {
1496 if let Some(matched) =
1497 pick_choice_with_cursor(schema, grammar, vertex_id, cursor, members)
1498 {
1499 emit_production(protocol, schema, grammar, vertex_id, matched, cursor, out)
1500 } else {
1501 Ok(())
1502 }
1503 }
1504 Production::Repeat { content } | Production::Repeat1 { content } => {
1505 // Detect a "separator-leading SEQ" iteration body: SEQ whose
1506 // first member is a CHOICE containing BLANK (or an OPTIONAL),
1507 // i.e. the source-level separator between two iterations is
1508 // syntactically optional. When the chosen alternative for
1509 // that separator slot emits zero content tokens at runtime,
1510 // there was no source-level separator between this iteration
1511 // and the previous one; the layout pass must suppress its
1512 // policy separator to match the source's tight adjacency.
1513 //
1514 // Categorical reading: REPEAT body `B = SEQ(SEP, BODY)` is
1515 // the pullback of two halves. The bytes emitted in iteration
1516 // k+1 are a concatenation of `SEP_k+1` and `BODY_k+1`; if
1517 // `SEP_k+1` is the empty word, the concatenation of
1518 // `BODY_k` and `BODY_k+1` must remain a single contiguous
1519 // span. Hence the NoSpace marker.
1520 let separator_leading_seq: Option<&[Production]> = match content.as_ref() {
1521 Production::Seq { members } if members.len() >= 2 => {
1522 let first = &members[0];
1523 let is_separator_slot = match first {
1524 Production::Choice { members } => {
1525 members.iter().any(|m| matches!(m, Production::Blank))
1526 }
1527 Production::Optional { .. } => true,
1528 _ => false,
1529 };
1530 if is_separator_slot {
1531 Some(members.as_slice())
1532 } else {
1533 None
1534 }
1535 }
1536 _ => None,
1537 };
1538
1539 let mut emitted_any = false;
1540 loop {
1541 let cursor_snap = cursor.consumed.clone();
1542 let out_snap = out.snapshot();
1543 let consumed_before = cursor.consumed.iter().filter(|&&c| c).count();
1544 let result: Result<(), ParseError> =
1545 if let Some(seq_members) = separator_leading_seq {
1546 // Emit the separator slot first and observe
1547 // whether it contributed any Lit. If not, push
1548 // a NoSpace marker before walking the remaining
1549 // SEQ members. The OutputSnapshot here covers
1550 // only the separator's emission window.
1551 let pre_sep = out.snapshot();
1552 let sep_result = emit_production(
1553 protocol,
1554 schema,
1555 grammar,
1556 vertex_id,
1557 &seq_members[0],
1558 cursor,
1559 out,
1560 );
1561 match sep_result {
1562 Err(e) => Err(e),
1563 Ok(()) => {
1564 if !out.lit_emitted_since(pre_sep) {
1565 out.no_space();
1566 }
1567 let mut rest_result = Ok(());
1568 for member in &seq_members[1..] {
1569 rest_result = emit_production(
1570 protocol, schema, grammar, vertex_id, member, cursor, out,
1571 );
1572 if rest_result.is_err() {
1573 break;
1574 }
1575 }
1576 rest_result
1577 }
1578 }
1579 } else {
1580 emit_production(protocol, schema, grammar, vertex_id, content, cursor, out)
1581 };
1582 let consumed_after = cursor.consumed.iter().filter(|&&c| c).count();
1583 if result.is_err() || consumed_after == consumed_before {
1584 cursor.consumed = cursor_snap;
1585 out.restore(out_snap);
1586 break;
1587 }
1588 emitted_any = true;
1589 }
1590 if matches!(production, Production::Repeat1 { .. }) && !emitted_any {
1591 emit_production(protocol, schema, grammar, vertex_id, content, cursor, out)?;
1592 }
1593 Ok(())
1594 }
1595 Production::Optional { content } => {
1596 let cursor_snap = cursor.consumed.clone();
1597 let out_snap = out.snapshot();
1598 let consumed_before = cursor.consumed.iter().filter(|&&c| c).count();
1599 let result =
1600 emit_production(protocol, schema, grammar, vertex_id, content, cursor, out);
1601 // OPTIONAL is a backtracking site: if the inner production
1602 // errored *or* made no progress without leaving a witness
1603 // constraint, restore both cursor and output to their
1604 // pre-attempt state. Mirrors `Repeat`'s loop body.
1605 if result.is_err() {
1606 cursor.consumed = cursor_snap;
1607 out.restore(out_snap);
1608 return result;
1609 }
1610 let consumed_after = cursor.consumed.iter().filter(|&&c| c).count();
1611 if consumed_after == consumed_before
1612 && !has_relevant_constraint(content, schema, vertex_id)
1613 {
1614 cursor.consumed = cursor_snap;
1615 out.restore(out_snap);
1616 }
1617 Ok(())
1618 }
1619 Production::Field { name, content } => {
1620 // Set the field context for the duration of `content`'s
1621 // walk and emit the content against the *outer* cursor.
1622 // The SYMBOL handler picks up the context and pulls
1623 // successive `take_field(name)` edges as it encounters
1624 // SYMBOLs anywhere under `content` (under SEQ, CHOICE,
1625 // REPEAT, OPTIONAL, ALIAS — arbitrarily nested). This
1626 // subsumes the prior carve-outs for FIELD(REPEAT(...)),
1627 // FIELD(REPEAT1(...)), and the bare FIELD(SYMBOL ...)
1628 // case, and adds coverage for
1629 // `field('xs', commaSep1($.X))` which expands to
1630 // FIELD(SEQ(SYMBOL X, REPEAT(SEQ(',', SYMBOL X)))) and
1631 // any other shape where REPEAT/REPEAT1 sits inside SEQ /
1632 // CHOICE / OPTIONAL under a FIELD. A FIELD that wraps a
1633 // non-SYMBOL production (e.g. `field('op', '+')` or
1634 // `field('op', CHOICE(STRING ...))`) still works: STRING
1635 // handlers ignore the context and emit literals
1636 // directly, so the operator token survives the round
1637 // trip.
1638 let _guard = push_field_context(name);
1639 emit_production(protocol, schema, grammar, vertex_id, content, cursor, out)
1640 }
1641 Production::Alias {
1642 content,
1643 named,
1644 value,
1645 } => {
1646 // A named ALIAS rewrites the parser-visible kind to
1647 // `value`. If the cursor has an unconsumed child whose
1648 // kind matches that alias name, take it and emit the
1649 // child using the alias's INNER content as the rule
1650 // (e.g. `ALIAS { SYMBOL real_rule, value: "kind_x" }`
1651 // means a `kind_x` vertex on the schema should be walked
1652 // through `real_rule`'s body, not through whatever rule
1653 // happens to be keyed under `kind_x`). This is the
1654 // dependent-optic shape: the rule the emitter walks at a
1655 // child position is determined by the parent's chosen
1656 // alias, not by the child kind alone — without it,
1657 // grammars like YAML that introduce the same kind through
1658 // many ALIAS sites lose the parent context the moment
1659 // emit_vertex is called.
1660 if *named && !value.is_empty() {
1661 if let Some(edge) = cursor.take_matching(|edge| {
1662 schema
1663 .vertices
1664 .get(&edge.tgt)
1665 .map(|v| v.kind.as_ref() == value.as_str())
1666 .unwrap_or(false)
1667 }) {
1668 return emit_aliased_child(protocol, schema, grammar, &edge.tgt, content, out);
1669 }
1670 }
1671 // For anonymous aliases (named: false) whose content is an
1672 // external scanner token with no grammar rule (e.g.
1673 // JavaScript's `_ternary_qmark` aliased to `"?"`), emit the
1674 // alias value directly. The content's SYMBOL handler would
1675 // fall through the external-token heuristic and produce
1676 // nothing; the alias value IS the token text.
1677 if !*named && !value.is_empty() {
1678 if let Production::Symbol { name: sym } = content.as_ref() {
1679 if sym.starts_with('_') && !grammar.rules.contains_key(sym) {
1680 out.token(value);
1681 return Ok(());
1682 }
1683 }
1684 }
1685 emit_production(protocol, schema, grammar, vertex_id, content, cursor, out)
1686 }
1687 Production::Token { content }
1688 | Production::ImmediateToken { content }
1689 | Production::Prec { content, .. }
1690 | Production::PrecLeft { content, .. }
1691 | Production::PrecRight { content, .. }
1692 | Production::PrecDynamic { content, .. }
1693 | Production::Reserved { content, .. } => {
1694 emit_production(protocol, schema, grammar, vertex_id, content, cursor, out)
1695 }
1696 }
1697}
1698
1699/// Take the next cursor edge whose target vertex's kind matches the
1700/// SYMBOL `name` directly or via inline expansion of a hidden rule.
1701fn take_symbol_match<'a>(
1702 grammar: &Grammar,
1703 schema: &Schema,
1704 cursor: &mut ChildCursor<'a>,
1705 name: &str,
1706) -> Option<&'a Edge> {
1707 // Prefer non-field edges (`child_of`) to avoid consuming a
1708 // field-named edge that a later FIELD handler should claim.
1709 // Field-named edges (edge.kind != "child_of") are reserved for
1710 // the FIELD production that names them; consuming one here would
1711 // steal it from its intended handler (e.g. `as_pattern`'s
1712 // `alias` field edge consumed by the leading `expression`
1713 // SYMBOL instead of the trailing FIELD "alias" handler).
1714 if let Some(edge) = cursor.take_matching(|edge| {
1715 edge.kind.as_ref() == "child_of" && {
1716 let target_kind = schema.vertices.get(&edge.tgt).map(|v| v.kind.as_ref());
1717 kind_satisfies_symbol(grammar, target_kind, name)
1718 }
1719 }) {
1720 return Some(edge);
1721 }
1722 cursor.take_matching(|edge| {
1723 let target_kind = schema.vertices.get(&edge.tgt).map(|v| v.kind.as_ref());
1724 kind_satisfies_symbol(grammar, target_kind, name)
1725 })
1726}
1727
1728/// Decide whether a schema vertex of kind `target_kind` satisfies a
1729/// SYMBOL `name` reference in the grammar.
1730///
1731/// Operates as an O(1) lookup against the precomputed subtype
1732/// closure built at [`Grammar::from_bytes`]. The semantic content is
1733/// "K satisfies SYMBOL S iff K is reachable from S by walking the
1734/// grammar's hidden, supertype, and named-alias dispatch": this is
1735/// exactly the relation tree-sitter induces on `(parser-visible kind,
1736/// rule-position)` pairs.
1737fn kind_satisfies_symbol(grammar: &Grammar, target_kind: Option<&str>, name: &str) -> bool {
1738 let Some(target) = target_kind else {
1739 return false;
1740 };
1741 if target == name {
1742 return true;
1743 }
1744 grammar
1745 .subtypes
1746 .get(target)
1747 .is_some_and(|set| set.contains(name))
1748}
1749
1750/// Emit a child reached through an ALIAS production using the
1751/// alias's inner content as the rule, not `grammar.rules[child.kind]`.
1752///
1753/// This carries the dependent-optic context across the ALIAS edge:
1754/// at the parent rule's site we know which underlying production the
1755/// alias wraps (typically `SYMBOL real_rule`), and that's the
1756/// production that should drive the emit walk on the child's
1757/// children. Looking up `grammar.rules.get(child.kind)` instead would
1758/// either fail (the renamed kind has no top-level rule, e.g. YAML's
1759/// `block_mapping_pair`) or pick an arbitrary same-kinded rule from
1760/// elsewhere in the grammar.
1761///
1762/// Walk-context invariant. The dependent-optic shape of `emit_pretty`
1763/// says: the production walked at any vertex is determined by the
1764/// path from the root through the grammar, not by the vertex kind in
1765/// isolation. Two dispatch sites realise that invariant:
1766///
1767/// * [`emit_vertex`] looks up `grammar.rules[child.kind]` and walks
1768/// it. Correct for supertype / hidden-rule dispatch: the child's
1769/// kind on the schema IS the subtype tree-sitter selected, so its
1770/// top-level rule is the right production to walk.
1771/// * `emit_aliased_child` threads the parent rule's `Production`
1772/// directly (the inner `content` of `Production::Alias`) and walks
1773/// it on the child's children. Correct for ALIAS dispatch: the
1774/// child's kind on the schema is the alias's `value` (a renamed
1775/// kind that may have no top-level rule), and the production to
1776/// walk is the alias's content body, supplied by the parent.
1777///
1778/// Together these cover every site where the rule-walked-at-child
1779/// diverges from `grammar.rules[child.kind]`; the recursion site for
1780/// plain SYMBOL therefore correctly delegates to `emit_vertex`, and
1781/// we do not need a richer `WalkContext` value passed by reference.
1782/// The grammar dependency is the thread.
1783fn emit_aliased_child(
1784 protocol: &str,
1785 schema: &Schema,
1786 grammar: &Grammar,
1787 child_id: &panproto_gat::Name,
1788 content: &Production,
1789 out: &mut Output<'_>,
1790) -> Result<(), ParseError> {
1791 // Leaf shortcut: if the child has a literal-value and no
1792 // structural children, emit the captured text. Identifiers and
1793 // similar terminals reach here when an ALIAS wraps a SYMBOL that
1794 // resolves to a PATTERN.
1795 if let Some(literal) = literal_value(schema, child_id) {
1796 if children_for(schema, child_id).is_empty() {
1797 out.token(literal);
1798 return Ok(());
1799 }
1800 }
1801
1802 // Clear the enclosing FIELD context so it does not leak into the
1803 // aliased child's production walk. Without this, a FIELD("alias")
1804 // containing an ALIAS whose content is SYMBOL "expression" would
1805 // cause the inner SYMBOL handler to pull by field name "alias"
1806 // instead of by symbol match, failing to find the child edge.
1807 let _guard = clear_field_context();
1808
1809 // Resolve `content` to a rule when it's a SYMBOL (the dominant
1810 // shape: `ALIAS { content: SYMBOL real_rule, value: "kind_x" }`).
1811 if let Production::Symbol { name } = content {
1812 if let Some(rule) = grammar.rules.get(name) {
1813 let edges = children_for(schema, child_id);
1814 let mut cursor = ChildCursor::new(&edges);
1815 return emit_production(protocol, schema, grammar, child_id, rule, &mut cursor, out);
1816 }
1817 }
1818
1819 // Other ALIAS contents (CHOICE, SEQ, literals) walk in place.
1820 let edges = children_for(schema, child_id);
1821 let mut cursor = ChildCursor::new(&edges);
1822 emit_production(
1823 protocol,
1824 schema,
1825 grammar,
1826 child_id,
1827 content,
1828 &mut cursor,
1829 out,
1830 )
1831}
1832
1833fn emit_in_child_context(
1834 protocol: &str,
1835 schema: &Schema,
1836 grammar: &Grammar,
1837 child_id: &panproto_gat::Name,
1838 production: &Production,
1839 out: &mut Output<'_>,
1840) -> Result<(), ParseError> {
1841 // The child walks under its own production tree, with its own
1842 // FIELDs setting their own contexts. Clear the outer FIELD hint
1843 // so it does not leak through and cause sibling SYMBOLs inside
1844 // the child's body to mistakenly pull edges from the child's
1845 // cursor by the parent's field name.
1846 let _guard = clear_field_context();
1847 // If `production` is a structural wrapper (CHOICE / SEQ /
1848 // OPTIONAL / ...) whose referenced symbols cover the child's own
1849 // kind, the child IS the production's target node and the right
1850 // emit path is `emit_vertex(child)` (which honours the
1851 // literal-value leaf shortcut). Without this guard, FIELD(pattern,
1852 // CHOICE { _pattern, self }) on an identifier child walks the
1853 // CHOICE on the identifier's empty cursor, falls through to the
1854 // first non-BLANK alt, and loses the captured identifier text.
1855 if !matches!(production, Production::Symbol { .. }) {
1856 let child_kind = schema.vertices.get(child_id).map(|v| v.kind.as_ref());
1857 let symbols = referenced_symbols(production);
1858 if symbols
1859 .iter()
1860 .any(|s| kind_satisfies_symbol(grammar, child_kind, s) || child_kind == Some(s))
1861 {
1862 return emit_vertex(protocol, schema, grammar, child_id, out);
1863 }
1864 }
1865 match production {
1866 Production::Symbol { .. } => emit_vertex(protocol, schema, grammar, child_id, out),
1867 _ => {
1868 let edges = children_for(schema, child_id);
1869 let mut cursor = ChildCursor::new(&edges);
1870 emit_production(
1871 protocol,
1872 schema,
1873 grammar,
1874 child_id,
1875 production,
1876 &mut cursor,
1877 out,
1878 )
1879 }
1880 }
1881}
1882
1883fn pick_choice_with_cursor<'a>(
1884 schema: &Schema,
1885 grammar: &Grammar,
1886 vertex_id: &panproto_gat::Name,
1887 cursor: &ChildCursor<'_>,
1888 alternatives: &'a [Production],
1889) -> Option<&'a Production> {
1890 // Discriminator-driven dispatch (highest priority): when the
1891 // walker recorded a `chose-alt-fingerprint` constraint at parse
1892 // time, dispatch directly against that. This is the categorical
1893 // discriminator: it survives stripping of byte-position
1894 // constraints (so by-construction round-trips work) and is the
1895 // explicit witness of which CHOICE alternative the parser took.
1896 //
1897 // Falls back to the live `interstitial-*` substring blob when no
1898 // fingerprint is present (e.g. instances built by callers that
1899 // bypass the AstWalker). Both blobs are scored by the longest
1900 // STRING-literal token in an alternative that matches; the
1901 // length tiebreak prefers `&&` over `&`, `==` over `=`, etc.
1902 let constraint_blob = schema
1903 .constraints
1904 .get(vertex_id)
1905 .map(|cs| {
1906 let fingerprint: Option<&str> = cs
1907 .iter()
1908 .find(|c| c.sort.as_ref() == "chose-alt-fingerprint")
1909 .map(|c| c.value.as_str());
1910 if let Some(fp) = fingerprint {
1911 fp.to_owned()
1912 } else {
1913 cs.iter()
1914 .filter(|c| {
1915 let s = c.sort.as_ref();
1916 s.starts_with("interstitial-") && !s.ends_with("-start-byte")
1917 })
1918 .map(|c| c.value.as_str())
1919 .collect::<Vec<&str>>()
1920 .join(" ")
1921 }
1922 })
1923 .unwrap_or_default();
1924 let child_kinds: Vec<&str> = schema
1925 .constraints
1926 .get(vertex_id)
1927 .and_then(|cs| {
1928 cs.iter()
1929 .find(|c| c.sort.as_ref() == "chose-alt-child-kinds")
1930 .map(|c| c.value.split_whitespace().collect())
1931 })
1932 .unwrap_or_default();
1933 // Cursor-exhaustion BLANK-preference: when all cursor edges have
1934 // been consumed AND `BLANK` is one of the alternatives, the only
1935 // alt that won't introduce a non-existent child is `BLANK`.
1936 //
1937 // This gate fires before the literal-blob discriminator because
1938 // the fingerprint is shared across every CHOICE position in the
1939 // vertex's rule body: a vertex like `sample_step` that ends in
1940 // `..., REPEAT(SEQ(",", arg)), CHOICE(",", BLANK)` records all of
1941 // its `","` interstitials in a single blob, so the literal-score
1942 // matcher would otherwise prefer `","` for the trailing CHOICE
1943 // even when the source had no trailing comma. By the time the
1944 // emitter reaches the trailing CHOICE, the REPEAT has consumed
1945 // every arg edge in cursor order; the residual unconsumed multiset
1946 // is empty; and the categorical reading of a CHOICE-with-BLANK at
1947 // a position with no remaining children is the no-op alternative.
1948 let any_unconsumed = cursor
1949 .edges
1950 .iter()
1951 .enumerate()
1952 .any(|(i, _)| !cursor.consumed[i]);
1953 let blank_present = alternatives.iter().any(|a| matches!(a, Production::Blank));
1954 if !any_unconsumed && blank_present {
1955 return alternatives.iter().find(|a| matches!(a, Production::Blank));
1956 }
1957 if !any_unconsumed && !blank_present {
1958 let mut visited = std::collections::HashSet::new();
1959 let mut yield_cache = grammar.yield_sets.clone();
1960 for alt in alternatives {
1961 let ys = yield_of_production(grammar, alt, &mut visited, &mut yield_cache);
1962 if ys.contains("") {
1963 return Some(alt);
1964 }
1965 visited.clear();
1966 }
1967 }
1968
1969 if !constraint_blob.is_empty() {
1970 // Primary score: literal-token match length. This dominates
1971 // alt selection so existing language tests that depend on
1972 // literal-only fingerprints keep working.
1973 // Secondary score (tiebreaker only): named-symbol kind match
1974 // count, read from the separate `chose-alt-child-kinds`
1975 // constraint (kept apart from the literal fingerprint so
1976 // identifiers like `:` in the kind list don't contaminate the
1977 // literal match). An alt that matches the recorded kinds is a
1978 // stronger witness than one whose only
1979 // overlap is literal punctuation.
1980 let mut best_literal: usize = 0;
1981 let mut best_symbols: usize = 0;
1982 let mut best_alt: Option<&Production> = None;
1983 let mut tied = false;
1984 for alt in alternatives {
1985 let strings = literal_strings(alt);
1986 if strings.is_empty() {
1987 continue;
1988 }
1989 let literal_score = strings
1990 .iter()
1991 .filter(|s| constraint_blob.contains(s.as_str()))
1992 .map(String::len)
1993 .sum::<usize>();
1994 if literal_score == 0 {
1995 continue;
1996 }
1997 // Symbol score is computed only as a tiebreaker among alts
1998 // whose literal-token coverage is the same; it never lifts
1999 // an alt above one with a strictly higher literal score.
2000 // Reads the `chose-alt-child-kinds` constraint (a separate
2001 // sequence the walker emits, kept apart from the literal
2002 // fingerprint to avoid cross-contamination).
2003 let symbol_score = if literal_score >= best_literal && !child_kinds.is_empty() {
2004 let symbols = referenced_symbols(alt);
2005 symbols
2006 .iter()
2007 .filter(|sym| {
2008 let sym_str: &str = sym;
2009 if child_kinds.contains(&sym_str) {
2010 return true;
2011 }
2012 grammar.subtypes.get(sym_str).is_some_and(|sub_set| {
2013 sub_set
2014 .iter()
2015 .any(|sub| child_kinds.contains(&sub.as_str()))
2016 })
2017 })
2018 .count()
2019 } else {
2020 0
2021 };
2022 let better = literal_score > best_literal
2023 || (literal_score == best_literal && symbol_score > best_symbols);
2024 let same = literal_score == best_literal && symbol_score == best_symbols;
2025 if better {
2026 best_literal = literal_score;
2027 best_symbols = symbol_score;
2028 best_alt = Some(alt);
2029 tied = false;
2030 } else if same && best_alt.is_some() {
2031 tied = true;
2032 }
2033 }
2034 // Only commit to an alt when the fingerprint discriminates it
2035 // uniquely. A tie means the alts share the same literal token
2036 // set (e.g. JSON's `string = CHOICE { SEQ { '"', '"' }, SEQ {
2037 // '"', _string_content, '"' } }` — both alts contain just the
2038 // two `"` tokens). In that case fall through to cursor-based
2039 // dispatch, which uses the actual edge structure.
2040 if let Some(alt) = best_alt {
2041 if !tied {
2042 return Some(alt);
2043 }
2044 }
2045 }
2046
2047 // Cursor-driven dispatch via Yield-set preimage.
2048 //
2049 // For a CHOICE C = A1 | ... | An, Yield(Ai) is the set of vertex
2050 // kinds that can appear as the first named child when Ai is taken
2051 // (see `yield_of_production`). Given the first unconsumed cursor
2052 // edge with target kind K, select the first Ai (grammar order)
2053 // where K ∈ Yield(Ai). This is deterministic: grammar order is
2054 // the tiebreak, matching tree-sitter's own disambiguation.
2055 let first_unconsumed_kind: Option<&str> = cursor
2056 .edges
2057 .iter()
2058 .enumerate()
2059 .find(|(i, _)| !cursor.consumed[*i])
2060 .and_then(|(_, edge)| schema.vertices.get(&edge.tgt).map(|v| v.kind.as_ref()));
2061 if let Some(target_kind) = first_unconsumed_kind {
2062 // The subtype closure `subtypes[target_kind]` contains every
2063 // symbol name S such that a vertex of kind `target_kind` can
2064 // appear where the grammar says `SYMBOL S`. For a CHOICE
2065 // C = A1 | ... | An, the correct alternative is the one whose
2066 // top-level symbol is in `subtypes[target_kind]` (the target
2067 // kind IS a subtype of that symbol, so the symbol's rule body
2068 // dispatches to the target kind at parse time). This is an
2069 // O(1) set-membership check per alternative — no recursive
2070 // Yield computation needed.
2071 //
2072 // Preference order:
2073 // 1. Direct name match (target_kind == symbol name)
2074 // 2. Subtype match (symbol name ∈ subtypes[target_kind])
2075 // 3. Yield-set match (target_kind ∈ Yield(alt)) as fallback
2076 // for non-SYMBOL alternatives (ALIAS, SEQ, etc.)
2077 let target_supers = grammar.subtypes.get(target_kind);
2078
2079 // Pass 1: direct name match
2080 for alt in alternatives {
2081 if let Production::Symbol { name } = alt {
2082 if name.as_str() == target_kind {
2083 return Some(alt);
2084 }
2085 }
2086 if let Production::Alias {
2087 named: true, value, ..
2088 } = alt
2089 {
2090 if value.as_str() == target_kind {
2091 return Some(alt);
2092 }
2093 }
2094 }
2095
2096 // Pass 2: subtype match (the target kind's supertype set
2097 // tells us which SYMBOL names it satisfies)
2098 if let Some(supers) = target_supers {
2099 for alt in alternatives {
2100 if let Production::Symbol { name } = alt {
2101 if supers.contains(name.as_str()) {
2102 return Some(alt);
2103 }
2104 }
2105 if let Production::Alias {
2106 named: true, value, ..
2107 } = alt
2108 {
2109 if supers.contains(value.as_str()) {
2110 return Some(alt);
2111 }
2112 }
2113 }
2114 }
2115
2116 // Pass 3: Yield-set fallback for alternatives that are not
2117 // plain SYMBOLs or named ALIASes (e.g. SEQ, PREC wrappers
2118 // around SYMBOLs that the above passes don't unwrap).
2119 let mut visited = std::collections::HashSet::new();
2120 let mut yield_cache = grammar.yield_sets.clone();
2121 for alt in alternatives {
2122 let ys = yield_of_production(grammar, alt, &mut visited, &mut yield_cache);
2123 if ys.contains(target_kind) {
2124 return Some(alt);
2125 }
2126 visited.clear();
2127 }
2128 }
2129
2130 // FIELD dispatch: pick an alternative whose FIELD name matches an
2131 // unconsumed edge kind.
2132 let edge_kinds: Vec<&str> = cursor
2133 .edges
2134 .iter()
2135 .enumerate()
2136 .filter(|(i, _)| !cursor.consumed[*i])
2137 .map(|(_, e)| e.kind.as_ref())
2138 .collect();
2139 for alt in alternatives {
2140 if has_field_in(alt, &edge_kinds) {
2141 return Some(alt);
2142 }
2143 }
2144
2145 // Indented-form preference: when no dispatch tier uniquely
2146 // identified an alternative and the cursor has children, prefer
2147 // the alternative whose production body contains an `_indent`
2148 // SYMBOL. The indented form is always valid for by-construction
2149 // schemas and round-trips cleanly; the inline form (e.g. Python's
2150 // `;`-separated `_simple_statements`) is a source-level
2151 // abbreviation that requires parse-time context to select correctly.
2152 if first_unconsumed_kind.is_some() {
2153 let indent_alt = alternatives.iter().find(|alt| {
2154 referenced_symbols(alt)
2155 .iter()
2156 .any(|s| *s == "_indent" || s.ends_with("_indent"))
2157 });
2158 if let Some(alt) = indent_alt {
2159 return Some(alt);
2160 }
2161 }
2162
2163 // No dispatch tier matched. The final selection follows the
2164 // categorical semantics of CHOICE-with-BLANK: BLANK represents ε
2165 // (produce nothing at this position). It is correct if and only
2166 // if no child remains to consume at this cursor position.
2167 //
2168 // When unconsumed non-extra children remain, selecting BLANK
2169 // would silently drop them. Select the first non-BLANK
2170 // alternative instead so the production walk can attempt to
2171 // consume them (the grammar rule may reference a symbol name
2172 // that doesn't exactly match the parse output's child kind,
2173 // e.g. Julia's macrocall_expression receives `argument_list`
2174 // children when grammar.json only references
2175 // `macro_argument_list`).
2176 let _ = (schema, vertex_id);
2177 let has_unconsumed_structural = cursor.edges.iter().enumerate().any(|(i, edge)| {
2178 !cursor.consumed[i]
2179 && schema
2180 .vertices
2181 .get(&edge.tgt)
2182 .map(|v| !grammar.extras.contains(v.kind.as_ref()))
2183 .unwrap_or(false)
2184 });
2185 if has_unconsumed_structural {
2186 return alternatives
2187 .iter()
2188 .find(|alt| !matches!(alt, Production::Blank));
2189 }
2190 if alternatives.iter().any(|a| matches!(a, Production::Blank)) {
2191 return alternatives.iter().find(|a| matches!(a, Production::Blank));
2192 }
2193 alternatives
2194 .iter()
2195 .find(|alt| !matches!(alt, Production::Blank))
2196}
2197
2198/// Collect every literal STRING token directly inside `production`
2199/// (without descending into SYMBOLs / hidden rules). Used to score
2200/// CHOICE alternatives against the parent vertex's interstitials so
2201/// the right operator / keyword form is picked when the schema
2202/// preserves interstitial fragments from a prior parse.
2203fn literal_strings(production: &Production) -> Vec<String> {
2204 let mut out = Vec::new();
2205 fn walk(p: &Production, out: &mut Vec<String>) {
2206 match p {
2207 Production::String { value } if !value.is_empty() => {
2208 out.push(value.clone());
2209 }
2210 Production::Choice { members } | Production::Seq { members } => {
2211 for m in members {
2212 walk(m, out);
2213 }
2214 }
2215 Production::Repeat { content }
2216 | Production::Repeat1 { content }
2217 | Production::Optional { content }
2218 | Production::Field { content, .. }
2219 | Production::Alias { content, .. }
2220 | Production::Token { content }
2221 | Production::ImmediateToken { content }
2222 | Production::Prec { content, .. }
2223 | Production::PrecLeft { content, .. }
2224 | Production::PrecRight { content, .. }
2225 | Production::PrecDynamic { content, .. }
2226 | Production::Reserved { content, .. } => walk(content, out),
2227 _ => {}
2228 }
2229 }
2230 walk(production, &mut out);
2231 out
2232}
2233
2234/// Collect every SYMBOL name reachable from `production` without
2235/// crossing into nested rules. Used by `pick_choice_with_cursor` to
2236/// rank alternatives by "any SYMBOL inside this alt matches something
2237/// on the cursor", instead of just the first SYMBOL: a leading
2238/// optional like `attribute_item` then `parameter` is otherwise
2239/// rejected when only the parameter children are present.
2240fn referenced_symbols(production: &Production) -> Vec<&str> {
2241 let mut out = Vec::new();
2242 fn walk<'a>(p: &'a Production, out: &mut Vec<&'a str>) {
2243 match p {
2244 Production::Symbol { name } => out.push(name.as_str()),
2245 Production::Choice { members } | Production::Seq { members } => {
2246 for m in members {
2247 walk(m, out);
2248 }
2249 }
2250 Production::Alias {
2251 content,
2252 named,
2253 value,
2254 } => {
2255 // A named ALIAS produces a child vertex whose kind is
2256 // the alias `value` (e.g. `ALIAS { content: STRING "=",
2257 // value: "punctuation", named: true }` introduces a
2258 // `punctuation` child). For cursor-driven dispatch to
2259 // recognise alts that emit such children, yield the
2260 // alias value as a referenced symbol. Anonymous aliases
2261 // do not introduce a named node and only need their
2262 // inner content's symbols.
2263 if *named && !value.is_empty() {
2264 out.push(value.as_str());
2265 }
2266 walk(content, out);
2267 }
2268 Production::Repeat { content }
2269 | Production::Repeat1 { content }
2270 | Production::Optional { content }
2271 | Production::Field { content, .. }
2272 | Production::Token { content }
2273 | Production::ImmediateToken { content }
2274 | Production::Prec { content, .. }
2275 | Production::PrecLeft { content, .. }
2276 | Production::PrecRight { content, .. }
2277 | Production::PrecDynamic { content, .. }
2278 | Production::Reserved { content, .. } => walk(content, out),
2279 _ => {}
2280 }
2281 }
2282 walk(production, &mut out);
2283 out
2284}
2285
2286#[cfg(test)]
2287fn first_symbol(production: &Production) -> Option<&str> {
2288 match production {
2289 Production::Symbol { name } => Some(name),
2290 Production::Seq { members } => members.iter().find_map(first_symbol),
2291 Production::Choice { members } => members.iter().find_map(first_symbol),
2292 Production::Repeat { content }
2293 | Production::Repeat1 { content }
2294 | Production::Optional { content }
2295 | Production::Field { content, .. }
2296 | Production::Alias { content, .. }
2297 | Production::Token { content }
2298 | Production::ImmediateToken { content }
2299 | Production::Prec { content, .. }
2300 | Production::PrecLeft { content, .. }
2301 | Production::PrecRight { content, .. }
2302 | Production::PrecDynamic { content, .. }
2303 | Production::Reserved { content, .. } => first_symbol(content),
2304 _ => None,
2305 }
2306}
2307
2308fn has_field_in(production: &Production, edge_kinds: &[&str]) -> bool {
2309 match production {
2310 Production::Field { name, .. } => edge_kinds.contains(&name.as_str()),
2311 Production::Seq { members } | Production::Choice { members } => {
2312 members.iter().any(|m| has_field_in(m, edge_kinds))
2313 }
2314 Production::Repeat { content }
2315 | Production::Repeat1 { content }
2316 | Production::Optional { content }
2317 | Production::Alias { content, .. }
2318 | Production::Token { content }
2319 | Production::ImmediateToken { content }
2320 | Production::Prec { content, .. }
2321 | Production::PrecLeft { content, .. }
2322 | Production::PrecRight { content, .. }
2323 | Production::PrecDynamic { content, .. }
2324 | Production::Reserved { content, .. } => has_field_in(content, edge_kinds),
2325 _ => false,
2326 }
2327}
2328
2329fn has_relevant_constraint(
2330 production: &Production,
2331 schema: &Schema,
2332 vertex_id: &panproto_gat::Name,
2333) -> bool {
2334 let constraints = match schema.constraints.get(vertex_id) {
2335 Some(c) => c,
2336 None => return false,
2337 };
2338 fn walk(production: &Production, constraints: &[panproto_schema::Constraint]) -> bool {
2339 match production {
2340 Production::String { value } => constraints
2341 .iter()
2342 .any(|c| c.value == *value || c.sort.as_ref() == value),
2343 Production::Field { name, content } => {
2344 constraints.iter().any(|c| c.sort.as_ref() == name) || walk(content, constraints)
2345 }
2346 Production::Seq { members } | Production::Choice { members } => {
2347 members.iter().any(|m| walk(m, constraints))
2348 }
2349 Production::Repeat { content }
2350 | Production::Repeat1 { content }
2351 | Production::Optional { content }
2352 | Production::Alias { content, .. }
2353 | Production::Token { content }
2354 | Production::ImmediateToken { content }
2355 | Production::Prec { content, .. }
2356 | Production::PrecLeft { content, .. }
2357 | Production::PrecRight { content, .. }
2358 | Production::PrecDynamic { content, .. }
2359 | Production::Reserved { content, .. } => walk(content, constraints),
2360 _ => false,
2361 }
2362 }
2363 walk(production, constraints)
2364}
2365
2366fn children_for<'a>(schema: &'a Schema, vertex_id: &panproto_gat::Name) -> Vec<&'a Edge> {
2367 // Walk `outgoing` (insertion-ordered by SchemaBuilder via SmallVec
2368 // append) rather than the unordered `edges` HashMap so abstract
2369 // schemas under REPEAT(CHOICE(...)) preserve the order their edges
2370 // were inserted in. The previous implementation walked the HashMap
2371 // and sorted lexicographically by (kind, target id), which fused
2372 // interleaved children of the same kind into runs (e.g. a sequence
2373 // [symbol, punct, int, symbol, punct, int] became [symbol, symbol,
2374 // punct, punct, int, int] after the lex sort).
2375 let Some(edges) = schema.outgoing.get(vertex_id) else {
2376 return Vec::new();
2377 };
2378
2379 // Look up the canonical Edge reference (the key in `schema.edges`)
2380 // for each entry in `outgoing`. Falls back to the SmallVec entry if
2381 // the canonical key is missing, which would indicate index drift.
2382 let mut indexed: Vec<(usize, u32, &Edge)> = edges
2383 .iter()
2384 .enumerate()
2385 .map(|(i, e)| {
2386 let canonical = schema.edges.get_key_value(e).map_or(e, |(k, _)| k);
2387 let pos = schema.orderings.get(canonical).copied().unwrap_or(u32::MAX);
2388 (i, pos, canonical)
2389 })
2390 .collect();
2391
2392 // Stable sort by (explicit-ordering, insertion-index). Edges with
2393 // an explicit `orderings` entry come first in their declared order;
2394 // the remainder fall through in insertion order.
2395 indexed.sort_by_key(|(i, pos, _)| (*pos, *i));
2396 indexed.into_iter().map(|(_, _, e)| e).collect()
2397}
2398
2399fn vertex_id_kind<'a>(schema: &'a Schema, vertex_id: &panproto_gat::Name) -> Option<&'a str> {
2400 schema.vertices.get(vertex_id).map(|v| v.kind.as_ref())
2401}
2402
2403fn literal_value<'a>(schema: &'a Schema, vertex_id: &panproto_gat::Name) -> Option<&'a str> {
2404 schema
2405 .constraints
2406 .get(vertex_id)?
2407 .iter()
2408 .find(|c| c.sort.as_ref() == "literal-value")
2409 .map(|c| c.value.as_str())
2410}
2411
2412/// True iff `pattern` matches a (possibly optional / repeated) sequence
2413/// of carriage-return and newline characters only. Examples: `\r?\n`,
2414/// `\n`, `\r\n`, `\n+`, `\r?\n+`. Distinguishes structural newline
2415/// terminals from generic whitespace and from other patterns that
2416/// happen to contain a newline escape inside a larger class.
2417fn is_newline_like_pattern(pattern: &str) -> bool {
2418 if pattern.is_empty() {
2419 return false;
2420 }
2421 let mut chars = pattern.chars();
2422 let mut saw_newline_atom = false;
2423 while let Some(c) = chars.next() {
2424 match c {
2425 '\\' => match chars.next() {
2426 Some('n' | 'r') => saw_newline_atom = true,
2427 _ => return false,
2428 },
2429 '?' | '*' | '+' => {} // quantifiers on the previous atom
2430 _ => return false,
2431 }
2432 }
2433 saw_newline_atom
2434}
2435
2436/// True iff `pattern` matches a (possibly quantified) run of generic
2437/// whitespace characters: `\s+`, `[ \t]+`, ` +`, `\s*`. Such patterns
2438/// describe interstitial spacing rather than syntactic content, so the
2439/// pretty emitter can drop them and let the layout pass insert the
2440/// configured separator.
2441fn is_whitespace_only_pattern(pattern: &str) -> bool {
2442 if pattern.is_empty() {
2443 return false;
2444 }
2445 // Strip an outer quantifier suffix.
2446 let trimmed = pattern.trim_end_matches(['?', '*', '+']);
2447 if trimmed.is_empty() {
2448 return false;
2449 }
2450 // Bare `\s` / ` ` / `\t`.
2451 if matches!(trimmed, "\\s" | " " | "\\t") {
2452 return true;
2453 }
2454 // Character class containing only whitespace atoms.
2455 if let Some(inner) = trimmed.strip_prefix('[').and_then(|s| s.strip_suffix(']')) {
2456 let mut chars = inner.chars();
2457 let mut saw_atom = false;
2458 while let Some(c) = chars.next() {
2459 match c {
2460 '\\' => match chars.next() {
2461 Some('s' | 't' | 'r' | 'n') => saw_atom = true,
2462 _ => return false,
2463 },
2464 ' ' | '\t' => saw_atom = true,
2465 _ => return false,
2466 }
2467 }
2468 return saw_atom;
2469 }
2470 false
2471}
2472
2473fn placeholder_for_pattern(pattern: &str) -> String {
2474 // Heuristic placeholder for unconstrained PATTERN terminals.
2475 //
2476 // First handle the "the regex IS a literal escape" cases that
2477 // tree-sitter grammars use as separators (`\n`, `\r\n`, `;`,
2478 // etc.); emitting the matching character is always preferable
2479 // to a `_x` identifier-like placeholder when the surrounding
2480 // grammar expects a separator.
2481 let simple_lit = decode_simple_pattern_literal(pattern);
2482 if let Some(lit) = simple_lit {
2483 return lit;
2484 }
2485
2486 if pattern.contains("[0-9]") || pattern.contains("\\d") {
2487 "0".into()
2488 } else if pattern.contains("[a-zA-Z_]") || pattern.contains("\\w") {
2489 "_x".into()
2490 } else if pattern.contains('"') || pattern.contains('\'') {
2491 "\"\"".into()
2492 } else {
2493 "_".into()
2494 }
2495}
2496
2497/// Decode a tree-sitter PATTERN whose regex is a simple literal
2498/// (newline, semicolon, comma, etc.) to the byte sequence it matches.
2499/// Returns `None` for patterns with character classes, alternations,
2500/// or quantifiers; the caller falls back to the heuristic placeholder.
2501fn decode_simple_pattern_literal(pattern: &str) -> Option<String> {
2502 // Skip patterns containing regex metachars that would broaden the
2503 // match beyond a single literal byte sequence.
2504 if pattern
2505 .chars()
2506 .any(|c| matches!(c, '[' | ']' | '(' | ')' | '*' | '+' | '?' | '|' | '{' | '}'))
2507 {
2508 return None;
2509 }
2510 let mut out = String::new();
2511 let mut chars = pattern.chars();
2512 while let Some(c) = chars.next() {
2513 if c == '\\' {
2514 match chars.next() {
2515 Some('n') => out.push('\n'),
2516 Some('r') => out.push('\r'),
2517 Some('t') => out.push('\t'),
2518 Some('\\') => out.push('\\'),
2519 Some('/') => out.push('/'),
2520 Some(other) => out.push(other),
2521 None => return None,
2522 }
2523 } else {
2524 out.push(c);
2525 }
2526 }
2527 Some(out)
2528}
2529
2530// ═══════════════════════════════════════════════════════════════════
2531// Token list output with Spacing algebra
2532// ═══════════════════════════════════════════════════════════════════
2533//
2534// Emit produces a free monoid over `Token`. Layout (spaces, newlines,
2535// indentation) is a homomorphism `Vec<Token> -> Vec<u8>` parameterised
2536// by `FormatPolicy`. Separating the structural output from the layout
2537// decision means each phase has one job: emit walks the grammar and
2538// pushes tokens; layout is a single fold, locally driven by adjacent
2539// pairs and a depth counter. Snapshot/restore is just `tokens.len()`.
2540
2541#[derive(Clone)]
2542enum Token {
2543 /// A user-visible terminal contributed by the grammar.
2544 Lit(String),
2545 /// `indent_open` marker emitted when a `Lit` matched the policy's
2546 /// open list. Carried as a separate token so layout can decide to
2547 /// break + indent without re-scanning.
2548 IndentOpen,
2549 /// `indent_close` marker emitted before a closer-`Lit`.
2550 IndentClose,
2551 /// "Break a line here if not already at line start" — used after
2552 /// statements/declarations and after open braces.
2553 LineBreak,
2554 /// Suppress the next inter-Lit separator. Pushed by the REPEAT
2555 /// walker when an iteration's "separator slot" (a CHOICE-with-BLANK
2556 /// or OPTIONAL at SEQ position 0) emitted zero content tokens, so
2557 /// the categorical reading is "no source-level separator existed
2558 /// between these two sibling iterations of the body".
2559 NoSpace,
2560}
2561
2562struct Output<'a> {
2563 tokens: Vec<Token>,
2564 policy: &'a FormatPolicy,
2565 suppress_brace_indent: bool,
2566}
2567
2568#[derive(Clone)]
2569struct OutputSnapshot {
2570 tokens_len: usize,
2571}
2572
2573impl<'a> Output<'a> {
2574 fn new(policy: &'a FormatPolicy) -> Self {
2575 Self {
2576 tokens: Vec::new(),
2577 policy,
2578 suppress_brace_indent: false,
2579 }
2580 }
2581
2582 fn token(&mut self, value: &str) {
2583 if value.is_empty() {
2584 return;
2585 }
2586
2587 // A grammar STRING whose value is a newline (e.g. abc's `_NL = "\n"`
2588 // or any rule that uses `"\n"` as a structural line terminator)
2589 // must route through the layout's `LineBreak` channel. Emitting it
2590 // as a `Lit` leaves the newline character in the byte stream but
2591 // also makes `needs_space_between` insert the configured separator
2592 // between the newline and the following token, producing leading
2593 // spaces on every line after the first.
2594 if value == "\n" || value == "\r\n" || value == "\r" {
2595 self.tokens.push(Token::LineBreak);
2596 return;
2597 }
2598
2599 // A captured literal value (typically a vertex's `literal-value`
2600 // constraint covering the full source span of a terminal-like
2601 // rule, e.g. abc's `reference_number_line` matching `"X:1\n"`)
2602 // may contain trailing newlines. Splitting the trailing newlines
2603 // off as a `LineBreak` lets the layout pass treat the next Lit
2604 // as starting a new line; otherwise the next Lit pair would
2605 // trigger `needs_space_between` against the embedded `\n` and
2606 // insert the policy separator at column 0 of the new line.
2607 let trimmed = value.trim_end_matches(['\n', '\r']);
2608 let trailing_newlines = value.len() - trimmed.len();
2609 if trailing_newlines > 0 && !trimmed.is_empty() {
2610 if !self.suppress_brace_indent && self.policy.indent_close.iter().any(|t| t == trimmed)
2611 {
2612 self.tokens.push(Token::IndentClose);
2613 }
2614 self.tokens.push(Token::Lit(trimmed.to_owned()));
2615 if !self.suppress_brace_indent && self.policy.indent_open.iter().any(|t| t == trimmed) {
2616 self.tokens.push(Token::IndentOpen);
2617 } else if self.policy.line_break_after.iter().any(|t| t == trimmed) {
2618 // already emitting a LineBreak below for the trailing \n
2619 }
2620 self.tokens.push(Token::LineBreak);
2621 return;
2622 }
2623
2624 if !self.suppress_brace_indent && self.policy.indent_close.iter().any(|t| t == value) {
2625 self.tokens.push(Token::IndentClose);
2626 }
2627
2628 self.tokens.push(Token::Lit(value.to_owned()));
2629
2630 if !self.suppress_brace_indent && self.policy.indent_open.iter().any(|t| t == value) {
2631 self.tokens.push(Token::IndentOpen);
2632 self.tokens.push(Token::LineBreak);
2633 } else if self.policy.line_break_after.iter().any(|t| t == value) {
2634 self.tokens.push(Token::LineBreak);
2635 }
2636 }
2637
2638 fn newline(&mut self) {
2639 self.tokens.push(Token::LineBreak);
2640 }
2641
2642 /// Open an indent scope: subsequent `LineBreak`s render at the
2643 /// new depth until a matching `indent_close` pops it. Used by the
2644 /// external-token fallback to render indent-based grammars'
2645 /// `_indent` scanner outputs.
2646 fn indent_open(&mut self) {
2647 self.tokens.push(Token::IndentOpen);
2648 self.tokens.push(Token::LineBreak);
2649 }
2650
2651 /// Close one indent scope opened by `indent_open`.
2652 fn indent_close(&mut self) {
2653 self.tokens.push(Token::IndentClose);
2654 }
2655
2656 fn snapshot(&self) -> OutputSnapshot {
2657 OutputSnapshot {
2658 tokens_len: self.tokens.len(),
2659 }
2660 }
2661
2662 fn restore(&mut self, snap: OutputSnapshot) {
2663 self.tokens.truncate(snap.tokens_len);
2664 }
2665
2666 /// True iff at least one `Token::Lit` was pushed since `snap`.
2667 /// Control-only emissions (`LineBreak`, `IndentOpen` / `IndentClose`,
2668 /// `NoSpace`) do not count as content. Used by the REPEAT walker
2669 /// to detect that a "separator slot" CHOICE picked its BLANK
2670 /// alternative, so the next iteration's content can be marked
2671 /// tight against the previous iteration's content.
2672 fn lit_emitted_since(&self, snap: OutputSnapshot) -> bool {
2673 self.tokens[snap.tokens_len..]
2674 .iter()
2675 .any(|t| matches!(t, Token::Lit(_)))
2676 }
2677
2678 /// Push a marker that suppresses the next inter-Lit separator the
2679 /// layout pass would otherwise insert. Used to encode "no source-
2680 /// level separator was emitted between these two Lits" without
2681 /// having to make per-grammar adjacency decisions in the layout.
2682 fn no_space(&mut self) {
2683 self.tokens.push(Token::NoSpace);
2684 }
2685
2686 fn finish(self) -> Vec<u8> {
2687 layout(&self.tokens, self.policy)
2688 }
2689}
2690
2691/// Fold a token list into bytes. The algebra:
2692/// * adjacent `Lit`s get a single space iff `needs_space_between(a, b)`,
2693/// * `IndentOpen` / `IndentClose` adjust a depth counter,
2694/// * `LineBreak` writes `\n` if not already at line start, then the
2695/// next `Lit` writes `indent * indent_width` spaces of indent.
2696fn layout(tokens: &[Token], policy: &FormatPolicy) -> Vec<u8> {
2697 let mut bytes = Vec::new();
2698 let mut indent: usize = 0;
2699 let mut at_line_start = true;
2700 let mut last_lit: Option<&str> = None;
2701 // True iff, at the moment `last_lit` was emitted, the cursor was at a
2702 // position where the grammar expects an operand: start of stream / line,
2703 // just after an open paren / bracket / brace, just after a separator like
2704 // `,` or `;`, or just after a binary / assignment operator. Used by
2705 // `needs_space_between` to recognise `last_lit` as a tight unary prefix
2706 // (`f(-1.0)`) rather than a spaced binary operator (`a - b`).
2707 let mut last_was_in_operand_position = true;
2708 let mut expecting_operand = true;
2709 // Set when a `Token::NoSpace` marker is seen; cleared when the next
2710 // Lit consumes it. While set, suppress the policy separator that
2711 // would otherwise be inserted before the next Lit.
2712 let mut suppress_next_separator = false;
2713 let newline = policy.newline.as_bytes();
2714 let separator = policy.separator.as_bytes();
2715
2716 for tok in tokens {
2717 match tok {
2718 Token::IndentOpen => indent += 1,
2719 Token::IndentClose => {
2720 indent = indent.saturating_sub(1);
2721 if !at_line_start {
2722 bytes.extend_from_slice(newline);
2723 at_line_start = true;
2724 expecting_operand = true;
2725 }
2726 }
2727 Token::LineBreak => {
2728 if !at_line_start {
2729 bytes.extend_from_slice(newline);
2730 at_line_start = true;
2731 expecting_operand = true;
2732 }
2733 }
2734 Token::NoSpace => {
2735 suppress_next_separator = true;
2736 }
2737 Token::Lit(value) => {
2738 if at_line_start {
2739 bytes.extend(std::iter::repeat_n(b' ', indent * policy.indent_width));
2740 } else if let Some(prev) = last_lit {
2741 if !suppress_next_separator
2742 && needs_space_between(prev, value, last_was_in_operand_position)
2743 {
2744 bytes.extend_from_slice(separator);
2745 }
2746 }
2747 suppress_next_separator = false;
2748 bytes.extend_from_slice(value.as_bytes());
2749 at_line_start = false;
2750 last_was_in_operand_position = expecting_operand;
2751 expecting_operand = leaves_operand_position(value);
2752 last_lit = Some(value.as_str());
2753 }
2754 }
2755 }
2756
2757 if !at_line_start {
2758 bytes.extend_from_slice(newline);
2759 }
2760 bytes
2761}
2762
2763/// True iff emitting `tok` leaves the cursor in a position where the
2764/// grammar expects an operand next. Operand-introducing tokens are open
2765/// punctuation, separators, and operator-like strings; operand-terminating
2766/// tokens are identifiers, literals, and closing punctuation.
2767fn leaves_operand_position(tok: &str) -> bool {
2768 if tok.is_empty() {
2769 return true;
2770 }
2771 if is_punct_open(tok) {
2772 return true;
2773 }
2774 if matches!(tok, "," | ";") {
2775 return true;
2776 }
2777 if is_punct_close(tok) {
2778 return false;
2779 }
2780 if first_is_alnum_or_underscore(tok) || last_ends_with_alnum(tok) {
2781 return false;
2782 }
2783 // Pure punctuation/operator runs (`=`, `+`, `-`, `<=`, `>>`, …) leave
2784 // the cursor expecting another operand.
2785 true
2786}
2787
2788fn needs_space_between(last: &str, next: &str, expecting_operand: bool) -> bool {
2789 if last.is_empty() || next.is_empty() {
2790 return false;
2791 }
2792 if is_punct_open(last) || is_punct_open(next) {
2793 return false;
2794 }
2795 if is_punct_close(next) {
2796 return false;
2797 }
2798 if is_punct_close(last) && is_punct_punctuation(next) {
2799 return false;
2800 }
2801 if last == "." || next == "." {
2802 return false;
2803 }
2804 // Tight unary prefix: `last` is a sign/logical-not operator emitted
2805 // where the grammar expected an operand, so it glues to `next`.
2806 // `expecting_operand` here means: just before `last` was emitted,
2807 // the cursor expected an operand, which makes `last` a unary prefix.
2808 // Examples: `f(-1.0)`, `[ -2, 3 ]`, `return -x`, `a = !flag`.
2809 if expecting_operand && is_unary_prefix_operator(last) && first_is_operand_start(next) {
2810 return false;
2811 }
2812 if last_is_word_like(last) && first_is_word_like(next) {
2813 return true;
2814 }
2815 if last_ends_with_alnum(last) && first_is_alnum_or_underscore(next) {
2816 return true;
2817 }
2818 // Adjacent operator runs: keep them apart so the lexer doesn't glue
2819 // `>` and `=` into `>=` unintentionally.
2820 true
2821}
2822
2823fn is_unary_prefix_operator(s: &str) -> bool {
2824 matches!(s, "-" | "+" | "!" | "~")
2825}
2826
2827fn first_is_operand_start(s: &str) -> bool {
2828 s.chars()
2829 .next()
2830 .map(|c| c.is_alphanumeric() || c == '_' || c == '.' || c == '(')
2831 .unwrap_or(false)
2832}
2833
2834fn is_punct_open(s: &str) -> bool {
2835 matches!(s, "(" | "[" | "{" | "\"" | "'" | "`" | "@" | "#")
2836}
2837
2838fn is_punct_close(s: &str) -> bool {
2839 matches!(s, ")" | "]" | "}" | "," | ";" | ":" | "\"" | "'" | "`")
2840}
2841
2842fn is_punct_punctuation(s: &str) -> bool {
2843 matches!(s, "," | ";" | ":" | "." | ")" | "]" | "}")
2844}
2845
2846fn last_is_word_like(s: &str) -> bool {
2847 s.chars()
2848 .next_back()
2849 .map(|c| c.is_alphanumeric() || c == '_')
2850 .unwrap_or(false)
2851}
2852
2853fn first_is_word_like(s: &str) -> bool {
2854 s.chars()
2855 .next()
2856 .map(|c| c.is_alphanumeric() || c == '_')
2857 .unwrap_or(false)
2858}
2859
2860fn last_ends_with_alnum(s: &str) -> bool {
2861 s.chars()
2862 .next_back()
2863 .map(char::is_alphanumeric)
2864 .unwrap_or(false)
2865}
2866
2867fn first_is_alnum_or_underscore(s: &str) -> bool {
2868 s.chars()
2869 .next()
2870 .map(|c| c.is_alphanumeric() || c == '_')
2871 .unwrap_or(false)
2872}
2873
2874#[cfg(test)]
2875#[allow(clippy::unwrap_used)]
2876mod tests {
2877 use super::*;
2878
2879 #[test]
2880 fn parses_simple_grammar_json() {
2881 let bytes = br#"{
2882 "name": "tiny",
2883 "rules": {
2884 "program": {
2885 "type": "SEQ",
2886 "members": [
2887 {"type": "STRING", "value": "hello"},
2888 {"type": "STRING", "value": ";"}
2889 ]
2890 }
2891 }
2892 }"#;
2893 let g = Grammar::from_bytes("tiny", bytes).expect("valid tiny grammar");
2894 assert!(g.rules.contains_key("program"));
2895 }
2896
2897 #[test]
2898 fn output_emits_punctuation_without_leading_space() {
2899 let policy = FormatPolicy::default();
2900 let mut out = Output::new(&policy);
2901 out.token("foo");
2902 out.token("(");
2903 out.token(")");
2904 out.token(";");
2905 let bytes = out.finish();
2906 let s = std::str::from_utf8(&bytes).expect("ascii output");
2907 assert!(s.starts_with("foo();"), "got {s:?}");
2908 }
2909
2910 #[test]
2911 fn grammar_from_bytes_rejects_malformed_input() {
2912 let result = Grammar::from_bytes("malformed", b"not json");
2913 let err = result.expect_err("malformed bytes must yield Err");
2914 let msg = err.to_string();
2915 assert!(
2916 msg.contains("malformed"),
2917 "error message should name the protocol: {msg:?}"
2918 );
2919 }
2920
2921 #[test]
2922 fn output_indents_after_open_brace() {
2923 let policy = FormatPolicy::default();
2924 let mut out = Output::new(&policy);
2925 out.token("fn");
2926 out.token("foo");
2927 out.token("(");
2928 out.token(")");
2929 out.token("{");
2930 out.token("body");
2931 out.token("}");
2932 let bytes = out.finish();
2933 let s = std::str::from_utf8(&bytes).expect("ascii output");
2934 assert!(s.contains("{\n"), "newline after opening brace: {s:?}");
2935 assert!(s.contains("body"), "body inside block: {s:?}");
2936 assert!(s.ends_with("}\n"), "newline after closing brace: {s:?}");
2937 }
2938
2939 #[test]
2940 fn output_no_space_between_word_and_dot() {
2941 let policy = FormatPolicy::default();
2942 let mut out = Output::new(&policy);
2943 out.token("foo");
2944 out.token(".");
2945 out.token("bar");
2946 let bytes = out.finish();
2947 let s = std::str::from_utf8(&bytes).expect("ascii output");
2948 assert!(s.starts_with("foo.bar"), "no space around dot: {s:?}");
2949 }
2950
2951 #[test]
2952 fn output_snapshot_restore_truncates_bytes() {
2953 let policy = FormatPolicy::default();
2954 let mut out = Output::new(&policy);
2955 out.token("keep");
2956 let snap = out.snapshot();
2957 out.token("drop");
2958 out.token("more");
2959 out.restore(snap);
2960 out.token("after");
2961 let bytes = out.finish();
2962 let s = std::str::from_utf8(&bytes).expect("ascii output");
2963 assert!(s.contains("keep"), "kept token survives: {s:?}");
2964 assert!(s.contains("after"), "post-restore token visible: {s:?}");
2965 assert!(!s.contains("drop"), "rolled-back token removed: {s:?}");
2966 assert!(!s.contains("more"), "rolled-back token removed: {s:?}");
2967 }
2968
2969 #[test]
2970 fn child_cursor_take_field_consumes_once() {
2971 let edges_owned: Vec<Edge> = vec![Edge {
2972 src: panproto_gat::Name::from("p"),
2973 tgt: panproto_gat::Name::from("c"),
2974 kind: panproto_gat::Name::from("name"),
2975 name: None,
2976 }];
2977 let edges: Vec<&Edge> = edges_owned.iter().collect();
2978 let mut cursor = ChildCursor::new(&edges);
2979 let first = cursor.take_field("name");
2980 let second = cursor.take_field("name");
2981 assert!(first.is_some(), "first take returns the edge");
2982 assert!(
2983 second.is_none(),
2984 "second take returns None (already consumed)"
2985 );
2986 }
2987
2988 #[test]
2989 fn child_cursor_take_matching_predicate() {
2990 let edges_owned: Vec<Edge> = vec![
2991 Edge {
2992 src: "p".into(),
2993 tgt: "c1".into(),
2994 kind: "child_of".into(),
2995 name: None,
2996 },
2997 Edge {
2998 src: "p".into(),
2999 tgt: "c2".into(),
3000 kind: "key".into(),
3001 name: None,
3002 },
3003 ];
3004 let edges: Vec<&Edge> = edges_owned.iter().collect();
3005 let mut cursor = ChildCursor::new(&edges);
3006 assert!(cursor.has_matching(|e| e.kind.as_ref() == "key"));
3007 let taken = cursor.take_matching(|e| e.kind.as_ref() == "key");
3008 assert!(taken.is_some());
3009 assert!(
3010 !cursor.has_matching(|e| e.kind.as_ref() == "key"),
3011 "consumed edge no longer matches"
3012 );
3013 assert!(
3014 cursor.has_matching(|e| e.kind.as_ref() == "child_of"),
3015 "the other edge is still available"
3016 );
3017 }
3018
3019 #[test]
3020 fn kind_satisfies_symbol_direct_match() {
3021 let bytes = br#"{
3022 "name": "tiny",
3023 "rules": {
3024 "x": {"type": "STRING", "value": "x"}
3025 }
3026 }"#;
3027 let g = Grammar::from_bytes("tiny", bytes).expect("valid grammar");
3028 assert!(kind_satisfies_symbol(&g, Some("x"), "x"));
3029 assert!(!kind_satisfies_symbol(&g, Some("y"), "x"));
3030 assert!(!kind_satisfies_symbol(&g, None, "x"));
3031 }
3032
3033 #[test]
3034 fn kind_satisfies_symbol_through_hidden_rule() {
3035 let bytes = br#"{
3036 "name": "tiny",
3037 "rules": {
3038 "_value": {
3039 "type": "CHOICE",
3040 "members": [
3041 {"type": "SYMBOL", "name": "object"},
3042 {"type": "SYMBOL", "name": "number"}
3043 ]
3044 },
3045 "object": {"type": "STRING", "value": "{}"},
3046 "number": {"type": "PATTERN", "value": "[0-9]+"}
3047 }
3048 }"#;
3049 let g = Grammar::from_bytes("tiny", bytes).expect("valid grammar");
3050 assert!(
3051 kind_satisfies_symbol(&g, Some("number"), "_value"),
3052 "number is reachable from _value via CHOICE"
3053 );
3054 assert!(
3055 kind_satisfies_symbol(&g, Some("object"), "_value"),
3056 "object is reachable from _value via CHOICE"
3057 );
3058 assert!(
3059 !kind_satisfies_symbol(&g, Some("string"), "_value"),
3060 "string is NOT among the alternatives"
3061 );
3062 }
3063
3064 #[test]
3065 fn first_symbol_skips_string_terminals() {
3066 let prod: Production = serde_json::from_str(
3067 r#"{
3068 "type": "SEQ",
3069 "members": [
3070 {"type": "STRING", "value": "{"},
3071 {"type": "SYMBOL", "name": "body"},
3072 {"type": "STRING", "value": "}"}
3073 ]
3074 }"#,
3075 )
3076 .expect("valid SEQ");
3077 assert_eq!(first_symbol(&prod), Some("body"));
3078 }
3079
3080 #[test]
3081 fn placeholder_for_pattern_routes_by_regex_class() {
3082 assert_eq!(placeholder_for_pattern("[0-9]+"), "0");
3083 assert_eq!(placeholder_for_pattern("[a-zA-Z_]\\w*"), "_x");
3084 assert_eq!(placeholder_for_pattern("\"[^\"]*\""), "\"\"");
3085 assert_eq!(placeholder_for_pattern("\\d+\\.\\d+"), "0");
3086 }
3087
3088 #[test]
3089 fn format_policy_default_breaks_after_semicolon() {
3090 let policy = FormatPolicy::default();
3091 assert!(policy.line_break_after.iter().any(|t| t == ";"));
3092 assert!(policy.indent_open.iter().any(|t| t == "{"));
3093 assert!(policy.indent_close.iter().any(|t| t == "}"));
3094 assert_eq!(policy.indent_width, 2);
3095 }
3096
3097 #[test]
3098 fn placeholder_decodes_literal_pattern_separators() {
3099 // PATTERN regexes that match a single literal byte sequence
3100 // (newline, semicolon, comma) emit the bytes verbatim instead
3101 // of falling through to the `_` catch-all.
3102 assert_eq!(placeholder_for_pattern("\\n"), "\n");
3103 assert_eq!(placeholder_for_pattern("\\r\\n"), "\r\n");
3104 assert_eq!(placeholder_for_pattern(";"), ";");
3105 // Patterns with character classes / alternation still route
3106 // through the heuristic.
3107 assert_eq!(placeholder_for_pattern("[0-9]+"), "0");
3108 assert_eq!(placeholder_for_pattern("a|b"), "_");
3109 }
3110
3111 #[test]
3112 fn supertypes_decode_from_grammar_json_strings() {
3113 // Tree-sitter older grammars list supertypes as bare strings.
3114 let bytes = br#"{
3115 "name": "tiny",
3116 "supertypes": ["expression"],
3117 "rules": {
3118 "expression": {
3119 "type": "CHOICE",
3120 "members": [
3121 {"type": "SYMBOL", "name": "binary_expression"},
3122 {"type": "SYMBOL", "name": "identifier"}
3123 ]
3124 },
3125 "binary_expression": {"type": "STRING", "value": "x"},
3126 "identifier": {"type": "PATTERN", "value": "[a-z]+"}
3127 }
3128 }"#;
3129 let g = Grammar::from_bytes("tiny", bytes).expect("parse");
3130 assert!(g.supertypes.contains("expression"));
3131 // identifier matches the supertype `expression`.
3132 assert!(kind_satisfies_symbol(&g, Some("identifier"), "expression"));
3133 // unrelated kinds do not.
3134 assert!(!kind_satisfies_symbol(&g, Some("string"), "expression"));
3135 }
3136
3137 #[test]
3138 fn supertypes_decode_from_grammar_json_objects() {
3139 // Recent grammars list supertypes as `{type: SYMBOL, name: ...}`
3140 // entries instead of bare strings.
3141 let bytes = br#"{
3142 "name": "tiny",
3143 "supertypes": [{"type": "SYMBOL", "name": "stmt"}],
3144 "rules": {
3145 "stmt": {
3146 "type": "CHOICE",
3147 "members": [
3148 {"type": "SYMBOL", "name": "while_stmt"},
3149 {"type": "SYMBOL", "name": "if_stmt"}
3150 ]
3151 },
3152 "while_stmt": {"type": "STRING", "value": "while"},
3153 "if_stmt": {"type": "STRING", "value": "if"}
3154 }
3155 }"#;
3156 let g = Grammar::from_bytes("tiny", bytes).expect("parse");
3157 assert!(g.supertypes.contains("stmt"));
3158 assert!(kind_satisfies_symbol(&g, Some("while_stmt"), "stmt"));
3159 }
3160
3161 #[test]
3162 fn alias_value_matches_kind() {
3163 // A named ALIAS rewrites the parser-visible kind to `value`;
3164 // `kind_satisfies_symbol` should accept that rewritten kind
3165 // when looking up the original SYMBOL.
3166 let bytes = br#"{
3167 "name": "tiny",
3168 "rules": {
3169 "_package_identifier": {
3170 "type": "ALIAS",
3171 "named": true,
3172 "value": "package_identifier",
3173 "content": {"type": "SYMBOL", "name": "identifier"}
3174 },
3175 "identifier": {"type": "PATTERN", "value": "[a-z]+"}
3176 }
3177 }"#;
3178 let g = Grammar::from_bytes("tiny", bytes).expect("parse");
3179 assert!(kind_satisfies_symbol(
3180 &g,
3181 Some("package_identifier"),
3182 "_package_identifier"
3183 ));
3184 }
3185
3186 #[test]
3187 fn referenced_symbols_walks_nested_seq() {
3188 let prod: Production = serde_json::from_str(
3189 r#"{
3190 "type": "SEQ",
3191 "members": [
3192 {"type": "CHOICE", "members": [
3193 {"type": "SYMBOL", "name": "attribute_item"},
3194 {"type": "BLANK"}
3195 ]},
3196 {"type": "SYMBOL", "name": "parameter"},
3197 {"type": "REPEAT", "content": {
3198 "type": "SEQ",
3199 "members": [
3200 {"type": "STRING", "value": ","},
3201 {"type": "SYMBOL", "name": "parameter"}
3202 ]
3203 }}
3204 ]
3205 }"#,
3206 )
3207 .expect("seq");
3208 let symbols = referenced_symbols(&prod);
3209 assert!(symbols.contains(&"attribute_item"));
3210 assert!(symbols.contains(&"parameter"));
3211 }
3212
3213 #[test]
3214 fn literal_strings_collects_choice_members() {
3215 let prod: Production = serde_json::from_str(
3216 r#"{
3217 "type": "CHOICE",
3218 "members": [
3219 {"type": "STRING", "value": "+"},
3220 {"type": "STRING", "value": "-"},
3221 {"type": "STRING", "value": "*"}
3222 ]
3223 }"#,
3224 )
3225 .expect("choice");
3226 let strings = literal_strings(&prod);
3227 assert_eq!(strings, vec!["+", "-", "*"]);
3228 }
3229
3230 /// The ocaml and javascript grammars (tree-sitter ≥ 0.25) emit a
3231 /// `RESERVED` rule kind that an earlier deserialiser rejected
3232 /// with `unknown variant "RESERVED"`. Verify both that the bare
3233 /// variant deserialises and that a `RESERVED`-wrapped grammar is
3234 /// loadable end-to-end via [`Grammar::from_bytes`].
3235 #[test]
3236 fn reserved_variant_deserialises() {
3237 let prod: Production = serde_json::from_str(
3238 r#"{
3239 "type": "RESERVED",
3240 "content": {"type": "SYMBOL", "name": "_lowercase_identifier"},
3241 "context_name": "attribute_id"
3242 }"#,
3243 )
3244 .expect("RESERVED parses");
3245 match prod {
3246 Production::Reserved { content, .. } => match *content {
3247 Production::Symbol { name } => assert_eq!(name, "_lowercase_identifier"),
3248 other => panic!("expected inner SYMBOL, got {other:?}"),
3249 },
3250 other => panic!("expected RESERVED, got {other:?}"),
3251 }
3252 }
3253
3254 #[test]
3255 fn reserved_grammar_loads_end_to_end() {
3256 let bytes = br#"{
3257 "name": "tiny_reserved",
3258 "rules": {
3259 "program": {
3260 "type": "RESERVED",
3261 "content": {"type": "SYMBOL", "name": "ident"},
3262 "context_name": "keywords"
3263 },
3264 "ident": {"type": "PATTERN", "value": "[a-z]+"}
3265 }
3266 }"#;
3267 let g = Grammar::from_bytes("tiny_reserved", bytes).expect("RESERVED-using grammar loads");
3268 assert!(g.rules.contains_key("program"));
3269 }
3270
3271 #[test]
3272 fn reserved_walker_helpers_recurse_into_content() {
3273 // The walker's helpers (first_symbol, has_field_in,
3274 // referenced_symbols, ...) all need to descend through
3275 // RESERVED into its content. If they bail at RESERVED, the
3276 // `pick_choice_with_cursor` heuristic ranks the alt below
3277 // alts that DO recurse, which produces wrong emit output
3278 // even when the deserialiser doesn't crash.
3279 let prod: Production = serde_json::from_str(
3280 r#"{
3281 "type": "RESERVED",
3282 "content": {
3283 "type": "FIELD",
3284 "name": "lhs",
3285 "content": {"type": "SYMBOL", "name": "expr"}
3286 },
3287 "context_name": "ctx"
3288 }"#,
3289 )
3290 .expect("nested RESERVED parses");
3291 assert_eq!(first_symbol(&prod), Some("expr"));
3292 assert!(has_field_in(&prod, &["lhs"]));
3293 let symbols = referenced_symbols(&prod);
3294 assert!(symbols.contains(&"expr"));
3295 }
3296
3297 // -- Yield-set tests --
3298
3299 fn yield_of(grammar: &Grammar, prod: &Production) -> std::collections::HashSet<String> {
3300 let mut visited = std::collections::HashSet::new();
3301 let mut cache = grammar.yield_sets.clone();
3302 yield_of_production(grammar, prod, &mut visited, &mut cache)
3303 }
3304
3305 #[test]
3306 fn yield_set_seq_only_first_member() {
3307 let prod: Production = serde_json::from_str(
3308 r#"{
3309 "type": "SEQ",
3310 "members": [
3311 {"type": "SYMBOL", "name": "identifier"},
3312 {"type": "STRING", "value": "as"},
3313 {"type": "SYMBOL", "name": "target"}
3314 ]
3315 }"#,
3316 )
3317 .expect("valid SEQ");
3318 let g = Grammar::from_bytes("test", b"{}").unwrap_or_else(|_| {
3319 serde_json::from_str::<Grammar>(r#"{"name":"t","rules":{}}"#).unwrap()
3320 });
3321 let ys = yield_of(&g, &prod);
3322 assert!(ys.contains("identifier"), "SEQ yields first member");
3323 assert!(
3324 !ys.contains("target"),
3325 "SEQ must NOT yield non-first members"
3326 );
3327 }
3328
3329 #[test]
3330 fn yield_set_choice_union() {
3331 let prod: Production = serde_json::from_str(
3332 r#"{
3333 "type": "CHOICE",
3334 "members": [
3335 {"type": "SYMBOL", "name": "a"},
3336 {"type": "SYMBOL", "name": "b"}
3337 ]
3338 }"#,
3339 )
3340 .expect("valid CHOICE");
3341 let g = serde_json::from_str::<Grammar>(r#"{"name":"t","rules":{}}"#).unwrap();
3342 let ys = yield_of(&g, &prod);
3343 assert_eq!(ys.len(), 2);
3344 assert!(ys.contains("a"));
3345 assert!(ys.contains("b"));
3346 }
3347
3348 #[test]
3349 fn yield_set_hidden_expansion() {
3350 let g = serde_json::from_str::<Grammar>(
3351 r#"{"name":"t","rules":{
3352 "_value": {
3353 "type": "CHOICE",
3354 "members": [
3355 {"type": "SYMBOL", "name": "number"},
3356 {"type": "SYMBOL", "name": "object"}
3357 ]
3358 }
3359 }}"#,
3360 )
3361 .unwrap();
3362 let mut g = g;
3363 g.subtypes = compute_subtype_closure(&g);
3364 g.yield_sets = compute_yield_sets(&g);
3365 let sym: Production =
3366 serde_json::from_str(r#"{"type": "SYMBOL", "name": "_value"}"#).unwrap();
3367 let ys = yield_of(&g, &sym);
3368 assert!(
3369 ys.contains("number"),
3370 "hidden rule expands into its CHOICE members"
3371 );
3372 assert!(ys.contains("object"));
3373 assert!(
3374 !ys.contains("_value"),
3375 "hidden rule name is not in yield set"
3376 );
3377 }
3378
3379 #[test]
3380 fn yield_set_optional_includes_epsilon() {
3381 let prod: Production = serde_json::from_str(
3382 r#"{"type": "OPTIONAL", "content": {"type": "SYMBOL", "name": "x"}}"#,
3383 )
3384 .unwrap();
3385 let g = serde_json::from_str::<Grammar>(r#"{"name":"t","rules":{}}"#).unwrap();
3386 let ys = yield_of(&g, &prod);
3387 assert!(ys.contains("x"));
3388 assert!(ys.contains(""), "OPTIONAL includes epsilon");
3389 }
3390
3391 #[test]
3392 fn yield_set_alias_uses_value() {
3393 let prod: Production = serde_json::from_str(
3394 r#"{"type": "ALIAS", "content": {"type": "SYMBOL", "name": "real"},
3395 "named": true, "value": "alias_name"}"#,
3396 )
3397 .unwrap();
3398 let g = serde_json::from_str::<Grammar>(r#"{"name":"t","rules":{}}"#).unwrap();
3399 let ys = yield_of(&g, &prod);
3400 assert_eq!(ys.len(), 1);
3401 assert!(ys.contains("alias_name"), "named ALIAS yields its value");
3402 }
3403}