Skip to main content

qail_core/parser/grammar/
binary_ops.rs

1//! Binary operator expression parsing.
2//!
3//! Handles parsing of binary operator chains with precedence:
4//! - Low: || (concat)
5//! - Medium: + -
6//! - High: * / %
7
8use super::expressions::parse_json_or_ident;
9use crate::ast::*;
10use nom::{IResult, Parser, bytes::complete::tag, character::complete::multispace0};
11
12/// Parse concatenation (lowest precedence): expr || expr
13pub fn parse_concat_expr(input: &str) -> IResult<&str, Expr> {
14    let (input, left) = parse_additive_expr(input)?;
15    parse_binary_chain(
16        input,
17        left,
18        parse_additive_expr,
19        &[("||", BinaryOp::Concat)],
20    )
21}
22
23/// Parse additive: expr + expr, expr - expr
24pub fn parse_additive_expr(input: &str) -> IResult<&str, Expr> {
25    let (input, left) = parse_multiplicative_expr(input)?;
26    parse_binary_chain(
27        input,
28        left,
29        parse_multiplicative_expr,
30        &[("+", BinaryOp::Add), ("-", BinaryOp::Sub)],
31    )
32}
33
34/// Parse multiplicative: expr * expr, expr / expr, expr % expr
35pub fn parse_multiplicative_expr(input: &str) -> IResult<&str, Expr> {
36    let (input, left) = parse_json_or_ident(input)?;
37    parse_binary_chain(
38        input,
39        left,
40        parse_json_or_ident,
41        &[
42            ("*", BinaryOp::Mul),
43            ("/", BinaryOp::Div),
44            ("%", BinaryOp::Rem),
45        ],
46    )
47}
48
49/// Generic left-associative binary chain parser
50pub fn parse_binary_chain<'a, F>(
51    mut input: &'a str,
52    mut left: Expr,
53    parse_operand: F,
54    operators: &[(&str, BinaryOp)],
55) -> IResult<&'a str, Expr>
56where
57    F: Fn(&'a str) -> IResult<&'a str, Expr>,
58{
59    loop {
60        let (remaining, _) = multispace0(input)?;
61
62        // Try each operator
63        let mut matched = None;
64        for (op_str, op_enum) in operators {
65            if let Ok((after_op, _)) =
66                tag::<_, _, nom::error::Error<&str>>(*op_str).parse(remaining)
67            {
68                matched = Some((after_op, *op_enum));
69                break;
70            }
71        }
72
73        if let Some((after_op, op)) = matched {
74            let (after_ws, _) = multispace0(after_op)?;
75            let (after_right, right) = parse_operand(after_ws)?;
76            left = Expr::Binary {
77                left: Box::new(left),
78                op,
79                right: Box::new(right),
80                alias: None,
81            };
82            input = after_right;
83        } else {
84            break;
85        }
86    }
87
88    Ok((input, left))
89}