Skip to main content

reddb_types/
operator.rs

1//! Binary-operator vocabulary.
2//!
3//! `BinOp` is the syntactic binary-operator enum the query AST emits and
4//! the coercion spine keys overload resolution on. It is **coercion
5//! vocabulary** as much as it is parser vocabulary: the spine
6//! (`coercion_spine::resolve_binop`) cannot resolve an operator overload
7//! without it, and that spine lives in this keystone crate (ADR 0052).
8//!
9//! Re-homing only the spine while leaving `BinOp` in the server would force
10//! this crate to depend back on `reddb-server` — the exact cycle ADR 0052
11//! exists to prevent. So the operator vocabulary moves here and the query
12//! AST (`reddb-server`'s `storage::query::ast`) re-exports it, keeping every
13//! existing `ast::BinOp` call-site untouched.
14//!
15//! The move is byte-faithful: the enum, its variant set, and the
16//! `precedence()` table are relocated verbatim from `storage::query::ast`.
17
18/// Syntactic binary operators. Parsed precedence determines grouping;
19/// this enum only identifies the operator itself. Comparison and logical
20/// operators live alongside arithmetic so a single `Expr::BinaryOp`
21/// walker can cover every infix form the parser emits.
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum BinOp {
24    // Arithmetic
25    Add,
26    Sub,
27    Mul,
28    Div,
29    Mod,
30    // String
31    Concat,
32    // Comparison
33    Eq,
34    Ne,
35    Lt,
36    Le,
37    Gt,
38    Ge,
39    // Logical
40    And,
41    Or,
42}
43
44impl BinOp {
45    /// Left-binding precedence for Pratt parsing. Higher = binds tighter.
46    /// Mirrors PG gram.y's precedence table for the operators we have.
47    pub fn precedence(self) -> u8 {
48        match self {
49            BinOp::Or => 10,
50            BinOp::And => 20,
51            BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge => 30,
52            BinOp::Concat => 40,
53            BinOp::Add | BinOp::Sub => 50,
54            BinOp::Mul | BinOp::Div | BinOp::Mod => 60,
55        }
56    }
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62
63    #[test]
64    fn precedence_groups_match_parser_contract() {
65        assert_eq!(BinOp::Or.precedence(), 10);
66        assert_eq!(BinOp::And.precedence(), 20);
67        for op in [
68            BinOp::Eq,
69            BinOp::Ne,
70            BinOp::Lt,
71            BinOp::Le,
72            BinOp::Gt,
73            BinOp::Ge,
74        ] {
75            assert_eq!(op.precedence(), 30, "{op:?}");
76        }
77        assert_eq!(BinOp::Concat.precedence(), 40);
78        for op in [BinOp::Add, BinOp::Sub] {
79            assert_eq!(op.precedence(), 50, "{op:?}");
80        }
81        for op in [BinOp::Mul, BinOp::Div, BinOp::Mod] {
82            assert_eq!(op.precedence(), 60, "{op:?}");
83        }
84    }
85}