Skip to main content

rs_teststand/expression/operator/
mod.rs

1//! The engine's expression operators, one module per family.
2//!
3//! Each family is its own file and its own type, so a caller can reach for
4//! exactly the set they mean and nothing here grows into one long list:
5//!
6//! * [`ArithmeticOperator`], `+ - * / % MOD ++ --`
7//! * [`AssignmentOperator`], `=` and the compound forms
8//! * [`BitwiseOperator`], `& | ^ ~ << >>` and the word spellings
9//! * [`ComparisonOperator`], `== != < > <= >=`
10//! * [`LogicalOperator`], `&& || !`
11//! * [`OtherOperator`], grouping, indexing, separators, comments
12//!
13//! [`Operator`] wraps all six when a single type is wanted.
14//!
15//! # Precedence was measured, not assumed
16//!
17//! No precedence table exists in the published reference, so rather than copy
18//! C's and hope, each relation below was established by evaluating an
19//! expression on a live engine and reading the result:
20//!
21//! | Expression | Result | What it establishes |
22//! |---|---|---|
23//! | `2+3*4` | `14` | `*` binds inside `+` |
24//! | `1 << 2 + 1` | `8` | `+` binds inside `<<` |
25//! | `1 & 3 == 3` | `1` | `==` binds inside `&` |
26//! | `1 \| 2 & 3` | `3` | `&` binds inside `\|` |
27//! | `1 ^ 2 & 3` | `3` | `&` binds inside `^` |
28//! | `0 \|\| 1 && 0` | `False` | `&&` binds inside `\|\|` |
29//!
30//! Giving levels 1 (tightest) through 10 (loosest). Assignment reports `None`:
31//! it binds loosest, but its exact level was never measured, and an unverified
32//! number presented as fact is the thing this crate refuses to ship.
33
34pub mod arithmetic;
35pub mod assignment;
36pub mod bitwise;
37pub mod comparison;
38pub mod logical;
39pub mod other;
40
41pub use arithmetic::ArithmeticOperator;
42pub use assignment::AssignmentOperator;
43pub use bitwise::BitwiseOperator;
44pub use comparison::ComparisonOperator;
45pub use logical::LogicalOperator;
46pub use other::OtherOperator;
47
48/// How many operands an operator takes.
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
50pub enum Arity {
51    /// One operand.
52    Unary,
53    /// Two operands.
54    Binary,
55}
56
57/// The family an operator belongs to.
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
59pub enum OperatorClass {
60    /// Numeric arithmetic.
61    Arithmetic,
62    /// Assignment, including the compound forms.
63    Assignment,
64    /// Bit manipulation.
65    Bitwise,
66    /// Value comparison, yielding a boolean.
67    Comparison,
68    /// Boolean logic.
69    Logical,
70    /// Structure: grouping, indexing, separators, comments.
71    Other,
72}
73
74/// Any operator in the expression language.
75///
76/// A thin wrapper over the six family types, for code that handles operators
77/// generically. Reach for the family type directly when only one set applies.
78#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
79pub enum Operator {
80    /// An arithmetic operator.
81    Arithmetic(ArithmeticOperator),
82    /// An assignment operator.
83    Assignment(AssignmentOperator),
84    /// A bitwise operator.
85    Bitwise(BitwiseOperator),
86    /// A comparison operator.
87    Comparison(ComparisonOperator),
88    /// A logical operator.
89    Logical(LogicalOperator),
90    /// A structural element.
91    Other(OtherOperator),
92}
93
94impl Operator {
95    /// How the operator is written in an expression.
96    #[must_use]
97    pub const fn symbol(self) -> &'static str {
98        match self {
99            Self::Arithmetic(operator) => operator.symbol(),
100            Self::Assignment(operator) => operator.symbol(),
101            Self::Bitwise(operator) => operator.symbol(),
102            Self::Comparison(operator) => operator.symbol(),
103            Self::Logical(operator) => operator.symbol(),
104            Self::Other(operator) => operator.symbol(),
105        }
106    }
107
108    /// The family it belongs to.
109    #[must_use]
110    pub const fn class(self) -> OperatorClass {
111        match self {
112            Self::Arithmetic(_) => OperatorClass::Arithmetic,
113            Self::Assignment(_) => OperatorClass::Assignment,
114            Self::Bitwise(_) => OperatorClass::Bitwise,
115            Self::Comparison(_) => OperatorClass::Comparison,
116            Self::Logical(_) => OperatorClass::Logical,
117            Self::Other(_) => OperatorClass::Other,
118        }
119    }
120
121    /// How many operands it takes, for the families where that applies.
122    ///
123    /// `None` for the structural elements: parentheses and comments do not take
124    /// operands in the way an operator does.
125    #[must_use]
126    pub const fn arity(self) -> Option<Arity> {
127        Some(match self {
128            Self::Arithmetic(operator) => operator.arity(),
129            Self::Assignment(operator) => operator.arity(),
130            Self::Bitwise(operator) => operator.arity(),
131            Self::Comparison(operator) => operator.arity(),
132            Self::Logical(operator) => operator.arity(),
133            Self::Other(_) => return None,
134        })
135    }
136
137    /// Binding strength; lower binds tighter. `None` where it was not measured.
138    #[must_use]
139    pub const fn precedence(self) -> Option<u8> {
140        match self {
141            Self::Arithmetic(operator) => Some(operator.precedence()),
142            Self::Bitwise(operator) => Some(operator.precedence()),
143            Self::Comparison(operator) => Some(operator.precedence()),
144            Self::Logical(operator) => Some(operator.precedence()),
145            Self::Assignment(operator) => operator.precedence(),
146            Self::Other(_) => None,
147        }
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::{
154        ArithmeticOperator, BitwiseOperator, ComparisonOperator, LogicalOperator, Operator,
155        OperatorClass, OtherOperator,
156    };
157
158    #[test]
159    fn the_wrapper_reports_the_family_it_holds() {
160        assert_eq!(
161            Operator::Arithmetic(ArithmeticOperator::Add).class(),
162            OperatorClass::Arithmetic
163        );
164        assert_eq!(
165            Operator::Bitwise(BitwiseOperator::AndWord).class(),
166            OperatorClass::Bitwise
167        );
168        assert_eq!(
169            Operator::Logical(LogicalOperator::And).class(),
170            OperatorClass::Logical
171        );
172    }
173
174    #[test]
175    fn the_word_forms_are_bitwise_and_the_doubled_symbols_are_logical() {
176        // The trap this split exists to make obvious: AND reads like &&, but
177        // the engine evaluates it as &.
178        assert_eq!(
179            Operator::Bitwise(BitwiseOperator::AndWord).class(),
180            OperatorClass::Bitwise
181        );
182        assert_eq!(
183            Operator::Logical(LogicalOperator::And).class(),
184            OperatorClass::Logical
185        );
186        assert_eq!(BitwiseOperator::AndWord.symbol(), "AND");
187        assert_eq!(LogicalOperator::And.symbol(), "&&");
188    }
189
190    #[test]
191    fn structural_elements_have_no_arity_or_precedence() {
192        let subscript = Operator::Other(OtherOperator::Subscript);
193        assert_eq!(subscript.arity(), None);
194        assert_eq!(subscript.precedence(), None);
195    }
196
197    #[test]
198    fn the_measured_order_holds_across_families() {
199        // One assertion per measured expression, so a change to any level has
200        // to be justified against a real evaluation.
201        let tighter = |left: Operator, right: Operator| {
202            left.precedence()
203                .zip(right.precedence())
204                .is_some_and(|(l, r)| l < r)
205        };
206        assert!(tighter(
207            Operator::Arithmetic(ArithmeticOperator::Multiply),
208            Operator::Arithmetic(ArithmeticOperator::Add)
209        ));
210        assert!(tighter(
211            Operator::Arithmetic(ArithmeticOperator::Add),
212            Operator::Bitwise(BitwiseOperator::ShiftLeft)
213        ));
214        assert!(tighter(
215            Operator::Bitwise(BitwiseOperator::ShiftLeft),
216            Operator::Comparison(ComparisonOperator::Equal)
217        ));
218        assert!(tighter(
219            Operator::Comparison(ComparisonOperator::Equal),
220            Operator::Bitwise(BitwiseOperator::And)
221        ));
222        assert!(tighter(
223            Operator::Bitwise(BitwiseOperator::Or),
224            Operator::Logical(LogicalOperator::And)
225        ));
226        assert!(tighter(
227            Operator::Logical(LogicalOperator::And),
228            Operator::Logical(LogicalOperator::Or)
229        ));
230    }
231}