Skip to main content

antlr4_runtime/
tree_pattern.rs

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