Skip to main content

zirql/
lib.rs

1#![doc = include_str!("../README.md")]
2
3mod eval;
4mod parser;
5mod program;
6mod value;
7
8use std::fmt;
9use std::ops::Range;
10
11pub use eval::{
12    Budget, EvaluationError, Host, Limits, NativeInput, NativeResult, NativeValue, ProgramLimits,
13    Truth, evaluate,
14};
15pub use parser::{ArgumentReader, parse_pipeline};
16pub use program::{
17    Bindings, EmissionPolicy, Expression, ExpressionKind, ExpressionStep, Program, ProgramHost,
18    Statement, evaluate_expression, evaluate_program,
19};
20pub use value::{
21    ArrayType, ConstructedValue, KeyAxis, KeyedArrayType, KeyedEntry, NominalMember,
22    NominalMemberId, NominalTypeId, SetElementType, ValueBinaryOperator, ValueConstructor,
23    ValueExpression, ValueExpressionKind, ValueGenerator, ValueKey, ValueKeyType, ValueType,
24    ValueUnaryOperator, constructed_value_type, copy_constructed_value, evaluate_value,
25    free_value_names, validate_value_expression,
26};
27
28/// The maximum logical nesting allowance and active nested evaluation calls.
29/// Hosts may choose a smaller logical allowance.
30pub const MAX_NESTING: usize = 64;
31
32/// A half-open UTF-8 byte range in the query or the host's original source.
33/// The host supplies any document identity needed to interpret source ranges.
34#[derive(Clone, Debug, PartialEq, Eq)]
35pub enum Location {
36    Query(Range<usize>),
37    Source(Range<usize>),
38}
39
40#[derive(Clone, Debug, PartialEq, Eq)]
41pub struct QueryError {
42    pub location: Location,
43    pub message: String,
44}
45
46impl QueryError {
47    pub fn query(range: Range<usize>, message: impl Into<String>) -> Self {
48        Self {
49            location: Location::Query(range),
50            message: message.into(),
51        }
52    }
53
54    pub fn source(range: Range<usize>, message: impl Into<String>) -> Self {
55        Self {
56            location: Location::Source(range),
57            message: message.into(),
58        }
59    }
60}
61
62impl fmt::Display for QueryError {
63    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
64        write!(formatter, "{:?}: {}", self.location, self.message)
65    }
66}
67
68impl std::error::Error for QueryError {}
69
70/// A value with its original query byte range. Typed frontends supply ranges
71/// from their own source; the core does not invent host-source coordinates.
72#[derive(Clone, Debug, PartialEq, Eq)]
73pub struct Located<T> {
74    pub value: T,
75    pub range: Range<usize>,
76}
77
78impl<T> Located<T> {
79    pub fn new(value: T, range: Range<usize>) -> Self {
80        Self { value, range }
81    }
82}
83
84pub type Predicate<L> = Located<PredicateKind<L>>;
85
86#[derive(Clone, Debug, PartialEq, Eq)]
87pub enum PredicateKind<L> {
88    Leaf(L),
89    Not(Box<Predicate<L>>),
90    Group(Box<Predicate<L>>),
91    And(Vec<Predicate<L>>),
92    Or(Vec<Predicate<L>>),
93}
94
95/// All stages accept a stream. Count and tally are terminal. Hosts supply typed
96/// navigation/projection payloads and check stream-kind applicability.
97#[derive(Clone, Debug, PartialEq, Eq)]
98pub enum Stage<L, S = ()> {
99    Filter(Predicate<L>),
100    Head(usize),
101    Count,
102    Unique,
103    Tally,
104    Host(S),
105}
106
107/// A validated pipeline shared by textual and typed frontends.
108#[derive(Clone, Debug, PartialEq, Eq)]
109pub struct Pipeline<L, S = ()> {
110    stages: Vec<Located<Stage<L, S>>>,
111    range: Range<usize>,
112    nesting: Nesting,
113}
114
115#[derive(Clone, Copy, Debug, PartialEq, Eq)]
116enum Nesting {
117    Nodes,
118    GroupNot,
119}
120
121impl Nesting {
122    fn pipeline_depth(self) -> usize {
123        usize::from(self == Self::Nodes)
124    }
125
126    fn predicate_depth<L>(self, predicate: &Predicate<L>) -> usize {
127        usize::from(
128            self == Self::Nodes
129                || matches!(
130                    predicate.value,
131                    PredicateKind::Group(_) | PredicateKind::Not(_)
132                ),
133        )
134    }
135}
136
137impl<L, S> Pipeline<L, S> {
138    /// Lower typed stages, checking result transitions and predicate depth.
139    /// An empty pipeline passes through its explicit input. Depth starts at one
140    /// for the pipeline; each predicate node adds one, including groups.
141    pub fn new(
142        stages: Vec<Located<Stage<L, S>>>,
143        range: Range<usize>,
144        nesting_limit: usize,
145    ) -> Result<Self, QueryError> {
146        Self::lower(stages, range, nesting_limit, Nesting::Nodes)
147    }
148
149    /// Lower a frontend that counts only written Group and Not wrappers.
150    /// Pipeline, leaf and And/Or nodes consume no logical depth. The core walks
151    /// predicates iteratively and separately caps active nested evaluation calls.
152    pub fn with_group_not_nesting(
153        stages: Vec<Located<Stage<L, S>>>,
154        range: Range<usize>,
155        nesting_limit: usize,
156    ) -> Result<Self, QueryError> {
157        Self::lower(stages, range, nesting_limit, Nesting::GroupNot)
158    }
159
160    fn lower(
161        stages: Vec<Located<Stage<L, S>>>,
162        range: Range<usize>,
163        nesting_limit: usize,
164        nesting: Nesting,
165    ) -> Result<Self, QueryError> {
166        let nesting_limit = nesting_limit.min(MAX_NESTING);
167        check_depth(nesting.pipeline_depth(), nesting_limit, &range)?;
168        let mut terminal = None;
169        for stage in &stages {
170            if let Some(name) = terminal {
171                return Err(QueryError::query(
172                    stage.range.clone(),
173                    format!("a stage cannot follow {name}"),
174                ));
175            }
176            match &stage.value {
177                Stage::Filter(predicate) => validate_predicate(predicate, nesting, nesting_limit)?,
178                Stage::Head(_) | Stage::Unique | Stage::Host(_) => {}
179                Stage::Count => terminal = Some("count"),
180                Stage::Tally => terminal = Some("tally"),
181            }
182        }
183        Ok(Self {
184            stages,
185            range,
186            nesting,
187        })
188    }
189
190    pub fn stages(&self) -> &[Located<Stage<L, S>>] {
191        &self.stages
192    }
193
194    pub fn range(&self) -> Range<usize> {
195        self.range.clone()
196    }
197}
198
199fn check_depth(depth: usize, limit: usize, range: &Range<usize>) -> Result<(), QueryError> {
200    if depth > limit {
201        return Err(QueryError::query(range.clone(), "nesting limit exceeded"));
202    }
203    Ok(())
204}
205
206fn validate_predicate<L>(
207    predicate: &Predicate<L>,
208    nesting: Nesting,
209    limit: usize,
210) -> Result<(), QueryError> {
211    let mut pending = vec![(predicate, nesting.pipeline_depth())];
212    while let Some((predicate, parent_depth)) = pending.pop() {
213        let depth = parent_depth + nesting.predicate_depth(predicate);
214        check_depth(depth, limit, &predicate.range)?;
215        match &predicate.value {
216            PredicateKind::Leaf(_) => {}
217            PredicateKind::Not(child) | PredicateKind::Group(child) => {
218                pending.push((child, depth));
219            }
220            PredicateKind::And(children) | PredicateKind::Or(children) => {
221                if children.is_empty() {
222                    return Err(QueryError::query(
223                        predicate.range.clone(),
224                        "a Boolean combination needs at least one predicate",
225                    ));
226                }
227                pending.extend(children.iter().rev().map(|child| (child, depth)));
228            }
229        }
230    }
231    Ok(())
232}