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