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 in `reddb-rql` imports it directly, keeping one operator definition.
13//!
14//! The move is byte-faithful: the enum, its variant set, and the
15//! `precedence()` table was relocated verbatim from the former server AST.
16
17/// Syntactic binary operators. Parsed precedence determines grouping;
18/// this enum only identifies the operator itself. Comparison and logical
19/// operators live alongside arithmetic so a single `Expr::BinaryOp`
20/// walker can cover every infix form the parser emits.
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum BinOp {
23    // Arithmetic
24    Add,
25    Sub,
26    Mul,
27    Div,
28    Mod,
29    // String
30    Concat,
31    // Comparison
32    Eq,
33    Ne,
34    Lt,
35    Le,
36    Gt,
37    Ge,
38    // Logical
39    And,
40    Or,
41}
42
43impl BinOp {
44    /// Left-binding precedence for Pratt parsing. Higher = binds tighter.
45    /// Mirrors PG gram.y's precedence table for the operators we have.
46    pub fn precedence(self) -> u8 {
47        match self {
48            BinOp::Or => 10,
49            BinOp::And => 20,
50            BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge => 30,
51            BinOp::Concat => 40,
52            BinOp::Add | BinOp::Sub => 50,
53            BinOp::Mul | BinOp::Div | BinOp::Mod => 60,
54        }
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    #[test]
63    fn precedence_groups_match_parser_contract() {
64        assert_eq!(BinOp::Or.precedence(), 10);
65        assert_eq!(BinOp::And.precedence(), 20);
66        for op in [
67            BinOp::Eq,
68            BinOp::Ne,
69            BinOp::Lt,
70            BinOp::Le,
71            BinOp::Gt,
72            BinOp::Ge,
73        ] {
74            assert_eq!(op.precedence(), 30, "{op:?}");
75        }
76        assert_eq!(BinOp::Concat.precedence(), 40);
77        for op in [BinOp::Add, BinOp::Sub] {
78            assert_eq!(op.precedence(), 50, "{op:?}");
79        }
80        for op in [BinOp::Mul, BinOp::Div, BinOp::Mod] {
81            assert_eq!(op.precedence(), 60, "{op:?}");
82        }
83    }
84}