1use super::compiled::CompiledLogic;
2use serde_json::Value;
3
4#[derive(Debug, Clone, Copy, PartialEq)]
6pub enum TableOp {
7 PushConst(f64),
9 PushIteration,
11 PushCol(usize),
13 PushValueAt {
15 col_idx: usize,
16 iter_delta: i32,
17 },
18 PushValueAtConstRow {
20 col_idx: usize,
21 const_row: usize,
22 },
23 Add,
25 Subtract,
26 Multiply,
27 Divide,
28 Modulo,
29 Negate,
30 Power,
31 Abs,
32 Round(i32),
34 RoundUp(i32),
35 RoundDown(i32),
36 Min(u8),
38 Max(u8),
39 Eq,
41 Ne,
42 Lt,
43 Lte,
44 Gt,
45 Gte,
46 Not,
48 Jump(usize),
50 JumpIfZero(usize),
51 JumpIfNotZero(usize),
52}
53
54#[derive(Debug, Clone)]
56pub struct TableBytecode {
57 pub ops: Vec<TableOp>,
58}
59
60impl TableBytecode {
61 #[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
456pub 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)); }
929
930 #[test]
931 fn test_bytecode_conditional() {
932 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 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, 0, 2, 1, &static_rows as *const Vec<Value>,
993 &col_names,
994 )
995 };
996 assert_eq!(res, Some(50.0));
997 }
998}