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}