Skip to main content

datalogic_rs/
opcode.rs

1//! OpCode-based dispatch system for built-in operators.
2//!
3//! This module implements a high-performance dispatch mechanism using enum variants
4//! instead of string matching or vtable lookups at runtime.
5//!
6//! # Performance Design
7//!
8//! The `OpCode` enum provides O(1) operator dispatch through:
9//!
10//! 1. **Compile-time resolution**: Operator strings are converted to `OpCode` variants
11//!    during the compilation phase, not during evaluation
12//! 2. **Direct dispatch**: The `evaluate_direct` method uses a match statement that
13//!    compiles to an efficient jump table
14//! 3. **No boxing or vtables**: Direct function calls without trait object overhead
15//! 4. **Cache-friendly**: The `#[repr(u8)]` attribute ensures compact memory layout
16//!
17//! # Operator Categories
18//!
19//! Operators are grouped by functionality for code organization:
20//!
21//! - Variable Access (0-1, 57): `var`, `val`, `exists`
22//! - Comparison (2-9): `==`, `===`, `!=`, `!==`, `>`, `>=`, `<`, `<=`
23//! - Logical (10-13): `!`, `!!`, `and`, `or`
24//! - Control Flow (14-15, 56, 59): `if`, `?:`, `??`, `switch`/`match`
25//! - Arithmetic (16-22, 49-51): `+`, `-`, `*`, `/`, `%`, `max`, `min`, `abs`, `ceil`, `floor`
26//! - String (23-25, 38-43, 53): `cat`, `substr`, `in`, `starts_with`, etc.
27//! - Array (26-32, 54-55): `merge`, `filter`, `map`, `reduce`, `all`, `some`, `none`, `sort`, `slice`
28//! - DateTime (44-48, 58): `datetime`, `timestamp`, `parse_date`, `format_date`, `date_diff`, `now`
29//! - Error Handling (35-36): `try`, `throw`
30//! - Type/Missing (33-34, 37): `missing`, `missing_some`, `type`
31//! - Special (52): `preserve`
32//!
33//! # Adding New Operators
34//!
35//! 1. Add a new variant to the `OpCode` enum
36//! 2. Add string mapping in `FromStr` implementation
37//! 3. Add reverse mapping in `as_str()` method
38//! 4. Add dispatch case in `evaluate_direct()`
39//! 5. Implement the operator function in the appropriate `operators/` module
40
41use std::str::FromStr;
42
43/// OpCode enum for fast built-in operator lookup
44#[repr(u8)]
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum OpCode {
47    // === Variable & Data Access ===
48    Var = 0,
49    Val = 1,
50    Exists = 57,
51
52    // === Comparison Operators ===
53    Equals = 2,
54    StrictEquals = 3,
55    NotEquals = 4,
56    StrictNotEquals = 5,
57    GreaterThan = 6,
58    GreaterThanEqual = 7,
59    LessThan = 8,
60    LessThanEqual = 9,
61
62    // === Logical Operators ===
63    Not = 10,
64    DoubleNot = 11,
65    And = 12,
66    Or = 13,
67
68    // === Control Flow ===
69    If = 14,
70    Ternary = 15,
71    Coalesce = 56,
72    Switch = 59,
73
74    // === Arithmetic Operators ===
75    Add = 16,
76    Subtract = 17,
77    Multiply = 18,
78    Divide = 19,
79    Modulo = 20,
80    Max = 21,
81    Min = 22,
82    Abs = 49,
83    Ceil = 50,
84    Floor = 51,
85
86    // === String Operations ===
87    Cat = 23,
88    Substr = 24,
89    In = 25,
90    Length = 53,
91    StartsWith = 38,
92    EndsWith = 39,
93    Upper = 40,
94    Lower = 41,
95    Trim = 42,
96    Split = 43,
97
98    // === Array Operations ===
99    Merge = 26,
100    Filter = 27,
101    Map = 28,
102    Reduce = 29,
103    All = 30,
104    Some = 31,
105    None = 32,
106    Sort = 54,
107    Slice = 55,
108
109    // === DateTime Operations ===
110    Datetime = 44,
111    Timestamp = 45,
112    ParseDate = 46,
113    FormatDate = 47,
114    DateDiff = 48,
115    Now = 58,
116
117    // === Error Handling ===
118    Try = 35,
119    Throw = 36,
120
121    // === Type Operations ===
122    Type = 37,
123
124    // === Missing Value Handling ===
125    Missing = 33,
126    MissingSome = 34,
127
128    // === Special Operations ===
129    Preserve = 52,
130}
131
132impl FromStr for OpCode {
133    type Err = ();
134
135    fn from_str(s: &str) -> Result<Self, Self::Err> {
136        match s {
137            "var" => Ok(OpCode::Var),
138            "val" => Ok(OpCode::Val),
139            "==" => Ok(OpCode::Equals),
140            "===" => Ok(OpCode::StrictEquals),
141            "!=" => Ok(OpCode::NotEquals),
142            "!==" => Ok(OpCode::StrictNotEquals),
143            ">" => Ok(OpCode::GreaterThan),
144            ">=" => Ok(OpCode::GreaterThanEqual),
145            "<" => Ok(OpCode::LessThan),
146            "<=" => Ok(OpCode::LessThanEqual),
147            "!" => Ok(OpCode::Not),
148            "!!" => Ok(OpCode::DoubleNot),
149            "and" => Ok(OpCode::And),
150            "or" => Ok(OpCode::Or),
151            "if" => Ok(OpCode::If),
152            "?:" => Ok(OpCode::Ternary),
153            "+" => Ok(OpCode::Add),
154            "-" => Ok(OpCode::Subtract),
155            "*" => Ok(OpCode::Multiply),
156            "/" => Ok(OpCode::Divide),
157            "%" => Ok(OpCode::Modulo),
158            "max" => Ok(OpCode::Max),
159            "min" => Ok(OpCode::Min),
160            "cat" => Ok(OpCode::Cat),
161            "substr" => Ok(OpCode::Substr),
162            "in" => Ok(OpCode::In),
163            "merge" => Ok(OpCode::Merge),
164            "filter" => Ok(OpCode::Filter),
165            "map" => Ok(OpCode::Map),
166            "reduce" => Ok(OpCode::Reduce),
167            "all" => Ok(OpCode::All),
168            "some" => Ok(OpCode::Some),
169            "none" => Ok(OpCode::None),
170            "missing" => Ok(OpCode::Missing),
171            "missing_some" => Ok(OpCode::MissingSome),
172            "try" => Ok(OpCode::Try),
173            "throw" => Ok(OpCode::Throw),
174            "type" => Ok(OpCode::Type),
175            "starts_with" => Ok(OpCode::StartsWith),
176            "ends_with" => Ok(OpCode::EndsWith),
177            "upper" => Ok(OpCode::Upper),
178            "lower" => Ok(OpCode::Lower),
179            "trim" => Ok(OpCode::Trim),
180            "split" => Ok(OpCode::Split),
181            "datetime" => Ok(OpCode::Datetime),
182            "timestamp" => Ok(OpCode::Timestamp),
183            "parse_date" => Ok(OpCode::ParseDate),
184            "format_date" => Ok(OpCode::FormatDate),
185            "date_diff" => Ok(OpCode::DateDiff),
186            "now" => Ok(OpCode::Now),
187            "abs" => Ok(OpCode::Abs),
188            "ceil" => Ok(OpCode::Ceil),
189            "floor" => Ok(OpCode::Floor),
190            "preserve" => Ok(OpCode::Preserve),
191            "length" => Ok(OpCode::Length),
192            "sort" => Ok(OpCode::Sort),
193            "slice" => Ok(OpCode::Slice),
194            "??" => Ok(OpCode::Coalesce),
195            "exists" => Ok(OpCode::Exists),
196            "switch" | "match" => Ok(OpCode::Switch),
197            _ => Err(()),
198        }
199    }
200}
201
202impl OpCode {
203    /// Total number of built-in operators
204    pub const COUNT: usize = 60;
205
206    /// Convert OpCode back to string (for debugging/display)
207    pub fn as_str(&self) -> &'static str {
208        match self {
209            OpCode::Var => "var",
210            OpCode::Val => "val",
211            OpCode::Equals => "==",
212            OpCode::StrictEquals => "===",
213            OpCode::NotEquals => "!=",
214            OpCode::StrictNotEquals => "!==",
215            OpCode::GreaterThan => ">",
216            OpCode::GreaterThanEqual => ">=",
217            OpCode::LessThan => "<",
218            OpCode::LessThanEqual => "<=",
219            OpCode::Not => "!",
220            OpCode::DoubleNot => "!!",
221            OpCode::And => "and",
222            OpCode::Or => "or",
223            OpCode::If => "if",
224            OpCode::Ternary => "?:",
225            OpCode::Add => "+",
226            OpCode::Subtract => "-",
227            OpCode::Multiply => "*",
228            OpCode::Divide => "/",
229            OpCode::Modulo => "%",
230            OpCode::Max => "max",
231            OpCode::Min => "min",
232            OpCode::Cat => "cat",
233            OpCode::Substr => "substr",
234            OpCode::In => "in",
235            OpCode::Merge => "merge",
236            OpCode::Filter => "filter",
237            OpCode::Map => "map",
238            OpCode::Reduce => "reduce",
239            OpCode::All => "all",
240            OpCode::Some => "some",
241            OpCode::None => "none",
242            OpCode::Missing => "missing",
243            OpCode::MissingSome => "missing_some",
244            OpCode::Try => "try",
245            OpCode::Throw => "throw",
246            OpCode::Type => "type",
247            OpCode::StartsWith => "starts_with",
248            OpCode::EndsWith => "ends_with",
249            OpCode::Upper => "upper",
250            OpCode::Lower => "lower",
251            OpCode::Trim => "trim",
252            OpCode::Split => "split",
253            OpCode::Datetime => "datetime",
254            OpCode::Timestamp => "timestamp",
255            OpCode::ParseDate => "parse_date",
256            OpCode::FormatDate => "format_date",
257            OpCode::DateDiff => "date_diff",
258            OpCode::Now => "now",
259            OpCode::Abs => "abs",
260            OpCode::Ceil => "ceil",
261            OpCode::Floor => "floor",
262            OpCode::Preserve => "preserve",
263            OpCode::Length => "length",
264            OpCode::Sort => "sort",
265            OpCode::Slice => "slice",
266            OpCode::Coalesce => "??",
267            OpCode::Exists => "exists",
268            OpCode::Switch => "switch",
269        }
270    }
271
272    /// Direct evaluation method - no boxing, no vtables, no array lookups
273    pub fn evaluate_direct(
274        &self,
275        args: &[crate::CompiledNode],
276        context: &mut crate::ContextStack,
277        engine: &crate::DataLogic,
278    ) -> crate::Result<serde_json::Value> {
279        use crate::operators::{
280            arithmetic, array, comparison, control, datetime, logical, math, missing, preserve,
281            string, throw, try_op, type_op, variable,
282        };
283
284        match self {
285            // Variable access operators - direct function calls
286            OpCode::Var => variable::evaluate_var(args, context, engine),
287            OpCode::Val => variable::evaluate_val(args, context, engine),
288            OpCode::Exists => variable::evaluate_exists(args, context, engine),
289
290            // Comparison operators - direct function calls
291            OpCode::Equals => comparison::evaluate_equals(args, context, engine),
292            OpCode::StrictEquals => comparison::evaluate_strict_equals(args, context, engine),
293            OpCode::NotEquals => comparison::evaluate_not_equals(args, context, engine),
294            OpCode::StrictNotEquals => {
295                comparison::evaluate_strict_not_equals(args, context, engine)
296            }
297            OpCode::GreaterThan => comparison::evaluate_greater_than(args, context, engine),
298            OpCode::GreaterThanEqual => {
299                comparison::evaluate_greater_than_equal(args, context, engine)
300            }
301            OpCode::LessThan => comparison::evaluate_less_than(args, context, engine),
302            OpCode::LessThanEqual => comparison::evaluate_less_than_equal(args, context, engine),
303
304            // Logical operators - direct function calls
305            OpCode::Not => logical::evaluate_not(args, context, engine),
306            OpCode::DoubleNot => logical::evaluate_double_not(args, context, engine),
307            OpCode::And => logical::evaluate_and(args, context, engine),
308            OpCode::Or => logical::evaluate_or(args, context, engine),
309
310            // Control flow - direct function calls
311            OpCode::If => control::evaluate_if(args, context, engine),
312            OpCode::Ternary => control::evaluate_ternary(args, context, engine),
313            OpCode::Coalesce => control::evaluate_coalesce(args, context, engine),
314            OpCode::Switch => control::evaluate_switch(args, context, engine),
315
316            // Arithmetic operators - direct function calls
317            OpCode::Add => arithmetic::evaluate_add(args, context, engine),
318            OpCode::Subtract => arithmetic::evaluate_subtract(args, context, engine),
319            OpCode::Multiply => arithmetic::evaluate_multiply(args, context, engine),
320            OpCode::Divide => arithmetic::evaluate_divide(args, context, engine),
321            OpCode::Modulo => arithmetic::evaluate_modulo(args, context, engine),
322            OpCode::Max => arithmetic::evaluate_max(args, context, engine),
323            OpCode::Min => arithmetic::evaluate_min(args, context, engine),
324            OpCode::Abs => math::evaluate_abs(args, context, engine),
325            OpCode::Ceil => math::evaluate_ceil(args, context, engine),
326            OpCode::Floor => math::evaluate_floor(args, context, engine),
327
328            // String operators - direct function calls
329            OpCode::Cat => string::evaluate_cat(args, context, engine),
330            OpCode::Substr => string::evaluate_substr(args, context, engine),
331            OpCode::In => string::evaluate_in(args, context, engine),
332            OpCode::Length => string::evaluate_length(args, context, engine),
333            OpCode::StartsWith => string::evaluate_starts_with(args, context, engine),
334            OpCode::EndsWith => string::evaluate_ends_with(args, context, engine),
335            OpCode::Upper => string::evaluate_upper(args, context, engine),
336            OpCode::Lower => string::evaluate_lower(args, context, engine),
337            OpCode::Trim => string::evaluate_trim(args, context, engine),
338            OpCode::Split => string::evaluate_split(args, context, engine),
339
340            // Array operators - direct function calls
341            OpCode::Merge => array::evaluate_merge(args, context, engine),
342            OpCode::Filter => array::evaluate_filter(args, context, engine),
343            OpCode::Map => array::evaluate_map(args, context, engine),
344            OpCode::Reduce => array::evaluate_reduce(args, context, engine),
345            OpCode::All => array::evaluate_all(args, context, engine),
346            OpCode::Some => array::evaluate_some(args, context, engine),
347            OpCode::None => array::evaluate_none(args, context, engine),
348            OpCode::Sort => array::evaluate_sort(args, context, engine),
349            OpCode::Slice => array::evaluate_slice(args, context, engine),
350
351            // Special operators - direct function calls
352            OpCode::Missing => missing::evaluate_missing(args, context, engine),
353            OpCode::MissingSome => missing::evaluate_missing_some(args, context, engine),
354            OpCode::Try => try_op::evaluate_try(args, context, engine),
355            OpCode::Throw => throw::evaluate_throw(args, context, engine),
356            OpCode::Type => type_op::evaluate_type(args, context, engine),
357            OpCode::Preserve => preserve::evaluate_preserve(args, context, engine),
358
359            // DateTime operators - direct function calls
360            OpCode::Datetime => datetime::evaluate_datetime(args, context, engine),
361            OpCode::Timestamp => datetime::evaluate_timestamp(args, context, engine),
362            OpCode::ParseDate => datetime::evaluate_parse_date(args, context, engine),
363            OpCode::FormatDate => datetime::evaluate_format_date(args, context, engine),
364            OpCode::DateDiff => datetime::evaluate_date_diff(args, context, engine),
365            OpCode::Now => datetime::evaluate_now(args, context, engine),
366        }
367    }
368
369    /// Traced evaluation method - records steps for debugging.
370    ///
371    /// This method dispatches to traced versions of operators that need special
372    /// handling (iteration and short-circuit operators), while regular operators
373    /// use the standard evaluation with child tracing.
374    pub fn evaluate_traced(
375        &self,
376        args: &[crate::CompiledNode],
377        context: &mut crate::ContextStack,
378        engine: &crate::DataLogic,
379        collector: &mut crate::trace::TraceCollector,
380        node_id_map: &std::collections::HashMap<usize, u32>,
381    ) -> crate::Result<serde_json::Value> {
382        use crate::operators::{array, control, logical, throw, try_op};
383
384        match self {
385            // Iteration operators - need traced versions
386            OpCode::Map => {
387                array::evaluate_map_traced(args, context, engine, collector, node_id_map)
388            }
389            OpCode::Filter => {
390                array::evaluate_filter_traced(args, context, engine, collector, node_id_map)
391            }
392            OpCode::Reduce => {
393                array::evaluate_reduce_traced(args, context, engine, collector, node_id_map)
394            }
395            OpCode::All => {
396                array::evaluate_all_traced(args, context, engine, collector, node_id_map)
397            }
398            OpCode::Some => {
399                array::evaluate_some_traced(args, context, engine, collector, node_id_map)
400            }
401            OpCode::None => {
402                array::evaluate_none_traced(args, context, engine, collector, node_id_map)
403            }
404
405            // Short-circuit logical operators - need traced versions
406            OpCode::And => {
407                logical::evaluate_and_traced(args, context, engine, collector, node_id_map)
408            }
409            OpCode::Or => {
410                logical::evaluate_or_traced(args, context, engine, collector, node_id_map)
411            }
412
413            // Control flow operators - need traced versions
414            OpCode::If => {
415                control::evaluate_if_traced(args, context, engine, collector, node_id_map)
416            }
417            OpCode::Ternary => {
418                control::evaluate_ternary_traced(args, context, engine, collector, node_id_map)
419            }
420            OpCode::Coalesce => {
421                control::evaluate_coalesce_traced(args, context, engine, collector, node_id_map)
422            }
423            OpCode::Switch => {
424                control::evaluate_switch_traced(args, context, engine, collector, node_id_map)
425            }
426
427            // Error handling operators - need traced versions
428            OpCode::Try => {
429                try_op::evaluate_try_traced(args, context, engine, collector, node_id_map)
430            }
431            OpCode::Throw => {
432                throw::evaluate_throw_traced(args, context, engine, collector, node_id_map)
433            }
434
435            // All other operators - evaluate children with tracing, then apply operator
436            _ => {
437                // Evaluate all arguments with tracing
438                let mut evaluated_args: Vec<serde_json::Value> = Vec::with_capacity(args.len());
439                for arg in args {
440                    let value =
441                        engine.evaluate_node_traced(arg, context, collector, node_id_map)?;
442                    evaluated_args.push(value);
443                }
444
445                // Create temporary Value nodes for the direct evaluation
446                let value_nodes: Vec<crate::CompiledNode> = evaluated_args
447                    .into_iter()
448                    .map(|v| crate::CompiledNode::Value { value: v })
449                    .collect();
450
451                // Evaluate the operator with pre-evaluated arguments
452                self.evaluate_direct(&value_nodes, context, engine)
453            }
454        }
455    }
456}