rusty_promql_parser/parser/
expr.rs

1//! Main expression parser for PromQL.
2//!
3//! This module provides the top-level [`expr`] function that parses any valid
4//! PromQL expression into an AST. It uses a Pratt parser (precedence climbing)
5//! algorithm for correct handling of binary operator precedence and associativity.
6//!
7//! # Expression Grammar (Simplified)
8//!
9//! ```text
10//! expr          = unary_expr | binary_expr
11//! binary_expr   = expr binary_op expr
12//! unary_expr    = unary_op? postfix_expr
13//! postfix_expr  = primary_expr postfix*
14//! postfix       = subquery_range | matrix_range
15//! primary_expr  = number | string | selector | paren_expr | function_call | aggregation
16//! paren_expr    = "(" expr ")"
17//! ```
18//!
19//! # Operator Precedence (Lowest to Highest)
20//!
21//! 1. `or` - Set union
22//! 2. `and`, `unless` - Set intersection/difference
23//! 3. `==`, `!=`, `<`, `<=`, `>`, `>=` - Comparison
24//! 4. `+`, `-` - Addition/subtraction
25//! 5. `*`, `/`, `%`, `atan2` - Multiplication/division
26//! 6. `^` - Power (right-associative)
27//!
28//! # Examples
29//!
30//! ```rust
31//! use rusty_promql_parser::parser::expr::expr;
32//!
33//! // Simple metric
34//! let (rest, e) = expr("http_requests_total").unwrap();
35//! assert!(rest.is_empty());
36//!
37//! // Binary expression with correct precedence
38//! let (rest, e) = expr("1 + 2 * 3").unwrap(); // Parses as: 1 + (2 * 3)
39//! assert!(rest.is_empty());
40//!
41//! // Complex aggregation
42//! let (rest, e) = expr("sum(rate(http_requests[5m])) by (job)").unwrap();
43//! assert!(rest.is_empty());
44//! ```
45
46use nom::{
47    IResult, Parser,
48    branch::alt,
49    character::complete::char,
50    combinator::{opt, peek},
51    multi::separated_list0,
52    sequence::{delimited, preceded, terminated},
53};
54
55use crate::ast::{Aggregation, BinaryExpr, Call, Expr, SubqueryExpr, UnaryExpr};
56use crate::lexer::{
57    duration::duration,
58    identifier::{Keyword, aggregation_op, metric_name},
59    number::number,
60    string::string_literal,
61    whitespace::ws_opt,
62};
63use crate::parser::{
64    aggregation::grouping,
65    binary::{binary_modifier, binary_op},
66    selector::{label_matchers, parse_modifiers},
67    subquery::{looks_like_subquery, subquery_range},
68    unary::unary_op,
69};
70
71/// Parse a PromQL expression
72///
73/// This is the main entry point for parsing PromQL expressions.
74///
75/// # Examples
76///
77/// ```
78/// use rusty_promql_parser::parser::expr::expr;
79///
80/// // Simple metric
81/// let (rest, e) = expr("http_requests").unwrap();
82/// assert!(rest.is_empty());
83///
84/// // Binary expression
85/// let (rest, e) = expr("foo + bar").unwrap();
86/// assert!(rest.is_empty());
87///
88/// // Complex expression
89/// let (rest, e) = expr("sum(rate(http_requests[5m])) by (job)").unwrap();
90/// assert!(rest.is_empty());
91/// ```
92pub fn expr(input: &str) -> IResult<&str, Expr> {
93    // Skip leading whitespace, then parse binary expression with minimum precedence 0
94    preceded(ws_opt, |i| parse_binary_expr(i, 0)).parse(input)
95}
96
97/// Parse a binary expression using Pratt parser (precedence climbing)
98///
99/// The `min_precedence` parameter ensures we only parse operators at or above
100/// the given precedence level, which handles precedence correctly.
101fn parse_binary_expr(input: &str, min_precedence: u8) -> IResult<&str, Expr> {
102    let (mut input, mut lhs) = parse_unary_expr(input)?;
103
104    loop {
105        // Try to parse: ws binary_op ws modifier? ws rhs
106        let Ok((after_ws, _)) = ws_opt(input) else {
107            break;
108        };
109        let Ok((after_op, op)) = binary_op(after_ws) else {
110            break;
111        };
112
113        // Check precedence
114        let op_precedence = op.precedence();
115        if op_precedence < min_precedence {
116            break;
117        }
118
119        // For right-associative operators (^), use same precedence for recursive call
120        // For left-associative operators, use precedence + 1
121        let next_min_precedence = if op.is_right_associative() {
122            op_precedence
123        } else {
124            op_precedence + 1
125        };
126
127        // Parse: ws modifier? ws rhs
128        let (remaining, (_, modifier, _, rhs)) = (ws_opt, opt(binary_modifier), ws_opt, |i| {
129            parse_binary_expr(i, next_min_precedence)
130        })
131            .parse(after_op)?;
132
133        lhs = Expr::Binary(Box::new(BinaryExpr {
134            op,
135            lhs,
136            rhs,
137            modifier,
138        }));
139        input = remaining;
140    }
141
142    Ok((input, lhs))
143}
144
145/// Parse a unary expression: `unary_op? postfix_expr`
146fn parse_unary_expr(input: &str) -> IResult<&str, Expr> {
147    alt((
148        // Unary operator followed by another unary expression (recursive)
149        // This handles chained unary operators like `--foo` or `-+foo`
150        // Note: -2^3 = -(2^3), not (-2)^3, because unary binds looser than ^
151        (unary_op, ws_opt, parse_unary_expr)
152            .map(|(op, _, operand)| Expr::Unary(Box::new(UnaryExpr { op, expr: operand }))),
153        // No unary operator, fall through to postfix
154        parse_postfix_expr,
155    ))
156    .parse(input)
157}
158
159/// Parse a postfix expression: `primary_expr postfix*`
160///
161/// Postfix operations include:
162/// - Subquery: `[5m:1m]`
163/// - Modifiers: `offset 5m`, `@ start()`
164fn parse_postfix_expr(input: &str) -> IResult<&str, Expr> {
165    let (mut rest, mut expr) = parse_primary_expr(input)?;
166
167    // Try to parse subquery postfix operations
168    // Use peek to check for subquery pattern without consuming input
169    while (ws_opt, peek_subquery_start).parse(rest).is_ok() {
170        let (remaining, (_, ((range, step), (at, offset)))) =
171            (ws_opt, (subquery_range, parse_modifiers)).parse(rest)?;
172
173        expr = Expr::Subquery(Box::new(SubqueryExpr {
174            expr,
175            range,
176            step,
177            offset,
178            at,
179        }));
180        rest = remaining;
181    }
182
183    Ok((rest, expr))
184}
185
186/// Peek for subquery start pattern: `[duration:`
187/// Helper with explicit return type for type inference
188fn peek_subquery_start(input: &str) -> IResult<&str, ()> {
189    if looks_like_subquery(input) {
190        Ok((input, ()))
191    } else {
192        Err(nom::Err::Error(nom::error::Error::new(
193            input,
194            nom::error::ErrorKind::Tag,
195        )))
196    }
197}
198
199/// Parse a primary expression (atoms)
200fn parse_primary_expr(input: &str) -> IResult<&str, Expr> {
201    alt((
202        // Parenthesized expression
203        parse_paren_expr,
204        // Number literal (must come before identifier to handle negative numbers correctly)
205        parse_number_literal,
206        // String literal
207        parse_string_literal,
208        // Selector starting with { (labels only, no metric name prefix)
209        parse_labels_only_selector,
210        // Aggregation, function call, or vector selector
211        // (these all start with an identifier, so we handle them together)
212        parse_identifier_expr,
213    ))
214    .parse(input)
215}
216
217/// Parse a parenthesized expression: `( expr )`
218fn parse_paren_expr(input: &str) -> IResult<&str, Expr> {
219    delimited((char('('), ws_opt), expr, (ws_opt, char(')')))
220        .map(|inner| Expr::Paren(Box::new(inner)))
221        .parse(input)
222}
223
224/// Parse a number literal
225fn parse_number_literal(input: &str) -> IResult<&str, Expr> {
226    number.map(Expr::Number).parse(input)
227}
228
229/// Parse a string literal
230fn parse_string_literal(input: &str) -> IResult<&str, Expr> {
231    string_literal.map(Expr::String).parse(input)
232}
233
234/// Parse an expression starting with an identifier
235///
236/// This handles:
237/// - Aggregation operators: `sum(...)`, `avg by (...) (...)`
238/// - Function calls: `rate(...)`, `abs(...)`
239/// - Vector selectors: `metric`, `metric{labels}`
240fn parse_identifier_expr(input: &str) -> IResult<&str, Expr> {
241    // First, check if this is an aggregation operator
242    if let Ok((rest, op)) = aggregation_op(input) {
243        return parse_aggregation_expr(rest, op);
244    }
245
246    // Parse metric name followed by optional whitespace, then dispatch
247    let (rest, (name, _)) = (metric_name, ws_opt).parse(input)?;
248
249    // Use peek to check for '(' without consuming
250    if peek_open_paren(rest).is_ok() {
251        parse_function_call(rest, name)
252    } else {
253        parse_vector_selector_with_name(rest, name)
254    }
255}
256
257/// Peek for opening parenthesis
258/// Helper with explicit return type for type inference
259fn peek_open_paren(input: &str) -> IResult<&str, char> {
260    peek(char('(')).parse(input)
261}
262
263/// Peek for opening brace
264/// Helper with explicit return type for type inference
265fn peek_open_brace(input: &str) -> IResult<&str, char> {
266    peek(char('{')).parse(input)
267}
268
269/// Parse an aggregation expression
270fn parse_aggregation_expr(input: &str, op: Keyword) -> IResult<&str, Expr> {
271    // Try to parse grouping before the expression
272    let (rest, grouping_before) =
273        preceded(ws_opt, opt(terminated(grouping, ws_opt))).parse(input)?;
274
275    // Parse the arguments in parentheses
276    let (rest, (param, inner_expr)) = delimited(
277        (char('('), ws_opt),
278        |i| {
279            if op.is_aggregation_with_param() {
280                // Parametric: parse parameter, comma, then inner expression
281                let (rest, (param, _, _, _, inner)) =
282                    (expr, ws_opt, char(','), ws_opt, expr).parse(i)?;
283                Ok((rest, (Some(param), inner)))
284            } else {
285                // Non-parametric: just parse inner expression
286                expr.map(|inner| (None, inner)).parse(i)
287            }
288        },
289        (ws_opt, char(')')),
290    )
291    .parse(rest)?;
292
293    // Try to parse grouping after the expression (if not already parsed)
294    let (rest, grouping_after) = if grouping_before.is_none() {
295        preceded(ws_opt, opt(grouping)).parse(rest)?
296    } else {
297        (rest, None)
298    };
299
300    let agg = Aggregation {
301        op: op.as_str().to_string(),
302        expr: inner_expr,
303        param,
304        grouping: grouping_before.or(grouping_after),
305    };
306
307    Ok((rest, Expr::Aggregation(Box::new(agg))))
308}
309
310/// Parse a function call
311fn parse_function_call<'a>(input: &'a str, name: &str) -> IResult<&'a str, Expr> {
312    delimited(
313        (char('('), ws_opt),
314        separated_list0((ws_opt, char(','), ws_opt), expr),
315        (ws_opt, opt((char(','), ws_opt)), char(')')),
316    )
317    .map(|args| Expr::Call(Call::new(name, args)))
318    .parse(input)
319}
320
321/// Parse a vector selector starting with a known metric name
322fn parse_vector_selector_with_name<'a>(input: &'a str, name: &str) -> IResult<&'a str, Expr> {
323    use crate::parser::selector::{MatrixSelector, VectorSelector};
324
325    // Parse optional label matchers (only if input starts with '{')
326    // Using peek to check without copying/trimming
327    let (rest, matchers) = if peek_open_brace(input).is_ok() {
328        label_matchers(input)?
329    } else {
330        (input, Vec::new())
331    };
332
333    // Check if this is a matrix selector: ws + '[' but NOT subquery pattern
334    if (ws_opt, peek_matrix_bracket).parse(rest).is_ok() {
335        // Matrix selector: ws [duration] modifiers
336        return (ws_opt, char('['), duration, char(']'), parse_modifiers)
337            .map(|(_, _, range, _, (at, offset))| {
338                let selector = VectorSelector {
339                    name: Some(name.to_string()),
340                    matchers: matchers.clone(),
341                    offset,
342                    at,
343                };
344                Expr::MatrixSelector(MatrixSelector { selector, range })
345            })
346            .parse(rest);
347    }
348
349    // Vector selector with optional modifiers
350    (ws_opt, parse_modifiers)
351        .map(|(_, (at, offset))| {
352            let selector = VectorSelector {
353                name: Some(name.to_string()),
354                matchers: matchers.clone(),
355                offset,
356                at,
357            };
358            Expr::VectorSelector(selector)
359        })
360        .parse(rest)
361}
362
363/// Peek for matrix bracket: `[` but NOT subquery pattern `[duration:`
364/// Helper with explicit return type for type inference
365fn peek_matrix_bracket(input: &str) -> IResult<&str, char> {
366    let (rest, c) = peek(char('[')).parse(input)?;
367    // Make sure it's NOT a subquery
368    if looks_like_subquery(input) {
369        return Err(nom::Err::Error(nom::error::Error::new(
370            input,
371            nom::error::ErrorKind::Tag,
372        )));
373    }
374    Ok((rest, c))
375}
376
377/// Parse a vector selector starting with just labels (no metric name)
378fn parse_labels_only_selector(input: &str) -> IResult<&str, Expr> {
379    use crate::parser::selector::{LabelMatchOp, MatrixSelector, VectorSelector};
380
381    let (rest, matchers) = label_matchers(input)?;
382
383    // Extract __name__ matcher if present
384    let name = matchers
385        .iter()
386        .find(|m| m.name == "__name__" && m.op == LabelMatchOp::Equal)
387        .map(|m| m.value.clone());
388
389    // Filter out the __name__= matcher that we're using as the name
390    let other_matchers: Vec<_> = if name.is_some() {
391        matchers
392            .into_iter()
393            .filter(|m| !(m.name == "__name__" && m.op == LabelMatchOp::Equal))
394            .collect()
395    } else {
396        matchers
397    };
398
399    // Check if this is a matrix selector: ws + '[' but NOT subquery pattern
400    if (ws_opt, peek_matrix_bracket).parse(rest).is_ok() {
401        return (ws_opt, char('['), duration, char(']'), parse_modifiers)
402            .map(|(_, _, range, _, (at, offset))| {
403                let selector = VectorSelector {
404                    name: name.clone(),
405                    matchers: other_matchers.clone(),
406                    offset,
407                    at,
408                };
409                Expr::MatrixSelector(MatrixSelector { selector, range })
410            })
411            .parse(rest);
412    }
413
414    // Vector selector
415    (ws_opt, parse_modifiers)
416        .map(|(_, (at, offset))| {
417            let selector = VectorSelector {
418                name: name.clone(),
419                matchers: other_matchers.clone(),
420                offset,
421                at,
422            };
423            Expr::VectorSelector(selector)
424        })
425        .parse(rest)
426}
427
428#[cfg(test)]
429mod tests {
430    use super::*;
431    use crate::ast::{BinaryOp, UnaryOp};
432
433    #[test]
434    fn test_parse_number() {
435        let (rest, e) = expr("42").unwrap();
436        assert!(rest.is_empty());
437        assert_eq!(e, Expr::Number(42.0));
438    }
439
440    #[test]
441    fn test_parse_string() {
442        let (rest, e) = expr(r#""hello""#).unwrap();
443        assert!(rest.is_empty());
444        assert_eq!(e, Expr::String("hello".to_string()));
445    }
446
447    #[test]
448    fn test_parse_vector_selector() {
449        let (rest, e) = expr("http_requests").unwrap();
450        assert!(rest.is_empty());
451        match e {
452            Expr::VectorSelector(v) => {
453                assert_eq!(v.name, Some("http_requests".to_string()));
454            }
455            _ => panic!("Expected VectorSelector"),
456        }
457    }
458
459    #[test]
460    fn test_parse_vector_selector_with_labels() {
461        let (rest, e) = expr(r#"http_requests{job="api"}"#).unwrap();
462        assert!(rest.is_empty());
463        match e {
464            Expr::VectorSelector(v) => {
465                assert_eq!(v.name, Some("http_requests".to_string()));
466                assert_eq!(v.matchers.len(), 1);
467                assert_eq!(v.matchers[0].name, "job");
468                assert_eq!(v.matchers[0].value, "api");
469            }
470            _ => panic!("Expected VectorSelector"),
471        }
472    }
473
474    #[test]
475    fn test_parse_matrix_selector() {
476        let (rest, e) = expr("http_requests[5m]").unwrap();
477        assert!(rest.is_empty());
478        match e {
479            Expr::MatrixSelector(m) => {
480                assert_eq!(m.selector.name, Some("http_requests".to_string()));
481                assert_eq!(m.range.as_millis(), 5 * 60 * 1000);
482            }
483            _ => panic!("Expected MatrixSelector"),
484        }
485    }
486
487    #[test]
488    fn test_parse_function_call() {
489        let (rest, e) = expr("rate(http_requests[5m])").unwrap();
490        assert!(rest.is_empty());
491        match e {
492            Expr::Call(c) => {
493                assert_eq!(c.name, "rate");
494                assert_eq!(c.args.len(), 1);
495            }
496            _ => panic!("Expected Call"),
497        }
498    }
499
500    #[test]
501    fn test_parse_aggregation() {
502        let (rest, e) = expr("sum(metric)").unwrap();
503        assert!(rest.is_empty());
504        match e {
505            Expr::Aggregation(a) => {
506                assert_eq!(a.op, "sum");
507            }
508            _ => panic!("Expected Aggregation"),
509        }
510    }
511
512    #[test]
513    fn test_parse_aggregation_with_grouping() {
514        let (rest, e) = expr("sum by (job) (metric)").unwrap();
515        assert!(rest.is_empty());
516        match e {
517            Expr::Aggregation(a) => {
518                assert_eq!(a.op, "sum");
519                assert!(a.grouping.is_some());
520            }
521            _ => panic!("Expected Aggregation"),
522        }
523    }
524
525    #[test]
526    fn test_parse_binary_add() {
527        let (rest, e) = expr("1 + 2").unwrap();
528        assert!(rest.is_empty());
529        match e {
530            Expr::Binary(b) => {
531                assert_eq!(b.op, BinaryOp::Add);
532            }
533            _ => panic!("Expected Binary"),
534        }
535    }
536
537    #[test]
538    fn test_parse_binary_precedence() {
539        // 1 + 2 * 3 should parse as 1 + (2 * 3)
540        let (rest, e) = expr("1 + 2 * 3").unwrap();
541        assert!(rest.is_empty());
542        match e {
543            Expr::Binary(b) => {
544                assert_eq!(b.op, BinaryOp::Add);
545                match b.rhs {
546                    Expr::Binary(inner) => {
547                        assert_eq!(inner.op, BinaryOp::Mul);
548                    }
549                    _ => panic!("Expected inner Binary"),
550                }
551            }
552            _ => panic!("Expected Binary"),
553        }
554    }
555
556    #[test]
557    fn test_parse_binary_right_associative() {
558        // 2 ^ 3 ^ 2 should parse as 2 ^ (3 ^ 2)
559        let (rest, e) = expr("2 ^ 3 ^ 2").unwrap();
560        assert!(rest.is_empty());
561        match e {
562            Expr::Binary(b) => {
563                assert_eq!(b.op, BinaryOp::Pow);
564                assert_eq!(b.lhs, Expr::Number(2.0));
565                match b.rhs {
566                    Expr::Binary(inner) => {
567                        assert_eq!(inner.op, BinaryOp::Pow);
568                        assert_eq!(inner.lhs, Expr::Number(3.0));
569                        assert_eq!(inner.rhs, Expr::Number(2.0));
570                    }
571                    _ => panic!("Expected inner Binary"),
572                }
573            }
574            _ => panic!("Expected Binary"),
575        }
576    }
577
578    #[test]
579    fn test_parse_unary_minus() {
580        let (rest, e) = expr("-42").unwrap();
581        assert!(rest.is_empty());
582        match e {
583            Expr::Unary(u) => {
584                assert_eq!(u.op, UnaryOp::Minus);
585                assert_eq!(u.expr, Expr::Number(42.0));
586            }
587            _ => panic!("Expected Unary"),
588        }
589    }
590
591    #[test]
592    fn test_parse_unary_with_binary() {
593        // -1 + 2 should parse as (-1) + 2
594        let (rest, e) = expr("-1 + 2").unwrap();
595        assert!(rest.is_empty());
596        match e {
597            Expr::Binary(b) => {
598                assert_eq!(b.op, BinaryOp::Add);
599                match b.lhs {
600                    Expr::Unary(u) => {
601                        assert_eq!(u.op, UnaryOp::Minus);
602                    }
603                    _ => panic!("Expected Unary as lhs"),
604                }
605            }
606            _ => panic!("Expected Binary"),
607        }
608    }
609
610    #[test]
611    fn test_parse_paren() {
612        let (rest, e) = expr("(1 + 2)").unwrap();
613        assert!(rest.is_empty());
614        match e {
615            Expr::Paren(inner) => match *inner {
616                Expr::Binary(b) => {
617                    assert_eq!(b.op, BinaryOp::Add);
618                }
619                _ => panic!("Expected Binary inside Paren"),
620            },
621            _ => panic!("Expected Paren"),
622        }
623    }
624
625    #[test]
626    fn test_parse_paren_affects_precedence() {
627        // (1 + 2) * 3 should parse differently than 1 + 2 * 3
628        let (rest, e) = expr("(1 + 2) * 3").unwrap();
629        assert!(rest.is_empty());
630        match e {
631            Expr::Binary(b) => {
632                assert_eq!(b.op, BinaryOp::Mul);
633                match b.lhs {
634                    Expr::Paren(inner) => match *inner {
635                        Expr::Binary(b) => {
636                            assert_eq!(b.op, BinaryOp::Add);
637                        }
638                        _ => panic!("Expected Binary inside Paren"),
639                    },
640                    _ => panic!("Expected Paren as lhs"),
641                }
642            }
643            _ => panic!("Expected Binary"),
644        }
645    }
646
647    #[test]
648    fn test_parse_subquery() {
649        let (rest, e) = expr("metric[5m:1m]").unwrap();
650        assert!(rest.is_empty());
651        match e {
652            Expr::Subquery(s) => {
653                assert_eq!(s.range.as_millis(), 5 * 60 * 1000);
654                assert_eq!(s.step.unwrap().as_millis(), 60 * 1000);
655            }
656            _ => panic!("Expected Subquery"),
657        }
658    }
659
660    #[test]
661    fn test_parse_complex_expression() {
662        let (rest, e) = expr("sum(rate(http_requests[5m])) by (job)").unwrap();
663        assert!(rest.is_empty());
664        match e {
665            Expr::Aggregation(a) => {
666                assert_eq!(a.op, "sum");
667                match a.expr {
668                    Expr::Call(c) => {
669                        assert_eq!(c.name, "rate");
670                    }
671                    _ => panic!("Expected Call inside Aggregation"),
672                }
673            }
674            _ => panic!("Expected Aggregation"),
675        }
676    }
677
678    #[test]
679    fn test_parse_binary_with_modifier() {
680        let (rest, e) = expr("foo + on(job) bar").unwrap();
681        assert!(rest.is_empty());
682        match e {
683            Expr::Binary(b) => {
684                assert_eq!(b.op, BinaryOp::Add);
685                assert!(b.modifier.is_some());
686                let m = b.modifier.unwrap();
687                assert!(m.matching.is_some());
688            }
689            _ => panic!("Expected Binary"),
690        }
691    }
692
693    #[test]
694    fn test_parse_binary_bool() {
695        let (rest, e) = expr("foo == bool bar").unwrap();
696        assert!(rest.is_empty());
697        match e {
698            Expr::Binary(b) => {
699                assert_eq!(b.op, BinaryOp::Eq);
700                assert!(b.modifier.is_some());
701                let m = b.modifier.unwrap();
702                assert!(m.return_bool);
703            }
704            _ => panic!("Expected Binary"),
705        }
706    }
707
708    #[test]
709    fn test_parse_set_operators() {
710        for (input, expected_op) in [
711            ("foo and bar", BinaryOp::And),
712            ("foo or bar", BinaryOp::Or),
713            ("foo unless bar", BinaryOp::Unless),
714        ] {
715            let (rest, e) = expr(input).unwrap();
716            assert!(rest.is_empty(), "Failed for: {}", input);
717            match e {
718                Expr::Binary(b) => {
719                    assert_eq!(b.op, expected_op, "Failed for: {}", input);
720                }
721                _ => panic!("Expected Binary for: {}", input),
722            }
723        }
724    }
725
726    #[test]
727    fn test_parse_offset_modifier() {
728        let (rest, e) = expr("foo offset 5m").unwrap();
729        assert!(rest.is_empty());
730        match e {
731            Expr::VectorSelector(v) => {
732                assert!(v.offset.is_some());
733                assert_eq!(v.offset.unwrap().as_millis(), 5 * 60 * 1000);
734            }
735            _ => panic!("Expected VectorSelector"),
736        }
737    }
738
739    #[test]
740    fn test_parse_at_modifier() {
741        let (rest, e) = expr("foo @ 1609459200").unwrap();
742        assert!(rest.is_empty());
743        match e {
744            Expr::VectorSelector(v) => {
745                assert!(v.at.is_some());
746            }
747            _ => panic!("Expected VectorSelector"),
748        }
749    }
750
751    #[test]
752    fn test_parse_topk() {
753        let (rest, e) = expr("topk(5, metric)").unwrap();
754        assert!(rest.is_empty());
755        match e {
756            Expr::Aggregation(a) => {
757                assert_eq!(a.op, "topk");
758                assert!(a.param.is_some());
759                assert_eq!(*a.param.as_ref().unwrap(), Expr::Number(5.0));
760            }
761            _ => panic!("Expected Aggregation"),
762        }
763    }
764
765    #[test]
766    fn test_parse_whitespace_handling() {
767        let (rest, e) = expr("  foo   +   bar  ").unwrap();
768        assert_eq!(rest.trim(), "");
769        match e {
770            Expr::Binary(b) => {
771                assert_eq!(b.op, BinaryOp::Add);
772            }
773            _ => panic!("Expected Binary"),
774        }
775    }
776
777    #[test]
778    fn test_parse_subquery_with_both_modifiers() {
779        // Test @ before offset
780        let (rest, e) = expr("some_metric[5m:1m] @ 1609459200 offset 10m").unwrap();
781        assert!(rest.is_empty());
782        match e {
783            Expr::Subquery(s) => {
784                assert!(s.at.is_some(), "@ modifier should be present");
785                assert!(s.offset.is_some(), "offset modifier should be present");
786            }
787            _ => panic!("Expected Subquery"),
788        }
789
790        // Test offset before @ - this order should also work
791        let (rest, e) = expr("some_metric[5m:1m] offset 10m @ 1609459200").unwrap();
792        assert!(
793            rest.is_empty(),
794            "Parser did not consume entire input, remaining: '{}'",
795            rest
796        );
797        match e {
798            Expr::Subquery(s) => {
799                assert!(s.at.is_some(), "@ modifier should be present");
800                assert!(s.offset.is_some(), "offset modifier should be present");
801            }
802            _ => panic!("Expected Subquery"),
803        }
804    }
805}