1use 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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
38enum TagKind {
39 Rule { rule_index: usize, bypass_type: i32 },
43 Token { token_type: i32 },
45}
46
47#[derive(Clone, Debug, Eq, PartialEq)]
55struct TagInfo {
56 kind: TagKind,
57 name: String,
59 label: Option<String>,
61}
62
63impl TagInfo {
64 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#[derive(Clone, Debug, Eq, PartialEq)]
75enum Chunk {
76 Text(String),
78 Tag { name: String, label: Option<String> },
80}
81
82#[derive(Clone, Debug, Eq, Error, PartialEq)]
84pub enum ParseTreePatternError {
85 #[error("unterminated tag in pattern: {pattern}")]
87 UnterminatedTag { pattern: String },
88 #[error("missing start tag in pattern: {pattern}")]
90 MissingStartTag { pattern: String },
91 #[error("tag delimiters out of order in pattern: {pattern}")]
93 DelimitersOutOfOrder { pattern: String },
94 #[error("empty tag in pattern: {pattern}")]
96 EmptyTag { pattern: String },
97 #[error("unknown token {name} in pattern: {pattern}")]
99 UnknownToken { name: String, pattern: String },
100 #[error("unknown rule {name} in pattern: {pattern}")]
102 UnknownRule { name: String, pattern: String },
103 #[error("invalid tag {tag} in pattern: {pattern}")]
106 InvalidTag { tag: String, pattern: String },
107 #[error("could not tokenize pattern chunk: {message}")]
109 Tokenization { message: String },
110 #[error("start rule did not consume the full pattern: {pattern}")]
112 StartRuleDoesNotConsumeFullPattern { pattern: String },
113 #[error("could not interpret pattern as rule {rule_index}: {message}")]
115 CannotInvokeStartRule { rule_index: usize, message: String },
116 #[error("could not build rule-bypass ATN: {message}")]
118 BypassAtn { message: String },
119 #[error("{which} delimiter cannot be empty")]
122 EmptyDelimiter { which: &'static str },
123}
124
125#[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
147fn 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 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 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 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 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
252fn 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
265fn 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
282pub trait PatternLexer {
293 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
311pub 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#[derive(Debug)]
367pub struct ParseTreePatternMatcher<'a> {
368 bypass_atn: ParserAtn,
369 data: &'a RecognizerData,
370 delimiters: Delimiters,
371}
372
373impl<'a> ParseTreePatternMatcher<'a> {
374 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 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 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 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 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 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 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 fn rule_index(&self, name: &str) -> Option<usize> {
572 self.data.rule_names().iter().rposition(|rule| rule == name)
573 }
574
575 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 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 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 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#[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
673fn 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
686fn tag_display(name: &str, label: Option<&str>) -> String {
688 label.map_or_else(|| format!("<{name}>"), |label| format!("<{label}:{name}>"))
689}
690
691#[derive(Debug)]
698struct PatternTree {
699 file: crate::tree::ParsedFile,
700 tags: BTreeMap<TokenId, TagInfo>,
701}
702
703#[derive(Debug)]
709pub struct ParseTreePattern {
710 pattern: String,
711 pattern_rule_index: usize,
712 tree: PatternTree,
713}
714
715impl ParseTreePattern {
716 #[must_use]
718 pub fn pattern(&self) -> &str {
719 &self.pattern
720 }
721
722 #[must_use]
724 pub const fn pattern_rule_index(&self) -> usize {
725 self.pattern_rule_index
726 }
727
728 #[must_use]
736 pub fn pattern_tree(&self) -> Node<'_> {
737 self.tree.file.tree()
738 }
739
740 #[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 #[must_use]
756 pub fn matches(&self, tree: Node<'_>) -> bool {
757 self.match_tree(tree).succeeded()
758 }
759
760 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#[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 #[must_use]
803 pub const fn succeeded(&self) -> bool {
804 self.mismatched_node.is_none()
805 }
806
807 #[must_use]
809 pub const fn tree(&self) -> Node<'subject> {
810 self.tree
811 }
812
813 #[must_use]
815 pub const fn mismatched_node(&self) -> Option<Node<'subject>> {
816 self.mismatched_node
817 }
818
819 #[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 #[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 #[must_use]
838 pub const fn labels(&self) -> &BTreeMap<String, Vec<Node<'subject>>> {
839 &self.labels
840 }
841}
842
843fn 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 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 _ => Some(tree),
869 }
870 })
871}
872
873fn 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 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 let (Some(tree_rule), Some(pattern_rule)) = (tree.as_rule(), pattern.as_rule()) else {
915 return Some(tree);
916 };
917
918 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
939fn 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
949fn 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
969fn 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
980fn 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)] mod tests {
991 use super::*;
992 use crate::token::{TokenSpec, TokenStore};
993 use crate::tree::{NodeId, ParseTreeStorage, ParsedFile, ParserRuleContext};
994
995 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 const BYPASS_EXPR: i32 = 6;
1005
1006 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 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 enum Build {
1067 Rule(usize, Vec<Self>),
1068 Token(i32, &'static str),
1070 TokenTag {
1072 token_type: i32,
1073 name: &'static str,
1074 label: Option<&'static str>,
1075 },
1076 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 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 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 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 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 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 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 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 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 assert!(result.mismatched_node().and_then(Node::as_rule).is_some());
1296 }
1297
1298 #[test]
1299 fn rule_tag_type_mismatch_fails() {
1300 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(); 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 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 assert_eq!(result.get_all("expr").len(), 2);
1352 }
1353
1354 use crate::atn::AtnStateKind;
1357 use crate::atn::parser_atn::{ParserAtn, ParserAtnBuilder, ParserTransitionSpec};
1358 use crate::vocabulary::Vocabulary;
1359
1360 fn stat_expr_atn() -> ParserAtn {
1366 let mut atn = ParserAtnBuilder::new(4);
1367 for (number, kind, rule) in [
1369 (0, AtnStateKind::RuleStart, 0), (1, AtnStateKind::Basic, 0), (2, AtnStateKind::Basic, 0), (3, AtnStateKind::Basic, 0), (4, AtnStateKind::RuleStop, 0), (5, AtnStateKind::RuleStart, 1), (6, AtnStateKind::BlockStart, 1), (7, AtnStateKind::Basic, 1), (8, AtnStateKind::Basic, 1), (9, AtnStateKind::BlockEnd, 1), (10, AtnStateKind::RuleStop, 1), ] {
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 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 atn.add_transition(10, ParserTransitionSpec::Epsilon { target: 3 })
1429 .expect("edge");
1430
1431 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 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 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 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 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 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 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 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 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 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 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 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 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 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 let none = pattern
1733 .find_all(subject.tree(), "//stat", &recognizer)
1734 .expect("valid xpath");
1735 assert!(none.is_empty());
1736 }
1737}