actyx_sdk/language/parser/
mod.rs

1#![allow(dead_code)]
2#![allow(clippy::upper_case_acronyms)]
3
4use std::{convert::TryInto, str::FromStr, sync::Arc};
5
6use super::{
7    non_empty::NonEmptyVec, AggrOp, Arr, FuncCall, Ind, Index, Num, Obj, Operation, Query, SimpleExpr, TagAtom, TagExpr,
8};
9use crate::{language::SortKey, tags::Tag, Timestamp};
10use anyhow::{bail, ensure, Result};
11use chrono::{TimeZone, Utc};
12use once_cell::sync::Lazy;
13use pest::{prec_climber::PrecClimber, Parser};
14use unicode_normalization::UnicodeNormalization;
15use utils::*;
16
17#[derive(Debug, Clone)]
18pub struct NoVal(&'static str);
19impl std::fmt::Display for NoVal {
20    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21        write!(f, "no value was present for {}", self.0)
22    }
23}
24impl std::error::Error for NoVal {}
25
26#[derive(pest_derive::Parser)]
27#[grammar = "language/aql.pest"]
28struct Aql;
29
30mod utils;
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub(crate) enum Context {
34    Simple,
35    Aggregate,
36}
37
38#[derive(Debug, Clone, derive_more::Display, derive_more::Error)]
39pub enum ContextError {
40    #[display(fmt = "aggregators are only valid in AGGREGATE clauses")]
41    AggregatorOutsideAggregate,
42    #[display(fmt = "current value _ not available in AGGREGATE clauses")]
43    CurrentValueInAggregate,
44}
45
46fn r_tag(p: P) -> Result<Tag> {
47    let quoted = p.single()?.single()?;
48    let s = quoted.as_str();
49    let s = &s[1..s.len() - 1];
50    match quoted.as_rule() {
51        Rule::single_quoted => Ok(Tag::from_str(s.replace("''", "'").as_ref())?),
52        Rule::double_quoted => Ok(Tag::from_str(s.replace("\"\"", "\"").as_ref())?),
53        x => bail!("unexpected token: {:?}", x),
54    }
55}
56
57enum FromTo {
58    From,
59    To,
60}
61fn r_tag_from_to(p: P, f: FromTo) -> Result<TagExpr> {
62    use TagAtom::*;
63    use TagExpr::Atom;
64    let mut p = p.inner()?;
65    let mut first = p.next().ok_or(NoVal("r_tag_from_to first"))?;
66    Ok(match first.as_rule() {
67        Rule::natural => {
68            let lamport = first.natural()?.into();
69            // if no streamId was given, use the first one (just like assuming 00:00:00 for a date)
70            let stream = p.parse_or_default()?;
71            match f {
72                FromTo::From => Atom(FromLamport(SortKey { lamport, stream })),
73                FromTo::To => Atom(ToLamport(SortKey { lamport, stream })),
74            }
75        }
76        Rule::isodate => match f {
77            FromTo::From => Atom(FromTime(r_timestamp(first)?)),
78            FromTo::To => Atom(ToTime(r_timestamp(first)?)),
79        },
80        x => bail!("unexpected token: {:?}", x),
81    })
82}
83
84fn r_tag_expr(p: P) -> Result<TagExpr> {
85    use TagAtom::*;
86    use TagExpr::Atom;
87
88    static CLIMBER: Lazy<PrecClimber<Rule>> = Lazy::new(|| {
89        use pest::prec_climber::{Assoc::*, Operator};
90
91        PrecClimber::new(vec![Operator::new(Rule::or, Left), Operator::new(Rule::and, Left)])
92    });
93
94    CLIMBER.climb(
95        p.inner()?,
96        |p| {
97            Ok(match p.as_rule() {
98                Rule::tag => Atom(Tag(r_tag(p)?)),
99                Rule::tag_expr => r_tag_expr(p)?,
100                Rule::all_events => Atom(AllEvents),
101                Rule::is_local => Atom(IsLocal),
102                Rule::tag_from => r_tag_from_to(p, FromTo::From)?,
103                Rule::tag_to => r_tag_from_to(p, FromTo::To)?,
104                Rule::tag_app => Atom(AppId(p.single()?.as_str().parse()?)),
105                x => bail!("unexpected token: {:?}", x),
106            })
107        },
108        |lhs, op, rhs| {
109            Ok(match op.as_rule() {
110                Rule::and => lhs?.and(rhs?),
111                Rule::or => lhs?.or(rhs?),
112                x => bail!("unexpected token: {:?}", x),
113            })
114        },
115    )
116}
117
118fn r_string(p: P) -> Result<String> {
119    let p = p.single()?;
120    Ok(match p.as_rule() {
121        Rule::nonempty_string => {
122            let p = p.single()?;
123            let s = p.as_str();
124            let s = &s[1..s.len() - 1];
125            match p.as_rule() {
126                Rule::single_quoted => s.replace("''", "'"),
127                Rule::double_quoted => s.replace("\"\"", "\""),
128                x => bail!("unexpected token: {:?}", x),
129            }
130        }
131        Rule::empty_string => String::new(),
132        x => bail!("unexpected token: {:?}", x),
133    })
134}
135
136fn r_var(p: P, ctx: Context) -> Result<SimpleExpr> {
137    let mut p = p.inner()?;
138    let s = p.next().ok_or(NoVal("no var"))?.as_str();
139    if s == "_" {
140        ensure!(ctx != Context::Aggregate, ContextError::CurrentValueInAggregate);
141    }
142    let head = SimpleExpr::Variable(super::var::Var(s.nfc().collect()));
143    let mut tail = vec![];
144    for mut i in p {
145        match i.as_rule() {
146            Rule::ident => tail.push(Index::String(i.string()?)),
147            Rule::natural => tail.push(Index::Number(i.natural()?)),
148            Rule::string => tail.push(Index::String(r_string(i)?)),
149            Rule::simple_expr => tail.push(Index::Expr(r_simple_expr(i, ctx)?)),
150            x => bail!("unexpected token: {:?}", x),
151        }
152    }
153    Ok(if tail.is_empty() {
154        head
155    } else {
156        SimpleExpr::Indexing(Ind {
157            head: Arc::new(head),
158            tail: tail.try_into()?,
159        })
160    })
161}
162
163fn r_expr_index(p: P, ctx: Context) -> Result<SimpleExpr> {
164    let mut p = p.inner()?;
165    let head = r_simple_expr(p.next().ok_or(NoVal("r_expr_index head"))?, ctx)?;
166    let mut tail = vec![];
167    for mut i in p {
168        match i.as_rule() {
169            Rule::ident => tail.push(Index::String(i.string()?)),
170            Rule::natural => tail.push(Index::Number(i.natural()?)),
171            Rule::string => tail.push(Index::String(r_string(i)?)),
172            Rule::simple_expr => tail.push(Index::Expr(r_simple_expr(i, ctx)?)),
173            x => bail!("unexpected token: {:?}", x),
174        }
175    }
176    Ok(if tail.is_empty() {
177        head
178    } else {
179        SimpleExpr::Indexing(Ind {
180            head: Arc::new(head),
181            tail: tail.try_into()?,
182        })
183    })
184}
185
186fn r_number(mut p: P) -> Result<Num> {
187    p.natural().map(Num::Natural).or_else(|_| p.decimal().map(Num::Decimal))
188}
189
190fn r_timestamp(p: P) -> Result<Timestamp> {
191    let mut p = p.inner()?;
192    let year: i32 = p.string()?.parse()?;
193    let month: u32 = p.string()?.parse()?;
194    let day: u32 = p.string()?.parse()?;
195    let hour: u32 = p.parse_or_default()?;
196    let min: u32 = p.parse_or_default()?;
197    let sec: u32 = p.parse_or_default()?;
198    let nano: u32 = if let Some(p) = p.next() {
199        match p.as_rule() {
200            Rule::millisecond => p.as_str().parse::<u32>()? * 1_000_000,
201            Rule::microsecond => p.as_str().parse::<u32>()? * 1_000,
202            Rule::nanosecond => p.as_str().parse::<u32>()?,
203            x => bail!("unexpected token: {:?}", x),
204        }
205    } else {
206        0
207    };
208    Ok(Utc.ymd(year, month, day).and_hms_nano(hour, min, sec, nano).into())
209}
210
211fn r_object(p: P, ctx: Context) -> Result<Obj> {
212    let mut props = vec![];
213    let mut p = p.inner()?;
214    while p.peek().is_some() {
215        let key = {
216            let mut i = p.next().ok_or(NoVal("key"))?;
217            match i.as_rule() {
218                Rule::ident => Index::String(i.string()?),
219                Rule::natural => Index::Number(i.natural()?),
220                Rule::string => Index::String(r_string(i)?),
221                Rule::simple_expr => Index::Expr(r_simple_expr(i, ctx)?),
222                x => bail!("unexpected token: {:?}", x),
223            }
224        };
225        let value = r_simple_expr(p.next().ok_or(NoVal("value"))?, ctx)?;
226        props.push((key, value));
227    }
228    Ok(Obj { props: props.into() })
229}
230
231fn r_array(p: P, ctx: Context) -> Result<Arr> {
232    Ok(Arr {
233        items: p.inner()?.map(|p| r_simple_expr(p, ctx)).collect::<Result<_>>()?,
234    })
235}
236
237fn r_bool(p: P) -> bool {
238    p.as_str() == "TRUE"
239}
240
241fn r_cases(p: P, ctx: Context) -> Result<NonEmptyVec<(SimpleExpr, SimpleExpr)>> {
242    let mut p = p.inner()?;
243    let mut ret = Vec::new();
244    while let Some(pred) = p.next() {
245        let pred = r_simple_expr(pred, ctx)?;
246        let expr = r_simple_expr(p.next().ok_or(NoVal("case expression"))?, ctx)?;
247        ret.push((pred, expr));
248    }
249    Ok(ret.try_into()?)
250}
251
252fn r_not(p: P) -> Result<P> {
253    p.single()
254}
255
256fn r_aggr(p: P, ctx: Context) -> Result<SimpleExpr> {
257    ensure!(ctx == Context::Aggregate, ContextError::AggregatorOutsideAggregate);
258    let p = p.single()?;
259    Ok(match p.as_rule() {
260        Rule::aggr_sum => SimpleExpr::AggrOp(Arc::new((AggrOp::Sum, r_simple_expr(p.single()?, Context::Simple)?))),
261        Rule::aggr_prod => SimpleExpr::AggrOp(Arc::new((AggrOp::Prod, r_simple_expr(p.single()?, Context::Simple)?))),
262        Rule::aggr_min => SimpleExpr::AggrOp(Arc::new((AggrOp::Min, r_simple_expr(p.single()?, Context::Simple)?))),
263        Rule::aggr_max => SimpleExpr::AggrOp(Arc::new((AggrOp::Max, r_simple_expr(p.single()?, Context::Simple)?))),
264        Rule::aggr_first => SimpleExpr::AggrOp(Arc::new((AggrOp::First, r_simple_expr(p.single()?, Context::Simple)?))),
265        Rule::aggr_last => SimpleExpr::AggrOp(Arc::new((AggrOp::Last, r_simple_expr(p.single()?, Context::Simple)?))),
266        x => bail!("unexpected token: {:?}", x),
267    })
268}
269
270fn r_func_call(p: P, ctx: Context) -> Result<FuncCall> {
271    let mut p = p.inner()?;
272    let name = p.string()?;
273    let mut args = vec![];
274    for p in p {
275        args.push(r_simple_expr(p, ctx)?);
276    }
277    Ok(FuncCall {
278        name,
279        args: args.into(),
280    })
281}
282
283fn r_simple_expr(p: P, ctx: Context) -> Result<SimpleExpr> {
284    static CLIMBER: Lazy<PrecClimber<Rule>> = Lazy::new(|| {
285        use pest::prec_climber::{Assoc::*, Operator};
286        let op = Operator::new;
287
288        PrecClimber::new(vec![
289            op(Rule::alternative, Left),
290            op(Rule::or, Left),
291            op(Rule::xor, Left),
292            op(Rule::and, Left),
293            op(Rule::eq, Left) | op(Rule::ne, Left),
294            op(Rule::lt, Left) | op(Rule::le, Left) | op(Rule::gt, Left) | op(Rule::ge, Left),
295            op(Rule::add, Left) | op(Rule::sub, Left),
296            op(Rule::mul, Left) | op(Rule::div, Left) | op(Rule::modulo, Left),
297            op(Rule::pow, Left),
298        ])
299    });
300
301    fn primary(p: P, ctx: Context) -> Result<SimpleExpr> {
302        Ok(match p.as_rule() {
303            Rule::decimal => SimpleExpr::Number(r_number(p)?),
304            Rule::var_index => r_var(p, ctx)?,
305            Rule::expr_index => r_expr_index(p, ctx)?,
306            Rule::simple_expr => r_simple_expr(p, ctx)?,
307            Rule::simple_not => SimpleExpr::Not(primary(r_not(p)?, ctx)?.into()),
308            Rule::string => SimpleExpr::String(r_string(p)?),
309            Rule::object => SimpleExpr::Object(r_object(p, ctx)?),
310            Rule::array => SimpleExpr::Array(r_array(p, ctx)?),
311            Rule::null => SimpleExpr::Null,
312            Rule::bool => SimpleExpr::Bool(r_bool(p)),
313            Rule::simple_cases => SimpleExpr::Cases(r_cases(p, ctx)?),
314            Rule::aggr_op => r_aggr(p, ctx)?,
315            Rule::func_call => SimpleExpr::FuncCall(r_func_call(p, ctx)?),
316            x => bail!("unexpected token: {:?}", x),
317        })
318    }
319
320    CLIMBER.climb(
321        p.inner()?,
322        |p| primary(p, ctx),
323        |lhs, op, rhs| {
324            Ok(match op.as_rule() {
325                Rule::add => lhs?.add(rhs?),
326                Rule::sub => lhs?.sub(rhs?),
327                Rule::mul => lhs?.mul(rhs?),
328                Rule::div => lhs?.div(rhs?),
329                Rule::modulo => lhs?.modulo(rhs?),
330                Rule::pow => lhs?.pow(rhs?),
331                Rule::and => lhs?.and(rhs?),
332                Rule::or => lhs?.or(rhs?),
333                Rule::xor => lhs?.xor(rhs?),
334                Rule::lt => lhs?.lt(rhs?),
335                Rule::le => lhs?.le(rhs?),
336                Rule::gt => lhs?.gt(rhs?),
337                Rule::ge => lhs?.ge(rhs?),
338                Rule::eq => lhs?.eq(rhs?),
339                Rule::ne => lhs?.ne(rhs?),
340                Rule::alternative => lhs?.alt(rhs?),
341                x => bail!("unexpected token: {:?}", x),
342            })
343        },
344    )
345}
346
347fn r_query(features: Vec<String>, p: P) -> Result<Query> {
348    let mut p = p.inner()?;
349    let mut q = Query {
350        features,
351        from: r_tag_expr(p.next().ok_or(NoVal("tag expression"))?)?,
352        ops: vec![],
353    };
354    for o in p {
355        match o.as_rule() {
356            Rule::filter => q
357                .ops
358                .push(Operation::Filter(r_simple_expr(o.single()?, Context::Simple)?)),
359            Rule::select => {
360                let v = o
361                    .inner()?
362                    .map(|p| r_simple_expr(p, Context::Simple))
363                    .collect::<Result<Vec<_>>>()?;
364                q.ops.push(Operation::Select(v.try_into()?))
365            }
366            Rule::aggregate => q
367                .ops
368                .push(Operation::Aggregate(r_simple_expr(o.single()?, Context::Aggregate)?)),
369            x => bail!("unexpected token: {:?}", x),
370        }
371    }
372    Ok(q)
373}
374
375impl FromStr for Query {
376    type Err = anyhow::Error;
377
378    fn from_str(s: &str) -> Result<Self, Self::Err> {
379        let mut p = Aql::parse(Rule::main_query, s)?.single()?.inner()?;
380        let mut f = p.next().ok_or(NoVal("main query"))?;
381        let features = if f.as_rule() == Rule::features {
382            let features = f.inner()?.map(|mut ff| ff.string()).collect::<Result<_>>()?;
383            f = p.next().ok_or(NoVal("FROM"))?;
384            features
385        } else {
386            vec![]
387        };
388        r_query(features, f)
389    }
390}
391
392impl FromStr for TagExpr {
393    type Err = anyhow::Error;
394
395    fn from_str(s: &str) -> Result<Self, Self::Err> {
396        let p = Aql::parse(Rule::main_tag_expr, s)?.single()?.single()?;
397        r_tag_expr(p)
398    }
399}
400
401impl FromStr for SimpleExpr {
402    type Err = anyhow::Error;
403
404    fn from_str(s: &str) -> Result<Self, Self::Err> {
405        let p = Aql::parse(Rule::main_simple_expr, s)?.single()?.single()?;
406        r_simple_expr(p, Context::Simple)
407    }
408}
409
410impl FromStr for super::var::Var {
411    type Err = anyhow::Error;
412
413    fn from_str(s: &str) -> Result<Self, Self::Err> {
414        let p = Aql::parse(Rule::main_ident, s)?.single()?;
415        Ok(Self(p.as_str().nfc().collect()))
416    }
417}
418
419pub fn is_ident(s: &str) -> bool {
420    Aql::parse(Rule::main_ident, s).is_ok()
421}
422
423#[cfg(test)]
424mod tests {
425    use super::*;
426    use crate::{language::var::Var, tag, NodeId, StreamId};
427    use pest::{fails_with, Parser};
428    use std::convert::TryFrom;
429
430    #[test]
431    fn tag() -> Result<()> {
432        let p = Aql::parse(Rule::tag, "'hello''s revenge'")?;
433        assert_eq!(r_tag(p.single()?)?, tag!("hello's revenge"));
434        let p = Aql::parse(Rule::tag, "\"hello\"\"s revenge\"")?;
435        assert_eq!(r_tag(p.single()?)?, tag!("hello\"s revenge"));
436        Ok(())
437    }
438
439    #[test]
440    fn tag_expr() -> Result<()> {
441        use TagAtom::Tag;
442        use TagExpr::*;
443        assert_eq!(
444            "'x' |\t'y'\n&'z'".parse::<TagExpr>()?,
445            Or((
446                Atom(Tag(tag!("x"))),
447                And((Atom(Tag(tag!("y"))), Atom(Tag(tag!("z")))).into())
448            )
449                .into())
450        );
451        Ok(())
452    }
453
454    #[test]
455    fn simple_expr() -> Result<()> {
456        use super::{Num::*, SimpleExpr::*};
457        assert_eq!(
458            "(x - 5.2 * 1234)^2 / 7 % 5".parse::<SimpleExpr>()?,
459            Variable("x".try_into()?)
460                .sub(Number(Decimal(5.2)).mul(Number(Natural(1234))))
461                .pow(Number(Natural(2)))
462                .div(Number(Natural(7)))
463                .modulo(Number(Natural(5)))
464        );
465
466        fails_with! {
467            parser: Aql,
468            input: "5+3!",
469            rule: Rule::main_simple_expr,
470            positives: vec![Rule::EOI, Rule::add, Rule::sub, Rule::mul, Rule::div, Rule::modulo, Rule::pow, Rule::and,
471                Rule::or, Rule::xor, Rule::lt, Rule::le, Rule::gt, Rule::ge, Rule::eq, Rule::ne, Rule::alternative],
472            negatives: vec![],
473            pos: 3
474        };
475
476        Ok(())
477    }
478
479    #[test]
480    fn query() -> Result<()> {
481        use super::Num::*;
482        use super::{Arr, Ind, Obj};
483        use crate::app_id;
484        use SimpleExpr::*;
485        use TagAtom::*;
486        use TagExpr::Atom;
487
488        assert_eq!(
489            "FROM 'machine' | 'user' END".parse::<Query>()?,
490            Query::new(Tag(tag!("machine")).or(Tag(tag!("user"))))
491        );
492        assert_eq!(
493            "FROM 'machine' |
494                -- or the other
495                  'user' & isLocal & from(2012-12-31Z) & to(12345678901234567) & \
496                  from(10/1234567890123456789012345678901234567890122-4312) & appId(hello-5.-x-) & allEvents
497                  FILTER _.x[42] > 5 SELECT { x: ! 'hello' y: 42 z: [1.3,_.x] } END --"
498                .parse::<Query>()?,
499            Query::new(
500                Atom(Tag(tag!("machine"))).or(Tag(tag!("user"))
501                    .and(IsLocal)
502                    .and(Atom(FromTime(1356912000000000.into())))
503                    .and(Atom(ToLamport(SortKey {
504                        lamport: 12345678901234567.into(),
505                        stream: StreamId::min(),
506                    })))
507                    .and(Atom(FromLamport(SortKey {
508                        lamport: 10.into(),
509                        stream: NodeId([
510                            12, 65, 70, 28, 130, 74, 44, 32, 196, 20, 97, 200, 36, 162, 194, 12, 65, 70, 28, 130, 74,
511                            44, 32, 196, 20, 97, 200, 36, 162, 194, 12, 65
512                        ])
513                        .stream(4312.into())
514                    })))
515                    .and(Atom(AppId(app_id!("hello-5.-x-"))))
516                    .and(Atom(AllEvents)))
517            )
518            .with_op(Operation::Filter(Ind::with("_", &[&"x", &42]).gt(Number(Natural(5)))))
519            .with_op(Operation::Select(
520                vec![Obj::with(&[
521                    ("x", Not(String("hello".to_owned()).into())),
522                    ("y", Number(Natural(42))),
523                    ("z", Arr::with(&[Number(Decimal(1.3)), Ind::with("_", &[&"x"])]))
524                ])]
525                .try_into()?
526            ))
527        );
528        Ok(())
529    }
530
531    #[test]
532    fn positive() {
533        let p = |str: &'static str| str.parse::<Query>().unwrap();
534        p("FROM 'machine' | 'user' & isLocal & from(2012-12-31Z) & to(12345678901234567) & appId(hello-5.-x-) & allEvents FILTER _.x[42] > 5 SELECT { x: !'hello', y: 42, z: [1.3, _.x] } END");
535        p("FROM from(2012-12-31T09:30:32.007Z) END");
536        p("FROM from(2012-12-31T09:30:32Z) END");
537        p("FROM from(2012-12-31T09:30:32.007008Z) END");
538        p("FROM 'hello''s revenge' END");
539        p("FROM 'hell''o' FILTER _.x = 'worl''d' END");
540        p("FROM 'a' & 'b' | 'c' END");
541        p("FROM 'a' | 'b' & 'c' END");
542        p("FROM 'a' & ('b' | 'c') END");
543        p("FROM 'a' & 'b' | 'c' & 'd' END");
544        p("FROM ('a' | 'b') & ('c' | 'd') END");
545    }
546
547    #[test]
548    fn negative() {
549        fails_with! {
550            parser: Aql,
551            input: "FROM x",
552            rule: Rule::main_query,
553            positives: vec![Rule::tag_expr],
554            negatives: vec![],
555            pos: 5
556        };
557        fails_with! {
558            parser: Aql,
559            input: "FROM 'x' ELECT 'x'",
560            rule: Rule::main_query,
561            positives: vec![Rule::EOI, Rule::filter, Rule::select, Rule::aggregate, Rule::and, Rule::or],
562            negatives: vec![],
563            pos: 9
564        };
565        fails_with! {
566            parser: Aql,
567            input: "FROM 'x' FITTER 'x'",
568            rule: Rule::main_query,
569            positives: vec![Rule::EOI, Rule::filter, Rule::select, Rule::aggregate, Rule::and, Rule::or],
570            negatives: vec![],
571            pos: 9
572        };
573    }
574
575    #[test]
576    fn expr() {
577        use super::Num::*;
578        use SimpleExpr::*;
579        let p = |s: &'static str| s.parse::<SimpleExpr>().unwrap();
580        assert_eq!(p("NULL"), Null);
581        assert_eq!(p("FALSE"), Bool(false));
582        assert_eq!(p("1"), Number(Natural(1)));
583        assert_eq!(p("1.0"), Number(Natural(1)));
584        assert_eq!(p("'s'"), String("s".into()));
585        assert_eq!(
586            p("[1,TRUE]"),
587            Array(Arr {
588                items: vec![Number(Natural(1)), Bool(true)].into()
589            })
590        );
591        assert_eq!(
592            p("{one:1 ['two']:2 [('three')]:3 [4]:4}"),
593            Object(Obj {
594                props: vec![
595                    (Index::String("one".into()), Number(Natural(1))),
596                    (Index::String("two".into()), Number(Natural(2))),
597                    (Index::Expr(String("three".into())), Number(Natural(3))),
598                    (Index::Number(4), Number(Natural(4))),
599                ]
600                .into()
601            })
602        );
603    }
604
605    #[test]
606    fn ident() {
607        let p = |s: &str, e: Option<&str>| {
608            let q = s.parse::<Query>();
609            if let Some(err) = e {
610                let e = q.unwrap_err().to_string();
611                assert!(e.contains(err), "received: {}", e);
612                None
613            } else {
614                match q.unwrap().ops[0].clone() {
615                    Operation::Select(v) => Some(v.to_vec()),
616                    _ => None,
617                }
618            }
619        };
620        let ind = |s: &str| {
621            SimpleExpr::Indexing(Ind {
622                head: Arc::new(SimpleExpr::Variable(Var::try_from("_").unwrap())),
623                tail: NonEmptyVec::try_from(vec![Index::String(s.to_owned())]).unwrap(),
624            })
625        };
626        let s = |s: &str| Index::String(s.to_owned());
627        let n = |n: u64| SimpleExpr::Number(Num::Natural(n));
628        let v = |s: &str| SimpleExpr::Variable(Var::try_from(s).unwrap());
629
630        p("FROM 'x' SELECT _.H", Some("expected ident"));
631        p("FROM 'x' SELECT _.HE", Some("expected ident"));
632        assert_eq!(
633            p("FROM 'x' SELECT i, iIö, PσΔ", None),
634            Some(vec![v("i"), v("iIö"), v("PσΔ")])
635        );
636        assert_eq!(
637            p("FROM 'x' SELECT _.i, _.iIö, _.PσΔ", None),
638            Some(vec![ind("i"), ind("iIö"), ind("PσΔ")])
639        );
640        assert_eq!(
641            p("FROM 'x' SELECT { i: 1 iIö: 2 PσΔ: 3 }", None),
642            Some(vec![SimpleExpr::Object(Obj {
643                props: Arc::from(vec![(s("i"), n(1)), (s("iIö"), n(2)), (s("PσΔ"), n(3))].as_slice())
644            })])
645        )
646    }
647
648    #[test]
649    fn index() {
650        let p = |s: &str| s.parse::<SimpleExpr>().unwrap();
651        assert_eq!(
652            p("a['ª']"),
653            SimpleExpr::Indexing(Ind {
654                head: Arc::new(SimpleExpr::Variable(Var::try_from("a").unwrap())),
655                tail: vec![Index::String("ª".to_owned())].try_into().unwrap()
656            })
657        );
658        assert_eq!(
659            p("a.ª"),
660            SimpleExpr::Indexing(Ind {
661                head: Arc::new(SimpleExpr::Variable(Var::try_from("a").unwrap())),
662                tail: vec![Index::String("ª".to_owned())].try_into().unwrap()
663            })
664        );
665        assert_eq!(p("a['ª']").to_string(), "a.ª");
666
667        assert_eq!(
668            p("{ⓐ:1}"),
669            SimpleExpr::Object(Obj {
670                props: vec![(Index::String("ⓐ".to_owned()), SimpleExpr::Number(Num::Natural(1)))].into()
671            })
672        );
673        assert_eq!(p("{ⓐ:1}").to_string(), "{ ⓐ: 1 }");
674    }
675
676    #[test]
677    fn aggregate() {
678        let p = |s: &str, e: Option<&str>| {
679            let q = s.parse::<Query>();
680            if let Some(err) = e {
681                let e = q.unwrap_err().to_string();
682                assert!(e.contains(err), "received: {}", e);
683            } else {
684                q.unwrap();
685            }
686        };
687
688        p(
689            "FROM 'x' FILTER SUM(1)",
690            Some("aggregators are only valid in AGGREGATE clauses"),
691        );
692        p(
693            "FROM 'x' SELECT 1 + SUM(1)",
694            Some("aggregators are only valid in AGGREGATE clauses"),
695        );
696        p("FROM 'x' FILTER _", None);
697        p(
698            "FROM 'x' AGGREGATE 1 + _",
699            Some("current value _ not available in AGGREGATE clauses"),
700        );
701        p("FROM 'x' AGGREGATE 1 + 2", None);
702        p(
703            "FROM 'x' AGGREGATE { a: LAST(_ + 1) b: FIRST(_.a.b) c: MIN(1 / _) d: MAX([_]) e: SUM(1.3 * _) }",
704            None,
705        );
706    }
707
708    #[test]
709    fn func_call() {
710        let p = |s: &str, e: Option<&str>| {
711            let q = s.parse::<Query>();
712            if let Some(err) = e {
713                let e = q.unwrap_err().to_string();
714                assert!(e.contains(err), "received: {}", e);
715                None
716            } else {
717                match q.unwrap().ops[0].clone() {
718                    Operation::Select(v) => Some(v.to_vec()),
719                    _ => None,
720                }
721            }
722        };
723
724        assert_eq!(
725            p("FROM 'x' SELECT Func()", None),
726            Some(vec![SimpleExpr::FuncCall(FuncCall {
727                name: "Func".to_owned(),
728                args: vec![].into()
729            })])
730        );
731        assert_eq!(
732            p("FROM 'x' SELECT Fÿnc('x')", None),
733            Some(vec![SimpleExpr::FuncCall(FuncCall {
734                name: "Fÿnc".to_owned(),
735                args: vec![SimpleExpr::String("x".to_owned())].into()
736            })])
737        );
738        assert_eq!(
739            p("FROM 'x' SELECT Func(x, 'x')", None),
740            Some(vec![SimpleExpr::FuncCall(FuncCall {
741                name: "Func".to_owned(),
742                args: vec![
743                    SimpleExpr::Variable(Var::try_from("x").unwrap()),
744                    SimpleExpr::String("x".to_owned())
745                ]
746                .into()
747            })])
748        );
749    }
750}