Skip to main content

meta_language/
query.rs

1use std::collections::HashMap;
2use std::fmt;
3
4mod parse;
5
6use crate::link_network::{Link, LinkId, LinkNetwork, LinkType};
7use parse::{QueryParser, tokenize};
8
9/// Structural query over links.
10#[derive(Clone, Debug, Default, PartialEq, Eq)]
11pub struct LinkQuery {
12    link_type: Option<LinkType>,
13    term: Option<String>,
14    language: Option<String>,
15    named: Option<bool>,
16    pattern: Option<QueryPattern>,
17    pattern_source: Option<String>,
18    predicates: Vec<QueryPredicate>,
19}
20
21impl LinkQuery {
22    /// Creates an empty query that matches every link.
23    #[must_use]
24    pub fn new() -> Self {
25        Self::default()
26    }
27
28    /// Creates a query restricted to a link type.
29    #[must_use]
30    pub fn by_type(link_type: LinkType) -> Self {
31        Self::new().with_link_type(link_type)
32    }
33
34    /// Parses a tree-sitter-query-like S-expression query.
35    ///
36    /// The structural engine binds captures and leaves predicate evaluation to
37    /// a caller-provided [`QueryPredicateHost`].
38    pub fn from_sexpression(source: &str) -> Result<Self, QueryParseError> {
39        let tokens = tokenize(source)?;
40        let mut parser = QueryParser::new(tokens);
41        let (pattern, predicates) = parser.parse()?;
42        Ok(Self {
43            pattern: Some(pattern),
44            pattern_source: Some(source.to_string()),
45            predicates,
46            ..Self::default()
47        })
48    }
49
50    /// Restricts matches to a link type.
51    #[must_use]
52    pub const fn with_link_type(mut self, link_type: LinkType) -> Self {
53        self.link_type = Some(link_type);
54        self
55    }
56
57    /// Restricts matches to a term.
58    #[must_use]
59    pub fn with_term(mut self, term: impl Into<String>) -> Self {
60        self.term = Some(term.into());
61        self
62    }
63
64    /// Restricts matches to a language label.
65    #[must_use]
66    pub fn with_language(mut self, language: impl Into<String>) -> Self {
67        self.language = Some(language.into());
68        self
69    }
70
71    /// Restricts matches by the named flag.
72    #[must_use]
73    pub const fn with_named(mut self, named: bool) -> Self {
74        self.named = Some(named);
75        self
76    }
77
78    pub(crate) fn matches_in_network(
79        &self,
80        network: &LinkNetwork,
81        link: &Link,
82        predicate_host: &impl QueryPredicateHost,
83    ) -> Vec<QueryMatch> {
84        if !self.matches_metadata(link) {
85            return Vec::new();
86        }
87
88        let captures = self.pattern.as_ref().map_or_else(
89            || vec![QueryCaptures::default()],
90            |pattern| pattern.match_root(network, link.id()),
91        );
92
93        captures
94            .into_iter()
95            .filter(|captures| {
96                self.predicates
97                    .iter()
98                    .all(|predicate| predicate_host.evaluate(predicate, captures, network))
99            })
100            .map(|captures| QueryMatch::new(link.id(), captures))
101            .collect()
102    }
103
104    fn matches_metadata(&self, link: &Link) -> bool {
105        let metadata = link.metadata();
106        self.link_type
107            .is_none_or(|link_type| metadata.link_type() == Some(link_type))
108            && self
109                .term
110                .as_deref()
111                .is_none_or(|term| metadata.term() == Some(term))
112            && self
113                .language
114                .as_deref()
115                .is_none_or(|language| metadata.language() == Some(language))
116            && self.named.is_none_or(|named| metadata.is_named() == named)
117    }
118
119    pub(crate) const fn link_type_filter(&self) -> Option<LinkType> {
120        self.link_type
121    }
122
123    pub(crate) fn term_filter(&self) -> Option<&str> {
124        self.term.as_deref()
125    }
126
127    pub(crate) fn language_filter(&self) -> Option<&str> {
128        self.language.as_deref()
129    }
130
131    pub(crate) const fn named_filter(&self) -> Option<bool> {
132        self.named
133    }
134
135    pub(crate) fn pattern_source(&self) -> Option<&str> {
136        self.pattern_source.as_deref()
137    }
138}
139
140/// A structural query match and its capture bindings.
141#[derive(Clone, Debug, PartialEq, Eq)]
142pub struct QueryMatch {
143    link_id: LinkId,
144    captures: QueryCaptures,
145}
146
147impl QueryMatch {
148    const fn new(link_id: LinkId, captures: QueryCaptures) -> Self {
149        Self { link_id, captures }
150    }
151
152    /// Link selected by the query root pattern.
153    #[must_use]
154    pub const fn link_id(&self) -> LinkId {
155        self.link_id
156    }
157
158    /// Captures bound while matching this result.
159    #[must_use]
160    pub const fn captures(&self) -> &QueryCaptures {
161        &self.captures
162    }
163}
164
165/// Ordered capture bindings for a query match.
166#[derive(Clone, Debug, Default, PartialEq, Eq)]
167pub struct QueryCaptures {
168    values: Vec<QueryCapture>,
169}
170
171impl QueryCaptures {
172    fn with_capture(mut self, name: &str, link_id: LinkId) -> Self {
173        self.values.push(QueryCapture {
174            name: name.to_string(),
175            link_id,
176        });
177        self
178    }
179
180    /// Returns the first link bound to a capture name.
181    #[must_use]
182    pub fn first(&self, name: &str) -> Option<LinkId> {
183        self.values
184            .iter()
185            .find(|capture| capture.name == name)
186            .map(|capture| capture.link_id)
187    }
188
189    /// Iterates capture bindings in match order.
190    pub fn iter(&self) -> impl Iterator<Item = &QueryCapture> {
191        self.values.iter()
192    }
193}
194
195/// One capture binding.
196#[derive(Clone, Debug, PartialEq, Eq)]
197pub struct QueryCapture {
198    name: String,
199    link_id: LinkId,
200}
201
202impl QueryCapture {
203    /// Capture name without the leading `@`.
204    #[must_use]
205    pub fn name(&self) -> &str {
206        &self.name
207    }
208
209    /// Link bound to this capture.
210    #[must_use]
211    pub const fn link_id(&self) -> LinkId {
212        self.link_id
213    }
214}
215
216/// Host hook for evaluating text, regex, semantic, or other predicates.
217pub trait QueryPredicateHost {
218    /// Returns whether a predicate accepts the current capture set.
219    fn evaluate(
220        &self,
221        predicate: &QueryPredicate,
222        captures: &QueryCaptures,
223        network: &LinkNetwork,
224    ) -> bool;
225}
226
227/// Predicate host that rejects every predicate: the host of a query run
228/// without one, as the JavaScript `rejectPredicateHost`.
229#[derive(Clone, Copy, Debug, Default)]
230pub struct RejectPredicateHost;
231
232impl QueryPredicateHost for RejectPredicateHost {
233    fn evaluate(
234        &self,
235        _predicate: &QueryPredicate,
236        _captures: &QueryCaptures,
237        _network: &LinkNetwork,
238    ) -> bool {
239        false
240    }
241}
242
243/// A query for the concept link spelled `term`, as the JavaScript
244/// `queryByConceptTerm`.
245#[must_use]
246pub fn query_by_concept_term(term: &str) -> LinkQuery {
247    LinkQuery::by_type(LinkType::Concept).with_term(term)
248}
249
250/// Structural child and field lookups for one network state, as the
251/// JavaScript `QueryIndex`: built once, so matching does not rescan every
252/// link for every node.
253///
254/// A tree stored bottom-up, every child referencing its parent first, and a
255/// tree stored top-down, a `Syntax` link referencing its children tokens
256/// included, hold the same children.
257#[derive(Clone, Debug)]
258pub struct QueryIndex<'a> {
259    network: &'a LinkNetwork,
260    children: HashMap<LinkId, Vec<LinkId>>,
261    fields: HashMap<LinkId, Vec<(LinkId, LinkId)>>,
262}
263
264impl<'a> QueryIndex<'a> {
265    /// Indexes the structural children and fields of `network`.
266    #[must_use]
267    pub fn new(network: &'a LinkNetwork) -> Self {
268        let link_type = |id: &LinkId| {
269            network
270                .link(*id)
271                .and_then(|link| link.metadata().link_type())
272        };
273        let top_down = network.links().any(|link| {
274            link.metadata().link_type() == Some(LinkType::Syntax)
275                && link
276                    .references()
277                    .iter()
278                    .any(|reference| link_type(reference) == Some(LinkType::Token))
279        });
280        let mut children: HashMap<LinkId, Vec<LinkId>> = HashMap::new();
281        let mut fields: HashMap<LinkId, Vec<(LinkId, LinkId)>> = HashMap::new();
282        for link in network.links() {
283            let references = link.references();
284            let Some(first) = references.first() else {
285                continue;
286            };
287            let kind = link.metadata().link_type();
288            if kind == Some(LinkType::Field) {
289                if let [parent, label, child] = references {
290                    fields.entry(*parent).or_default().push((*label, *child));
291                }
292            } else if top_down {
293                if kind == Some(LinkType::Syntax) {
294                    for reference in references {
295                        if !matches!(
296                            link_type(reference),
297                            Some(LinkType::Field | LinkType::Trivia)
298                        ) {
299                            children.entry(link.id()).or_default().push(*reference);
300                        }
301                    }
302                }
303            } else if kind != Some(LinkType::Trivia) {
304                children.entry(*first).or_default().push(link.id());
305            }
306        }
307        Self {
308            network,
309            children,
310            fields,
311        }
312    }
313
314    /// The link `id` names.
315    #[must_use]
316    pub fn link(&self, id: LinkId) -> Option<&'a Link> {
317        self.network.link(id)
318    }
319
320    /// The structural children of `parent`, in network order, without field
321    /// and trivia links.
322    #[must_use]
323    pub fn structural_children(&self, parent: LinkId) -> &[LinkId] {
324        self.children.get(&parent).map_or(&[], Vec::as_slice)
325    }
326
327    /// The children `parent` holds under field `label`.
328    #[must_use]
329    pub fn field_targets(&self, parent: LinkId, label: &str) -> Vec<LinkId> {
330        self.fields
331            .get(&parent)
332            .into_iter()
333            .flatten()
334            .filter(|(label_id, _)| {
335                self.network
336                    .link(*label_id)
337                    .and_then(|link| link.metadata().term())
338                    == Some(label)
339            })
340            .map(|(_, child)| *child)
341            .collect()
342    }
343}
344
345/// Predicate expression parsed from an S-expression query.
346#[derive(Clone, Debug, PartialEq, Eq)]
347pub struct QueryPredicate {
348    name: String,
349    arguments: Vec<QueryPredicateArgument>,
350}
351
352impl QueryPredicate {
353    /// Predicate name without the leading `#`.
354    #[must_use]
355    pub fn name(&self) -> &str {
356        &self.name
357    }
358
359    /// Predicate arguments in source order.
360    #[must_use]
361    pub fn arguments(&self) -> &[QueryPredicateArgument] {
362        &self.arguments
363    }
364}
365
366/// Predicate argument parsed from an S-expression query.
367#[derive(Clone, Debug, PartialEq, Eq)]
368pub enum QueryPredicateArgument {
369    /// Capture reference such as `@name`.
370    Capture(String),
371    /// Host literal such as `"main"` or an unquoted atom.
372    Literal(String),
373}
374
375impl QueryPredicateArgument {
376    /// Returns the referenced capture name, if this argument is a capture.
377    #[must_use]
378    pub fn capture_name(&self) -> Option<&str> {
379        match self {
380            Self::Capture(name) => Some(name),
381            Self::Literal(_) => None,
382        }
383    }
384
385    /// Returns the literal value, if this argument is a literal.
386    #[must_use]
387    pub fn literal(&self) -> Option<&str> {
388        match self {
389            Self::Capture(_) => None,
390            Self::Literal(value) => Some(value),
391        }
392    }
393}
394
395/// Error returned while parsing an S-expression query.
396#[derive(Clone, Debug, PartialEq, Eq)]
397pub struct QueryParseError {
398    message: String,
399}
400
401impl QueryParseError {
402    pub(crate) fn new(message: impl Into<String>) -> Self {
403        Self {
404            message: message.into(),
405        }
406    }
407}
408
409impl fmt::Display for QueryParseError {
410    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
411        formatter.write_str(&self.message)
412    }
413}
414
415impl std::error::Error for QueryParseError {}
416
417#[derive(Clone, Debug, PartialEq, Eq)]
418struct QueryPattern {
419    root: QueryNodePattern,
420    capture: Option<String>,
421}
422
423impl QueryPattern {
424    fn match_root(&self, network: &LinkNetwork, link_id: LinkId) -> Vec<QueryCaptures> {
425        let captures = self
426            .root
427            .matches(network, link_id, QueryCaptures::default());
428        captures
429            .into_iter()
430            .map(|captures| {
431                if let Some(capture) = &self.capture {
432                    captures.with_capture(capture, link_id)
433                } else {
434                    captures
435                }
436            })
437            .collect()
438    }
439}
440
441#[derive(Clone, Debug, PartialEq, Eq)]
442struct QueryNodePattern {
443    kind: QueryNodeKind,
444    children: Vec<QueryChildPattern>,
445}
446
447impl QueryNodePattern {
448    fn matches(
449        &self,
450        network: &LinkNetwork,
451        link_id: LinkId,
452        captures: QueryCaptures,
453    ) -> Vec<QueryCaptures> {
454        let Some(link) = network.link(link_id) else {
455            return Vec::new();
456        };
457        if !self.kind.matches(link) || self.has_negated_field(network, link_id) {
458            return Vec::new();
459        }
460
461        let structural_children = structural_children(network, link_id);
462        let context = MatchContext {
463            network,
464            parent: link_id,
465            structural_children: &structural_children,
466        };
467        match_child_patterns(&context, &self.children, 0, false, captures)
468            .into_iter()
469            .map(|(_child_index, captures)| captures)
470            .collect()
471    }
472
473    fn has_negated_field(&self, network: &LinkNetwork, link_id: LinkId) -> bool {
474        self.children.iter().any(|child| {
475            if let QueryChildPattern::NegatedField(label) = child {
476                has_field(network, link_id, label)
477            } else {
478                false
479            }
480        })
481    }
482}
483
484#[derive(Clone, Debug, PartialEq, Eq)]
485enum QueryNodeKind {
486    Exact(String),
487    Wildcard,
488}
489
490impl QueryNodeKind {
491    fn matches(&self, link: &Link) -> bool {
492        match self {
493            Self::Exact(kind) => link.metadata().term() == Some(kind.as_str()),
494            Self::Wildcard => true,
495        }
496    }
497}
498
499#[derive(Clone, Debug, PartialEq, Eq)]
500enum QueryChildPattern {
501    Anchor,
502    NegatedField(String),
503    Pattern(QueryChildExpression),
504}
505
506#[derive(Clone, Debug, PartialEq, Eq)]
507struct QueryChildExpression {
508    field: Option<String>,
509    expression: QueryExpression,
510    capture: Option<String>,
511    quantifier: QueryQuantifier,
512}
513
514#[derive(Clone, Debug, PartialEq, Eq)]
515enum QueryExpression {
516    Node(QueryNodePattern),
517    Alternation(Vec<QueryNodePattern>),
518}
519
520#[derive(Clone, Copy, Debug, PartialEq, Eq)]
521enum QueryQuantifier {
522    One,
523    ZeroOrOne,
524    ZeroOrMore,
525    OneOrMore,
526}
527
528struct MatchContext<'a> {
529    network: &'a LinkNetwork,
530    parent: LinkId,
531    structural_children: &'a [LinkId],
532}
533
534fn match_child_patterns(
535    context: &MatchContext<'_>,
536    patterns: &[QueryChildPattern],
537    child_index: usize,
538    anchored: bool,
539    captures: QueryCaptures,
540) -> Vec<(usize, QueryCaptures)> {
541    let Some((pattern, remaining)) = patterns.split_first() else {
542        return vec![(child_index, captures)];
543    };
544
545    match pattern {
546        QueryChildPattern::Anchor => {
547            if remaining.is_empty() || remaining.iter().all(is_negated_field_pattern) {
548                if child_index == context.structural_children.len() {
549                    match_child_patterns(context, remaining, child_index, true, captures)
550                } else {
551                    Vec::new()
552                }
553            } else {
554                match_child_patterns(context, remaining, child_index, true, captures)
555            }
556        }
557        QueryChildPattern::NegatedField(_) => {
558            match_child_patterns(context, remaining, child_index, anchored, captures)
559        }
560        QueryChildPattern::Pattern(expression) => match_quantified_expression(
561            context,
562            expression,
563            remaining,
564            child_index,
565            anchored,
566            &captures,
567        ),
568    }
569}
570
571const fn is_negated_field_pattern(pattern: &QueryChildPattern) -> bool {
572    matches!(pattern, QueryChildPattern::NegatedField(_))
573}
574
575fn match_quantified_expression(
576    context: &MatchContext<'_>,
577    expression: &QueryChildExpression,
578    remaining: &[QueryChildPattern],
579    child_index: usize,
580    anchored: bool,
581    captures: &QueryCaptures,
582) -> Vec<(usize, QueryCaptures)> {
583    match expression.quantifier {
584        QueryQuantifier::One => match_one_then_continue(
585            context,
586            expression,
587            remaining,
588            child_index,
589            anchored,
590            captures,
591        ),
592        QueryQuantifier::ZeroOrOne => {
593            let mut results =
594                match_child_patterns(context, remaining, child_index, anchored, captures.clone());
595            results.extend(match_one_then_continue(
596                context,
597                expression,
598                remaining,
599                child_index,
600                anchored,
601                captures,
602            ));
603            results
604        }
605        QueryQuantifier::ZeroOrMore => {
606            let mut results =
607                match_child_patterns(context, remaining, child_index, anchored, captures.clone());
608            results.extend(match_repeated_expression(
609                context,
610                expression,
611                remaining,
612                child_index,
613                anchored,
614                captures,
615            ));
616            results
617        }
618        QueryQuantifier::OneOrMore => match_repeated_expression(
619            context,
620            expression,
621            remaining,
622            child_index,
623            anchored,
624            captures,
625        ),
626    }
627}
628
629fn match_repeated_expression(
630    context: &MatchContext<'_>,
631    expression: &QueryChildExpression,
632    remaining: &[QueryChildPattern],
633    child_index: usize,
634    anchored: bool,
635    captures: &QueryCaptures,
636) -> Vec<(usize, QueryCaptures)> {
637    let mut results = Vec::new();
638    for (next_index, next_captures) in
639        match_expression_at_positions(context, expression, child_index, anchored, captures)
640    {
641        results.extend(match_child_patterns(
642            context,
643            remaining,
644            next_index,
645            false,
646            next_captures.clone(),
647        ));
648        results.extend(match_repeated_expression(
649            context,
650            expression,
651            remaining,
652            next_index,
653            true,
654            &next_captures,
655        ));
656    }
657    results
658}
659
660fn match_one_then_continue(
661    context: &MatchContext<'_>,
662    expression: &QueryChildExpression,
663    remaining: &[QueryChildPattern],
664    child_index: usize,
665    anchored: bool,
666    captures: &QueryCaptures,
667) -> Vec<(usize, QueryCaptures)> {
668    match_expression_at_positions(context, expression, child_index, anchored, captures)
669        .into_iter()
670        .flat_map(|(next_index, captures)| {
671            match_child_patterns(context, remaining, next_index, false, captures)
672        })
673        .collect()
674}
675
676fn match_expression_at_positions(
677    context: &MatchContext<'_>,
678    expression: &QueryChildExpression,
679    child_index: usize,
680    anchored: bool,
681    captures: &QueryCaptures,
682) -> Vec<(usize, QueryCaptures)> {
683    let positions: Box<dyn Iterator<Item = usize>> = if anchored {
684        Box::new(std::iter::once(child_index))
685    } else {
686        Box::new(child_index..context.structural_children.len())
687    };
688
689    positions
690        .filter(|position| *position < context.structural_children.len())
691        .flat_map(|position| {
692            let child = context.structural_children[position];
693            match_expression(
694                context.network,
695                context.parent,
696                child,
697                expression,
698                captures.clone(),
699            )
700            .into_iter()
701            .map(move |captures| (position + 1, captures))
702        })
703        .collect()
704}
705
706fn match_expression(
707    network: &LinkNetwork,
708    parent: LinkId,
709    child: LinkId,
710    expression: &QueryChildExpression,
711    captures: QueryCaptures,
712) -> Vec<QueryCaptures> {
713    if let Some(field) = &expression.field {
714        let field_targets = field_targets(network, parent, field);
715        if !field_targets.contains(&child) {
716            return Vec::new();
717        }
718    }
719
720    let matches = match &expression.expression {
721        QueryExpression::Node(node) => node.matches(network, child, captures),
722        QueryExpression::Alternation(alternatives) => alternatives
723            .iter()
724            .flat_map(|alternative| alternative.matches(network, child, captures.clone()))
725            .collect(),
726    };
727
728    matches
729        .into_iter()
730        .map(|captures| {
731            if let Some(capture) = &expression.capture {
732                captures.with_capture(capture, child)
733            } else {
734                captures
735            }
736        })
737        .collect()
738}
739
740fn structural_children(network: &LinkNetwork, parent: LinkId) -> Vec<LinkId> {
741    network
742        .links()
743        .filter(|link| link.references().first().copied() == Some(parent))
744        .filter(|link| {
745            !matches!(
746                link.metadata().link_type(),
747                Some(LinkType::Field | LinkType::Trivia)
748            )
749        })
750        .map(Link::id)
751        .collect()
752}
753
754fn has_field(network: &LinkNetwork, parent: LinkId, label: &str) -> bool {
755    !field_targets(network, parent, label).is_empty()
756}
757
758fn field_targets(network: &LinkNetwork, parent: LinkId, label: &str) -> Vec<LinkId> {
759    network
760        .links()
761        .filter(|link| link.metadata().link_type() == Some(LinkType::Field))
762        .filter_map(|link| {
763            let references = link.references();
764            let [field_parent, field_label, field_child] = references else {
765                return None;
766            };
767            if *field_parent != parent {
768                return None;
769            }
770            let label_link = network.link(*field_label)?;
771            (label_link.metadata().term() == Some(label)).then_some(*field_child)
772        })
773        .collect()
774}