blots_core/
precedence.rs

1use crate::ast::BinaryOp;
2use crate::parser::Rule;
3use pest::pratt_parser::{Op, PrattParser};
4use std::sync::LazyLock;
5
6// Re-export Assoc for convenience
7pub use pest::pratt_parser::Assoc;
8
9/// Macro to define operator precedence in a declarative way
10/// This ensures BinaryOp variants and grammar Rules stay in sync
11macro_rules! define_precedence {
12    (
13        $(
14            precedence $prec:expr, $assoc:ident => {
15                $( $binop:ident : $rule:ident ),* $(,)?
16            }
17        )*
18    ) => {
19        /// Operator precedence table - single source of truth
20        /// Maps BinaryOp to (precedence, associativity, grammar rule)
21        /// Lower precedence numbers = bind less tightly
22        static PRECEDENCE_TABLE: &[(u8, Assoc, BinaryOp, Rule)] = &[
23            $(
24                $(
25                    ($prec, Assoc::$assoc, BinaryOp::$binop, Rule::$rule),
26                )*
27            )*
28        ];
29    };
30}
31
32// Define all operator precedence here - single source of truth
33define_precedence! {
34    // Precedence 1 (lowest): via, into, where, and, or
35    precedence 1, Left => {
36        And: and,
37        NaturalAnd: natural_and,
38        Or: or,
39        NaturalOr: natural_or,
40        Via: via,
41        Into: into,
42        Where: where_,
43    }
44
45    // Precedence 2: comparisons
46    precedence 2, Left => {
47        Equal: equal,
48        NotEqual: not_equal,
49        Less: less,
50        LessEq: less_eq,
51        Greater: greater,
52        GreaterEq: greater_eq,
53        DotEqual: dot_equal,
54        DotNotEqual: dot_not_equal,
55        DotLess: dot_less,
56        DotLessEq: dot_less_eq,
57        DotGreater: dot_greater,
58        DotGreaterEq: dot_greater_eq,
59    }
60
61    // Precedence 3: add, subtract
62    precedence 3, Left => {
63        Add: add,
64        Subtract: subtract,
65    }
66
67    // Precedence 4: multiply, divide, modulo
68    precedence 4, Left => {
69        Multiply: multiply,
70        Divide: divide,
71        Modulo: modulo,
72    }
73
74    // Precedence 5 (highest)
75    precedence 5, Right => {
76        Power: power,
77    }
78    precedence 5, Left => {
79        Coalesce: coalesce,
80    }
81}
82
83/// Build a Pratt parser from the precedence table
84pub fn build_pratt_parser() -> PrattParser<Rule> {
85    let mut parser = PrattParser::new();
86
87    // Group operators by (precedence, associativity)
88    let mut precedence_groups: Vec<(u8, Assoc, Vec<Rule>)> = Vec::new();
89
90    for &(prec, assoc, _binop, rule) in PRECEDENCE_TABLE {
91        // Find or create the group for this precedence/associativity
92        if let Some(group) = precedence_groups
93            .iter_mut()
94            .find(|(p, a, _)| *p == prec && *a == assoc)
95        {
96            group.2.push(rule);
97        } else {
98            precedence_groups.push((prec, assoc, vec![rule]));
99        }
100    }
101
102    // Sort by precedence (higher precedence = added later in Pratt parser)
103    precedence_groups.sort_by_key(|(prec, _, _)| *prec);
104
105    // Build the Pratt parser
106    for (_prec, assoc, rules) in precedence_groups {
107        let mut op_chain = Op::infix(rules[0], assoc);
108        for &rule in &rules[1..] {
109            op_chain = op_chain | Op::infix(rule, assoc);
110        }
111        parser = parser.op(op_chain);
112    }
113
114    // Prefix operators
115    parser = parser.op(Op::prefix(Rule::negation)
116        | Op::prefix(Rule::spread_operator)
117        | Op::prefix(Rule::invert)
118        | Op::prefix(Rule::natural_not));
119
120    // Postfix operators
121    parser = parser.op(Op::postfix(Rule::factorial));
122    parser = parser.op(Op::postfix(Rule::access)
123        | Op::postfix(Rule::dot_access)
124        | Op::postfix(Rule::call_list));
125
126    parser
127}
128
129/// Shared Pratt parser instance
130pub static PRATT: LazyLock<PrattParser<Rule>> = LazyLock::new(build_pratt_parser);
131
132/// Get the precedence level and associativity of a binary operator
133/// Looks up the operator in the precedence table
134pub fn operator_info(op: &BinaryOp) -> (u8, Assoc) {
135    PRECEDENCE_TABLE
136        .iter()
137        .find(|(_, _, binop, _)| binop == op)
138        .map(|(prec, assoc, _, _)| (*prec, *assoc))
139        .expect("All BinaryOp variants must be in PRECEDENCE_TABLE")
140}