Skip to main content

json_eval_rs/rlogic/
bytecode.rs

1use super::compiled::CompiledLogic;
2use serde_json::Value;
3
4/// Compact opcodes for table cell evaluation without AST tree recursion
5#[derive(Debug, Clone, Copy, PartialEq)]
6pub enum TableOp {
7    /// Push literal constant number onto the stack
8    PushConst(f64),
9    /// Push current $iteration raw integer as f64
10    PushIteration,
11    /// Push column from current row in flat_cells
12    PushCol(usize),
13    /// Push cell from self table at (iteration_raw + iter_delta, col_idx)
14    PushValueAt {
15        col_idx: usize,
16        iter_delta: i32,
17    },
18    /// Push cell from self table at constant row index (e.g. static row 0)
19    PushValueAtConstRow {
20        col_idx: usize,
21        const_row: usize,
22    },
23    /// Basic arithmetic
24    Add,
25    Subtract,
26    Multiply,
27    Divide,
28    Modulo,
29    Negate,
30    Power,
31    Abs,
32    /// Excel rounding
33    Round(i32),
34    RoundUp(i32),
35    RoundDown(i32),
36    /// Aggregation
37    Min(u8),
38    Max(u8),
39    /// Comparison
40    Eq,
41    Ne,
42    Lt,
43    Lte,
44    Gt,
45    Gte,
46    /// Logical
47    Not,
48    /// Control flow
49    Jump(usize),
50    JumpIfZero(usize),
51    JumpIfNotZero(usize),
52}
53
54/// Compiled linear bytecode for high-speed inner table cell evaluation
55#[derive(Debug, Clone)]
56pub struct TableBytecode {
57    pub ops: Vec<TableOp>,
58}
59
60impl TableBytecode {
61    /// Execute bytecode expression against flat cell buffer in a tight stack loop
62    ///
63    /// # Safety
64    /// `flat_cells` must point to a buffer of at least `total_rows * col_count` Values.
65    /// `static_rows` must point to valid `Vec<Value>` of previous rows.
66    #[inline(always)]
67    pub unsafe fn execute(
68        &self,
69        flat_cells: *const Value,
70        col_count: usize,
71        row_offset: usize,
72        existing_row_count: usize,
73        total_rows: usize,
74        iteration_raw: i64,
75        static_rows: *const Vec<Value>,
76        col_names: &[String],
77    ) -> Option<f64> {
78        let mut stack: [f64; 64] = [0.0; 64];
79        let mut sp: usize = 0;
80        let mut pc: usize = 0;
81        let mut steps: usize = 0;
82        let num_ops = self.ops.len();
83
84        while pc < num_ops {
85            steps += 1;
86            if steps > 512 {
87                return None;
88            }
89
90            match self.ops[pc] {
91                TableOp::PushConst(val) => {
92                    if sp >= 64 {
93                        return None;
94                    }
95                    stack[sp] = val;
96                    sp += 1;
97                    pc += 1;
98                }
99                TableOp::PushIteration => {
100                    if sp >= 64 {
101                        return None;
102                    }
103                    stack[sp] = iteration_raw as f64;
104                    sp += 1;
105                    pc += 1;
106                }
107                TableOp::PushCol(col_idx) => {
108                    if sp >= 64 {
109                        return None;
110                    }
111                    let cell = &*flat_cells.add(row_offset * col_count + col_idx);
112                    let val = match cell {
113                        Value::Number(n) => n.as_f64().unwrap_or(0.0),
114                        _ => super::evaluator::helpers::to_f64(cell),
115                    };
116                    stack[sp] = val;
117                    sp += 1;
118                    pc += 1;
119                }
120                TableOp::PushValueAt {
121                    col_idx,
122                    iter_delta,
123                } => {
124                    if sp >= 64 {
125                        return None;
126                    }
127                    let target_iter = iteration_raw + iter_delta as i64;
128                    let val = if target_iter >= 0 {
129                        let target_row = target_iter as usize;
130                        if target_row < existing_row_count {
131                            let rows = &*static_rows;
132                            if let Some(row) = rows.get(target_row) {
133                                if let Value::Object(map) = row {
134                                    let col_name = &col_names[col_idx];
135                                    if let Some(cell) = map.get(col_name) {
136                                        match cell {
137                                            Value::Number(n) => n.as_f64().unwrap_or(0.0),
138                                            _ => super::evaluator::helpers::to_f64(cell),
139                                        }
140                                    } else {
141                                        0.0
142                                    }
143                                } else {
144                                    0.0
145                                }
146                            } else {
147                                0.0
148                            }
149                        } else if target_row < existing_row_count + total_rows {
150                            let cell_offset = target_row - existing_row_count;
151                            let cell = &*flat_cells.add(cell_offset * col_count + col_idx);
152                            match cell {
153                                Value::Number(n) => n.as_f64().unwrap_or(0.0),
154                                _ => super::evaluator::helpers::to_f64(cell),
155                            }
156                        } else {
157                            0.0
158                        }
159                    } else {
160                        0.0
161                    };
162                    stack[sp] = val;
163                    sp += 1;
164                    pc += 1;
165                }
166                TableOp::PushValueAtConstRow { col_idx, const_row } => {
167                    if sp >= 64 {
168                        return None;
169                    }
170                    let val = if const_row < existing_row_count {
171                        let rows = &*static_rows;
172                        if let Some(row) = rows.get(const_row) {
173                            if let Value::Object(map) = row {
174                                let col_name = &col_names[col_idx];
175                                if let Some(cell) = map.get(col_name) {
176                                    match cell {
177                                        Value::Number(n) => n.as_f64().unwrap_or(0.0),
178                                        _ => super::evaluator::helpers::to_f64(cell),
179                                    }
180                                } else {
181                                    0.0
182                                }
183                            } else {
184                                0.0
185                            }
186                        } else {
187                            0.0
188                        }
189                    } else if const_row < existing_row_count + total_rows {
190                        let cell_offset = const_row - existing_row_count;
191                        let cell = &*flat_cells.add(cell_offset * col_count + col_idx);
192                        match cell {
193                            Value::Number(n) => n.as_f64().unwrap_or(0.0),
194                            _ => super::evaluator::helpers::to_f64(cell),
195                        }
196                    } else {
197                        0.0
198                    };
199                    stack[sp] = val;
200                    sp += 1;
201                    pc += 1;
202                }
203                TableOp::Add => {
204                    if sp < 2 {
205                        return None;
206                    }
207                    sp -= 1;
208                    stack[sp - 1] += stack[sp];
209                    pc += 1;
210                }
211                TableOp::Subtract => {
212                    if sp < 2 {
213                        return None;
214                    }
215                    sp -= 1;
216                    stack[sp - 1] -= stack[sp];
217                    pc += 1;
218                }
219                TableOp::Multiply => {
220                    if sp < 2 {
221                        return None;
222                    }
223                    sp -= 1;
224                    stack[sp - 1] *= stack[sp];
225                    pc += 1;
226                }
227                TableOp::Divide => {
228                    if sp < 2 {
229                        return None;
230                    }
231                    sp -= 1;
232                    let divisor = stack[sp];
233                    if divisor == 0.0 {
234                        return None;
235                    }
236                    stack[sp - 1] /= divisor;
237                    pc += 1;
238                }
239                TableOp::Modulo => {
240                    if sp < 2 {
241                        return None;
242                    }
243                    sp -= 1;
244                    let divisor = stack[sp];
245                    if divisor == 0.0 {
246                        return None;
247                    }
248                    stack[sp - 1] %= divisor;
249                    pc += 1;
250                }
251                TableOp::Negate => {
252                    if sp < 1 {
253                        return None;
254                    }
255                    stack[sp - 1] = -stack[sp - 1];
256                    pc += 1;
257                }
258                TableOp::Power => {
259                    if sp < 2 {
260                        return None;
261                    }
262                    sp -= 1;
263                    stack[sp - 1] = stack[sp - 1].powf(stack[sp]);
264                    pc += 1;
265                }
266                TableOp::Abs => {
267                    if sp < 1 {
268                        return None;
269                    }
270                    stack[sp - 1] = stack[sp - 1].abs();
271                    pc += 1;
272                }
273                TableOp::Round(decimals) => {
274                    if sp < 1 {
275                        return None;
276                    }
277                    let num = stack[sp - 1];
278                    stack[sp - 1] = if decimals == 0 {
279                        num.round()
280                    } else if decimals > 0 {
281                        let mult = 10f64.powi(decimals);
282                        (num * mult).round() / mult
283                    } else {
284                        let div = 10f64.powi(-decimals);
285                        (num / div).round() * div
286                    };
287                    pc += 1;
288                }
289                TableOp::RoundUp(decimals) => {
290                    if sp < 1 {
291                        return None;
292                    }
293                    let num = stack[sp - 1];
294                    stack[sp - 1] = if decimals == 0 {
295                        num.ceil()
296                    } else if decimals > 0 {
297                        let mult = 10f64.powi(decimals);
298                        (num * mult).ceil() / mult
299                    } else {
300                        let div = 10f64.powi(-decimals);
301                        (num / div).ceil() * div
302                    };
303                    pc += 1;
304                }
305                TableOp::RoundDown(decimals) => {
306                    if sp < 1 {
307                        return None;
308                    }
309                    let num = stack[sp - 1];
310                    stack[sp - 1] = if decimals == 0 {
311                        num.floor()
312                    } else if decimals > 0 {
313                        let mult = 10f64.powi(decimals);
314                        (num * mult).floor() / mult
315                    } else {
316                        let div = 10f64.powi(-decimals);
317                        (num / div).floor() * div
318                    };
319                    pc += 1;
320                }
321                TableOp::Min(count) => {
322                    let n = count as usize;
323                    if sp < n || n == 0 {
324                        return None;
325                    }
326                    let mut min_val = stack[sp - n];
327                    for i in 1..n {
328                        let v = stack[sp - n + i];
329                        if v < min_val {
330                            min_val = v;
331                        }
332                    }
333                    sp -= n - 1;
334                    stack[sp - 1] = min_val;
335                    pc += 1;
336                }
337                TableOp::Max(count) => {
338                    let n = count as usize;
339                    if sp < n || n == 0 {
340                        return None;
341                    }
342                    let mut max_val = stack[sp - n];
343                    for i in 1..n {
344                        let v = stack[sp - n + i];
345                        if v > max_val {
346                            max_val = v;
347                        }
348                    }
349                    sp -= n - 1;
350                    stack[sp - 1] = max_val;
351                    pc += 1;
352                }
353                TableOp::Eq => {
354                    if sp < 2 {
355                        return None;
356                    }
357                    sp -= 1;
358                    let b = stack[sp];
359                    let a = stack[sp - 1];
360                    stack[sp - 1] = if (a == b) || (a - b).abs() < 1e-9 {
361                        1.0
362                    } else {
363                        0.0
364                    };
365                    pc += 1;
366                }
367                TableOp::Ne => {
368                    if sp < 2 {
369                        return None;
370                    }
371                    sp -= 1;
372                    let b = stack[sp];
373                    let a = stack[sp - 1];
374                    stack[sp - 1] = if (a != b) && (a - b).abs() >= 1e-9 {
375                        1.0
376                    } else {
377                        0.0
378                    };
379                    pc += 1;
380                }
381                TableOp::Lt => {
382                    if sp < 2 {
383                        return None;
384                    }
385                    sp -= 1;
386                    stack[sp - 1] = if stack[sp - 1] < stack[sp] { 1.0 } else { 0.0 };
387                    pc += 1;
388                }
389                TableOp::Lte => {
390                    if sp < 2 {
391                        return None;
392                    }
393                    sp -= 1;
394                    stack[sp - 1] = if stack[sp - 1] <= stack[sp] { 1.0 } else { 0.0 };
395                    pc += 1;
396                }
397                TableOp::Gt => {
398                    if sp < 2 {
399                        return None;
400                    }
401                    sp -= 1;
402                    stack[sp - 1] = if stack[sp - 1] > stack[sp] { 1.0 } else { 0.0 };
403                    pc += 1;
404                }
405                TableOp::Gte => {
406                    if sp < 2 {
407                        return None;
408                    }
409                    sp -= 1;
410                    stack[sp - 1] = if stack[sp - 1] >= stack[sp] { 1.0 } else { 0.0 };
411                    pc += 1;
412                }
413                TableOp::Not => {
414                    if sp < 1 {
415                        return None;
416                    }
417                    stack[sp - 1] = if stack[sp - 1] == 0.0 { 1.0 } else { 0.0 };
418                    pc += 1;
419                }
420                TableOp::Jump(target) => {
421                    pc = target;
422                }
423                TableOp::JumpIfZero(target) => {
424                    if sp < 1 {
425                        return None;
426                    }
427                    sp -= 1;
428                    if stack[sp] == 0.0 {
429                        pc = target;
430                    } else {
431                        pc += 1;
432                    }
433                }
434                TableOp::JumpIfNotZero(target) => {
435                    if sp < 1 {
436                        return None;
437                    }
438                    sp -= 1;
439                    if stack[sp] != 0.0 {
440                        pc = target;
441                    } else {
442                        pc += 1;
443                    }
444                }
445            }
446        }
447
448        if sp == 1 {
449            Some(stack[0])
450        } else {
451            None
452        }
453    }
454}
455
456/// Attempt to lower a CompiledLogic expression to linear TableBytecode
457pub fn try_lower_to_bytecode(
458    logic: &CompiledLogic,
459    table_path: &str,
460    table_no_hash: &str,
461    col_map: &rapidhash::RapidHashMap<String, usize>,
462) -> Option<TableBytecode> {
463    let mut ops = Vec::new();
464    if lower_node(logic, &mut ops, table_path, table_no_hash, col_map) {
465        Some(TableBytecode { ops })
466    } else {
467        None
468    }
469}
470
471fn lower_node(
472    logic: &CompiledLogic,
473    ops: &mut Vec<TableOp>,
474    table_path: &str,
475    table_no_hash: &str,
476    col_map: &rapidhash::RapidHashMap<String, usize>,
477) -> bool {
478    match logic {
479        CompiledLogic::Number(n) => {
480            ops.push(TableOp::PushConst(*n));
481            true
482        }
483        CompiledLogic::Bool(b) => {
484            ops.push(TableOp::PushConst(if *b { 1.0 } else { 0.0 }));
485            true
486        }
487        CompiledLogic::Null => {
488            ops.push(TableOp::PushConst(0.0));
489            true
490        }
491        CompiledLogic::String(s) => {
492            if let Ok(n) = s.parse::<f64>() {
493                ops.push(TableOp::PushConst(n));
494                true
495            } else {
496                false
497            }
498        }
499        CompiledLogic::Var(name, _) | CompiledLogic::Ref(name, _)
500            if name == "$iteration" || name == "/$iteration" =>
501        {
502            ops.push(TableOp::PushIteration);
503            true
504        }
505        CompiledLogic::Var(name, _) | CompiledLogic::Ref(name, _) => {
506            let stripped = if name.starts_with("/$") {
507                &name[2..]
508            } else if name.starts_with('$') {
509                &name[1..]
510            } else {
511                name.as_str()
512            };
513            if !stripped.contains('/') {
514                if let Some(&col_idx) = col_map.get(stripped) {
515                    ops.push(TableOp::PushCol(col_idx));
516                    return true;
517                }
518            }
519            false
520        }
521        CompiledLogic::ValueAt(table, row_idx_expr, col_name_expr) => {
522            let table_name = table_no_hash.rsplit('/').next().unwrap_or(table_no_hash);
523            let is_self_table = match table.as_ref() {
524                CompiledLogic::Var(name, _) | CompiledLogic::Ref(name, _) => {
525                    name == table_path
526                        || name == table_no_hash
527                        || name.strip_prefix('#').unwrap_or(name) == table_no_hash
528                        || (!table_name.is_empty()
529                            && (name == table_name
530                                || name.ends_with(&format!("/{}", table_name))
531                                || name.ends_with(&format!(".{}", table_name))))
532                }
533                _ => false,
534            };
535            if !is_self_table {
536                return false;
537            }
538
539            let col_name = match col_name_expr.as_ref() {
540                Some(c) => match c.as_ref() {
541                    CompiledLogic::String(s) => s.as_str(),
542                    _ => return false,
543                },
544                None => return false,
545            };
546            let col_idx = match col_map.get(col_name) {
547                Some(&idx) => idx,
548                None => return false,
549            };
550
551            match row_idx_expr.as_ref() {
552                CompiledLogic::Var(var, _) | CompiledLogic::Ref(var, _)
553                    if var == "$iteration" || var == "/$iteration" =>
554                {
555                    ops.push(TableOp::PushValueAt {
556                        col_idx,
557                        iter_delta: 0,
558                    });
559                    true
560                }
561                CompiledLogic::Subtract(items) if items.len() == 2 => {
562                    match (&items[0], &items[1]) {
563                        (
564                            CompiledLogic::Var(var, _) | CompiledLogic::Ref(var, _),
565                            CompiledLogic::Number(n),
566                        ) if var == "$iteration" || var == "/$iteration" => {
567                            ops.push(TableOp::PushValueAt {
568                                col_idx,
569                                iter_delta: -(*n as i32),
570                            });
571                            true
572                        }
573                        _ => false,
574                    }
575                }
576                CompiledLogic::Add(items) if items.len() == 2 => match (&items[0], &items[1]) {
577                    (
578                        CompiledLogic::Var(var, _) | CompiledLogic::Ref(var, _),
579                        CompiledLogic::Number(n),
580                    )
581                    | (
582                        CompiledLogic::Number(n),
583                        CompiledLogic::Var(var, _) | CompiledLogic::Ref(var, _),
584                    ) if var == "$iteration" || var == "/$iteration" => {
585                        ops.push(TableOp::PushValueAt {
586                            col_idx,
587                            iter_delta: *n as i32,
588                        });
589                        true
590                    }
591                    _ => false,
592                },
593                CompiledLogic::Number(n) if *n >= 0.0 => {
594                    ops.push(TableOp::PushValueAtConstRow {
595                        col_idx,
596                        const_row: *n as usize,
597                    });
598                    true
599                }
600                _ => false,
601            }
602        }
603        CompiledLogic::Add(items) => {
604            if items.is_empty() {
605                return false;
606            }
607            if !lower_node(&items[0], ops, table_path, table_no_hash, col_map) {
608                return false;
609            }
610            for item in &items[1..] {
611                if !lower_node(item, ops, table_path, table_no_hash, col_map) {
612                    return false;
613                }
614                ops.push(TableOp::Add);
615            }
616            true
617        }
618        CompiledLogic::Subtract(items) => {
619            if items.is_empty() {
620                return false;
621            }
622            if !lower_node(&items[0], ops, table_path, table_no_hash, col_map) {
623                return false;
624            }
625            if items.len() == 1 {
626                ops.push(TableOp::Negate);
627                return true;
628            }
629            for item in &items[1..] {
630                if !lower_node(item, ops, table_path, table_no_hash, col_map) {
631                    return false;
632                }
633                ops.push(TableOp::Subtract);
634            }
635            true
636        }
637        CompiledLogic::Multiply(items) => {
638            if items.is_empty() {
639                return false;
640            }
641            if !lower_node(&items[0], ops, table_path, table_no_hash, col_map) {
642                return false;
643            }
644            for item in &items[1..] {
645                if !lower_node(item, ops, table_path, table_no_hash, col_map) {
646                    return false;
647                }
648                ops.push(TableOp::Multiply);
649            }
650            true
651        }
652        CompiledLogic::Divide(items) => {
653            if items.is_empty() {
654                return false;
655            }
656            if !lower_node(&items[0], ops, table_path, table_no_hash, col_map) {
657                return false;
658            }
659            for item in &items[1..] {
660                if !lower_node(item, ops, table_path, table_no_hash, col_map) {
661                    return false;
662                }
663                ops.push(TableOp::Divide);
664            }
665            true
666        }
667        CompiledLogic::Modulo(a, b) => {
668            if !lower_node(a, ops, table_path, table_no_hash, col_map) {
669                return false;
670            }
671            if !lower_node(b, ops, table_path, table_no_hash, col_map) {
672                return false;
673            }
674            ops.push(TableOp::Modulo);
675            true
676        }
677        CompiledLogic::Power(a, b) => {
678            if !lower_node(a, ops, table_path, table_no_hash, col_map) {
679                return false;
680            }
681            if !lower_node(b, ops, table_path, table_no_hash, col_map) {
682                return false;
683            }
684            ops.push(TableOp::Power);
685            true
686        }
687        CompiledLogic::Abs(a) => {
688            if !lower_node(a, ops, table_path, table_no_hash, col_map) {
689                return false;
690            }
691            ops.push(TableOp::Abs);
692            true
693        }
694        CompiledLogic::Round(a, decimals_expr) => {
695            let decimals = match decimals_expr.as_ref() {
696                Some(d) => match d.as_ref() {
697                    CompiledLogic::Number(n) => *n as i32,
698                    _ => return false,
699                },
700                None => 0,
701            };
702            if !lower_node(a, ops, table_path, table_no_hash, col_map) {
703                return false;
704            }
705            ops.push(TableOp::Round(decimals));
706            true
707        }
708        CompiledLogic::RoundUp(a, decimals_expr) => {
709            let decimals = match decimals_expr.as_ref() {
710                Some(d) => match d.as_ref() {
711                    CompiledLogic::Number(n) => *n as i32,
712                    _ => return false,
713                },
714                None => 0,
715            };
716            if !lower_node(a, ops, table_path, table_no_hash, col_map) {
717                return false;
718            }
719            ops.push(TableOp::RoundUp(decimals));
720            true
721        }
722        CompiledLogic::RoundDown(a, decimals_expr) => {
723            let decimals = match decimals_expr.as_ref() {
724                Some(d) => match d.as_ref() {
725                    CompiledLogic::Number(n) => *n as i32,
726                    _ => return false,
727                },
728                None => 0,
729            };
730            if !lower_node(a, ops, table_path, table_no_hash, col_map) {
731                return false;
732            }
733            ops.push(TableOp::RoundDown(decimals));
734            true
735        }
736        CompiledLogic::Min(items) => {
737            if items.is_empty() || items.len() > 32 {
738                return false;
739            }
740            for item in items {
741                if !lower_node(item, ops, table_path, table_no_hash, col_map) {
742                    return false;
743                }
744            }
745            ops.push(TableOp::Min(items.len() as u8));
746            true
747        }
748        CompiledLogic::Max(items) => {
749            if items.is_empty() || items.len() > 32 {
750                return false;
751            }
752            for item in items {
753                if !lower_node(item, ops, table_path, table_no_hash, col_map) {
754                    return false;
755                }
756            }
757            ops.push(TableOp::Max(items.len() as u8));
758            true
759        }
760        CompiledLogic::Equal(a, b) | CompiledLogic::StrictEqual(a, b) => {
761            if !lower_node(a, ops, table_path, table_no_hash, col_map)
762                || !lower_node(b, ops, table_path, table_no_hash, col_map)
763            {
764                return false;
765            }
766            ops.push(TableOp::Eq);
767            true
768        }
769        CompiledLogic::NotEqual(a, b) | CompiledLogic::StrictNotEqual(a, b) => {
770            if !lower_node(a, ops, table_path, table_no_hash, col_map)
771                || !lower_node(b, ops, table_path, table_no_hash, col_map)
772            {
773                return false;
774            }
775            ops.push(TableOp::Ne);
776            true
777        }
778        CompiledLogic::LessThan(a, b) => {
779            if !lower_node(a, ops, table_path, table_no_hash, col_map)
780                || !lower_node(b, ops, table_path, table_no_hash, col_map)
781            {
782                return false;
783            }
784            ops.push(TableOp::Lt);
785            true
786        }
787        CompiledLogic::LessThanOrEqual(a, b) => {
788            if !lower_node(a, ops, table_path, table_no_hash, col_map)
789                || !lower_node(b, ops, table_path, table_no_hash, col_map)
790            {
791                return false;
792            }
793            ops.push(TableOp::Lte);
794            true
795        }
796        CompiledLogic::GreaterThan(a, b) => {
797            if !lower_node(a, ops, table_path, table_no_hash, col_map)
798                || !lower_node(b, ops, table_path, table_no_hash, col_map)
799            {
800                return false;
801            }
802            ops.push(TableOp::Gt);
803            true
804        }
805        CompiledLogic::GreaterThanOrEqual(a, b) => {
806            if !lower_node(a, ops, table_path, table_no_hash, col_map)
807                || !lower_node(b, ops, table_path, table_no_hash, col_map)
808            {
809                return false;
810            }
811            ops.push(TableOp::Gte);
812            true
813        }
814        CompiledLogic::Not(inner) => {
815            if !lower_node(inner, ops, table_path, table_no_hash, col_map) {
816                return false;
817            }
818            ops.push(TableOp::Not);
819            true
820        }
821        CompiledLogic::If(cond, then, else_expr) => {
822            if !lower_node(cond, ops, table_path, table_no_hash, col_map) {
823                return false;
824            }
825            let jz_idx = ops.len();
826            ops.push(TableOp::JumpIfZero(0));
827            if !lower_node(then, ops, table_path, table_no_hash, col_map) {
828                return false;
829            }
830            let jmp_idx = ops.len();
831            ops.push(TableOp::Jump(0));
832            ops[jz_idx] = TableOp::JumpIfZero(ops.len());
833            if !lower_node(else_expr, ops, table_path, table_no_hash, col_map) {
834                return false;
835            }
836            ops[jmp_idx] = TableOp::Jump(ops.len());
837            true
838        }
839        CompiledLogic::And(items) => {
840            if items.is_empty() {
841                ops.push(TableOp::PushConst(1.0));
842                return true;
843            }
844            if items.len() == 1 {
845                return lower_node(&items[0], ops, table_path, table_no_hash, col_map);
846            }
847            let mut jz_indices = Vec::new();
848            for item in items {
849                if !lower_node(item, ops, table_path, table_no_hash, col_map) {
850                    return false;
851                }
852                jz_indices.push(ops.len());
853                ops.push(TableOp::JumpIfZero(0));
854            }
855            ops.push(TableOp::PushConst(1.0));
856            let jmp_end = ops.len();
857            ops.push(TableOp::Jump(0));
858            let false_target = ops.len();
859            for jz in jz_indices {
860                ops[jz] = TableOp::JumpIfZero(false_target);
861            }
862            ops.push(TableOp::PushConst(0.0));
863            ops[jmp_end] = TableOp::Jump(ops.len());
864            true
865        }
866        CompiledLogic::Or(items) => {
867            if items.is_empty() {
868                ops.push(TableOp::PushConst(0.0));
869                return true;
870            }
871            if items.len() == 1 {
872                return lower_node(&items[0], ops, table_path, table_no_hash, col_map);
873            }
874            let mut jnz_indices = Vec::new();
875            for item in items {
876                if !lower_node(item, ops, table_path, table_no_hash, col_map) {
877                    return false;
878                }
879                jnz_indices.push(ops.len());
880                ops.push(TableOp::JumpIfNotZero(0));
881            }
882            ops.push(TableOp::PushConst(0.0));
883            let jmp_end = ops.len();
884            ops.push(TableOp::Jump(0));
885            let true_target = ops.len();
886            for jnz in jnz_indices {
887                ops[jnz] = TableOp::JumpIfNotZero(true_target);
888            }
889            ops.push(TableOp::PushConst(1.0));
890            ops[jmp_end] = TableOp::Jump(ops.len());
891            true
892        }
893        _ => false,
894    }
895}
896
897#[cfg(test)]
898mod tests {
899    use super::*;
900
901    #[test]
902    fn test_bytecode_arithmetic() {
903        let bc = TableBytecode {
904            ops: vec![
905                TableOp::PushConst(10.0),
906                TableOp::PushConst(5.0),
907                TableOp::Add,
908                TableOp::PushConst(3.0),
909                TableOp::Multiply,
910            ],
911        };
912        let dummy_cells = vec![];
913        let dummy_rows = vec![];
914        let col_names = vec![];
915        let res = unsafe {
916            bc.execute(
917                dummy_cells.as_ptr(),
918                0,
919                0,
920                0,
921                0,
922                1,
923                &dummy_rows as *const Vec<Value>,
924                &col_names,
925            )
926        };
927        assert_eq!(res, Some(45.0)); // (10 + 5) * 3 = 45
928    }
929
930    #[test]
931    fn test_bytecode_conditional() {
932        // if 10 > 5 then 100 else 200
933        let bc = TableBytecode {
934            ops: vec![
935                TableOp::PushConst(10.0),
936                TableOp::PushConst(5.0),
937                TableOp::Gt,
938                TableOp::JumpIfZero(6),
939                TableOp::PushConst(100.0),
940                TableOp::Jump(7),
941                TableOp::PushConst(200.0),
942            ],
943        };
944        let dummy_cells = vec![];
945        let dummy_rows = vec![];
946        let col_names = vec![];
947        let res = unsafe {
948            bc.execute(
949                dummy_cells.as_ptr(),
950                0,
951                0,
952                0,
953                0,
954                1,
955                &dummy_rows as *const Vec<Value>,
956                &col_names,
957            )
958        };
959        assert_eq!(res, Some(100.0));
960    }
961
962    #[test]
963    fn test_bytecode_push_cell_and_value_at() {
964        let cells = vec![
965            Value::from(10.0),
966            Value::from(20.0),
967            Value::from(30.0),
968            Value::from(40.0),
969        ];
970        let static_rows = vec![];
971        let col_names = vec!["A".to_string(), "B".to_string()];
972
973        // row 1: PushCol(0) [30.0] + PushValueAt(1, -1) [20.0] = 50.0
974        let bc = TableBytecode {
975            ops: vec![
976                TableOp::PushCol(0),
977                TableOp::PushValueAt {
978                    col_idx: 1,
979                    iter_delta: -1,
980                },
981                TableOp::Add,
982            ],
983        };
984        let res = unsafe {
985            bc.execute(
986                cells.as_ptr(),
987                2,
988                1, // row_offset = 1
989                0, // existing_row_count = 0
990                2, // total_rows = 2
991                1, // iteration = 1
992                &static_rows as *const Vec<Value>,
993                &col_names,
994            )
995        };
996        assert_eq!(res, Some(50.0));
997    }
998}