Skip to main content

omnigraph_compiler/query/
parser.rs

1use pest::Parser;
2use pest::error::InputLocation;
3use pest_derive::Parser;
4
5use crate::error::{
6    NanoError, ParseDiagnostic, Result, SourceSpan, decode_string_literal, render_span,
7};
8
9use super::ast::*;
10
11#[derive(Parser)]
12#[grammar = "query/query.pest"]
13struct QueryParser;
14
15pub fn parse_query(input: &str) -> Result<QueryFile> {
16    parse_query_diagnostic(input).map_err(|e| NanoError::Parse(e.to_string()))
17}
18
19pub fn parse_query_diagnostic(input: &str) -> std::result::Result<QueryFile, ParseDiagnostic> {
20    let pairs = QueryParser::parse(Rule::query_file, input).map_err(pest_error_to_diagnostic)?;
21
22    let mut queries = Vec::new();
23    for pair in pairs {
24        if let Rule::query_file = pair.as_rule() {
25            for inner in pair.into_inner() {
26                if let Rule::query_decl = inner.as_rule() {
27                    queries.push(parse_query_decl(inner).map_err(nano_error_to_diagnostic)?);
28                }
29            }
30        }
31    }
32    Ok(QueryFile { queries })
33}
34
35fn pest_error_to_diagnostic(err: pest::error::Error<Rule>) -> ParseDiagnostic {
36    let span = match err.location {
37        InputLocation::Pos(pos) => Some(render_span(SourceSpan::new(pos, pos))),
38        InputLocation::Span((start, end)) => Some(render_span(SourceSpan::new(start, end))),
39    };
40    ParseDiagnostic::new(err.to_string(), span)
41}
42
43fn nano_error_to_diagnostic(err: NanoError) -> ParseDiagnostic {
44    ParseDiagnostic::new(err.to_string(), None)
45}
46
47fn parse_query_decl(pair: pest::iterators::Pair<Rule>) -> Result<QueryDecl> {
48    let mut inner = pair.into_inner();
49    let name = inner.next().unwrap().as_str().to_string();
50
51    let mut description = None;
52    let mut instruction = None;
53    let mut params = Vec::new();
54    let mut match_clause = Vec::new();
55    let mut return_clause = Vec::new();
56    let mut order_clause = Vec::new();
57    let mut limit = None;
58    let mut mutations = Vec::new();
59
60    for item in inner {
61        match item.as_rule() {
62            Rule::param_list => {
63                for p in item.into_inner() {
64                    if let Rule::param = p.as_rule() {
65                        params.push(parse_param(p)?);
66                    }
67                }
68            }
69            Rule::query_annotation => {
70                let (annotation_name, value) = parse_query_annotation(item)?;
71                match annotation_name {
72                    "description" => {
73                        if description.replace(value).is_some() {
74                            return Err(NanoError::Parse(format!(
75                                "query `{}` cannot include duplicate @description annotations",
76                                name
77                            )));
78                        }
79                    }
80                    "instruction" => {
81                        if instruction.replace(value).is_some() {
82                            return Err(NanoError::Parse(format!(
83                                "query `{}` cannot include duplicate @instruction annotations",
84                                name
85                            )));
86                        }
87                    }
88                    other => {
89                        return Err(NanoError::Parse(format!(
90                            "unsupported query annotation: @{}",
91                            other
92                        )));
93                    }
94                }
95            }
96            Rule::query_body => {
97                let body = item
98                    .into_inner()
99                    .next()
100                    .ok_or_else(|| NanoError::Parse("query body cannot be empty".to_string()))?;
101                match body.as_rule() {
102                    Rule::read_query_body => {
103                        for section in body.into_inner() {
104                            match section.as_rule() {
105                                Rule::match_clause => {
106                                    for c in section.into_inner() {
107                                        if let Rule::clause = c.as_rule() {
108                                            match_clause.push(parse_clause(c)?);
109                                        }
110                                    }
111                                }
112                                Rule::return_clause => {
113                                    for proj in section.into_inner() {
114                                        if let Rule::projection = proj.as_rule() {
115                                            return_clause.push(parse_projection(proj)?);
116                                        }
117                                    }
118                                }
119                                Rule::order_clause => {
120                                    for ord in section.into_inner() {
121                                        if let Rule::ordering = ord.as_rule() {
122                                            order_clause.push(parse_ordering(ord)?);
123                                        }
124                                    }
125                                }
126                                Rule::limit_clause => {
127                                    let int_pair = section.into_inner().next().unwrap();
128                                    limit =
129                                        Some(int_pair.as_str().parse::<u64>().map_err(|e| {
130                                            NanoError::Parse(format!("invalid limit: {}", e))
131                                        })?);
132                                }
133                                _ => {}
134                            }
135                        }
136                    }
137                    Rule::mutation_body => {
138                        for mutation_pair in body.into_inner() {
139                            if let Rule::mutation_stmt = mutation_pair.as_rule() {
140                                let stmt =
141                                    mutation_pair.into_inner().next().ok_or_else(|| {
142                                        NanoError::Parse(
143                                            "mutation statement cannot be empty".to_string(),
144                                        )
145                                    })?;
146                                mutations.push(parse_mutation_stmt(stmt)?);
147                            }
148                        }
149                    }
150                    _ => {}
151                }
152            }
153            _ => {}
154        }
155    }
156
157    Ok(QueryDecl {
158        name,
159        description,
160        instruction,
161        params,
162        match_clause,
163        return_clause,
164        order_clause,
165        limit,
166        mutations,
167    })
168}
169
170fn parse_query_annotation(pair: pest::iterators::Pair<Rule>) -> Result<(&'static str, String)> {
171    let inner = pair
172        .into_inner()
173        .next()
174        .ok_or_else(|| NanoError::Parse("query annotation cannot be empty".to_string()))?;
175    match inner.as_rule() {
176        Rule::description_annotation => {
177            let value = inner
178                .into_inner()
179                .next()
180                .ok_or_else(|| {
181                    NanoError::Parse("@description requires a string literal".to_string())
182                })
183                .map(|value| parse_string_lit(value.as_str()))??;
184            Ok(("description", value))
185        }
186        Rule::instruction_annotation => {
187            let value = inner
188                .into_inner()
189                .next()
190                .ok_or_else(|| {
191                    NanoError::Parse("@instruction requires a string literal".to_string())
192                })
193                .map(|value| parse_string_lit(value.as_str()))??;
194            Ok(("instruction", value))
195        }
196        other => Err(NanoError::Parse(format!(
197            "unexpected query annotation rule: {:?}",
198            other
199        ))),
200    }
201}
202
203fn parse_param(pair: pest::iterators::Pair<Rule>) -> Result<Param> {
204    let mut inner = pair.into_inner();
205    let var = inner.next().unwrap().as_str();
206    let name = var.strip_prefix('$').unwrap_or(var).to_string();
207    let type_ref = inner.next().unwrap();
208    let nullable = type_ref.as_str().trim_end().ends_with('?');
209    let mut type_inner = type_ref.into_inner();
210    let core = type_inner
211        .next()
212        .ok_or_else(|| NanoError::Parse("parameter type is missing".to_string()))?;
213    let base = match core.as_rule() {
214        Rule::base_type => core.as_str().to_string(),
215        Rule::list_type => {
216            let inner = core
217                .into_inner()
218                .next()
219                .ok_or_else(|| NanoError::Parse("list type missing item type".to_string()))?;
220            format!("[{}]", inner.as_str().trim())
221        }
222        Rule::vector_type => {
223            let vector = core
224                .into_inner()
225                .next()
226                .ok_or_else(|| NanoError::Parse("Vector type missing dimension".to_string()))?;
227            format!("Vector({})", vector.as_str().trim())
228        }
229        other => {
230            return Err(NanoError::Parse(format!(
231                "unexpected param type rule: {:?}",
232                other
233            )));
234        }
235    };
236
237    Ok(Param {
238        name,
239        type_name: base,
240        nullable,
241    })
242}
243
244fn parse_clause(pair: pest::iterators::Pair<Rule>) -> Result<Clause> {
245    let inner = pair.into_inner().next().unwrap();
246    match inner.as_rule() {
247        Rule::binding => Ok(Clause::Binding(parse_binding(inner)?)),
248        Rule::traversal => Ok(Clause::Traversal(parse_traversal(inner)?)),
249        Rule::filter => Ok(Clause::Filter(parse_filter(inner)?)),
250        Rule::text_search_clause => Ok(parse_text_search_clause(inner)?),
251        Rule::negation => {
252            let mut clauses = Vec::new();
253            for c in inner.into_inner() {
254                if let Rule::clause = c.as_rule() {
255                    clauses.push(parse_clause(c)?);
256                }
257            }
258            Ok(Clause::Negation(clauses))
259        }
260        _ => Err(NanoError::Parse(format!(
261            "unexpected clause rule: {:?}",
262            inner.as_rule()
263        ))),
264    }
265}
266
267fn parse_text_search_clause(pair: pest::iterators::Pair<Rule>) -> Result<Clause> {
268    let inner = pair
269        .into_inner()
270        .next()
271        .ok_or_else(|| NanoError::Parse("text search clause cannot be empty".to_string()))?;
272    let expr = match inner.as_rule() {
273        Rule::search_call => parse_search_call(inner)?,
274        Rule::fuzzy_call => parse_fuzzy_call(inner)?,
275        Rule::match_text_call => parse_match_text_call(inner)?,
276        other => {
277            return Err(NanoError::Parse(format!(
278                "unexpected text search clause rule: {:?}",
279                other
280            )));
281        }
282    };
283
284    Ok(Clause::Filter(Filter {
285        left: expr,
286        op: CompOp::Eq,
287        right: Expr::Literal(Literal::Bool(true)),
288    }))
289}
290
291fn parse_binding(pair: pest::iterators::Pair<Rule>) -> Result<Binding> {
292    let mut inner = pair.into_inner();
293    let var = inner.next().unwrap().as_str();
294    let variable = var.strip_prefix('$').unwrap_or(var).to_string();
295    let type_name = inner.next().unwrap().as_str().to_string();
296
297    let mut prop_matches = Vec::new();
298    for item in inner {
299        if let Rule::prop_match_list = item.as_rule() {
300            for pm in item.into_inner() {
301                if let Rule::prop_match = pm.as_rule() {
302                    prop_matches.push(parse_prop_match(pm)?);
303                }
304            }
305        }
306    }
307
308    Ok(Binding {
309        variable,
310        type_name,
311        prop_matches,
312    })
313}
314
315fn parse_prop_match(pair: pest::iterators::Pair<Rule>) -> Result<PropMatch> {
316    let mut inner = pair.into_inner();
317    let prop_name = inner.next().unwrap().as_str().to_string();
318    let value_pair = inner.next().unwrap();
319    let value = parse_match_value(value_pair)?;
320
321    Ok(PropMatch { prop_name, value })
322}
323
324fn parse_mutation_stmt(pair: pest::iterators::Pair<Rule>) -> Result<Mutation> {
325    match pair.as_rule() {
326        Rule::insert_stmt => parse_insert_mutation(pair).map(Mutation::Insert),
327        Rule::update_stmt => parse_update_mutation(pair).map(Mutation::Update),
328        Rule::delete_stmt => parse_delete_mutation(pair).map(Mutation::Delete),
329        other => Err(NanoError::Parse(format!(
330            "unexpected mutation statement rule: {:?}",
331            other
332        ))),
333    }
334}
335
336fn parse_insert_mutation(pair: pest::iterators::Pair<Rule>) -> Result<InsertMutation> {
337    let mut inner = pair.into_inner();
338    let type_name = inner.next().unwrap().as_str().to_string();
339    let mut assignments = Vec::new();
340    for item in inner {
341        if let Rule::mutation_assignment = item.as_rule() {
342            assignments.push(parse_mutation_assignment(item)?);
343        }
344    }
345    Ok(InsertMutation {
346        type_name,
347        assignments,
348    })
349}
350
351fn parse_update_mutation(pair: pest::iterators::Pair<Rule>) -> Result<UpdateMutation> {
352    let mut inner = pair.into_inner();
353    let type_name = inner.next().unwrap().as_str().to_string();
354
355    let mut assignments = Vec::new();
356    let mut predicate = None;
357
358    for item in inner {
359        match item.as_rule() {
360            Rule::mutation_assignment => assignments.push(parse_mutation_assignment(item)?),
361            Rule::mutation_predicate => predicate = Some(parse_mutation_predicate(item)?),
362            _ => {}
363        }
364    }
365
366    let predicate = predicate.ok_or_else(|| {
367        NanoError::Parse("update mutation requires a where predicate".to_string())
368    })?;
369
370    Ok(UpdateMutation {
371        type_name,
372        assignments,
373        predicate,
374    })
375}
376
377fn parse_delete_mutation(pair: pest::iterators::Pair<Rule>) -> Result<DeleteMutation> {
378    let mut inner = pair.into_inner();
379    let type_name = inner.next().unwrap().as_str().to_string();
380    let predicate = inner
381        .next()
382        .ok_or_else(|| NanoError::Parse("delete mutation requires a where predicate".to_string()))
383        .and_then(parse_mutation_predicate)?;
384    Ok(DeleteMutation {
385        type_name,
386        predicate,
387    })
388}
389
390fn parse_mutation_assignment(pair: pest::iterators::Pair<Rule>) -> Result<MutationAssignment> {
391    let mut inner = pair.into_inner();
392    let property = inner.next().unwrap().as_str().to_string();
393    let value = parse_match_value(inner.next().unwrap())?;
394    Ok(MutationAssignment { property, value })
395}
396
397fn parse_mutation_predicate(pair: pest::iterators::Pair<Rule>) -> Result<MutationPredicate> {
398    let mut inner = pair.into_inner();
399    let property = inner.next().unwrap().as_str().to_string();
400    let op = parse_comp_op(inner.next().unwrap())?;
401    let value = parse_match_value(inner.next().unwrap())?;
402    Ok(MutationPredicate {
403        property,
404        op,
405        value,
406    })
407}
408
409fn parse_match_value(pair: pest::iterators::Pair<Rule>) -> Result<MatchValue> {
410    let value_inner = pair.into_inner().next().unwrap();
411    match value_inner.as_rule() {
412        Rule::variable => {
413            let v = value_inner.as_str();
414            Ok(MatchValue::Variable(
415                v.strip_prefix('$').unwrap_or(v).to_string(),
416            ))
417        }
418        Rule::now_call => Ok(MatchValue::Now),
419        Rule::literal => Ok(MatchValue::Literal(parse_literal(value_inner)?)),
420        _ => Err(NanoError::Parse(format!(
421            "unexpected match value: {:?}",
422            value_inner.as_rule()
423        ))),
424    }
425}
426
427fn parse_traversal(pair: pest::iterators::Pair<Rule>) -> Result<Traversal> {
428    let mut inner = pair.into_inner();
429    let src_var = inner.next().unwrap().as_str();
430    let src = src_var.strip_prefix('$').unwrap_or(src_var).to_string();
431    let edge_name = inner.next().unwrap().as_str().to_string();
432    let mut min_hops = 1u32;
433    let mut max_hops = Some(1u32);
434
435    let next = inner.next().unwrap();
436    let dst_pair = if let Rule::traversal_bounds = next.as_rule() {
437        let (min, max) = parse_traversal_bounds(next)?;
438        min_hops = min;
439        max_hops = max;
440        inner
441            .next()
442            .ok_or_else(|| NanoError::Parse("traversal missing destination variable".to_string()))?
443    } else {
444        next
445    };
446
447    let dst_var = dst_pair.as_str();
448    let dst = dst_var.strip_prefix('$').unwrap_or(dst_var).to_string();
449
450    Ok(Traversal {
451        src,
452        edge_name,
453        dst,
454        min_hops,
455        max_hops,
456    })
457}
458
459fn parse_traversal_bounds(pair: pest::iterators::Pair<Rule>) -> Result<(u32, Option<u32>)> {
460    let mut inner = pair.into_inner();
461    let min = inner
462        .next()
463        .ok_or_else(|| NanoError::Parse("traversal bound missing min hop".to_string()))?
464        .as_str()
465        .parse::<u32>()
466        .map_err(|e| NanoError::Parse(format!("invalid traversal min bound: {}", e)))?;
467    let max = inner
468        .next()
469        .map(|p| {
470            p.as_str()
471                .parse::<u32>()
472                .map_err(|e| NanoError::Parse(format!("invalid traversal max bound: {}", e)))
473        })
474        .transpose()?;
475    Ok((min, max))
476}
477
478fn parse_filter(pair: pest::iterators::Pair<Rule>) -> Result<Filter> {
479    let mut inner = pair.into_inner();
480    let left = parse_expr(inner.next().unwrap())?;
481    let op = parse_filter_op(inner.next().unwrap())?;
482    let right = parse_expr(inner.next().unwrap())?;
483
484    Ok(Filter { left, op, right })
485}
486
487fn parse_expr(pair: pest::iterators::Pair<Rule>) -> Result<Expr> {
488    let inner = pair.into_inner().next().unwrap();
489    match inner.as_rule() {
490        Rule::now_call => Ok(Expr::Now),
491        Rule::prop_access => {
492            let mut parts = inner.into_inner();
493            let var = parts.next().unwrap().as_str();
494            let variable = var.strip_prefix('$').unwrap_or(var).to_string();
495            let property = parts.next().unwrap().as_str().to_string();
496            Ok(Expr::PropAccess { variable, property })
497        }
498        Rule::variable => {
499            let v = inner.as_str();
500            Ok(Expr::Variable(v.strip_prefix('$').unwrap_or(v).to_string()))
501        }
502        Rule::literal => Ok(Expr::Literal(parse_literal(inner)?)),
503        Rule::agg_call => {
504            let mut parts = inner.into_inner();
505            let func = match parts.next().unwrap().as_str() {
506                "count" => AggFunc::Count,
507                "sum" => AggFunc::Sum,
508                "avg" => AggFunc::Avg,
509                "min" => AggFunc::Min,
510                "max" => AggFunc::Max,
511                other => return Err(NanoError::Parse(format!("unknown aggregate: {}", other))),
512            };
513            let arg = parse_expr(parts.next().unwrap())?;
514            Ok(Expr::Aggregate {
515                func,
516                arg: Box::new(arg),
517            })
518        }
519        Rule::search_call => parse_search_call(inner),
520        Rule::fuzzy_call => parse_fuzzy_call(inner),
521        Rule::match_text_call => parse_match_text_call(inner),
522        Rule::nearest_ordering => parse_nearest_ordering(inner),
523        Rule::bm25_call => parse_bm25_call(inner),
524        Rule::rrf_call => parse_rrf_call(inner),
525        Rule::ident => Ok(Expr::AliasRef(inner.as_str().to_string())),
526        _ => Err(NanoError::Parse(format!(
527            "unexpected expr rule: {:?}",
528            inner.as_rule()
529        ))),
530    }
531}
532
533fn parse_search_call(pair: pest::iterators::Pair<Rule>) -> Result<Expr> {
534    let mut args = pair.into_inner();
535    let field = args
536        .next()
537        .ok_or_else(|| NanoError::Parse("search() missing field argument".to_string()))?;
538    let query = args
539        .next()
540        .ok_or_else(|| NanoError::Parse("search() missing query argument".to_string()))?;
541    if args.next().is_some() {
542        return Err(NanoError::Parse(
543            "search() accepts exactly 2 arguments".to_string(),
544        ));
545    }
546    Ok(Expr::Search {
547        field: Box::new(parse_expr(field)?),
548        query: Box::new(parse_expr(query)?),
549    })
550}
551
552fn parse_fuzzy_call(pair: pest::iterators::Pair<Rule>) -> Result<Expr> {
553    let mut args = pair.into_inner();
554    let field = args
555        .next()
556        .ok_or_else(|| NanoError::Parse("fuzzy() missing field argument".to_string()))?;
557    let query = args
558        .next()
559        .ok_or_else(|| NanoError::Parse("fuzzy() missing query argument".to_string()))?;
560    let max_edits = args.next().map(parse_expr).transpose()?.map(Box::new);
561    if args.next().is_some() {
562        return Err(NanoError::Parse(
563            "fuzzy() accepts at most 3 arguments".to_string(),
564        ));
565    }
566    Ok(Expr::Fuzzy {
567        field: Box::new(parse_expr(field)?),
568        query: Box::new(parse_expr(query)?),
569        max_edits,
570    })
571}
572
573fn parse_match_text_call(pair: pest::iterators::Pair<Rule>) -> Result<Expr> {
574    let mut args = pair.into_inner();
575    let field = args
576        .next()
577        .ok_or_else(|| NanoError::Parse("match_text() missing field argument".to_string()))?;
578    let query = args
579        .next()
580        .ok_or_else(|| NanoError::Parse("match_text() missing query argument".to_string()))?;
581    if args.next().is_some() {
582        return Err(NanoError::Parse(
583            "match_text() accepts exactly 2 arguments".to_string(),
584        ));
585    }
586    Ok(Expr::MatchText {
587        field: Box::new(parse_expr(field)?),
588        query: Box::new(parse_expr(query)?),
589    })
590}
591
592fn parse_bm25_call(pair: pest::iterators::Pair<Rule>) -> Result<Expr> {
593    let mut args = pair.into_inner();
594    let field = args
595        .next()
596        .ok_or_else(|| NanoError::Parse("bm25() missing field argument".to_string()))?;
597    let query = args
598        .next()
599        .ok_or_else(|| NanoError::Parse("bm25() missing query argument".to_string()))?;
600    if args.next().is_some() {
601        return Err(NanoError::Parse(
602            "bm25() accepts exactly 2 arguments".to_string(),
603        ));
604    }
605    Ok(Expr::Bm25 {
606        field: Box::new(parse_expr(field)?),
607        query: Box::new(parse_expr(query)?),
608    })
609}
610
611fn parse_rank_expr(pair: pest::iterators::Pair<Rule>) -> Result<Expr> {
612    let inner = if pair.as_rule() == Rule::rank_expr {
613        pair.into_inner()
614            .next()
615            .ok_or_else(|| NanoError::Parse("rank expression cannot be empty".to_string()))?
616    } else {
617        pair
618    };
619    match inner.as_rule() {
620        Rule::nearest_ordering => parse_nearest_ordering(inner),
621        Rule::bm25_call => parse_bm25_call(inner),
622        other => Err(NanoError::Parse(format!(
623            "rrf() rank expression must be nearest(...) or bm25(...), got {:?}",
624            other
625        ))),
626    }
627}
628
629fn parse_rrf_call(pair: pest::iterators::Pair<Rule>) -> Result<Expr> {
630    let mut args = pair.into_inner();
631    let primary = args
632        .next()
633        .ok_or_else(|| NanoError::Parse("rrf() missing primary rank expression".to_string()))?;
634    let secondary = args
635        .next()
636        .ok_or_else(|| NanoError::Parse("rrf() missing secondary rank expression".to_string()))?;
637    let k = args.next().map(parse_expr).transpose()?.map(Box::new);
638    if args.next().is_some() {
639        return Err(NanoError::Parse(
640            "rrf() accepts at most 3 arguments".to_string(),
641        ));
642    }
643    Ok(Expr::Rrf {
644        primary: Box::new(parse_rank_expr(primary)?),
645        secondary: Box::new(parse_rank_expr(secondary)?),
646        k,
647    })
648}
649
650fn parse_comp_op(pair: pest::iterators::Pair<Rule>) -> Result<CompOp> {
651    match pair.as_str() {
652        "=" => Ok(CompOp::Eq),
653        "!=" => Ok(CompOp::Ne),
654        ">" => Ok(CompOp::Gt),
655        "<" => Ok(CompOp::Lt),
656        ">=" => Ok(CompOp::Ge),
657        "<=" => Ok(CompOp::Le),
658        other => Err(NanoError::Parse(format!("unknown operator: {}", other))),
659    }
660}
661
662fn parse_filter_op(pair: pest::iterators::Pair<Rule>) -> Result<CompOp> {
663    match pair.as_str() {
664        "contains" => Ok(CompOp::Contains),
665        _ => parse_comp_op(pair),
666    }
667}
668
669fn parse_literal(pair: pest::iterators::Pair<Rule>) -> Result<Literal> {
670    let inner = pair.into_inner().next().unwrap();
671    match inner.as_rule() {
672        Rule::string_lit => Ok(Literal::String(parse_string_lit(inner.as_str())?)),
673        Rule::integer => {
674            let n: i64 = inner
675                .as_str()
676                .parse()
677                .map_err(|e| NanoError::Parse(format!("invalid integer: {}", e)))?;
678            Ok(Literal::Integer(n))
679        }
680        Rule::float_lit => {
681            let f: f64 = inner
682                .as_str()
683                .parse()
684                .map_err(|e| NanoError::Parse(format!("invalid float: {}", e)))?;
685            Ok(Literal::Float(f))
686        }
687        Rule::bool_lit => {
688            let b = match inner.as_str() {
689                "true" => true,
690                "false" => false,
691                other => {
692                    return Err(NanoError::Parse(format!(
693                        "invalid boolean literal: {}",
694                        other
695                    )));
696                }
697            };
698            Ok(Literal::Bool(b))
699        }
700        Rule::date_lit => {
701            let date_str = inner
702                .into_inner()
703                .next()
704                .map(|s| parse_string_lit(s.as_str()))
705                .ok_or_else(|| NanoError::Parse("date literal requires a string".to_string()))?;
706            Ok(Literal::Date(date_str?))
707        }
708        Rule::datetime_lit => {
709            let dt_str = inner
710                .into_inner()
711                .next()
712                .map(|s| parse_string_lit(s.as_str()))
713                .ok_or_else(|| {
714                    NanoError::Parse("datetime literal requires a string".to_string())
715                })?;
716            Ok(Literal::DateTime(dt_str?))
717        }
718        Rule::list_lit => {
719            let mut items = Vec::new();
720            for item in inner.into_inner() {
721                if item.as_rule() == Rule::literal {
722                    items.push(parse_literal(item)?);
723                }
724            }
725            Ok(Literal::List(items))
726        }
727        _ => Err(NanoError::Parse(format!(
728            "unexpected literal: {:?}",
729            inner.as_rule()
730        ))),
731    }
732}
733
734fn parse_string_lit(raw: &str) -> Result<String> {
735    decode_string_literal(raw)
736}
737
738fn parse_projection(pair: pest::iterators::Pair<Rule>) -> Result<Projection> {
739    let mut inner = pair.into_inner();
740    let expr = parse_expr(inner.next().unwrap())?;
741    let alias = inner.next().map(|p| p.as_str().to_string());
742
743    Ok(Projection { expr, alias })
744}
745
746fn parse_ordering(pair: pest::iterators::Pair<Rule>) -> Result<Ordering> {
747    let mut inner = pair.into_inner();
748    let first = inner
749        .next()
750        .ok_or_else(|| NanoError::Parse("ordering cannot be empty".to_string()))?;
751    let (expr, descending) = match first.as_rule() {
752        Rule::nearest_ordering => (parse_nearest_ordering(first)?, false),
753        Rule::expr => {
754            let expr = parse_expr(first)?;
755            let direction = inner.next().map(|p| p.as_str().to_string());
756            if matches!(expr, Expr::Nearest { .. }) && direction.is_some() {
757                return Err(NanoError::Parse(
758                    "nearest() ordering does not accept asc/desc modifiers".to_string(),
759                ));
760            }
761            let descending = matches!(direction.as_deref(), Some("desc"));
762            (expr, descending)
763        }
764        other => {
765            return Err(NanoError::Parse(format!(
766                "unexpected ordering rule: {:?}",
767                other
768            )));
769        }
770    };
771
772    Ok(Ordering { expr, descending })
773}
774
775fn parse_nearest_ordering(pair: pest::iterators::Pair<Rule>) -> Result<Expr> {
776    let mut inner = pair.into_inner();
777    let prop = inner
778        .next()
779        .ok_or_else(|| NanoError::Parse("nearest() missing property".to_string()))?;
780    let mut prop_parts = prop.into_inner();
781    let var = prop_parts
782        .next()
783        .ok_or_else(|| NanoError::Parse("nearest() missing variable".to_string()))?
784        .as_str();
785    let variable = var.strip_prefix('$').unwrap_or(var).to_string();
786    let property = prop_parts
787        .next()
788        .ok_or_else(|| NanoError::Parse("nearest() missing property name".to_string()))?
789        .as_str()
790        .to_string();
791
792    let query = inner
793        .next()
794        .ok_or_else(|| NanoError::Parse("nearest() missing query expression".to_string()))?;
795    Ok(Expr::Nearest {
796        variable,
797        property,
798        query: Box::new(parse_expr(query)?),
799    })
800}
801
802#[cfg(test)]
803#[path = "parser_tests.rs"]
804mod tests;