Skip to main content

antlr4_runtime/
tree_pattern.rs

1// SPDX-License-Identifier: BSD-3-Clause
2// Copyright (c) 2026 Konstantin Vyatkin
3//! ANTLR parse-tree pattern matching.
4//!
5//! A *tree pattern* is a string of ordinary grammar input with embedded
6//! `<tag>` placeholders — for example `<ID> = <expr>;` matched as the rule
7//! `stat`. Literals must match exactly; a `<rule>` tag matches any subtree of
8//! that parser rule and a `<TOKEN>` tag matches any token of that type. Tags
9//! may carry a label, `<lhs:ID>`, so matched nodes can be looked up by name.
10//!
11//! This mirrors ANTLR's `org.antlr.v4.runtime.tree.pattern` package. Compiling
12//! a pattern lexes its literal chunks with the real lexer, converts each tag
13//! into a synthetic rule/token tag token (ANTLR's `RuleTagToken` /
14//! `TokenTagToken`), and interprets that hybrid token stream over a rule-bypass
15//! ATN (see [`crate::atn::parser_atn::ParserAtn::with_bypass_alternatives`]) to
16//! build a *pattern tree*. [`ParseTreePattern::match_tree`] then walks a
17//! subject tree and the pattern tree in lockstep, binding tag labels.
18//!
19//! The ergonomic entry point is the `compile_parse_tree_pattern` method every
20//! generated parser exposes; [`ParseTreePatternMatcher`] is the lower-level,
21//! reusable compiler behind it.
22
23use std::collections::BTreeMap;
24
25use thiserror::Error;
26
27use crate::atn::parser_atn::ParserAtn;
28use crate::recognizer::{Recognizer, RecognizerData};
29use crate::token::{Token, TokenId, TokenSink, TokenSource, TokenSpec, TokenStoreError};
30use crate::tree::{Node, NodeKind};
31use crate::{BaseParser, CommonTokenStream, TOKEN_EOF};
32
33const MATCH_STACK_RED_ZONE: usize = 1024 * 1024;
34const MATCH_STACK_SIZE: usize = 4 * 1024 * 1024;
35
36/// A tag's disposition: a reference to a parser rule or to a token type.
37#[derive(Clone, Copy, Debug, Eq, PartialEq)]
38enum TagKind {
39    /// `<expr>` — matches an entire subtree produced by the named parser rule.
40    /// Carries the rule index and the imaginary bypass token type used to drive
41    /// the interpreter.
42    Rule { rule_index: usize, bypass_type: i32 },
43    /// `<ID>` — matches a single token of the named type.
44    Token { token_type: i32 },
45}
46
47/// Identity of a synthetic tag token, tracked out of band keyed by the
48/// [`TokenId`] the tag occupies in the pattern token store.
49///
50/// The compact [`crate::token::TokenStore`] has no room for the rule/token
51/// name and label a tag carries, so — like the store's own sparse
52/// `explicit_text` side table — the matcher keeps this data beside the store
53/// rather than inside it.
54#[derive(Clone, Debug, Eq, PartialEq)]
55struct TagInfo {
56    kind: TagKind,
57    /// The rule or token name the tag references (e.g. `"expr"`, `"ID"`).
58    name: String,
59    /// The explicit label, if the tag was written `<label:name>`.
60    label: Option<String>,
61}
62
63impl TagInfo {
64    /// The names a matched node is filed under: always the referenced rule or
65    /// token name, plus the explicit label when present. Mirrors the dual
66    /// `labels.map(name, ...)` / `labels.map(label, ...)` calls in ANTLR's
67    /// `matchImpl`.
68    fn label_keys(&self) -> impl Iterator<Item = &str> {
69        std::iter::once(self.name.as_str()).chain(self.label.as_deref())
70    }
71}
72
73/// A raw fragment of a split pattern: either literal text or a `<tag>`.
74#[derive(Clone, Debug, Eq, PartialEq)]
75enum Chunk {
76    /// Literal input text, with escape sequences already stripped.
77    Text(String),
78    /// A `<tag>` island: the referenced rule/token name and optional label.
79    Tag { name: String, label: Option<String> },
80}
81
82/// An invalid tree pattern or a failure while compiling one.
83#[derive(Clone, Debug, Eq, Error, PartialEq)]
84pub enum ParseTreePatternError {
85    /// A start delimiter was seen without a matching stop delimiter.
86    #[error("unterminated tag in pattern: {pattern}")]
87    UnterminatedTag { pattern: String },
88    /// A stop delimiter was seen without a preceding start delimiter.
89    #[error("missing start tag in pattern: {pattern}")]
90    MissingStartTag { pattern: String },
91    /// A stop delimiter appeared at or before its start delimiter.
92    #[error("tag delimiters out of order in pattern: {pattern}")]
93    DelimitersOutOfOrder { pattern: String },
94    /// A tag was empty (`<>` or `<label:>`).
95    #[error("empty tag in pattern: {pattern}")]
96    EmptyTag { pattern: String },
97    /// A `<TOKEN>` tag named a token the grammar does not define.
98    #[error("unknown token {name} in pattern: {pattern}")]
99    UnknownToken { name: String, pattern: String },
100    /// A `<rule>` tag named a parser rule the grammar does not define.
101    #[error("unknown rule {name} in pattern: {pattern}")]
102    UnknownRule { name: String, pattern: String },
103    /// A tag started with neither an upper- nor lower-case letter, so it could
104    /// not be classified as a token or rule reference.
105    #[error("invalid tag {tag} in pattern: {pattern}")]
106    InvalidTag { tag: String, pattern: String },
107    /// Lexing a literal chunk with the real lexer failed.
108    #[error("could not tokenize pattern chunk: {message}")]
109    Tokenization { message: String },
110    /// The start rule did not consume the whole pattern (ANTLR issue #413).
111    #[error("start rule did not consume the full pattern: {pattern}")]
112    StartRuleDoesNotConsumeFullPattern { pattern: String },
113    /// Interpreting the pattern token stream failed.
114    #[error("could not interpret pattern as rule {rule_index}: {message}")]
115    CannotInvokeStartRule { rule_index: usize, message: String },
116    /// The rule-bypass transform of the grammar ATN failed.
117    #[error("could not build rule-bypass ATN: {message}")]
118    BypassAtn { message: String },
119    /// [`ParseTreePatternMatcher::set_delimiters`] was given an empty start or
120    /// stop delimiter.
121    #[error("{which} delimiter cannot be empty")]
122    EmptyDelimiter { which: &'static str },
123}
124
125/// Tag delimiters and escape string used to split a pattern.
126///
127/// Defaults to `<`, `>`, and `\`, matching ANTLR. Grammars that use `<...>` in
128/// their own concrete syntax (e.g. Java generics) can pick different delimiters
129/// via [`ParseTreePatternMatcher::set_delimiters`].
130#[derive(Clone, Debug, Eq, PartialEq)]
131struct Delimiters {
132    start: String,
133    stop: String,
134    escape: String,
135}
136
137impl Default for Delimiters {
138    fn default() -> Self {
139        Self {
140            start: "<".to_owned(),
141            stop: ">".to_owned(),
142            escape: "\\".to_owned(),
143        }
144    }
145}
146
147/// Splits a pattern into interleaved literal text and `<tag>` chunks.
148///
149/// Faithful port of ANTLR's `ParseTreePatternMatcher.split`: it scans for the
150/// escaped and unescaped delimiters, validates that starts and stops are
151/// balanced and ordered, slices the chunks, then strips escape sequences from
152/// the text chunks only (never from tags). Operates on `char` boundaries so
153/// multi-byte delimiters and Unicode input are handled correctly.
154fn split(pattern: &str, delimiters: &Delimiters) -> Result<Vec<Chunk>, ParseTreePatternError> {
155    let chars: Vec<char> = pattern.chars().collect();
156    let start: Vec<char> = delimiters.start.chars().collect();
157    let stop: Vec<char> = delimiters.stop.chars().collect();
158    let escape: Vec<char> = delimiters.escape.chars().collect();
159
160    let matches_at = |at: usize, needle: &[char]| -> bool {
161        !needle.is_empty() && chars[at..].starts_with(needle)
162    };
163
164    // Pass 1: locate every unescaped start/stop delimiter, by char index.
165    let mut starts = Vec::new();
166    let mut stops = Vec::new();
167    let mut position = 0;
168    while position < chars.len() {
169        if matches_at(position, &escape) && matches_at(position + escape.len(), &start) {
170            position += escape.len() + start.len();
171        } else if matches_at(position, &escape) && matches_at(position + escape.len(), &stop) {
172            position += escape.len() + stop.len();
173        } else if matches_at(position, &start) {
174            starts.push(position);
175            position += start.len();
176        } else if matches_at(position, &stop) {
177            stops.push(position);
178            position += stop.len();
179        } else {
180            position += 1;
181        }
182    }
183
184    if starts.len() > stops.len() {
185        return Err(ParseTreePatternError::UnterminatedTag {
186            pattern: pattern.to_owned(),
187        });
188    }
189    if starts.len() < stops.len() {
190        return Err(ParseTreePatternError::MissingStartTag {
191            pattern: pattern.to_owned(),
192        });
193    }
194    for (open, close) in starts.iter().zip(&stops) {
195        if open >= close {
196            return Err(ParseTreePatternError::DelimitersOutOfOrder {
197                pattern: pattern.to_owned(),
198            });
199        }
200    }
201    // Tags must also not overlap each other (e.g. `<a<b>>` pairs 0/4 and 2/5):
202    // each close must come before the next open, or the inter-tag text slice
203    // below would be an inverted range. Upstream reaches the same shape and
204    // throws from `String.substring`; returning the structured error is safer.
205    for (close, next_open) in stops.iter().zip(starts.iter().skip(1)) {
206        if close + stop.len() > *next_open {
207            return Err(ParseTreePatternError::DelimitersOutOfOrder {
208                pattern: pattern.to_owned(),
209            });
210        }
211    }
212
213    let slice = |from: usize, to: usize| -> String { chars[from..to].iter().collect() };
214
215    // Pass 2: collect chunks between the located delimiters.
216    let ntags = starts.len();
217    let mut chunks = Vec::new();
218    if ntags == 0 {
219        chunks.push(Chunk::Text(slice(0, chars.len())));
220    } else if starts[0] > 0 {
221        chunks.push(Chunk::Text(slice(0, starts[0])));
222    }
223    for index in 0..ntags {
224        let tag = slice(starts[index] + start.len(), stops[index]);
225        chunks.push(parse_tag(&tag, pattern)?);
226        if index + 1 < ntags {
227            chunks.push(Chunk::Text(slice(
228                stops[index] + stop.len(),
229                starts[index + 1],
230            )));
231        }
232    }
233    if ntags > 0 {
234        let after_last = stops[ntags - 1] + stop.len();
235        if after_last < chars.len() {
236            chunks.push(Chunk::Text(slice(after_last, chars.len())));
237        }
238    }
239
240    // Strip escape sequences from text chunks (tags are left untouched).
241    if !delimiters.escape.is_empty() {
242        for chunk in &mut chunks {
243            if let Chunk::Text(text) = chunk {
244                *text = strip_escape(text, &delimiters.escape);
245            }
246        }
247    }
248
249    Ok(chunks)
250}
251
252/// Removes every occurrence of `escape` from `text`, non-overlapping and
253/// left to right — the escape-stripping ANTLR does with `String.replace`.
254fn strip_escape(text: &str, escape: &str) -> String {
255    let mut out = String::with_capacity(text.len());
256    let mut rest = text;
257    while let Some(at) = rest.find(escape) {
258        out.push_str(&rest[..at]);
259        rest = &rest[at + escape.len()..];
260    }
261    out.push_str(rest);
262    out
263}
264
265/// Parses the inside of a `<...>` into a tag chunk, splitting an optional
266/// `label:` prefix. Empty tags (`<>`, `<label:>`) are rejected.
267fn parse_tag(tag: &str, pattern: &str) -> Result<Chunk, ParseTreePatternError> {
268    let (label, name) = tag.find(':').map_or((None, tag), |colon| {
269        (Some(tag[..colon].to_owned()), &tag[colon + 1..])
270    });
271    if name.is_empty() {
272        return Err(ParseTreePatternError::EmptyTag {
273            pattern: pattern.to_owned(),
274        });
275    }
276    Ok(Chunk::Tag {
277        name: name.to_owned(),
278        label,
279    })
280}
281
282/// Lexes one literal pattern chunk into the token specs it produces.
283///
284/// The matcher owns all split/tag/interpret logic; this trait is the single
285/// grammar-specific hook, supplying the real lexer's output for a run of
286/// concrete input text. The trailing EOF must be excluded; off-default-channel
287/// tokens (whitespace, comments) may be returned on their own channel and are
288/// skipped by the interpreter exactly as in a normal parse, matching ANTLR's
289/// `tokenize`. Implemented for
290/// `FnMut(&str) -> Result<Vec<TokenSpec>, ParseTreePatternError>` so a closure
291/// suffices.
292pub trait PatternLexer {
293    /// Tokenizes `text` into token specs (no EOF), each on its original channel.
294    ///
295    /// # Errors
296    ///
297    /// Returns a [`ParseTreePatternError::Tokenization`] if the lexer rejects
298    /// the chunk.
299    fn tokenize_chunk(&mut self, text: &str) -> Result<Vec<TokenSpec>, ParseTreePatternError>;
300}
301
302impl<F> PatternLexer for F
303where
304    F: FnMut(&str) -> Result<Vec<TokenSpec>, ParseTreePatternError>,
305{
306    fn tokenize_chunk(&mut self, text: &str) -> Result<Vec<TokenSpec>, ParseTreePatternError> {
307        self(text)
308    }
309}
310
311/// Runs a lexer over one chunk of pattern text and returns its token specs
312/// (every non-EOF token, on its original channel), suitable for a
313/// [`PatternLexer`].
314///
315/// This is the bridge generated parsers use to satisfy [`PatternLexer`] from
316/// their concrete lexer: `make_lexer` builds a fresh lexer over the chunk's
317/// [`InputStream`](crate::InputStream), the tokens are buffered, and each
318/// non-EOF token becomes a `TokenSpec::explicit(type, text)` carrying its
319/// channel. Like ANTLR's `tokenize`, hidden-channel tokens (whitespace,
320/// comments) are preserved on their channel so the interpreter skips them the
321/// same way it does during a normal parse. Positions are dropped because
322/// pattern trees compare by type and text, not span.
323///
324/// # Errors
325///
326/// Returns [`ParseTreePatternError::Tokenization`] if the lexer reports a
327/// tokenization error for the chunk.
328pub fn lex_pattern_chunk<L>(
329    text: &str,
330    make_lexer: impl FnOnce(crate::InputStream) -> L,
331) -> Result<Vec<TokenSpec>, ParseTreePatternError>
332where
333    L: TokenSource,
334{
335    let lexer = make_lexer(crate::InputStream::new(text));
336    let mut stream =
337        CommonTokenStream::try_new(lexer).map_err(|error| ParseTreePatternError::Tokenization {
338            message: error.to_string(),
339        })?;
340    stream.fill();
341    if let Some(error) = stream.drain_source_errors().into_iter().next() {
342        return Err(ParseTreePatternError::Tokenization {
343            message: format!("lexer error at {}:{}", error.line, error.column),
344        });
345    }
346    Ok(stream
347        .tokens()
348        .filter(|token| token.token_type() != TOKEN_EOF)
349        .map(|token| {
350            TokenSpec::explicit(token.token_type(), token.text_or_empty())
351                .with_channel(token.channel())
352        })
353        .collect())
354}
355
356/// Compiles tree patterns for one grammar.
357///
358/// Holds the grammar's rule-bypass ATN and recognizer metadata; each
359/// [`Self::compile`] call lexes the pattern's literal chunks (via the supplied
360/// [`PatternLexer`]), converts tags into synthetic tokens, and interprets the
361/// hybrid stream to build a reusable [`ParseTreePattern`].
362///
363/// Most callers reach this through
364/// the `compile_parse_tree_pattern` method on generated parsers; construct one directly to
365/// reuse the bypass ATN across many patterns or to customize delimiters.
366#[derive(Debug)]
367pub struct ParseTreePatternMatcher<'a> {
368    bypass_atn: ParserAtn,
369    data: &'a RecognizerData,
370    delimiters: Delimiters,
371}
372
373impl<'a> ParseTreePatternMatcher<'a> {
374    /// Creates a matcher for a grammar's parser ATN and recognizer metadata.
375    ///
376    /// The bypass ATN is derived from `atn` once here and reused by every
377    /// compile. `data` supplies rule and token names for resolving tags.
378    ///
379    /// # Errors
380    ///
381    /// Returns [`ParseTreePatternError::BypassAtn`] if the rule-bypass
382    /// transform of `atn` fails (e.g. an unrecognizable left-recursive
383    /// precedence prefix).
384    pub fn new(atn: &ParserAtn, data: &'a RecognizerData) -> Result<Self, ParseTreePatternError> {
385        let bypass_atn =
386            atn.with_bypass_alternatives()
387                .map_err(|error| ParseTreePatternError::BypassAtn {
388                    message: error.to_string(),
389                })?;
390        Ok(Self {
391            bypass_atn,
392            data,
393            delimiters: Delimiters::default(),
394        })
395    }
396
397    /// Overrides the tag delimiters and escape string (defaults `<`, `>`, `\`).
398    ///
399    /// Useful for grammars whose concrete syntax already uses `<...>`. Unlike
400    /// upstream, an empty `escape` is accepted and simply disables escaping
401    /// (Java's `indexOf`-based scan misbehaves on an empty escape string).
402    ///
403    /// # Errors
404    ///
405    /// Returns [`ParseTreePatternError::EmptyDelimiter`] when `start` or `stop`
406    /// is empty, mirroring upstream's `IllegalArgumentException` — an empty
407    /// delimiter would silently collapse every pattern into one text chunk.
408    pub fn set_delimiters(
409        &mut self,
410        start: impl Into<String>,
411        stop: impl Into<String>,
412        escape: impl Into<String>,
413    ) -> Result<(), ParseTreePatternError> {
414        let start = start.into();
415        let stop = stop.into();
416        if start.is_empty() {
417            return Err(ParseTreePatternError::EmptyDelimiter { which: "start" });
418        }
419        if stop.is_empty() {
420            return Err(ParseTreePatternError::EmptyDelimiter { which: "stop" });
421        }
422        self.delimiters = Delimiters {
423            start,
424            stop,
425            escape: escape.into(),
426        };
427        Ok(())
428    }
429
430    /// Compiles `pattern`, rooted at parser rule `rule_index`, into a reusable
431    /// [`ParseTreePattern`].
432    ///
433    /// `lexer` tokenizes the pattern's literal chunks; tags become synthetic
434    /// rule/token tokens, and the hybrid stream is interpreted over the bypass
435    /// ATN starting at `rule_index`.
436    ///
437    /// # Errors
438    ///
439    /// Returns a [`ParseTreePatternError`] for a malformed pattern, an unknown
440    /// rule/token tag, a lexer failure, an interpretation failure, or a pattern
441    /// the start rule does not fully consume.
442    pub fn compile(
443        &self,
444        pattern: &str,
445        rule_index: usize,
446        lexer: impl PatternLexer,
447    ) -> Result<ParseTreePattern, ParseTreePatternError> {
448        let chunks = split(pattern, &self.delimiters)?;
449        let (specs, tags_by_index) = self.tokenize(&chunks, pattern, lexer)?;
450        let tree = self.interpret(specs, &tags_by_index, rule_index, pattern)?;
451        Ok(ParseTreePattern {
452            pattern: pattern.to_owned(),
453            pattern_rule_index: rule_index,
454            tree,
455        })
456    }
457
458    /// Converts chunks into a flat token-spec list, recording which flat indices
459    /// are tags. Mirrors ANTLR's `tokenize`: upper-case tags are token
460    /// references, lower-case tags are rule references, literals are lexed.
461    fn tokenize(
462        &self,
463        chunks: &[Chunk],
464        pattern: &str,
465        mut lexer: impl PatternLexer,
466    ) -> Result<(Vec<TokenSpec>, BTreeMap<usize, TagInfo>), ParseTreePatternError> {
467        let mut specs = Vec::new();
468        let mut tags_by_index = BTreeMap::new();
469        for chunk in chunks {
470            match chunk {
471                Chunk::Tag { name, label } => {
472                    let (spec, tag) = self.tag_token(name, label.clone(), pattern)?;
473                    tags_by_index.insert(specs.len(), tag);
474                    specs.push(spec);
475                }
476                Chunk::Text(text) => {
477                    specs.extend(lexer.tokenize_chunk(text)?);
478                }
479            }
480        }
481        // An EOF-typed token (an `<EOF>` tag, or a stray EOF from a custom
482        // lexer) terminates the buffered token stream, so anything after it
483        // would be dropped before the full-consumption check could see it.
484        // A trailing EOF is legitimate for rules that end in `EOF`; anywhere
485        // else the pattern is broken and must fail loudly instead of silently
486        // truncating (upstream ANTLR silently ignores the suffix here).
487        if let Some(at) = specs
488            .iter()
489            .position(|spec| spec.token_type == TOKEN_EOF)
490            .filter(|at| at + 1 < specs.len())
491        {
492            return Err(ParseTreePatternError::Tokenization {
493                message: format!(
494                    "EOF at pattern token {at} terminates the stream; {} following token(s) \
495                     would be ignored",
496                    specs.len() - at - 1
497                ),
498            });
499        }
500        Ok((specs, tags_by_index))
501    }
502
503    /// Builds the synthetic token and tag record for one `<tag>`.
504    ///
505    /// An upper-case initial classifies a token reference (`<ID>`), a lower-case
506    /// initial a rule reference (`<expr>`). Names resolve to token types via the
507    /// vocabulary and to rule indices via the rule-name list.
508    fn tag_token(
509        &self,
510        name: &str,
511        label: Option<String>,
512        pattern: &str,
513    ) -> Result<(TokenSpec, TagInfo), ParseTreePatternError> {
514        let display = tag_display(name, label.as_deref());
515        let first = name
516            .chars()
517            .next()
518            .ok_or_else(|| ParseTreePatternError::InvalidTag {
519                tag: name.to_owned(),
520                pattern: pattern.to_owned(),
521            })?;
522        if first.is_uppercase() {
523            let token_type = self.data.vocabulary().token_type(name).ok_or_else(|| {
524                ParseTreePatternError::UnknownToken {
525                    name: name.to_owned(),
526                    pattern: pattern.to_owned(),
527                }
528            })?;
529            let spec = TokenSpec::explicit(token_type, display);
530            let tag = TagInfo {
531                kind: TagKind::Token { token_type },
532                name: name.to_owned(),
533                label,
534            };
535            Ok((spec, tag))
536        } else if first.is_lowercase() {
537            let rule_index =
538                self.rule_index(name)
539                    .ok_or_else(|| ParseTreePatternError::UnknownRule {
540                        name: name.to_owned(),
541                        pattern: pattern.to_owned(),
542                    })?;
543            // The bypass ATN owns the imaginary-type formula, so the matcher
544            // and the ATN's bypass `Atom` edges can never disagree.
545            let bypass_type = self
546                .bypass_atn
547                .bypass_token_type(rule_index)
548                .map_err(|error| ParseTreePatternError::BypassAtn {
549                    message: error.to_string(),
550                })?;
551            let spec = TokenSpec::explicit(bypass_type, display);
552            let tag = TagInfo {
553                kind: TagKind::Rule {
554                    rule_index,
555                    bypass_type,
556                },
557                name: name.to_owned(),
558                label,
559            };
560            Ok((spec, tag))
561        } else {
562            Err(ParseTreePatternError::InvalidTag {
563                tag: name.to_owned(),
564                pattern: pattern.to_owned(),
565            })
566        }
567    }
568
569    /// Resolves a parser rule name to its index (last wins, like the runtime's
570    /// other name lookups).
571    fn rule_index(&self, name: &str) -> Option<usize> {
572        self.data.rule_names().iter().rposition(|rule| rule == name)
573    }
574
575    /// Interprets the hybrid token specs over the bypass ATN, producing the
576    /// pattern tree and re-keying the tag table by the tokens' final store IDs.
577    fn interpret(
578        &self,
579        specs: Vec<TokenSpec>,
580        tags_by_index: &BTreeMap<usize, TagInfo>,
581        rule_index: usize,
582        pattern: &str,
583    ) -> Result<PatternTree, ParseTreePatternError> {
584        let trailing_eof = specs
585            .last()
586            .is_some_and(|spec| spec.token_type == TOKEN_EOF);
587        let source = PatternTokenSource { specs, index: 0 };
588        let mut parser = BaseParser::new(CommonTokenStream::new(source), self.data.clone());
589        // ANTLR installs a BailErrorStrategy for the pattern parse: a pattern
590        // the grammar only accepts through error recovery must fail loudly, not
591        // bake `<missing ...>` error nodes into the pattern tree (which would
592        // then match nothing). Recovery diagnostics are checked below; the
593        // default console listener is removed so a rejected pattern does not
594        // also print to stderr.
595        parser.remove_error_listeners();
596        let root = parser
597            .parse_atn_rule(&self.bypass_atn, rule_index)
598            .map_err(|error| ParseTreePatternError::CannotInvokeStartRule {
599                rule_index,
600                message: error.to_string(),
601            })?;
602        if parser.number_of_syntax_errors() > 0 {
603            return Err(ParseTreePatternError::CannotInvokeStartRule {
604                rule_index,
605                message: format!(
606                    "pattern is not valid for the rule: {} syntax error(s) during pattern parse",
607                    parser.number_of_syntax_errors()
608                ),
609            });
610        }
611
612        // The start rule must consume the whole pattern (ANTLR issue #413):
613        // the next visible token after the parse must be EOF.
614        if parser.token_stream().la_token(1) != TOKEN_EOF {
615            return Err(ParseTreePatternError::StartRuleDoesNotConsumeFullPattern {
616                pattern: pattern.to_owned(),
617            });
618        }
619
620        let file = parser.into_parsed_file(root);
621        // A trailing `<EOF>` tag doubles as the stream terminator, so the
622        // lookahead check above cannot tell "the rule matched EOF" from "the
623        // tag was silently ignored". Rules that end in `EOF` put an EOF
624        // terminal in the tree; require it, and reject the pattern otherwise
625        // (upstream silently drops the tag here).
626        if trailing_eof
627            && !file.tree().descendants().any(|node| {
628                node.as_terminal()
629                    .is_some_and(|terminal| terminal.symbol().token_type() == TOKEN_EOF)
630            })
631        {
632            return Err(ParseTreePatternError::StartRuleDoesNotConsumeFullPattern {
633                pattern: pattern.to_owned(),
634            });
635        }
636        let tags = rekey_tags_by_token_id(tags_by_index);
637        Ok(PatternTree { file, tags })
638    }
639}
640
641/// A token source over pre-lexed pattern specs, terminated by EOF — the runtime
642/// analog of ANTLR's `ListTokenSource`.
643#[derive(Debug)]
644struct PatternTokenSource {
645    specs: Vec<TokenSpec>,
646    index: usize,
647}
648
649impl TokenSource for PatternTokenSource {
650    fn next_token(&mut self, sink: &mut TokenSink<'_>) -> Result<TokenId, TokenStoreError> {
651        let spec = self
652            .specs
653            .get(self.index)
654            .cloned()
655            .unwrap_or_else(|| TokenSpec::eof(self.index, self.index, 1, self.index));
656        self.index += 1;
657        sink.push(spec)
658    }
659
660    fn line(&self) -> usize {
661        1
662    }
663
664    fn column(&self) -> usize {
665        self.index
666    }
667
668    fn source_name(&self) -> &'static str {
669        "tree-pattern"
670    }
671}
672
673/// Re-keys the tag table from flat spec indices to the [`TokenId`]s the tokens
674/// occupy in the finished store.
675///
676/// [`PatternTokenSource`] pushes specs in order from index 0, and
677/// `buffer_token_source` asserts each pushed token lands at its expected index,
678/// so a tag recorded at flat index `i` occupies exactly `TokenId(i)`.
679fn rekey_tags_by_token_id(tags_by_index: &BTreeMap<usize, TagInfo>) -> BTreeMap<TokenId, TagInfo> {
680    tags_by_index
681        .iter()
682        .filter_map(|(&index, tag)| Some((TokenId::try_from(index).ok()?, tag.clone())))
683        .collect()
684}
685
686/// Formats a tag for a synthetic token's text, e.g. `<expr>` or `<e:expr>`.
687fn tag_display(name: &str, label: Option<&str>) -> String {
688    label.map_or_else(|| format!("<{name}>"), |label| format!("<{label}:{name}>"))
689}
690
691/// The compiled pattern tree plus the tag side table describing its tag leaves.
692///
693/// The tree is owned as a [`ParsedFile`](crate::tree::ParsedFile); `tags` maps
694/// each tag leaf's [`TokenId`] to the rule/token it stands in for. Both are
695/// produced once by [`ParseTreePatternMatcher::compile`] and shared by every
696/// match.
697#[derive(Debug)]
698struct PatternTree {
699    file: crate::tree::ParsedFile,
700    tags: BTreeMap<TokenId, TagInfo>,
701}
702
703/// A pattern like `<ID> = <expr>;` compiled to a reusable tree.
704///
705/// Created by [`ParseTreePatternMatcher::compile`] or
706/// a generated parser's `compile_parse_tree_pattern`. Match a subject tree with
707/// [`Self::match_tree`] (full result) or [`Self::matches`] (boolean).
708#[derive(Debug)]
709pub struct ParseTreePattern {
710    pattern: String,
711    pattern_rule_index: usize,
712    tree: PatternTree,
713}
714
715impl ParseTreePattern {
716    /// The tree-pattern source string this was compiled from.
717    #[must_use]
718    pub fn pattern(&self) -> &str {
719        &self.pattern
720    }
721
722    /// The parser rule index that roots the pattern.
723    #[must_use]
724    pub const fn pattern_rule_index(&self) -> usize {
725        self.pattern_rule_index
726    }
727
728    /// The compiled pattern as a parse tree, with tags present as terminal
729    /// leaves (a rule tag is a rule node whose single child carries the
730    /// imaginary bypass token).
731    ///
732    /// Mirrors ANTLR's `ParseTreePattern.getPatternTree`; useful for inspecting
733    /// why a pattern that compiled does not match a subject —
734    /// `pattern_tree().text()` renders the tag placeholders inline.
735    #[must_use]
736    pub fn pattern_tree(&self) -> Node<'_> {
737        self.tree.file.tree()
738    }
739
740    /// Matches `tree` against this pattern, returning the full result including
741    /// bound labels and the first mismatched node (if any).
742    #[must_use]
743    pub fn match_tree<'subject>(&self, tree: Node<'subject>) -> ParseTreeMatch<'subject> {
744        let mut labels: BTreeMap<String, Vec<Node<'subject>>> = BTreeMap::new();
745        let pattern_root = self.tree.file.tree();
746        let mismatched = match_impl(tree, pattern_root, &self.tree.tags, &mut labels);
747        ParseTreeMatch {
748            tree,
749            labels,
750            mismatched_node: mismatched,
751        }
752    }
753
754    /// Returns whether `tree` matches this pattern.
755    #[must_use]
756    pub fn matches(&self, tree: Node<'_>) -> bool {
757        self.match_tree(tree).succeeded()
758    }
759
760    /// Finds nodes under `tree` with an `XPath` expression, then returns the
761    /// successful matches of this pattern against those subtrees.
762    ///
763    /// Mirrors ANTLR's `ParseTreePattern.findAll`: unsuccessful matches are
764    /// omitted, whatever the reason for the failure. `recognizer` resolves the
765    /// rule and token names in `xpath`, exactly as in
766    /// [`XPath::find_all`](crate::XPath::find_all).
767    ///
768    /// # Errors
769    ///
770    /// Returns [`XPathError`](crate::XPathError) when `xpath` is not a valid
771    /// parse-tree path expression.
772    pub fn find_all<'subject, R>(
773        &self,
774        tree: Node<'subject>,
775        xpath: &str,
776        recognizer: &R,
777    ) -> Result<Vec<ParseTreeMatch<'subject>>, crate::XPathError>
778    where
779        R: Recognizer + ?Sized,
780    {
781        Ok(crate::XPath::find_all(tree, xpath, recognizer)?
782            .into_iter()
783            .map(|subtree| self.match_tree(subtree))
784            .filter(ParseTreeMatch::succeeded)
785            .collect())
786    }
787}
788
789/// The result of matching a subject tree against a [`ParseTreePattern`].
790///
791/// Holds the label bindings discovered during the match and, on failure, the
792/// first subject node that did not match. Borrows the subject tree.
793#[derive(Clone, Debug)]
794pub struct ParseTreeMatch<'subject> {
795    tree: Node<'subject>,
796    labels: BTreeMap<String, Vec<Node<'subject>>>,
797    mismatched_node: Option<Node<'subject>>,
798}
799
800impl<'subject> ParseTreeMatch<'subject> {
801    /// Returns whether the match succeeded (no node mismatched).
802    #[must_use]
803    pub const fn succeeded(&self) -> bool {
804        self.mismatched_node.is_none()
805    }
806
807    /// The subject tree this match was computed against.
808    #[must_use]
809    pub const fn tree(&self) -> Node<'subject> {
810        self.tree
811    }
812
813    /// The first subject node that failed to match, or `None` on success.
814    #[must_use]
815    pub const fn mismatched_node(&self) -> Option<Node<'subject>> {
816        self.mismatched_node
817    }
818
819    /// The last node bound to `label`, or `None` if nothing matched it.
820    ///
821    /// Unlabeled tags `<ID>`/`<expr>` are filed under their rule/token name, so
822    /// `get("expr")` returns a node matched by `<expr>`.
823    #[must_use]
824    pub fn get(&self, label: &str) -> Option<Node<'subject>> {
825        self.labels
826            .get(label)
827            .and_then(|nodes| nodes.last().copied())
828    }
829
830    /// Every node bound to `label`, in match order.
831    #[must_use]
832    pub fn get_all(&self, label: &str) -> &[Node<'subject>] {
833        self.labels.get(label).map_or(&[], Vec::as_slice)
834    }
835
836    /// All label bindings, keyed by label name.
837    #[must_use]
838    pub const fn labels(&self) -> &BTreeMap<String, Vec<Node<'subject>>> {
839        &self.labels
840    }
841}
842
843/// Walks a subject node and a pattern node in lockstep, recording label
844/// bindings and returning the first subject node that failed to match.
845///
846/// Faithful port of ANTLR's `ParseTreePatternMatcher.matchImpl`:
847/// - two terminals match when their token types agree; if the pattern terminal
848///   is a token tag it binds, else the texts must be equal;
849/// - a rule node paired with a single-terminal rule-tag subtree binds if the
850///   rule indices agree;
851/// - otherwise two rule nodes must have equal child counts and matching
852///   children;
853/// - a shape mismatch (terminal vs rule) fails at the subject node.
854fn match_impl<'subject>(
855    tree: Node<'subject>,
856    pattern: Node<'_>,
857    tags: &BTreeMap<TokenId, TagInfo>,
858    labels: &mut BTreeMap<String, Vec<Node<'subject>>>,
859) -> Option<Node<'subject>> {
860    // Grown like the runtime's other recursive tree descents
861    // (`ParseTreeVisitor::visit_children`) so a deep subject/pattern pair
862    // cannot overflow the native stack.
863    stacker::maybe_grow(MATCH_STACK_RED_ZONE, MATCH_STACK_SIZE, || {
864        match (leaf_kind(tree), leaf_kind(pattern)) {
865            (Some(_), Some(_)) => match_terminals(tree, pattern, tags, labels),
866            (None, None) => match_rules(tree, pattern, tags, labels),
867            // One is a leaf and the other a rule: shape mismatch.
868            _ => Some(tree),
869        }
870    })
871}
872
873/// Returns the token type of a leaf (terminal or error node), or `None` for a
874/// rule node. Error nodes carry a symbol just like terminals, so they compare
875/// by token type too.
876fn leaf_kind(node: Node<'_>) -> Option<i32> {
877    match node.kind() {
878        NodeKind::Terminal => node.as_terminal().map(|t| t.symbol().token_type()),
879        NodeKind::Error => node.as_error().map(|e| e.symbol().token_type()),
880        NodeKind::Rule => None,
881    }
882}
883
884fn match_terminals<'subject>(
885    tree: Node<'subject>,
886    pattern: Node<'_>,
887    tags: &BTreeMap<TokenId, TagInfo>,
888    labels: &mut BTreeMap<String, Vec<Node<'subject>>>,
889) -> Option<Node<'subject>> {
890    let tree_type = leaf_kind(tree);
891    let pattern_type = leaf_kind(pattern);
892    if tree_type != pattern_type {
893        return Some(tree);
894    }
895    // A token tag binds; otherwise the concrete texts must be equal.
896    match pattern_token_tag(pattern, tags) {
897        Some(tag) => {
898            bind(labels, tag, tree);
899            None
900        }
901        None if leaf_text(tree) == leaf_text(pattern) => None,
902        None => Some(tree),
903    }
904}
905
906fn match_rules<'subject>(
907    tree: Node<'subject>,
908    pattern: Node<'_>,
909    tags: &BTreeMap<TokenId, TagInfo>,
910    labels: &mut BTreeMap<String, Vec<Node<'subject>>>,
911) -> Option<Node<'subject>> {
912    // `match_impl` only routes rule-kinded nodes here, so a failed view is a
913    // structural inconsistency; fail the match rather than fail open.
914    let (Some(tree_rule), Some(pattern_rule)) = (tree.as_rule(), pattern.as_rule()) else {
915        return Some(tree);
916    };
917
918    // (expr ...) matched against a `<expr>` rule-tag subtree.
919    if let Some((tag_rule_index, tag)) = rule_tag_of(pattern, tags) {
920        return if tree_rule.rule_index() == tag_rule_index {
921            bind(labels, tag, tree);
922            None
923        } else {
924            Some(tree)
925        };
926    }
927
928    if tree_rule.child_count() != pattern_rule.child_count() {
929        return Some(tree);
930    }
931    for (tree_child, pattern_child) in tree.children().zip(pattern.children()) {
932        if let Some(mismatch) = match_impl(tree_child, pattern_child, tags, labels) {
933            return Some(mismatch);
934        }
935    }
936    None
937}
938
939/// Returns the tag for a `<TOKEN>` terminal leaf, if this pattern leaf is one.
940fn pattern_token_tag<'a>(
941    pattern: Node<'_>,
942    tags: &'a BTreeMap<TokenId, TagInfo>,
943) -> Option<&'a TagInfo> {
944    let token_id = pattern.as_terminal()?.token_id();
945    let tag = tags.get(&token_id)?;
946    matches!(tag.kind, TagKind::Token { .. }).then_some(tag)
947}
948
949/// Detects a `<rule>` tag subtree — a rule node with exactly one terminal child
950/// whose symbol is a rule tag — returning the referenced rule index alongside
951/// the tag. Mirrors ANTLR's `getRuleTagToken`.
952fn rule_tag_of<'a>(
953    pattern: Node<'_>,
954    tags: &'a BTreeMap<TokenId, TagInfo>,
955) -> Option<(usize, &'a TagInfo)> {
956    let rule = pattern.as_rule()?;
957    if rule.child_count() != 1 {
958        return None;
959    }
960    let child = pattern.children().next()?;
961    let token_id = child.as_terminal()?.token_id();
962    let tag = tags.get(&token_id)?;
963    match tag.kind {
964        TagKind::Rule { rule_index, .. } => Some((rule_index, tag)),
965        TagKind::Token { .. } => None,
966    }
967}
968
969/// Files `node` under every label key the tag contributes.
970fn bind<'subject>(
971    labels: &mut BTreeMap<String, Vec<Node<'subject>>>,
972    tag: &TagInfo,
973    node: Node<'subject>,
974) {
975    for key in tag.label_keys() {
976        labels.entry(key.to_owned()).or_default().push(node);
977    }
978}
979
980/// Borrowed text of a leaf node (terminal or error), for literal comparison.
981fn leaf_text(node: Node<'_>) -> &str {
982    node.as_terminal()
983        .map(crate::tree::TerminalNodeView::text)
984        .or_else(|| node.as_error().map(crate::tree::ErrorNodeView::text))
985        .unwrap_or("")
986}
987
988#[cfg(test)]
989#[allow(clippy::disallowed_methods)] // `insta` assertion macros unwrap internal I/O.
990mod tests {
991    use super::*;
992    use crate::token::{TokenSpec, TokenStore};
993    use crate::tree::{NodeId, ParseTreeStorage, ParsedFile, ParserRuleContext};
994
995    // Fixture grammar: `stat : ID '=' expr ';' ;  expr : INT | ID ;`
996    const RULE_STAT: usize = 0;
997    const RULE_EXPR: usize = 1;
998    const ASSIGN: i32 = 1;
999    const SEMI: i32 = 2;
1000    const ID: i32 = 3;
1001    const INT: i32 = 4;
1002    // Imaginary bypass token types live above max_token_type (=4); rule 0's
1003    // would be 5, rule 1's is 6.
1004    const BYPASS_EXPR: i32 = 6;
1005
1006    // ---- chunk splitting -------------------------------------------------
1007
1008    fn split_default(pattern: &str) -> Result<Vec<Chunk>, ParseTreePatternError> {
1009        split(pattern, &Delimiters::default())
1010    }
1011
1012    #[test]
1013    fn split_interleaves_text_and_tags() {
1014        let chunks = split_default("<ID> = <expr> ;").expect("valid pattern");
1015        insta::assert_debug_snapshot!("split_interleaves_text_and_tags", chunks);
1016    }
1017
1018    #[test]
1019    fn split_parses_labeled_tags() {
1020        let chunks = split_default("<lhs:ID> = <e:expr>").expect("valid pattern");
1021        insta::assert_debug_snapshot!("split_parses_labeled_tags", chunks);
1022    }
1023
1024    #[test]
1025    fn split_strips_escapes_from_text_only() {
1026        // Escaped delimiters are literal text; the tag survives.
1027        let chunks = split_default(r"a \< b <ID> c \> d").expect("valid pattern");
1028        insta::assert_debug_snapshot!("split_strips_escapes", chunks);
1029    }
1030
1031    #[test]
1032    fn split_no_tags_is_single_text_chunk() {
1033        let chunks = split_default("a = 3 ;").expect("valid pattern");
1034        insta::assert_debug_snapshot!("split_no_tags", chunks);
1035    }
1036
1037    #[test]
1038    fn split_rejects_malformed_patterns() {
1039        let cases = ["<ID", "ID>", "><", "<>", "<a:>", "<a<b>>"];
1040        let errors: Vec<_> = cases
1041            .into_iter()
1042            .map(|pattern| {
1043                (
1044                    pattern,
1045                    split_default(pattern).expect_err("invalid").to_string(),
1046                )
1047            })
1048            .collect();
1049        insta::assert_debug_snapshot!("split_rejects_malformed", errors);
1050    }
1051
1052    #[test]
1053    fn split_honors_custom_delimiters() {
1054        let delimiters = Delimiters {
1055            start: "[[".to_owned(),
1056            stop: "]]".to_owned(),
1057            escape: "%".to_owned(),
1058        };
1059        let chunks = split("x [[expr]] y", &delimiters).expect("valid pattern");
1060        insta::assert_debug_snapshot!("split_custom_delimiters", chunks);
1061    }
1062
1063    // ---- lockstep matching against hand-built pattern trees --------------
1064
1065    /// Builds one tree node recursively, recording any tag leaves.
1066    enum Build {
1067        Rule(usize, Vec<Self>),
1068        /// A concrete terminal: token type + text.
1069        Token(i32, &'static str),
1070        /// A token tag `<name>` occupying an imaginary/real slot.
1071        TokenTag {
1072            token_type: i32,
1073            name: &'static str,
1074            label: Option<&'static str>,
1075        },
1076        /// A rule tag `<name>`: a single-terminal rule subtree.
1077        RuleTag {
1078            rule_index: usize,
1079            bypass_type: i32,
1080            name: &'static str,
1081            label: Option<&'static str>,
1082        },
1083    }
1084
1085    struct TreeFactory {
1086        tokens: TokenStore,
1087        storage: ParseTreeStorage,
1088        tags: BTreeMap<TokenId, TagInfo>,
1089    }
1090
1091    impl TreeFactory {
1092        fn new() -> Self {
1093            Self {
1094                tokens: TokenStore::new(None, "TreePattern"),
1095                storage: ParseTreeStorage::new(),
1096                tags: BTreeMap::new(),
1097            }
1098        }
1099
1100        fn push_token(&mut self, token_type: i32, text: &str) -> TokenId {
1101            self.tokens
1102                .push(TokenSpec::explicit(token_type, text))
1103                .expect("test token fits")
1104        }
1105
1106        fn build(&mut self, spec: &Build) -> NodeId {
1107            match spec {
1108                Build::Token(token_type, text) => {
1109                    let id = self.push_token(*token_type, text);
1110                    self.storage.terminal(id)
1111                }
1112                Build::TokenTag {
1113                    token_type,
1114                    name,
1115                    label,
1116                } => {
1117                    let id = self.push_token(*token_type, &format!("<{name}>"));
1118                    self.tags.insert(
1119                        id,
1120                        TagInfo {
1121                            kind: TagKind::Token {
1122                                token_type: *token_type,
1123                            },
1124                            name: (*name).to_owned(),
1125                            label: label.map(str::to_owned),
1126                        },
1127                    );
1128                    self.storage.terminal(id)
1129                }
1130                Build::RuleTag {
1131                    rule_index,
1132                    bypass_type,
1133                    name,
1134                    label,
1135                } => {
1136                    let id = self.push_token(*bypass_type, &format!("<{name}>"));
1137                    self.tags.insert(
1138                        id,
1139                        TagInfo {
1140                            kind: TagKind::Rule {
1141                                rule_index: *rule_index,
1142                                bypass_type: *bypass_type,
1143                            },
1144                            name: (*name).to_owned(),
1145                            label: label.map(str::to_owned),
1146                        },
1147                    );
1148                    // Rule tag renders as a single-terminal rule subtree.
1149                    let leaf = self.storage.terminal(id);
1150                    let mut context = ParserRuleContext::new(*rule_index, -1);
1151                    self.storage.add_child(&mut context, leaf);
1152                    self.storage.finish_rule(context)
1153                }
1154                Build::Rule(rule_index, children) => {
1155                    let child_ids: Vec<_> = children.iter().map(|c| self.build(c)).collect();
1156                    let mut context = ParserRuleContext::new(*rule_index, -1);
1157                    for child in child_ids {
1158                        self.storage.add_child(&mut context, child);
1159                    }
1160                    self.storage.finish_rule(context)
1161                }
1162            }
1163        }
1164
1165        fn into_file(self, root: NodeId) -> (ParsedFile, BTreeMap<TokenId, TagInfo>) {
1166            (ParsedFile::new(self.tokens, self.storage, root), self.tags)
1167        }
1168    }
1169
1170    /// Builds a subject tree (no tags expected).
1171    fn subject_tree(spec: &Build) -> ParsedFile {
1172        let mut factory = TreeFactory::new();
1173        let root = factory.build(spec);
1174        factory.into_file(root).0
1175    }
1176
1177    /// Builds a pattern from a spec, wrapping it as a `ParseTreePattern`.
1178    fn pattern_from(rule_index: usize, spec: &Build) -> ParseTreePattern {
1179        let mut factory = TreeFactory::new();
1180        let root = factory.build(spec);
1181        let (file, tags) = factory.into_file(root);
1182        ParseTreePattern {
1183            pattern: "<test>".to_owned(),
1184            pattern_rule_index: rule_index,
1185            tree: PatternTree { file, tags },
1186        }
1187    }
1188
1189    /// Subject `x = 3 ;` as `stat`.
1190    fn subject_x_eq_3() -> ParsedFile {
1191        subject_tree(&Build::Rule(
1192            RULE_STAT,
1193            vec![
1194                Build::Token(ID, "x"),
1195                Build::Token(ASSIGN, "="),
1196                Build::Rule(RULE_EXPR, vec![Build::Token(INT, "3")]),
1197                Build::Token(SEMI, ";"),
1198            ],
1199        ))
1200    }
1201
1202    #[test]
1203    fn matches_rule_tag_and_binds_label() {
1204        // Pattern: `<ID> = <e:expr> ;`
1205        let pattern = pattern_from(
1206            RULE_STAT,
1207            &Build::Rule(
1208                RULE_STAT,
1209                vec![
1210                    Build::TokenTag {
1211                        token_type: ID,
1212                        name: "ID",
1213                        label: None,
1214                    },
1215                    Build::Token(ASSIGN, "="),
1216                    Build::RuleTag {
1217                        rule_index: RULE_EXPR,
1218                        bypass_type: BYPASS_EXPR,
1219                        name: "expr",
1220                        label: Some("e"),
1221                    },
1222                    Build::Token(SEMI, ";"),
1223                ],
1224            ),
1225        );
1226        let subject = subject_x_eq_3();
1227        let result = pattern.match_tree(subject.tree());
1228
1229        assert!(result.succeeded(), "pattern should match");
1230        // Unlabeled <ID> files under "ID"; labeled <e:expr> under both "e" and "expr".
1231        assert_eq!(result.get("ID").map(Node::text), Some("x".to_owned()));
1232        assert_eq!(result.get("e").map(Node::text), Some("3".to_owned()));
1233        assert_eq!(result.get("expr").map(Node::text), Some("3".to_owned()));
1234        assert!(result.get("absent").is_none());
1235    }
1236
1237    #[test]
1238    fn literal_mismatch_reports_first_bad_node() {
1239        // Pattern requires the identifier to be exactly `y`, subject has `x`.
1240        let pattern = pattern_from(
1241            RULE_STAT,
1242            &Build::Rule(
1243                RULE_STAT,
1244                vec![
1245                    Build::Token(ID, "y"),
1246                    Build::Token(ASSIGN, "="),
1247                    Build::RuleTag {
1248                        rule_index: RULE_EXPR,
1249                        bypass_type: BYPASS_EXPR,
1250                        name: "expr",
1251                        label: None,
1252                    },
1253                    Build::Token(SEMI, ";"),
1254                ],
1255            ),
1256        );
1257        let subject = subject_x_eq_3();
1258        let result = pattern.match_tree(subject.tree());
1259
1260        assert!(!result.succeeded());
1261        assert_eq!(
1262            result.mismatched_node().map(Node::text),
1263            Some("x".to_owned())
1264        );
1265    }
1266
1267    #[test]
1268    fn child_count_mismatch_fails_at_rule() {
1269        // Pattern `stat` with only 3 children vs subject's 4.
1270        let pattern = pattern_from(
1271            RULE_STAT,
1272            &Build::Rule(
1273                RULE_STAT,
1274                vec![
1275                    Build::TokenTag {
1276                        token_type: ID,
1277                        name: "ID",
1278                        label: None,
1279                    },
1280                    Build::Token(ASSIGN, "="),
1281                    Build::RuleTag {
1282                        rule_index: RULE_EXPR,
1283                        bypass_type: BYPASS_EXPR,
1284                        name: "expr",
1285                        label: None,
1286                    },
1287                ],
1288            ),
1289        );
1290        let subject = subject_x_eq_3();
1291        let result = pattern.match_tree(subject.tree());
1292
1293        assert!(!result.succeeded());
1294        // The whole stat node mismatches on arity.
1295        assert!(result.mismatched_node().and_then(Node::as_rule).is_some());
1296    }
1297
1298    #[test]
1299    fn rule_tag_type_mismatch_fails() {
1300        // A `<expr>` rule tag positioned where the subject has a `stat`.
1301        let pattern = pattern_from(
1302            RULE_STAT,
1303            &Build::RuleTag {
1304                rule_index: RULE_EXPR,
1305                bypass_type: BYPASS_EXPR,
1306                name: "expr",
1307                label: None,
1308            },
1309        );
1310        let subject = subject_x_eq_3(); // root is stat, not expr
1311        let result = pattern.match_tree(subject.tree());
1312        assert!(!result.succeeded());
1313    }
1314
1315    #[test]
1316    fn get_all_returns_every_binding_in_order() {
1317        // Pattern `expr expr` (two INT tags) against subject with two exprs.
1318        let pattern = pattern_from(
1319            RULE_STAT,
1320            &Build::Rule(
1321                RULE_STAT,
1322                vec![
1323                    Build::RuleTag {
1324                        rule_index: RULE_EXPR,
1325                        bypass_type: BYPASS_EXPR,
1326                        name: "expr",
1327                        label: Some("operand"),
1328                    },
1329                    Build::RuleTag {
1330                        rule_index: RULE_EXPR,
1331                        bypass_type: BYPASS_EXPR,
1332                        name: "expr",
1333                        label: Some("operand"),
1334                    },
1335                ],
1336            ),
1337        );
1338        let subject = subject_tree(&Build::Rule(
1339            RULE_STAT,
1340            vec![
1341                Build::Rule(RULE_EXPR, vec![Build::Token(INT, "1")]),
1342                Build::Rule(RULE_EXPR, vec![Build::Token(INT, "2")]),
1343            ],
1344        ));
1345        let result = pattern.match_tree(subject.tree());
1346
1347        assert!(result.succeeded());
1348        let operands: Vec<_> = result.get_all("operand").iter().map(|n| n.text()).collect();
1349        assert_eq!(operands, vec!["1".to_owned(), "2".to_owned()]);
1350        // Unlabeled rule name "expr" also collects both.
1351        assert_eq!(result.get_all("expr").len(), 2);
1352    }
1353
1354    // ---- end-to-end compile() against a real ATN ------------------------
1355
1356    use crate::atn::AtnStateKind;
1357    use crate::atn::parser_atn::{ParserAtn, ParserAtnBuilder, ParserTransitionSpec};
1358    use crate::vocabulary::Vocabulary;
1359
1360    /// Real parser ATN for `stat : ID '=' expr ';' ;  expr : INT | ID ;`.
1361    ///
1362    /// Hand-built rather than generated so the test stays self-contained; the
1363    /// shape (rule start/stop, a two-alt block in `expr`, a rule-call from
1364    /// `stat`) exercises the bypass transform on genuine grammar structure.
1365    fn stat_expr_atn() -> ParserAtn {
1366        let mut atn = ParserAtnBuilder::new(4);
1367        // States: stat = 0..=6, expr = 7..=12.
1368        for (number, kind, rule) in [
1369            (0, AtnStateKind::RuleStart, 0),  // stat start
1370            (1, AtnStateKind::Basic, 0),      // after ID
1371            (2, AtnStateKind::Basic, 0),      // after '='
1372            (3, AtnStateKind::Basic, 0),      // after expr
1373            (4, AtnStateKind::RuleStop, 0),   // stat stop
1374            (5, AtnStateKind::RuleStart, 1),  // expr start
1375            (6, AtnStateKind::BlockStart, 1), // expr decision
1376            (7, AtnStateKind::Basic, 1),      // INT alt
1377            (8, AtnStateKind::Basic, 1),      // ID alt
1378            (9, AtnStateKind::BlockEnd, 1),   // expr block end
1379            (10, AtnStateKind::RuleStop, 1),  // expr stop
1380        ] {
1381            assert_eq!(
1382                atn.add_state(kind, Some(rule)).expect("state").index(),
1383                number
1384            );
1385        }
1386        atn.set_rule_to_start_state(vec![0, 5]).expect("starts");
1387        atn.set_rule_to_stop_state(vec![4, 10]).expect("stops");
1388        atn.set_end_state(6, 9).expect("expr block end");
1389        atn.add_decision_state(6).expect("decision");
1390
1391        // stat : ID '=' expr ';' ;
1392        atn.add_transition(
1393            0,
1394            ParserTransitionSpec::Atom {
1395                target: 1,
1396                label: ID,
1397            },
1398        )
1399        .expect("edge");
1400        atn.add_transition(
1401            1,
1402            ParserTransitionSpec::Atom {
1403                target: 2,
1404                label: ASSIGN,
1405            },
1406        )
1407        .expect("edge");
1408        atn.add_transition(
1409            2,
1410            ParserTransitionSpec::Rule {
1411                target: 5,
1412                rule_index: 1,
1413                follow_state: 3,
1414                precedence: 0,
1415            },
1416        )
1417        .expect("edge");
1418        atn.add_transition(
1419            3,
1420            ParserTransitionSpec::Atom {
1421                target: 4,
1422                label: SEMI,
1423            },
1424        )
1425        .expect("edge");
1426        // Synthetic rule-return edge (expr stop -> stat follow), as a packed ATN
1427        // would already contain.
1428        atn.add_transition(10, ParserTransitionSpec::Epsilon { target: 3 })
1429            .expect("edge");
1430
1431        // expr : INT | ID ;
1432        atn.add_transition(5, ParserTransitionSpec::Epsilon { target: 6 })
1433            .expect("edge");
1434        atn.add_transition(6, ParserTransitionSpec::Epsilon { target: 7 })
1435            .expect("edge");
1436        atn.add_transition(6, ParserTransitionSpec::Epsilon { target: 8 })
1437            .expect("edge");
1438        atn.add_transition(
1439            7,
1440            ParserTransitionSpec::Atom {
1441                target: 9,
1442                label: INT,
1443            },
1444        )
1445        .expect("edge");
1446        atn.add_transition(
1447            8,
1448            ParserTransitionSpec::Atom {
1449                target: 9,
1450                label: ID,
1451            },
1452        )
1453        .expect("edge");
1454        atn.add_transition(9, ParserTransitionSpec::Epsilon { target: 10 })
1455            .expect("edge");
1456
1457        atn.finish().expect("valid stat/expr ATN")
1458    }
1459
1460    fn stat_expr_data() -> RecognizerData {
1461        RecognizerData::new(
1462            "StatExpr.g4",
1463            Vocabulary::new(
1464                [None, Some("'='"), Some("';'"), None, None],
1465                [None, Some("ASSIGN"), Some("SEMI"), Some("ID"), Some("INT")],
1466                [None::<&str>, None],
1467            ),
1468        )
1469        .with_rule_names(["stat", "expr"])
1470    }
1471
1472    /// A minimal whitespace-splitting chunk lexer for the fixture grammar.
1473    ///
1474    /// Stands in for a real generated lexer: it turns each whitespace-delimited
1475    /// word of a literal chunk into a token spec, classifying identifiers,
1476    /// integers, and the two punctuation tokens.
1477    fn stat_expr_chunk_lexer(text: &str) -> Result<Vec<TokenSpec>, ParseTreePatternError> {
1478        let mut specs = Vec::new();
1479        for word in text.split_whitespace() {
1480            let token_type = match word {
1481                "=" => ASSIGN,
1482                ";" => SEMI,
1483                _ if word.chars().all(|c| c.is_ascii_digit()) => INT,
1484                _ if word.chars().all(|c| c.is_ascii_alphanumeric()) => ID,
1485                other => {
1486                    return Err(ParseTreePatternError::Tokenization {
1487                        message: format!("unexpected chunk word {other:?}"),
1488                    });
1489                }
1490            };
1491            specs.push(TokenSpec::explicit(token_type, word));
1492        }
1493        Ok(specs)
1494    }
1495
1496    fn stat_expr_matcher_and_data() -> (ParserAtn, RecognizerData) {
1497        (stat_expr_atn(), stat_expr_data())
1498    }
1499
1500    #[test]
1501    fn compile_and_match_full_pattern() {
1502        let (atn, data) = stat_expr_matcher_and_data();
1503        let matcher = ParseTreePatternMatcher::new(&atn, &data).expect("matcher");
1504        let pattern = matcher
1505            .compile("<ID> = <e:expr> ;", RULE_STAT, stat_expr_chunk_lexer)
1506            .expect("compiles");
1507
1508        // Subject `x = 3 ;` parsed by the same ATN (no tags).
1509        let mut parser = BaseParser::new(
1510            CommonTokenStream::new(stat_expr_subject("x = 3 ;")),
1511            data.clone(),
1512        );
1513        let root = parser
1514            .parse_atn_rule(&atn, RULE_STAT)
1515            .expect("subject parse");
1516        let subject = parser.into_parsed_file(root);
1517
1518        let result = pattern.match_tree(subject.tree());
1519        assert!(result.succeeded(), "pattern should match `x = 3 ;`");
1520        assert_eq!(result.get("ID").map(Node::text), Some("x".to_owned()));
1521        assert_eq!(result.get("e").map(Node::text), Some("3".to_owned()));
1522    }
1523
1524    #[test]
1525    fn compile_rejects_patterns_that_only_parse_via_recovery() {
1526        // Upstream installs a BailErrorStrategy for the pattern parse. Without
1527        // the syntax-error gate these all "compile" by error recovery, baking
1528        // `<missing ...>` error nodes into pattern trees that then match
1529        // nothing: missing '=', missing expr, missing leading ID.
1530        let (atn, data) = stat_expr_matcher_and_data();
1531        let matcher = ParseTreePatternMatcher::new(&atn, &data).expect("matcher");
1532        for pattern in ["<ID> <e:expr> ;", "<ID> = ;", "= <expr> ;", "x 3 ;"] {
1533            let error = matcher
1534                .compile(pattern, RULE_STAT, stat_expr_chunk_lexer)
1535                .expect_err("recovered pattern parse must be rejected");
1536            assert!(
1537                matches!(error, ParseTreePatternError::CannotInvokeStartRule { .. }),
1538                "unexpected error for {pattern:?}: {error}"
1539            );
1540        }
1541    }
1542
1543    #[test]
1544    fn split_rejects_overlapping_tags_without_panicking() {
1545        // `<a<b>>` pairs starts [0, 2] with stops [4, 5]; the inter-tag text
1546        // slice would be inverted (5..2). Must surface as an error, not a panic.
1547        let error = split_default("<a<b>>").expect_err("overlapping tags");
1548        assert!(matches!(
1549            error,
1550            ParseTreePatternError::DelimitersOutOfOrder { .. }
1551        ));
1552    }
1553
1554    #[test]
1555    fn compile_rejects_tokens_after_an_eof_tag() {
1556        // An EOF-typed token terminates the buffered stream, so a suffix after
1557        // `<EOF>` would silently vanish before the full-consumption check.
1558        let (atn, data) = stat_expr_matcher_and_data();
1559        let matcher = ParseTreePatternMatcher::new(&atn, &data).expect("matcher");
1560        let error = matcher
1561            .compile(
1562                "<ID> = <expr> ; <EOF> garbage",
1563                RULE_STAT,
1564                stat_expr_chunk_lexer,
1565            )
1566            .expect_err("tokens after an EOF tag must be rejected");
1567        assert!(
1568            matches!(error, ParseTreePatternError::Tokenization { .. }),
1569            "unexpected error: {error}"
1570        );
1571    }
1572
1573    #[test]
1574    fn compile_rejects_unconsumed_trailing_eof_tag() {
1575        // `stat` does not end in EOF, so a trailing `<EOF>` tag can never be
1576        // consumed by the rule — it only terminates the token stream, and the
1577        // resulting tree is identical to the pattern without the tag. That
1578        // must be an error, not a silently dropped tag.
1579        let (atn, data) = stat_expr_matcher_and_data();
1580        let matcher = ParseTreePatternMatcher::new(&atn, &data).expect("matcher");
1581        let error = matcher
1582            .compile("<ID> = <expr> ; <EOF>", RULE_STAT, stat_expr_chunk_lexer)
1583            .expect_err("unconsumed trailing EOF tag must be rejected");
1584        assert!(
1585            matches!(
1586                error,
1587                ParseTreePatternError::StartRuleDoesNotConsumeFullPattern { .. }
1588            ),
1589            "unexpected error: {error}"
1590        );
1591    }
1592
1593    #[test]
1594    fn compile_rejects_partial_pattern() {
1595        // ANTLR issue #413: the start rule must consume the whole pattern. A
1596        // pattern that stops short of `;` leaves an unconsumed token.
1597        let (atn, data) = stat_expr_matcher_and_data();
1598        let matcher = ParseTreePatternMatcher::new(&atn, &data).expect("matcher");
1599        let error = matcher
1600            .compile("<ID> = <expr> ; extra", RULE_STAT, stat_expr_chunk_lexer)
1601            .expect_err("trailing token should be rejected");
1602        assert!(
1603            matches!(
1604                error,
1605                ParseTreePatternError::StartRuleDoesNotConsumeFullPattern { .. }
1606            ),
1607            "unexpected error: {error}"
1608        );
1609    }
1610
1611    #[test]
1612    fn compile_rejects_unknown_tag_names() {
1613        let (atn, data) = stat_expr_matcher_and_data();
1614        let matcher = ParseTreePatternMatcher::new(&atn, &data).expect("matcher");
1615        let unknown_token = matcher
1616            .compile("<NOPE> = <expr> ;", RULE_STAT, stat_expr_chunk_lexer)
1617            .expect_err("unknown token tag");
1618        assert!(matches!(
1619            unknown_token,
1620            ParseTreePatternError::UnknownToken { .. }
1621        ));
1622        let unknown_rule = matcher
1623            .compile("<ID> = <nope> ;", RULE_STAT, stat_expr_chunk_lexer)
1624            .expect_err("unknown rule tag");
1625        assert!(matches!(
1626            unknown_rule,
1627            ParseTreePatternError::UnknownRule { .. }
1628        ));
1629    }
1630
1631    #[test]
1632    fn set_delimiters_validates_and_switches_tag_syntax() {
1633        let (atn, data) = stat_expr_matcher_and_data();
1634        let mut matcher = ParseTreePatternMatcher::new(&atn, &data).expect("matcher");
1635
1636        // Empty start/stop are rejected like upstream's IllegalArgumentException.
1637        assert!(matches!(
1638            matcher.set_delimiters("", ">", "\\"),
1639            Err(ParseTreePatternError::EmptyDelimiter { which: "start" })
1640        ));
1641        assert!(matches!(
1642            matcher.set_delimiters("<", "", "\\"),
1643            Err(ParseTreePatternError::EmptyDelimiter { which: "stop" })
1644        ));
1645
1646        // Custom delimiters compile end-to-end; the old `<...>` is now literal
1647        // text the chunk lexer rejects.
1648        matcher
1649            .set_delimiters("[[", "]]", "%")
1650            .expect("valid delimiters");
1651        matcher
1652            .compile("[[ID]] = [[e:expr]] ;", RULE_STAT, stat_expr_chunk_lexer)
1653            .expect("custom-delimiter pattern compiles");
1654        matcher
1655            .compile("<ID> = <expr> ;", RULE_STAT, stat_expr_chunk_lexer)
1656            .expect_err("old delimiters are literal text now");
1657    }
1658
1659    #[test]
1660    fn compiled_pattern_does_not_match_different_structure() {
1661        let (atn, data) = stat_expr_matcher_and_data();
1662        let matcher = ParseTreePatternMatcher::new(&atn, &data).expect("matcher");
1663        // Pattern requires the literal identifier `y`.
1664        let pattern = matcher
1665            .compile("y = <expr> ;", RULE_STAT, stat_expr_chunk_lexer)
1666            .expect("compiles");
1667
1668        let mut parser = BaseParser::new(
1669            CommonTokenStream::new(stat_expr_subject("x = 3 ;")),
1670            data.clone(),
1671        );
1672        let root = parser
1673            .parse_atn_rule(&atn, RULE_STAT)
1674            .expect("subject parse");
1675        let subject = parser.into_parsed_file(root);
1676
1677        let result = pattern.match_tree(subject.tree());
1678        assert!(
1679            !result.succeeded(),
1680            "identifier `x` should not match literal `y`"
1681        );
1682    }
1683
1684    /// Subject-side token source: lexes a whole input string like the chunk
1685    /// lexer, then appends EOF.
1686    fn stat_expr_subject(input: &str) -> PatternTokenSource {
1687        let specs = stat_expr_chunk_lexer(input).expect("valid subject input");
1688        PatternTokenSource { specs, index: 0 }
1689    }
1690
1691    #[derive(Debug)]
1692    struct StatExprRecognizer {
1693        data: RecognizerData,
1694    }
1695
1696    impl Recognizer for StatExprRecognizer {
1697        fn data(&self) -> &RecognizerData {
1698            &self.data
1699        }
1700
1701        fn data_mut(&mut self) -> &mut RecognizerData {
1702            &mut self.data
1703        }
1704    }
1705
1706    #[test]
1707    fn find_all_pairs_xpath_selection_with_pattern_matching() {
1708        let (atn, data) = stat_expr_matcher_and_data();
1709        let matcher = ParseTreePatternMatcher::new(&atn, &data).expect("matcher");
1710        // Matches only integer expressions.
1711        let pattern = matcher
1712            .compile("<INT>", RULE_EXPR, stat_expr_chunk_lexer)
1713            .expect("compiles");
1714
1715        let mut parser = BaseParser::new(
1716            CommonTokenStream::new(stat_expr_subject("x = 3 ;")),
1717            data.clone(),
1718        );
1719        let root = parser
1720            .parse_atn_rule(&atn, RULE_STAT)
1721            .expect("subject parse");
1722        let subject = parser.into_parsed_file(root);
1723        let recognizer = StatExprRecognizer { data };
1724
1725        // `//expr` selects the one expr subtree; the `<INT>` pattern matches it.
1726        let matches = pattern
1727            .find_all(subject.tree(), "//expr", &recognizer)
1728            .expect("valid xpath");
1729        assert_eq!(matches.len(), 1);
1730        assert_eq!(matches[0].tree().text(), "3");
1731        // A path selecting nothing that matches yields no results.
1732        let none = pattern
1733            .find_all(subject.tree(), "//stat", &recognizer)
1734            .expect("valid xpath");
1735        assert!(none.is_empty());
1736    }
1737}