From d791fae7f5204f5ddb072708a69eb03a4a744724 Mon Sep 17 00:00:00 2001 From: "Heinz N. Gies" Date: Thu, 8 Aug 2024 21:04:33 +0200 Subject: [PATCH 01/12] initial state Signed-off-by: Heinz N. Gies --- .vscode/settings.json | 6 - Cargo.lock | 36 +- tremor-script/src/ast.rs | 2 +- tremor-script/src/ast/binary.rs | 2 +- .../src/ast/visitors/impls/const_folder.rs | 12 +- tremor-script/src/errors.rs | 9 - tremor-script/src/interpreter.rs | 61 +- tremor-script/src/interpreter/imut_expr.rs | 10 +- tremor-script/src/lib.rs | 5 +- tremor-script/src/vm.rs | 578 +++++++++++++++ tremor-script/src/vm/compiler.rs | 524 ++++++++++++++ tremor-script/src/vm/tests.rs | 675 ++++++++++++++++++ tremor-value/src/value.rs | 7 + 13 files changed, 1849 insertions(+), 78 deletions(-) create mode 100644 tremor-script/src/vm.rs create mode 100644 tremor-script/src/vm/compiler.rs create mode 100644 tremor-script/src/vm/tests.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index 680c453722..49d4caf034 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -131,11 +131,5 @@ ], "rust-analyzer.linkedProjects": [ "./Cargo.toml", - "./tremor-connectors/Cargo.toml", - "./tremor-connectors/Cargo.toml", - "./tremor-connectors/Cargo.toml", - "./tremor-connectors-gcp/Cargo.toml", - "./tremor-connectors-gcp/Cargo.toml", - "./tremor-connectors-gcp/Cargo.toml" ] } \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 50850b1c5d..339641c56a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -292,9 +292,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.12" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fec134f64e2bc57411226dfc4e52dec859ddfc7e711fc5e07b612584f000e4aa" +checksum = "cd066d0b4ef8ecb03a55319dc13aa6910616d0f44008a045bb1835af830abff5" dependencies = [ "flate2", "futures-core", @@ -1223,9 +1223,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.1" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" [[package]] name = "bytes-utils" @@ -1427,9 +1427,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.9" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462" +checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d" dependencies = [ "clap_builder", "clap_derive", @@ -1437,9 +1437,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.9" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942" +checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708" dependencies = [ "anstream", "anstyle", @@ -2299,9 +2299,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.5" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" dependencies = [ "anstream", "anstyle", @@ -4208,9 +4208,9 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" -version = "0.10.66" +version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ "bitflags 2.5.0", "cfg-if", @@ -4240,9 +4240,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.103" +version = "0.9.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" dependencies = [ "cc", "libc", @@ -6685,18 +6685,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", diff --git a/tremor-script/src/ast.rs b/tremor-script/src/ast.rs index a6814466ae..7a0522dea0 100644 --- a/tremor-script/src/ast.rs +++ b/tremor-script/src/ast.rs @@ -197,7 +197,7 @@ impl<'script> BytesPart<'script> { /// Binary semiliteral #[derive(Clone, Debug, PartialEq, Serialize)] pub struct Bytes<'script> { - mid: Box, + pub(crate) mid: Box, /// Bytes pub value: Vec>, } diff --git a/tremor-script/src/ast/binary.rs b/tremor-script/src/ast/binary.rs index 74298a4315..b3b621299d 100644 --- a/tremor-script/src/ast/binary.rs +++ b/tremor-script/src/ast/binary.rs @@ -165,7 +165,7 @@ fn write_bits_le( } } -fn write_bits( +pub(crate) fn write_bits( bytes: &mut Vec, bits: u8, endianess: Endian, diff --git a/tremor-script/src/ast/visitors/impls/const_folder.rs b/tremor-script/src/ast/visitors/impls/const_folder.rs index 832650cde4..58bd4540c3 100644 --- a/tremor-script/src/ast/visitors/impls/const_folder.rs +++ b/tremor-script/src/ast/visitors/impls/const_folder.rs @@ -17,8 +17,8 @@ use crate::ast::{BooleanBinExpr, BooleanBinOpKind}; use crate::{ ast::{base_expr::Ranged, binary::extend_bytes_from_value, NodeMeta}, errors::{ - err_generic, err_invalid_unary, err_need_int, error_array_out_of_bound, error_bad_key, - error_decreasing_range, error_need_arr, error_need_obj, + err_generic, err_need_int, error_array_out_of_bound, error_bad_key, error_decreasing_range, + error_need_arr, error_need_obj, }, interpreter::{exec_binary, exec_unary, Env}, lexer::Span, @@ -160,13 +160,7 @@ impl<'run, 'script: 'run> ImutExprVisitor<'script> for ConstFolder<'run, 'script mid, } = b.as_ref() { - let value = exec_unary(*kind, value) - .ok_or_else(|| { - let inner = b.extent(); - let outer = b.extent(); - err_invalid_unary(&outer, &inner, *kind, value) - })? - .into_owned(); + let value = exec_unary(b.as_ref(), b.as_ref(), *kind, value)?.into_owned(); Lit(Literal { mid: mid.clone(), value, diff --git a/tremor-script/src/errors.rs b/tremor-script/src/errors.rs index becc8c87ee..b4f04c5dfa 100644 --- a/tremor-script/src/errors.rs +++ b/tremor-script/src/errors.rs @@ -1211,15 +1211,6 @@ pub(crate) fn error_guard_not_bool( error_type_conflict(outer, inner, got.value_type(), ValueType::Bool) } -pub(crate) fn error_invalid_unary( - outer: &O, - inner: &I, - op: ast::UnaryOpKind, - val: &Value, -) -> Result { - Err(err_invalid_unary(outer, inner, op, val)) -} - pub(crate) fn err_invalid_unary( outer: &O, inner: &I, diff --git a/tremor-script/src/interpreter.rs b/tremor-script/src/interpreter.rs index 9ae3f35508..0df01a753d 100644 --- a/tremor-script/src/interpreter.rs +++ b/tremor-script/src/interpreter.rs @@ -43,11 +43,12 @@ use crate::{ }, ctx::NO_CONTEXT, errors::{ - err_need_obj, error_array_out_of_bound, error_bad_array_index, error_bad_key, - error_bad_key_err, error_decreasing_range, error_division_by_zero, error_guard_not_bool, - error_invalid_binary, error_invalid_bitshift, error_need_arr, error_need_int, - error_need_obj, error_need_str, error_oops, error_overflow, error_patch_key_exists, - error_patch_merge_type_conflict, error_patch_update_key_missing, unknown_local, Result, + err_invalid_unary, err_need_obj, error_array_out_of_bound, error_bad_array_index, + error_bad_key, error_bad_key_err, error_decreasing_range, error_division_by_zero, + error_guard_not_bool, error_invalid_binary, error_invalid_bitshift, error_need_arr, + error_need_int, error_need_obj, error_need_str, error_oops, error_overflow, + error_patch_key_exists, error_patch_merge_type_conflict, error_patch_update_key_missing, + unknown_local, Result, }, prelude::*, stry, NO_AGGRS, NO_CONSTS, @@ -385,16 +386,14 @@ where } } #[inline] -pub(crate) fn exec_binary<'run, 'event, OuterExpr, InnerExpr>( - outer: &OuterExpr, - inner: &InnerExpr, +pub(crate) fn exec_binary<'run, 'event>( + outer: &impl BaseExpr, + inner: &impl BaseExpr, op: BinOpKind, lhs: &Value<'event>, rhs: &Value<'event>, ) -> Result>> where - OuterExpr: BaseExpr, - InnerExpr: BaseExpr, 'event: 'run, { // Lazy Heinz doesn't want to write that 10000 times @@ -499,49 +498,56 @@ where #[inline] pub(crate) fn exec_unary<'run, 'event: 'run>( + outer: &impl BaseExpr, + inner: &impl BaseExpr, op: UnaryOpKind, val: &Value<'event>, -) -> Option>> { +) -> Result>> { // Lazy Heinz doesn't want to write that 10000 times // - snot badger - Darach use UnaryOpKind::{BitNot, Minus, Not, Plus}; if let Some(x) = val.as_f64() { match &op { - Minus => Some(Cow::Owned(Value::from(-x))), - Plus => Some(Cow::Owned(Value::from(x))), - _ => None, + Minus => Ok(Cow::Owned(Value::from(-x))), + Plus => Ok(Cow::Owned(Value::from(x))), + _ => Err(err_invalid_unary(outer, inner, op, val)), } } else if let Some(x) = val.as_u64() { match &op { Minus => { if x == 9_223_372_036_854_775_808 { - Some(Cow::Owned(Value::from(i64::MIN))) + Ok(Cow::Owned(Value::from(i64::MIN))) } else { x.try_into() .ok() .and_then(i64::checked_neg) .map(Value::from) .map(Cow::Owned) + .ok_or_else(|| err_invalid_unary(outer, inner, op, val)) } } - Plus => Some(Cow::Owned(Value::from(x))), - BitNot => Some(Cow::Owned(Value::from(!x))), - Not => None, + Plus => Ok(Cow::Owned(Value::from(x))), + BitNot => Ok(Cow::Owned(Value::from(!x))), + Not => Err(err_invalid_unary(outer, inner, op, val)), } } else if let Some(x) = val.as_i64() { match &op { - Minus => x.checked_neg().map(Value::from).map(Cow::Owned), - Plus => Some(Cow::Owned(Value::from(x))), - BitNot => Some(Cow::Owned(Value::from(!x))), - Not => None, + Minus => x + .checked_neg() + .map(Value::from) + .map(Cow::Owned) + .ok_or_else(|| err_invalid_unary(outer, inner, op, val)), + Plus => Ok(Cow::Owned(Value::from(x))), + BitNot => Ok(Cow::Owned(Value::from(!x))), + Not => Err(err_invalid_unary(outer, inner, op, val)), } } else if let Some(x) = val.as_bool() { match &op { - BitNot | Not => Some(static_bool!(!x)), - _ => None, + BitNot | Not => Ok(static_bool!(!x)), + _ => Err(err_invalid_unary(outer, inner, op, val)), } } else { - None + Err(err_invalid_unary(outer, inner, op, val)) } } @@ -745,7 +751,10 @@ where )) } -fn merge_values<'event>(value: &mut Value<'event>, replacement: &Value<'event>) -> Result<()> { +pub(crate) fn merge_values<'event>( + value: &mut Value<'event>, + replacement: &Value<'event>, +) -> Result<()> { if let Some((rep, map)) = replacement.as_object().zip(value.as_object_mut()) { for (k, v) in rep { if let Some(k) = map.get_mut(k) { diff --git a/tremor-script/src/interpreter/imut_expr.rs b/tremor-script/src/interpreter/imut_expr.rs index 808f52a02a..72f6b89ca5 100644 --- a/tremor-script/src/interpreter/imut_expr.rs +++ b/tremor-script/src/interpreter/imut_expr.rs @@ -21,8 +21,7 @@ use crate::{ errors::Kind as ErrorKind, errors::{ err_invalid_fold, error_bad_key, error_decreasing_range, error_invalid_bool_op, - error_invalid_unary, error_need_obj, error_need_str, error_no_clause_hit, error_oops, - error_oops_err, Result, + error_need_obj, error_need_str, error_no_clause_hit, error_oops, error_oops_err, Result, }, interpreter::{ exec_binary, exec_unary, merge_values, patch_value, resolve, set_local_shadow, test_guard, @@ -726,11 +725,8 @@ impl<'script> ImutExpr<'script> { 'script: 'event, { let rhs = stry!(expr.expr.run(opts, env, event, state, meta, local)); - // TODO align this implemenation to be similar to exec_binary? - match exec_unary(expr.kind, &rhs) { - Some(v) => Ok(v), - None => error_invalid_unary(self, &expr.expr, expr.kind, &rhs), - } + // TOO align this implemenation to be similar to exec_binary? + exec_unary(self, expr, expr.kind, &rhs) } // TODO: Quite some overlap with `interpreter::resolve` (and some with `expr::assign`) diff --git a/tremor-script/src/lib.rs b/tremor-script/src/lib.rs index 52b620aed4..3e923bd012 100644 --- a/tremor-script/src/lib.rs +++ b/tremor-script/src/lib.rs @@ -71,7 +71,8 @@ pub mod srs; mod std_lib; /// Utility functions pub mod utils; -pub use srs::{EventPayload, ValueAndMeta}; + +mod vm; pub use crate::ast::deploy::raw::run_script; pub use crate::ast::module; @@ -85,6 +86,8 @@ pub use crate::registry::{ TremorAggrFnWrapper, TremorFn, TremorFnWrapper, }; pub use crate::script::{Return, Script}; +pub use crate::srs::{EventPayload, ValueAndMeta}; +pub use crate::vm::compiler::Compiler; use ast::{Consts, InvokeAggrFn}; pub use interpreter::{AggrType, FALSE, NULL, TRUE}; use lazy_static::lazy_static; diff --git a/tremor-script/src/vm.rs b/tremor-script/src/vm.rs new file mode 100644 index 0000000000..ceb93b2da5 --- /dev/null +++ b/tremor-script/src/vm.rs @@ -0,0 +1,578 @@ +use std::{borrow::Cow, collections::HashMap, fmt::Display}; + +use simd_json::{prelude::*, ValueBuilder}; +use tremor_value::Value; + +use crate::{ + ast::{ + binary::write_bits, + raw::{BytesDataType, Endian}, + BinOpKind, UnaryOpKind, + }, + errors::{error_generic, Result}, + interpreter::{exec_binary, exec_unary, merge_values}, + prelude::Ranged, + NodeMeta, Return, +}; + +pub(super) mod compiler; + +#[derive(Debug, PartialEq, Copy, Clone, Default, Eq)] +pub(crate) enum Op { + /// do absolutely nothing + #[default] + Nop, + /// take the top most value from the stack and delete it + Pop, + /// swap the top two values on the stack + Swap, + /// duplicate the top of the stack + #[allow(dead_code)] + Duplicate, + /// Puts the event on the stack + LoadEvent, + /// puts a variable on the stack + LoadLocal { + idx: u32, + }, + StoreLocal { + idx: u32, + }, + /// emits an error + Error, + #[allow(dead_code)] + Begin, + #[allow(dead_code)] + End, + /// emits the top of the stack + Emit { + dflt: bool, + }, + /// drops the event + Drop, + /// jumps to the given offset if the top of the stack is true does not op the stack + JumpTrue { + dst: u32, + }, + /// jumps to the given offset if the top of the stack is true does not op the stack + JumpFalse { + dst: u32, + }, + Const { + idx: u32, + }, + + // Values + #[allow(dead_code)] + True, + #[allow(dead_code)] + False, + Record { + size: u32, + }, + Array { + size: u32, + }, + String { + size: u32, + }, + Bytes { + size: u32, + }, + // Logical XOP + Xor, + + Binary { + op: BinOpKind, + }, + Unary { + op: UnaryOpKind, + }, + + GetKey { + key: u32, + }, + Get, + Index, + IndexFast { + idx: u32, + }, + Range, + RangeFast { + start: u16, + end: u16, + }, + + // Tests + TestRecortPresent, + TestIsU64, + TestIsI64, + TestIsBytes, + + // Patch + RecordSet, + RecordRemove, + RecordGet, + // Merge + Merge, +} + +impl Display for Op { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Op::Nop => write!(f, "nop"), + Op::Pop => write!(f, "pop"), + Op::Swap => write!(f, "swap"), + Op::Duplicate => write!(f, "duplicate"), + Op::Error => write!(f, "error"), + Op::LoadEvent => write!(f, "laod_event"), + Op::LoadLocal { idx } => write!(f, "{:30} {}", "load_local", idx), + Op::StoreLocal { idx } => write!(f, "{:30} {}", "store_local", idx), + Op::Begin => write!(f, "begin"), + Op::End => write!(f, "end"), + Op::Emit { dflt } => write!(f, "{:30} {dflt}", "emit"), + Op::Drop => write!(f, "drop"), + Op::JumpTrue { dst } => write!(f, "{:30} {}", "jump_true", dst), + Op::JumpFalse { dst } => write!(f, "{:30} {}", "jump_false", dst), + Op::True => write!(f, "true"), + Op::False => write!(f, "false"), + Op::Const { idx } => write!(f, "{:30} {}", "const", idx), + Op::Record { size } => write!(f, "{:30} {}", "record", size), + Op::Array { size } => write!(f, "{:30} {}", "array", size), + Op::String { size } => write!(f, "{:30} {}", "string", size), + Op::Bytes { size } => write!(f, "{:30} {}", "bytes", size), + Op::Xor => write!(f, "xor"), + Op::Binary { op } => write!(f, "{:30} {:?}", "binary", op), + Op::Unary { op } => write!(f, "{:30} {:?}", "unary", op), + Op::GetKey { key } => write!(f, "{:30} {}", "lookup_key", key), + Op::Get => write!(f, "lookup"), + Op::Index => write!(f, "idx"), + Op::IndexFast { idx } => write!(f, "{:30} {}", "idx_fast", idx), + Op::Range => write!(f, "range"), + Op::RangeFast { start, end } => write!(f, "{:30} {} {}", "range_fast", start, end), + Op::TestRecortPresent => write!(f, "test_record_present"), + Op::TestIsU64 => write!(f, "test_is_u64"), + Op::TestIsI64 => write!(f, "test_is_i64"), + Op::TestIsBytes => write!(f, "test_is_bytes"), + Op::RecordSet => write!(f, "record_set"), + Op::RecordRemove => write!(f, "record_remove"), + Op::RecordGet => write!(f, "record_get"), + Op::Merge => write!(f, "merge"), + } + } +} + +#[derive(Debug, PartialEq, Clone, Default, Eq)] +/// A compiler for tremor script +pub struct Program<'script> { + opcodes: Vec, + meta: Vec, + jump_table: HashMap, + consts: Vec>, + keys: Vec>, + max_locals: usize, +} + +impl Display for Program<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for (idx, op) in self.opcodes.iter().enumerate() { + if let Some(dst) = self.jump_table.get(&idx).copied() { + writeln!(f, "JMP<{dst:03}>")?; + } + match op { + Op::JumpTrue { dst } => writeln!( + f, + " {idx:04}: {:30} JMP<{:03}>", + "jump_true", + self.jump_table + .get(&(*dst as usize)) + .copied() + .unwrap_or_default() + )?, + Op::JumpFalse { dst } => writeln!( + f, + " {idx:04}: {:30} JMP<{:03}>", + "jump_false", + self.jump_table + .get(&(*dst as usize)) + .copied() + .unwrap_or_default() + )?, + _ => writeln!(f, " {idx:04}: {op}")?, + } + } + Ok(()) + } +} + +#[allow(dead_code)] +pub struct Vm {} + +pub struct Scope<'run, 'event> { + // value: &'run mut Value<'event>, + keys: &'run [tremor_value::KnownKey<'event>], +} +#[allow(dead_code)] +impl Vm { + pub fn new() -> Self { + Self {} + } + #[allow(clippy::too_many_lines)] + #[allow(clippy::unused_self)] + pub fn run<'run, 'prog, 'event>( + &self, + event: &mut Value<'event>, + program: &Program<'prog>, + ) -> Result> + where + 'prog: 'event, + { + // our locals start at zero, so we need to allocate a length of max_locals + 1 + let mut locals = Vec::with_capacity(program.max_locals + 1); + for _ in 0..=program.max_locals { + locals.push(None); + } + // value: &mut null, + + let mut root = Scope { + keys: &program.keys, + }; + let mut pc = 0; + + // ensure that the opcodes and meta are the same length + assert_eq!(program.opcodes.len(), program.meta.len()); + root.run(event, &mut locals, program, &mut pc) + } +} + +impl<'run, 'event> Scope<'run, 'event> { + #[allow(clippy::too_many_lines)] + pub fn run<'prog>( + &mut self, + event: &mut Value<'event>, + locals: &mut [Option>], + program: &Program<'prog>, + pc: &mut usize, + ) -> Result> + where + 'prog: 'event, + { + let mut stack: Vec> = Vec::with_capacity(8); + + while *pc < program.opcodes.len() { + let mid = &program.meta[*pc]; + // ALLOW: we test that pc is always in bounds in the while loop above + match unsafe { *program.opcodes.get_unchecked(*pc) } { + Op::Nop => continue, + // Loads + Op::LoadEvent => stack.push(Cow::Borrowed(event)), + Op::LoadLocal { idx } => { + let idx = idx as usize; + stack.push(Cow::Owned( + locals[idx].as_ref().ok_or("Local not set")?.clone(), + )); + } + Op::StoreLocal { idx } => { + let idx = idx as usize; + locals[idx] = Some(stack.pop().ok_or("Stack underflow")?.into_owned()); + } + + Op::True => stack.push(Cow::Owned(Value::const_true())), + Op::False => stack.push(Cow::Owned(Value::const_false())), + Op::Const { idx } => stack.push(Cow::Borrowed(&program.consts[idx as usize])), + Op::Begin => { + let _value: &mut Value = stack.last_mut().ok_or("Stack underflow")?.to_mut(); + // let mut scopte = Scope { + // value, + // keys: self.keys, + // }; + *pc += 1; + // scopte.run(event, program, pc)?; + } + Op::End => return Ok(Return::EmitEvent { port: None }), + Op::Pop => { + stack.pop().ok_or("Stack underflow")?; + } + Op::Swap => { + let a = stack.pop().ok_or("Stack underflow")?; + let b = stack.pop().ok_or("Stack underflow")?; + stack.push(a); + stack.push(b); + } + Op::Duplicate => { + let a = stack.last().ok_or("Stack underflow")?; + stack.push(a.clone()); + } + Op::Error => { + let msg = stack.pop().ok_or("Stack underflow")?; + let mid = mid.clone(); + return Err(error_generic( + &mid.extent(), + &mid.extent(), + &msg.to_string(), + )); + } + Op::Emit { dflt: true } => { + let value = stack.pop().ok_or("Stack underflow")?; + return Ok(Return::Emit { + value: value.into_owned(), + port: None, + }); + } + Op::Emit { dflt: false } => { + let port = stack.pop().ok_or("Stack underflow")?; + let value = stack.pop().ok_or("Stack underflow")?; + return Ok(Return::Emit { + value: value.into_owned(), + port: Some(port.try_as_str()?.to_string().into()), + }); + } + Op::Drop => { + return Ok(Return::Drop); + } + #[allow(clippy::cast_abs_to_unsigned)] + Op::JumpTrue { dst } => { + if stack.last().ok_or("Stack underflow")?.try_as_bool()? { + *pc = dst as usize; + } + } + #[allow(clippy::cast_abs_to_unsigned)] + Op::JumpFalse { dst } => { + if !stack.last().ok_or("Stack underflow")?.try_as_bool()? { + *pc = dst as usize; + } + } + Op::Record { size } => { + let size = size as usize; + let mut record = Value::object_with_capacity(size); + for _ in 0..size { + let value = stack.pop().ok_or("Stack underflow")?; + let key = stack.pop().ok_or("Stack underflow")?; + // FIXME: we can do better than clone here + let key = key.into_owned().try_into_string()?; + record.try_insert(key, value.into_owned()); + } + stack.push(Cow::Owned(record)); + } + Op::Array { size } => { + let size = size as usize; + let mut v = stack.pop().ok_or("Stack underflow")?; + let array = v.to_mut().as_array_mut().ok_or("Not an array")?; + array.reserve(size); + for value in stack.drain(stack.len() - size..) { + array.push(value.into_owned()); + } + stack.push(v); + } + Op::String { size } => { + let size = size as usize; + // FIXME: is this a good heuristic? + let mut builder = String::with_capacity(size * 16); + for value in stack.drain(stack.len() - size..) { + builder.push_str(value.try_as_str()?); + } + stack.push(Cow::Owned(builder.into())); + } + #[allow( + clippy::cast_lossless, + clippy::cast_possible_truncation, + clippy::cast_sign_loss + )] + Op::Bytes { size } => { + let size = size as usize; + let mut bytes = Vec::with_capacity(size * 8); + let mut pending = 0; + let mut buf = 0; + for _ in 0..size { + let mut format = stack.pop().ok_or("Stack underflow")?.try_as_i64()?; + let endianess = Endian::from(format as u8 & 0b1); + format >>= 1; + let data_type = BytesDataType::from(format as u8 & 0b11); + let bits = (format >> 2) as u8; + let value = stack.pop().ok_or("Stack underflow")?; + match data_type { + BytesDataType::UnsignedInteger => write_bits( + &mut bytes, + bits, + endianess, + &mut buf, + &mut pending, + value.try_as_u64()?, + )?, + BytesDataType::SignedInteger => write_bits( + &mut bytes, + bits, + endianess, + &mut buf, + &mut pending, + value.try_as_i64()? as u64, + )?, + BytesDataType::Binary => { + let mut b = value.try_as_bytes()?; + if bits > 0 { + b = b + .get(..(bits as usize)) + .ok_or("Not a long enough binary")?; + } + if pending == 0 { + bytes.extend_from_slice(b); + } else { + for v in b { + write_bits( + &mut bytes, + 8, + endianess, + &mut buf, + &mut pending, + *v as u64, + )?; + } + } + } + } + } + stack.push(Cow::Owned(Value::Bytes(bytes.into()))); + } + + Op::Binary { op } => { + let rhs = stack.pop().ok_or("Stack underflow")?; + let lhs = stack.pop().ok_or("Stack underflow")?; + stack.push(exec_binary(mid, mid, op, &lhs, &rhs)?); + } + Op::Unary { op } => { + let value = stack.pop().ok_or("Stack underflow")?; + stack.push(exec_unary(mid, mid, op, &value)?); + } + Op::Xor => { + let rhs = stack.pop().ok_or("Stack underflow")?; + let lhs = stack.pop().ok_or("Stack underflow")?; + stack.push(Cow::Owned(Value::from( + lhs.try_as_bool()? ^ rhs.try_as_bool()?, + ))); + } + // tests + Op::TestRecortPresent => { + let key = stack.pop().ok_or("Stack underflow")?; + let r = stack.last().ok_or("Stack underflow")?; + let present = r.contains_key(key.try_as_str()?); + stack.push(key); + stack.push(Cow::Owned(Value::from(present))); + } // record operations on scope + + Op::TestIsU64 => { + let v = stack.last().ok_or("Stack underflow")?; + let is_u64 = v.is_u64(); + stack.push(Cow::Owned(Value::from(is_u64))); + } + Op::TestIsI64 => { + let v = stack.last().ok_or("Stack underflow")?; + let is_i64 = v.is_i64(); + stack.push(Cow::Owned(Value::from(is_i64))); + } + Op::TestIsBytes => { + let v = stack.last().ok_or("Stack underflow")?; + let is_bytes = v.is_bytes(); + stack.push(Cow::Owned(Value::from(is_bytes))); + } + Op::RecordSet => { + let value = stack.pop().ok_or("Stack underflow")?; + let key = stack.pop().ok_or("Stack underflow")?; + let r = stack.last_mut().ok_or("Stack underflow")?; + // FIXME: we can do better than clone here + let key = key.into_owned().try_into_string()?; + r.to_mut().insert(key, value.into_owned())?; + } + Op::RecordRemove => { + let key = stack.pop().ok_or("Stack underflow")?; + let r = stack.last_mut().ok_or("Stack underflow")?; + let key = key.try_as_str()?; + let v = r.to_mut().remove(key)?.unwrap_or_default(); + stack.push(Cow::Owned(v)); + } + Op::RecordGet => { + let key = stack.pop().ok_or("Stack underflow")?; + let r = stack.last().ok_or("Stack underflow")?; + let key = key.try_as_str()?; + // FIXME: can we avoid this clone here + let v = r.get(key).ok_or("not a record")?.clone(); + stack.push(Cow::Owned(v)); + } + // merge + Op::Merge => { + let arg = stack.pop().ok_or("Stack underflow")?; + let mut target = stack.pop().ok_or("Stack underflow")?; + merge_values(target.to_mut(), &arg)?; + stack.push(target); + } + // Path + Op::GetKey { key } => { + let key = &self.keys[key as usize]; + let v = stack.pop().ok_or("Stack underflow")?; + // FIXME: can we avoid this clone here + let res = key.lookup(&v).ok_or("Missing Key FIXME")?.clone(); + stack.push(Cow::Owned(res)); + } + Op::Get => { + let key = stack.pop().ok_or("Stack underflow")?; + let v = stack.pop().ok_or("Stack underflow")?; + let v = if let Some(key) = key.as_str() { + v.get(key).ok_or("not a record")?.clone() + } else if let Some(idx) = key.as_usize() { + v.get_idx(idx).ok_or("Index out of bounds")?.clone() + } else { + return Err(error_generic(mid, mid, &"Invalid key type")); + }; + // FIXME: can we avoid this clone here + stack.push(Cow::Owned(v)); + } + Op::Index => { + let idx = stack.pop().ok_or("Stack underflow")?; + let idx = idx.try_as_usize()?; + let v = stack.pop().ok_or("Stack underflow")?; + let v = v.get_idx(idx).ok_or("Index out of bounds")?.clone(); + stack.push(Cow::Owned(v)); + } + Op::IndexFast { idx } => { + let idx = idx as usize; + let v = stack.pop().ok_or("Stack underflow")?; + let v = v.get_idx(idx).ok_or("Index out of bounds")?.clone(); + stack.push(Cow::Owned(v)); + } + Op::Range => { + let end = stack.pop().ok_or("Stack underflow")?; + let start = stack.pop().ok_or("Stack underflow")?; + let end = end.try_as_usize()?; + let start = start.try_as_usize()?; + let v = stack.pop().ok_or("Stack underflow")?; + let v = v + .try_as_array()? + .get(start..end) + .ok_or("Index out of bounds")? + .to_vec(); + stack.push(Cow::Owned(v.into())); + } + Op::RangeFast { start, end } => { + let start = start as usize; + let end = end as usize; + let v = stack.pop().ok_or("Stack underflow")?; + let v = v + .try_as_array()? + .get(start..end) + .ok_or("Index out of bounds")? + .to_vec(); + stack.push(Cow::Owned(v.into())); + } + } + *pc += 1; + } + + let value = stack.pop().ok_or("Stack underflow")?; + Ok(Return::Emit { + value: value.into_owned(), + port: None, + }) + } +} + +#[cfg(test)] +mod tests; diff --git a/tremor-script/src/vm/compiler.rs b/tremor-script/src/vm/compiler.rs new file mode 100644 index 0000000000..54c545d4aa --- /dev/null +++ b/tremor-script/src/vm/compiler.rs @@ -0,0 +1,524 @@ +use crate::{ + ast::{ + raw::{BytesDataType, Endian}, + BooleanBinExpr, BooleanBinOpKind, BytesPart, EmitExpr, Expr, Field, ImutExpr, + PatchOperation, Path, Script, Segment, StrLitElement, StringLit, + }, + errors::Result, + NodeMeta, +}; +use std::{collections::HashMap, mem}; +use tremor_value::Value; + +use super::{Op, Program}; + +/// A compiler for tremor script +pub struct Compiler<'script> { + opcodes: Vec, + meta: Vec, + jump_table: Vec, + consts: Vec>, + keys: Vec>, + max_locals: usize, +} + +impl<'script> Compiler<'script> { + /// compiles a script into a program + /// # Errors + /// if the script can't be compiled + pub fn compile(&mut self, script: Script<'script>) -> Result> { + script.compile(self)?; + let mut opcodes = mem::take(&mut self.opcodes); + let meta = mem::take(&mut self.meta); + let consts = mem::take(&mut self.consts); + let mut jump_table = HashMap::with_capacity(self.jump_table.len()); + let keys = mem::take(&mut self.keys); + + for op in &mut opcodes { + match op { + Op::JumpTrue { dst } | Op::JumpFalse { dst } => { + *dst = self.jump_table[*dst as usize]; + } + _ => (), + } + } + for (idx, dst) in mem::take(&mut self.jump_table).into_iter().enumerate() { + jump_table.insert(dst as usize, idx); + } + + Ok(Program { + opcodes, + meta, + jump_table, + consts, + keys, + max_locals: self.max_locals, + }) + } + + /// creates a new compiler + #[must_use] + pub fn new() -> Self { + Self { + opcodes: Vec::new(), + meta: Vec::new(), + jump_table: Vec::new(), + consts: Vec::new(), + keys: Vec::new(), + max_locals: 0, + } + } + + #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] + pub(crate) fn last_code_offset(&self) -> u32 { + assert!(!self.opcodes.is_empty()); + self.opcodes.len() as u32 - 1 + } + + pub(crate) fn emit(&mut self, op: Op, mid: NodeMeta) { + self.opcodes.push(op); + self.meta.push(mid); + } + + #[allow(clippy::cast_possible_truncation)] + pub(crate) fn add_const(&mut self, value: Value<'script>) -> u32 { + for (idx, v) in self.consts.iter().enumerate() { + if v == &value { + return idx as u32; + } + } + self.consts.push(value); + (self.consts.len() - 1) as u32 + } + pub(crate) fn emit_const>>(&mut self, v: T, mid: NodeMeta) { + let idx = self.add_const(v.into()); + self.emit(Op::Const { idx }, mid); + } + + #[allow(clippy::cast_possible_truncation)] + fn add_key(&mut self, key: tremor_value::KnownKey<'script>) -> u32 { + for (idx, v) in self.keys.iter().enumerate() { + if v == &key { + return idx as u32; + } + } + self.keys.push(key); + (self.keys.len() - 1) as u32 + } + + #[allow(clippy::cast_possible_truncation)] + pub(crate) fn new_jump_point(&mut self) -> u32 { + self.jump_table.push(0); + self.jump_table.len() as u32 - 1 + } + + pub(crate) fn set_jump(&mut self, jump_point: u32) { + self.jump_table[jump_point as usize] = self.last_code_offset(); + } +} + +impl<'script> Default for Compiler<'script> { + fn default() -> Self { + Self::new() + } +} + +impl<'script> Script<'script> { + pub(crate) fn compile(self, compiler: &mut Compiler<'script>) -> Result { + for e in self.exprs { + e.compile(compiler)?; + } + Ok(compiler.last_code_offset()) + } +} + +impl<'script> Expr<'script> { + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + match self { + Expr::Match(_) => todo!(), + Expr::IfElse(_) => todo!(), + #[allow(clippy::cast_possible_truncation)] + Expr::Assign { mid: _, path, expr } => { + let Path::Local(p) = path else { + todo!("we only allow local pasth?"); + }; + if !p.segments.is_empty() { + todo!("we only allow non nested asignments pasth?"); + } + expr.compile(compiler)?; + compiler.max_locals = compiler.max_locals.max(p.idx); + compiler.emit(Op::StoreLocal { idx: p.idx as u32 }, *p.mid); + } + Expr::AssignMoveLocal { + mid: _, + path: _, + idx: _, + } => {} + Expr::Comprehension(_) => todo!(), + Expr::Drop { mid } => compiler.emit(Op::Drop, *mid), + Expr::Emit(e) => e.compile(compiler)?, + Expr::Imut(e) => e.compile(compiler)?, + } + Ok(()) + } +} + +#[allow(clippy::too_many_lines)] +impl<'script> ImutExpr<'script> { + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + match self { + #[allow(clippy::cast_possible_truncation)] + ImutExpr::Record(r) => { + let size = r.fields.len() as u32; + for f in r.fields { + f.compile(compiler)?; + } + compiler.emit(Op::Record { size }, *r.mid); + } + #[allow(clippy::cast_possible_truncation)] + ImutExpr::List(l) => { + let size = l.exprs.len() as u32; + for e in l.exprs { + e.compile(compiler)?; + } + compiler.emit_const(Value::array(), *l.mid.clone()); + compiler.emit(Op::Array { size }, *l.mid); + } + ImutExpr::Binary(b) => { + b.lhs.compile(compiler)?; + b.rhs.compile(compiler)?; + compiler.emit(Op::Binary { op: b.kind }, *b.mid); + } + ImutExpr::BinaryBoolean(b) => { + b.compile(compiler)?; + } + ImutExpr::Unary(u) => { + u.expr.compile(compiler)?; + compiler.emit(Op::Unary { op: u.kind }, *u.mid); + } + ImutExpr::Patch(p) => { + p.target.compile(compiler)?; + for op in p.operations { + op.compile(compiler)?; + } + } + ImutExpr::Match(_) => todo!(), + ImutExpr::Comprehension(_) => todo!(), + ImutExpr::Merge(m) => { + m.target.compile(compiler)?; + m.expr.compile(compiler)?; + compiler.emit(Op::Merge, *m.mid); + } + ImutExpr::Path(p) => p.compile(compiler)?, + ImutExpr::String(s) => s.compile(compiler)?, + #[allow(clippy::cast_possible_truncation)] + ImutExpr::Local { idx, mid } => { + compiler.max_locals = compiler.max_locals.max(idx); + compiler.emit(Op::LoadLocal { idx: idx as u32 }, *mid); + } + ImutExpr::Literal(l) => { + compiler.emit_const(l.value, *l.mid); + } + ImutExpr::Present { + path: _path, + mid: _mid, + } => todo!(), + ImutExpr::Invoke1(_) => todo!(), + ImutExpr::Invoke2(_) => todo!(), + ImutExpr::Invoke3(_) => todo!(), + ImutExpr::Invoke(_) => todo!(), + ImutExpr::InvokeAggr(_) => todo!(), + ImutExpr::Recur(_) => todo!(), + #[allow(clippy::cast_possible_truncation)] + ImutExpr::Bytes(b) => { + let size = b.value.len() as u32; + for b in b.value { + b.compile(compiler)?; + } + compiler.emit(Op::Bytes { size }, *b.mid); + } + #[allow(clippy::cast_possible_truncation)] + ImutExpr::ArrayAppend(a) => { + let size = a.right.len() as u32; + for r in a.right { + r.compile(compiler)?; + } + a.left.compile(compiler)?; + compiler.emit(Op::Array { size }, *a.mid); + } + } + Ok(()) + } +} + +impl<'script> Field<'script> { + pub(crate) fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + self.name.compile(compiler)?; + self.value.compile(compiler)?; + Ok(()) + } +} + +impl<'script> StringLit<'script> { + #[allow(clippy::cast_possible_truncation)] + pub(crate) fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + let size = self.elements.len() as u32; + for e in self.elements { + match e { + StrLitElement::Lit(s) => { + compiler.emit_const(s, *self.mid.clone()); + } + StrLitElement::Expr(e) => { + e.compile(compiler)?; + } + } + } + compiler.emit(Op::String { size }, *self.mid); + Ok(()) + } +} + +impl<'script> EmitExpr<'script> { + pub(crate) fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + self.expr.compile(compiler)?; + let dflt = if let Some(port) = self.port { + port.compile(compiler)?; + false + } else { + true + }; + compiler.emit(Op::Emit { dflt }, *self.mid); + Ok(()) + } +} + +impl<'script> PatchOperation<'script> { + #[allow(unused_variables)] + pub(crate) fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + match self { + PatchOperation::Insert { ident, expr, mid } => { + ident.compile(compiler)?; + compiler.emit(Op::TestRecortPresent, *mid.clone()); + let dst = compiler.new_jump_point(); + compiler.emit(Op::JumpFalse { dst }, *mid.clone()); + compiler.emit_const("Key already present", *mid.clone()); + compiler.emit(Op::Error, *mid.clone()); + compiler.set_jump(dst); + compiler.emit(Op::Pop, *mid.clone()); + expr.compile(compiler)?; + compiler.emit(Op::RecordSet, *mid); + } + PatchOperation::Upsert { ident, expr, mid } => { + ident.compile(compiler)?; + expr.compile(compiler)?; + compiler.emit(Op::RecordSet, *mid); + } + PatchOperation::Update { ident, expr, mid } => { + ident.compile(compiler)?; + compiler.emit(Op::TestRecortPresent, *mid.clone()); + let dst = compiler.new_jump_point(); + compiler.emit(Op::JumpTrue { dst }, *mid.clone()); + compiler.emit_const("Key already present", *mid.clone()); + compiler.emit(Op::Error, *mid.clone()); + compiler.set_jump(dst); + compiler.emit(Op::Pop, *mid.clone()); + expr.compile(compiler)?; + compiler.emit(Op::RecordSet, *mid); + } + PatchOperation::Erase { ident, mid } => { + ident.compile(compiler)?; + compiler.emit(Op::RecordRemove, *mid.clone()); + compiler.emit(Op::Pop, *mid); + } + PatchOperation::Copy { from, to, mid } => { + from.compile(compiler)?; + compiler.emit(Op::RecordGet, *mid.clone()); + to.compile(compiler)?; + compiler.emit(Op::Swap, *mid.clone()); + compiler.emit(Op::RecordSet, *mid); + } + PatchOperation::Move { from, to, mid } => { + from.compile(compiler)?; + compiler.emit(Op::RecordRemove, *mid.clone()); + to.compile(compiler)?; + compiler.emit(Op::Swap, *mid.clone()); + compiler.emit(Op::RecordSet, *mid); + } + PatchOperation::Merge { ident, expr, mid } => todo!(), + PatchOperation::MergeRecord { expr, mid } => todo!(), + PatchOperation::Default { ident, expr, mid } => todo!(), + PatchOperation::DefaultRecord { expr, mid } => todo!(), + } + Ok(()) + } +} + +impl<'script> Segment<'script> { + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + match self { + Segment::Id { mid, key } => { + let key = compiler.add_key(key); + compiler.emit(Op::GetKey { key }, *mid); + } + Segment::Element { expr, mid } => { + expr.compile(compiler)?; + compiler.emit(Op::Get, *mid); + } + #[allow(clippy::cast_possible_truncation)] + Segment::Idx { idx, mid } => { + if let Ok(idx) = u32::try_from(idx) { + compiler.emit(Op::IndexFast { idx }, *mid); + } else { + compiler.emit_const(idx, *mid.clone()); + compiler.emit(Op::Index, *mid); + } + } + #[allow(clippy::cast_possible_truncation)] + Segment::Range { mid, start, end } => { + if let Some((start, end)) = u16::try_from(start).ok().zip(u16::try_from(end).ok()) { + compiler.emit(Op::RangeFast { start, end }, *mid); + } else { + compiler.emit_const(start, *mid.clone()); + compiler.emit_const(end, *mid.clone()); + compiler.emit(Op::Range, *mid); + } + } + + Segment::RangeExpr { mid, start, end } => { + start.compile(compiler)?; + end.compile(compiler)?; + compiler.emit(Op::Range, *mid); + } + } + Ok(()) + } +} + +impl<'script> BooleanBinExpr<'script> { + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + self.lhs.compile(compiler)?; + match self.kind { + #[allow(clippy::cast_sign_loss)] + BooleanBinOpKind::Or => { + let dst = compiler.new_jump_point(); + compiler.emit(Op::JumpTrue { dst }, *self.mid); + self.rhs.compile(compiler)?; + compiler.set_jump(dst); + } + BooleanBinOpKind::Xor => { + self.rhs.compile(compiler)?; + compiler.emit(Op::Xor, *self.mid); + } + #[allow(clippy::cast_sign_loss)] + BooleanBinOpKind::And => { + let dst = compiler.new_jump_point(); + compiler.emit(Op::JumpFalse { dst }, *self.mid); + self.rhs.compile(compiler)?; + compiler.set_jump(dst); + } + } + Ok(()) + } +} + +impl From for u8 { + fn from(v: Endian) -> Self { + match v { + Endian::Big => 0, + Endian::Little => 1, + } + } +} + +impl From for Endian { + fn from(v: u8) -> Self { + match v { + 0 => Endian::Big, + _ => Endian::Little, + } + } +} + +impl From for u8 { + fn from(v: BytesDataType) -> Self { + match v { + BytesDataType::SignedInteger => 0, + BytesDataType::UnsignedInteger => 1, + BytesDataType::Binary => 2, + } + } +} +impl From for BytesDataType { + fn from(v: u8) -> Self { + match v { + 0 => BytesDataType::SignedInteger, + 1 => BytesDataType::UnsignedInteger, + _ => BytesDataType::Binary, + } + } +} + +impl<'script> BytesPart<'script> { + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + let BytesPart { + mid, + data, + data_type, + endianess, + bits, + }: BytesPart = self; + + // format is bits:48 | data_type:2 |endianess:1 + let mut format = bits; + format = format << 2 | u64::from(u8::from(data_type)) & 0b11; + format = format << 1 | u64::from(u8::from(endianess)) & 0b1; + + let mid = *mid; + data.compile(compiler)?; + let dst = compiler.new_jump_point(); + + match data_type { + BytesDataType::SignedInteger => compiler.emit(Op::TestIsI64, mid.clone()), + BytesDataType::UnsignedInteger => compiler.emit(Op::TestIsU64, mid.clone()), + BytesDataType::Binary => compiler.emit(Op::TestIsBytes, mid.clone()), + } + compiler.emit(Op::JumpFalse { dst }, mid.clone()); + compiler.emit_const("invalid type conversion for binary", mid.clone()); + compiler.emit(Op::Error, mid.clone()); + compiler.set_jump(dst); + compiler.emit_const(format, mid); + + Ok(()) + } +} + +impl<'script> Path<'script> { + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + match self { + #[allow(clippy::cast_possible_truncation)] + Path::Local(p) => { + compiler.max_locals = compiler.max_locals.max(p.idx); + compiler.emit(Op::LoadLocal { idx: p.idx as u32 }, *p.mid); + for s in p.segments { + s.compile(compiler)?; + } + } + Path::Event(p) => { + compiler.emit(Op::LoadEvent, *p.mid); + for s in p.segments { + s.compile(compiler)?; + } + } + Path::Expr(p) => { + p.expr.compile(compiler)?; + for s in p.segments { + s.compile(compiler)?; + } + } + Path::Meta(_p) => todo!(), + Path::Reserved(_p) => todo!(), + Path::State(_p) => todo!(), + } + Ok(()) + } +} diff --git a/tremor-script/src/vm/tests.rs b/tremor-script/src/vm/tests.rs new file mode 100644 index 0000000000..bdd40956a1 --- /dev/null +++ b/tremor-script/src/vm/tests.rs @@ -0,0 +1,675 @@ +use tremor_value::literal; + +use super::*; +use crate::{ + arena::Arena, + ast::{Helper, Script}, + lexer::Lexer, + parser::g::ScriptParser, + registry, AggrRegistry, Compiler, +}; + +fn parse(src: &str) -> Result> { + let reg = registry::registry(); + let fake_aggr_reg = AggrRegistry::default(); + let (aid, src) = Arena::insert(src)?; + + let tokens = Lexer::new(src, aid).collect::>>()?; + let filtered_tokens = tokens.into_iter().filter(|t| !t.value.is_ignorable()); + + let script_raw = ScriptParser::new().parse(filtered_tokens)?; + let mut helper = Helper::new(®, &fake_aggr_reg); + // helper.consts.args = args.clone_static(); + let script = script_raw.up_script(&mut helper)?; + // Optimizer::new(&helper).walk_script(&mut script)?; + Ok(script) +} + +fn run<'v>(p: &Program<'v>) -> Result> { + let vm = Vm::new(); + let mut event = literal!({ + "int": 42, + "float": 42.0, + "bool": true, + "null": null, + "string": "string", + "array": [1, 2, 3], + "object": { + "a": 1, + "b": 2, + "c": 3 + } + }); + if let Return::Emit { value, .. } = vm.run(&mut event, p)? { + Ok(value) + } else { + Err("Expected Emit".into()) + } +} + +// We are using 32 bytes for all indexes and other arguments, this allows us +// to keep the opcodes to 64 bytes total and in result fit in a single register +// with the lower part of the register being the argument to the op code +// and the upper part being the opcode itself. +// We ensure this by havbinga a gest for the size of the opcodes +#[test] +fn op_size() { + assert_eq!(std::mem::size_of::(), 8); +} +#[test] +fn simple() -> Result<()> { + let mut compiler: Compiler = Compiler::new(); + + let script = parse("42")?; + let p = compiler.compile(script)?; + + assert_eq!(p.opcodes.len(), 1); + assert_eq!(p.consts.len(), 1); + assert_eq!(p.opcodes, [Op::Const { idx: 0 }]); + assert_eq!(run(&p)?, 42); + Ok(()) +} + +#[test] +fn simple_add() -> Result<()> { + let mut compiler: Compiler = Compiler::new(); + + let script = parse("42 + 42")?; + let p = compiler.compile(script)?; + + assert_eq!(p.opcodes.len(), 3); + assert_eq!(p.consts.len(), 1); + assert_eq!( + p.opcodes, + [ + Op::Const { idx: 0 }, + Op::Const { idx: 0 }, + Op::Binary { + op: crate::ast::BinOpKind::Add + } + ] + ); + assert_eq!(run(&p)?, 84); + + Ok(()) +} + +#[test] +fn simple_add_sub() -> Result<()> { + let mut compiler: Compiler = Compiler::new(); + + let script = parse("42 + 43 - 44")?; + let p = compiler.compile(script)?; + + assert_eq!(p.opcodes.len(), 5); + assert_eq!(p.consts.len(), 3); + assert_eq!( + p.opcodes, + [ + Op::Const { idx: 0 }, + Op::Const { idx: 1 }, + Op::Binary { + op: crate::ast::BinOpKind::Add + }, + Op::Const { idx: 2 }, + Op::Binary { + op: crate::ast::BinOpKind::Sub + } + ] + ); + assert_eq!(run(&p)?, 41); + + Ok(()) +} + +#[test] +fn logical_and() -> Result<()> { + let mut compiler: Compiler = Compiler::new(); + + let script = parse("true and false")?; + let p = compiler.compile(script)?; + + assert_eq!(p.opcodes.len(), 3); + assert_eq!(p.consts.len(), 2); + assert_eq!( + p.opcodes, + [ + Op::Const { idx: 0 }, + Op::JumpFalse { dst: 2 }, + Op::Const { idx: 1 } + ] + ); + assert_eq!(run(&p)?, false); + Ok(()) +} + +#[test] +fn logical_or() -> Result<()> { + let mut compiler: Compiler = Compiler::new(); + + let script = parse("true or false")?; + let p = compiler.compile(script)?; + + assert_eq!(p.opcodes.len(), 3); + assert_eq!(p.consts.len(), 2); + assert_eq!( + p.opcodes, + [ + Op::Const { idx: 0 }, + Op::JumpTrue { dst: 2 }, + Op::Const { idx: 1 } + ] + ); + assert_eq!(run(&p)?, true); + Ok(()) +} + +#[test] +fn logical_not() -> Result<()> { + let mut compiler: Compiler = Compiler::new(); + + let script = parse("not true")?; + let p = compiler.compile(script)?; + + assert_eq!(p.opcodes.len(), 2); + assert_eq!(p.consts.len(), 1); + assert_eq!( + p.opcodes, + [ + Op::Const { idx: 0 }, + Op::Unary { + op: UnaryOpKind::Not + } + ] + ); + assert_eq!(run(&p)?, false); + Ok(()) +} + +#[test] +fn simple_eq() -> Result<()> { + let mut compiler: Compiler = Compiler::new(); + + let script = parse("42 == 42")?; + let p = compiler.compile(script)?; + + assert_eq!(p.opcodes.len(), 3); + assert_eq!(p.consts.len(), 1); + assert_eq!( + p.opcodes, + [ + Op::Const { idx: 0 }, + Op::Const { idx: 0 }, + Op::Binary { + op: crate::ast::BinOpKind::Eq + } + ] + ); + assert_eq!(run(&p)?, true); + Ok(()) +} + +#[test] +fn patch_insert() -> Result<()> { + let mut compiler: Compiler = Compiler::new(); + + let script = parse(r#"patch {} of insert "foo" => 42 end"#)?; + let p = compiler.compile(script)?; + + assert_eq!(p.consts.len(), 3); + assert_eq!( + p.opcodes, + &[ + Op::Record { size: 0 }, + Op::Begin, + Op::Const { idx: 0 }, + Op::String { size: 1 }, + Op::TestRecortPresent, + Op::JumpFalse { dst: 7 }, + Op::Const { idx: 1 }, + Op::Error, + Op::Pop, + Op::Const { idx: 2 }, + Op::RecordSet, + Op::End, + ] + ); + + assert_eq!( + run(&p)?, + literal!({ + "foo": 42 + }) + ); + Ok(()) +} + +#[test] +fn patch_insert_error() -> Result<()> { + let mut compiler: Compiler = Compiler::new(); + + let script = parse(r#"patch {"foo":"bar"} of insert "foo" => 42 end"#)?; + let p = compiler.compile(script)?; + + assert_eq!(p.consts.len(), 4); + assert_eq!( + p.opcodes, + &[ + Op::Const { idx: 0 }, + Op::String { size: 1 }, + Op::Const { idx: 1 }, + Op::String { size: 1 }, + Op::Record { size: 1 }, + Op::Begin, + Op::Const { idx: 0 }, + Op::String { size: 1 }, + Op::TestRecortPresent, + Op::JumpFalse { dst: 11 }, + Op::Const { idx: 2 }, + Op::Error, + Op::Pop, + Op::Const { idx: 3 }, + Op::RecordSet, + Op::End, + ] + ); + + assert!(run(&p).is_err(),); + Ok(()) +} + +#[test] +fn patch_update() -> Result<()> { + let mut compiler: Compiler = Compiler::new(); + + let script = parse(r#"patch {"foo":"bar"} of update "foo" => 42 end"#)?; + let p = compiler.compile(script)?; + + assert_eq!(p.consts.len(), 4); + assert_eq!( + p.opcodes, + &[ + Op::Const { idx: 0 }, + Op::String { size: 1 }, + Op::Const { idx: 1 }, + Op::String { size: 1 }, + Op::Record { size: 1 }, + Op::Const { idx: 0 }, + Op::String { size: 1 }, + Op::TestRecortPresent, + Op::JumpTrue { dst: 10 }, + Op::Const { idx: 2 }, + Op::Error, + Op::Pop, + Op::Const { idx: 3 }, + Op::RecordSet, + ] + ); + + assert_eq!( + run(&p)?, + literal!({ + "foo": 42 + }) + ); + Ok(()) +} + +#[test] +fn patch_update_error() -> Result<()> { + let mut compiler: Compiler = Compiler::new(); + + let script = parse(r#"patch {} of update "foo" => 42 end"#)?; + let p = compiler.compile(script)?; + + assert_eq!(p.consts.len(), 3); + assert_eq!( + p.opcodes, + &[ + Op::Record { size: 0 }, + Op::Const { idx: 0 }, + Op::String { size: 1 }, + Op::TestRecortPresent, + Op::JumpTrue { dst: 6 }, + Op::Const { idx: 1 }, + Op::Error, + Op::Pop, + Op::Const { idx: 2 }, + Op::RecordSet, + ] + ); + + assert!(run(&p).is_err(),); + Ok(()) +} + +#[test] +fn patch_upsert_1() -> Result<()> { + let mut compiler: Compiler = Compiler::new(); + + let script = parse(r#"patch {"foo":"bar"} of upsert "foo" => 42 end"#)?; + let p = compiler.compile(script)?; + + assert_eq!(p.consts.len(), 3); + assert_eq!( + p.opcodes, + &[ + Op::Const { idx: 0 }, + Op::String { size: 1 }, + Op::Const { idx: 1 }, + Op::String { size: 1 }, + Op::Record { size: 1 }, + Op::Const { idx: 0 }, + Op::String { size: 1 }, + Op::Const { idx: 2 }, + Op::RecordSet, + ] + ); + + assert_eq!( + run(&p)?, + literal!({ + "foo": 42 + }) + ); + Ok(()) +} + +#[test] +fn patch_upsert_2() -> Result<()> { + let mut compiler: Compiler = Compiler::new(); + + let script = parse(r#"patch {} of upsert "foo" => 42 end"#)?; + let p = compiler.compile(script)?; + + assert_eq!(p.consts.len(), 2); + assert_eq!( + p.opcodes, + &[ + Op::Record { size: 0 }, + Op::Const { idx: 0 }, + Op::String { size: 1 }, + Op::Const { idx: 1 }, + Op::RecordSet, + ] + ); + + assert_eq!( + run(&p)?, + literal!({ + "foo": 42 + }) + ); + Ok(()) +} + +#[test] +fn patch_patch_patch() -> Result<()> { + let mut compiler: Compiler = Compiler::new(); + + let script = parse( + r#"patch patch {"foo":"bar"} of upsert "bar" => "baz" end of insert "baz" => 42 end"#, + )?; + let p = compiler.compile(script)?; + + println!("{p}"); + assert_eq!(p.consts.len(), 5); + assert_eq!( + p.opcodes, + &[ + Op::Const { idx: 0 }, + Op::String { size: 1 }, + Op::Const { idx: 1 }, + Op::String { size: 1 }, + Op::Record { size: 1 }, + Op::Const { idx: 1 }, + Op::String { size: 1 }, + Op::Const { idx: 2 }, + Op::String { size: 1 }, + Op::RecordSet, + Op::Const { idx: 2 }, + Op::String { size: 1 }, + Op::TestRecortPresent, + Op::JumpFalse { dst: 15 }, + Op::Const { idx: 3 }, + Op::Error, + Op::Pop, + Op::Const { idx: 4 }, + Op::RecordSet, + ] + ); + + assert_eq!( + run(&p)?, + literal!({ + "foo": "bar", + "bar": "baz", + "baz": 42 + }) + ); + Ok(()) +} + +#[test] +fn array_index_fast() -> Result<()> { + let mut compiler: Compiler = Compiler::new(); + + let script = parse("[1,2,3][1]")?; + let p = compiler.compile(script)?; + + assert_eq!(p.consts.len(), 3); + assert_eq!( + p.opcodes, + &[ + Op::Const { idx: 0 }, + Op::Const { idx: 1 }, + Op::Const { idx: 2 }, + Op::Array { size: 3 }, + Op::IndexFast { idx: 1 }, + ] + ); + + assert_eq!(run(&p)?, 2); + Ok(()) +} + +#[test] +fn array_index() -> Result<()> { + let mut compiler: Compiler = Compiler::new(); + + let script = parse("[1,2,3][1+1]")?; + let p = compiler.compile(script)?; + + assert_eq!(p.consts.len(), 3); + assert_eq!( + p.opcodes, + &[ + Op::Const { idx: 0 }, + Op::Const { idx: 1 }, + Op::Const { idx: 2 }, + Op::Array { size: 3 }, + Op::Const { idx: 0 }, + Op::Const { idx: 0 }, + Op::Binary { op: BinOpKind::Add }, + Op::Get, + ] + ); + + assert_eq!(run(&p)?, 3); + Ok(()) +} + +#[test] +fn record_key() -> Result<()> { + let mut compiler: Compiler = Compiler::new(); + + let script = parse(r#"{"snot":"badger"}.snot"#)?; + let p = compiler.compile(script)?; + + assert_eq!(p.consts.len(), 2); + assert_eq!( + p.opcodes, + &[ + Op::Const { idx: 0 }, + Op::String { size: 1 }, + Op::Const { idx: 1 }, + Op::String { size: 1 }, + Op::Record { size: 1 }, + Op::GetKey { key: 0 }, + ] + ); + + assert_eq!(run(&p)?, "badger"); + Ok(()) +} + +#[test] +fn record_path() -> Result<()> { + let mut compiler: Compiler = Compiler::new(); + + let script = parse(r#"{"snot":"badger"}["snot"]"#)?; + let p = compiler.compile(script)?; + + assert_eq!(p.consts.len(), 2); + assert_eq!( + p.opcodes, + &[ + Op::Const { idx: 0 }, + Op::String { size: 1 }, + Op::Const { idx: 1 }, + Op::String { size: 1 }, + Op::Record { size: 1 }, + Op::Const { idx: 0 }, + Op::String { size: 1 }, + Op::Get, + ] + ); + + assert_eq!(run(&p)?, "badger"); + Ok(()) +} + +#[test] +fn array_range() -> Result<()> { + let mut compiler: Compiler = Compiler::new(); + + let script = parse("[1,2,3][0:2]")?; + let p = compiler.compile(script)?; + + assert_eq!(p.consts.len(), 4); + assert_eq!( + p.opcodes, + &[ + Op::Const { idx: 0 }, + Op::Const { idx: 1 }, + Op::Const { idx: 2 }, + Op::Array { size: 3 }, + Op::Const { idx: 3 }, + Op::Const { idx: 1 }, + Op::Range + ] + ); + + let r = run(&p)?; + assert_eq!(r[0], 1); + assert_eq!(r[1], 2); + Ok(()) +} + +#[test] +fn event_key() -> Result<()> { + let mut compiler: Compiler = Compiler::new(); + + let script = parse("event.int")?; + let p = compiler.compile(script)?; + + assert_eq!(p.consts.len(), 0); + assert_eq!(p.opcodes, &[Op::LoadEvent, Op::GetKey { key: 0 },]); + + assert_eq!(run(&p)?, 42); + Ok(()) +} + +#[test] +fn event_nested() -> Result<()> { + let mut compiler: Compiler = Compiler::new(); + + let script = parse("event.object.a")?; + let p = compiler.compile(script)?; + + assert_eq!(p.consts.len(), 0); + assert_eq!( + p.opcodes, + &[Op::LoadEvent, Op::GetKey { key: 0 }, Op::GetKey { key: 1 }] + ); + + assert_eq!(run(&p)?, 1); + Ok(()) +} + +#[test] +fn event_nested_index() -> Result<()> { + let mut compiler: Compiler = Compiler::new(); + + let script = parse("event.array[1]")?; + let p = compiler.compile(script)?; + + assert_eq!(p.consts.len(), 0); + assert_eq!( + p.opcodes, + &[ + Op::LoadEvent, + Op::GetKey { key: 5 }, + Op::Const { idx: 0 }, + Op::Get, + ] + ); + + assert_eq!(run(&p)?, 2); + Ok(()) +} + +#[test] +fn test_local() -> Result<()> { + let mut compiler: Compiler = Compiler::new(); + + let script = parse("let a = 42; a")?; + let p = compiler.compile(script)?; + + assert_eq!(p.consts.len(), 1); + assert_eq!(p.max_locals, 0); + assert_eq!( + p.opcodes, + &[ + Op::Const { idx: 0 }, + Op::StoreLocal { idx: 0 }, + Op::LoadLocal { idx: 0 }, + ] + ); + + assert_eq!(run(&p)?, 42); + Ok(()) +} + +#[test] +fn test_local_event() -> Result<()> { + let mut compiler: Compiler = Compiler::new(); + + let script = parse("let a = event; a.int")?; + let p = compiler.compile(script)?; + + assert_eq!(p.consts.len(), 0); + assert_eq!(p.max_locals, 0); + assert_eq!( + p.opcodes, + &[ + Op::LoadEvent, + Op::StoreLocal { idx: 0 }, + Op::LoadLocal { idx: 0 }, + Op::GetKey { key: 0 }, + ] + ); + + assert_eq!(run(&p)?, 42); + Ok(()) +} diff --git a/tremor-value/src/value.rs b/tremor-value/src/value.rs index 6ccf845dde..83cca90e58 100644 --- a/tremor-value/src/value.rs +++ b/tremor-value/src/value.rs @@ -316,6 +316,13 @@ impl<'value> Value<'value> { _ => None, } } + /// Tries to get the bytes from a Value + #[inline] + #[must_use] + pub fn is_bytes(&self) -> bool { + self.as_bytes().is_some() + } + /// Tries to get the bytes from a Value /// /// # Errors From 4bc7f6cf0f7a10d3482a04530408ca1223aabb45 Mon Sep 17 00:00:00 2001 From: "Heinz N. Gies" Date: Sat, 10 Aug 2024 22:50:35 +0200 Subject: [PATCH 02/12] Some match support Signed-off-by: Heinz N. Gies --- tremor-script/src/vm.rs | 180 +++++++++---- tremor-script/src/vm/compiler.rs | 377 ++++++++++++++++++++++++---- tremor-script/src/vm/tests.rs | 418 +++++++++++++++++++------------ 3 files changed, 712 insertions(+), 263 deletions(-) diff --git a/tremor-script/src/vm.rs b/tremor-script/src/vm.rs index ceb93b2da5..6be8c3569e 100644 --- a/tremor-script/src/vm.rs +++ b/tremor-script/src/vm.rs @@ -29,21 +29,31 @@ pub(crate) enum Op { /// duplicate the top of the stack #[allow(dead_code)] Duplicate, + /// Load V1, pops the stack and stores the value in V1 + LoadV1, + /// Stores the value in V1 on the stack and sets it to null + StoreV1, + /// Swaps the value in V1 with the top of the stack + SwapV1, + /// Copies the content of V1 to the top of the stack + CopyV1, + /// Load boolean register from the top of the stack + LoadRB, + /// Store boolean register to the top of the stack + #[allow(dead_code)] + StoreRB, /// Puts the event on the stack LoadEvent, /// puts a variable on the stack LoadLocal { idx: u32, }, + /// stores a variable from the stack StoreLocal { idx: u32, }, /// emits an error Error, - #[allow(dead_code)] - Begin, - #[allow(dead_code)] - End, /// emits the top of the stack Emit { dflt: bool, @@ -58,6 +68,10 @@ pub(crate) enum Op { JumpFalse { dst: u32, }, + /// jumps to the given offset if the top of the stack is true does not op the stack + Jump { + dst: u32, + }, Const { idx: u32, }, @@ -67,6 +81,7 @@ pub(crate) enum Op { True, #[allow(dead_code)] False, + Null, Record { size: u32, }, @@ -115,6 +130,8 @@ pub(crate) enum Op { RecordGet, // Merge Merge, + TestIsRecord, + TestIsArray, } impl Display for Op { @@ -125,17 +142,26 @@ impl Display for Op { Op::Swap => write!(f, "swap"), Op::Duplicate => write!(f, "duplicate"), Op::Error => write!(f, "error"), + + Op::LoadV1 => write!(f, "{:30} V1", "load_reg"), + Op::StoreV1 => write!(f, "{:30} V1", "store_reg"), + Op::SwapV1 => write!(f, "{:30} V1", "swap_reg"), + Op::CopyV1 => write!(f, "{:30} V1", "copy_reg"), + + Op::LoadRB => write!(f, "{:30} B1", "load_reg"), + Op::StoreRB => write!(f, "{:30} B1", "store_reg"), + Op::LoadEvent => write!(f, "laod_event"), Op::LoadLocal { idx } => write!(f, "{:30} {}", "load_local", idx), Op::StoreLocal { idx } => write!(f, "{:30} {}", "store_local", idx), - Op::Begin => write!(f, "begin"), - Op::End => write!(f, "end"), Op::Emit { dflt } => write!(f, "{:30} {dflt}", "emit"), Op::Drop => write!(f, "drop"), Op::JumpTrue { dst } => write!(f, "{:30} {}", "jump_true", dst), Op::JumpFalse { dst } => write!(f, "{:30} {}", "jump_false", dst), + Op::Jump { dst } => write!(f, "{:30} {}", "jump", dst), Op::True => write!(f, "true"), Op::False => write!(f, "false"), + Op::Null => write!(f, "null"), Op::Const { idx } => write!(f, "{:30} {}", "const", idx), Op::Record { size } => write!(f, "{:30} {}", "record", size), Op::Array { size } => write!(f, "{:30} {}", "array", size), @@ -154,6 +180,8 @@ impl Display for Op { Op::TestIsU64 => write!(f, "test_is_u64"), Op::TestIsI64 => write!(f, "test_is_i64"), Op::TestIsBytes => write!(f, "test_is_bytes"), + Op::TestIsRecord => write!(f, "test_is_record"), + Op::TestIsArray => write!(f, "test_is_array"), Op::RecordSet => write!(f, "record_set"), Op::RecordRemove => write!(f, "record_remove"), Op::RecordGet => write!(f, "record_get"), @@ -198,9 +226,23 @@ impl Display for Program<'_> { .copied() .unwrap_or_default() )?, + Op::Jump { dst } => writeln!( + f, + " {idx:04}: {:30} JMP<{:03}>", + "jump", + self.jump_table + .get(&(*dst as usize)) + .copied() + .unwrap_or_default() + )?, _ => writeln!(f, " {idx:04}: {op}")?, } } + for (i, dst) in &self.jump_table { + if *i > self.opcodes.len() { + writeln!(f, "JMP<{dst:03}>")?; + } + } Ok(()) } } @@ -208,9 +250,18 @@ impl Display for Program<'_> { #[allow(dead_code)] pub struct Vm {} +struct Registers<'run, 'event> { + /// Value register 1 + v1: Cow<'run, Value<'event>>, + /// Boolean register 1 + b1: bool, +} + pub struct Scope<'run, 'event> { // value: &'run mut Value<'event>, - keys: &'run [tremor_value::KnownKey<'event>], + program: &'run Program<'event>, + registers: &'run mut Registers<'run, 'event>, + locals: &'run mut [Option>], } #[allow(dead_code)] impl Vm { @@ -222,26 +273,32 @@ impl Vm { pub fn run<'run, 'prog, 'event>( &self, event: &mut Value<'event>, - program: &Program<'prog>, + program: &'run Program<'prog>, ) -> Result> where 'prog: 'event, { // our locals start at zero, so we need to allocate a length of max_locals + 1 let mut locals = Vec::with_capacity(program.max_locals + 1); + let mut registers: Registers = Registers { + v1: Cow::Owned(Value::null()), + b1: false, + }; for _ in 0..=program.max_locals { locals.push(None); } // value: &mut null, let mut root = Scope { - keys: &program.keys, + program, + locals: &mut locals, + registers: &mut registers, }; let mut pc = 0; // ensure that the opcodes and meta are the same length assert_eq!(program.opcodes.len(), program.meta.len()); - root.run(event, &mut locals, program, &mut pc) + root.run(event, &mut pc) } } @@ -249,9 +306,7 @@ impl<'run, 'event> Scope<'run, 'event> { #[allow(clippy::too_many_lines)] pub fn run<'prog>( &mut self, - event: &mut Value<'event>, - locals: &mut [Option>], - program: &Program<'prog>, + event: &'run mut Value<'event>, pc: &mut usize, ) -> Result> where @@ -259,37 +314,39 @@ impl<'run, 'event> Scope<'run, 'event> { { let mut stack: Vec> = Vec::with_capacity(8); - while *pc < program.opcodes.len() { - let mid = &program.meta[*pc]; + while *pc < self.program.opcodes.len() { + let mid = &self.program.meta[*pc]; // ALLOW: we test that pc is always in bounds in the while loop above - match unsafe { *program.opcodes.get_unchecked(*pc) } { + match unsafe { *self.program.opcodes.get_unchecked(*pc) } { Op::Nop => continue, // Loads + Op::LoadV1 => self.registers.v1 = stack.pop().ok_or("Stack underflow")?, + Op::StoreV1 => stack.push(std::mem::take(&mut self.registers.v1)), + Op::CopyV1 => stack.push(self.registers.v1.clone()), + Op::SwapV1 => std::mem::swap( + &mut self.registers.v1, + stack.last_mut().ok_or("Stack underflow")?, + ), + Op::LoadRB => { + self.registers.b1 = stack.pop().ok_or("Stack underflow")?.try_as_bool()?; + } + Op::StoreRB => stack.push(Cow::Owned(Value::from(self.registers.b1))), Op::LoadEvent => stack.push(Cow::Borrowed(event)), Op::LoadLocal { idx } => { let idx = idx as usize; stack.push(Cow::Owned( - locals[idx].as_ref().ok_or("Local not set")?.clone(), + self.locals[idx].as_ref().ok_or("Local not set")?.clone(), )); } Op::StoreLocal { idx } => { let idx = idx as usize; - locals[idx] = Some(stack.pop().ok_or("Stack underflow")?.into_owned()); + self.locals[idx] = Some(stack.pop().ok_or("Stack underflow")?.into_owned()); } Op::True => stack.push(Cow::Owned(Value::const_true())), Op::False => stack.push(Cow::Owned(Value::const_false())), - Op::Const { idx } => stack.push(Cow::Borrowed(&program.consts[idx as usize])), - Op::Begin => { - let _value: &mut Value = stack.last_mut().ok_or("Stack underflow")?.to_mut(); - // let mut scopte = Scope { - // value, - // keys: self.keys, - // }; - *pc += 1; - // scopte.run(event, program, pc)?; - } - Op::End => return Ok(Return::EmitEvent { port: None }), + Op::Null => stack.push(Cow::Owned(Value::null())), + Op::Const { idx } => stack.push(Cow::Borrowed(&self.program.consts[idx as usize])), Op::Pop => { stack.pop().ok_or("Stack underflow")?; } @@ -332,16 +389,23 @@ impl<'run, 'event> Scope<'run, 'event> { } #[allow(clippy::cast_abs_to_unsigned)] Op::JumpTrue { dst } => { - if stack.last().ok_or("Stack underflow")?.try_as_bool()? { + if self.registers.b1 { *pc = dst as usize; + continue; } } #[allow(clippy::cast_abs_to_unsigned)] Op::JumpFalse { dst } => { - if !stack.last().ok_or("Stack underflow")?.try_as_bool()? { + if !self.registers.b1 { *pc = dst as usize; + continue; } } + #[allow(clippy::cast_abs_to_unsigned)] + Op::Jump { dst } => { + *pc = dst as usize; + continue; + } Op::Record { size } => { let size = size as usize; let mut record = Value::object_with_capacity(size); @@ -434,6 +498,20 @@ impl<'run, 'event> Scope<'run, 'event> { stack.push(Cow::Owned(Value::Bytes(bytes.into()))); } + // Compairsons ops that store in the B1 register + // Op::Binary { + // op: + // op @ (BinOpKind::Eq + // | BinOpKind::NotEq + // | BinOpKind::Gte + // | BinOpKind::Gt + // | BinOpKind::Lte + // | BinOpKind::Lt), + // } => { + // let rhs = stack.pop().ok_or("Stack underflow")?; + // let lhs = stack.pop().ok_or("Stack underflow")?; + // self.registers.b1 = exec_binary(mid, mid, op, &lhs, &rhs)?.try_as_bool()?; + // } Op::Binary { op } => { let rhs = stack.pop().ok_or("Stack underflow")?; let lhs = stack.pop().ok_or("Stack underflow")?; @@ -445,56 +523,50 @@ impl<'run, 'event> Scope<'run, 'event> { } Op::Xor => { let rhs = stack.pop().ok_or("Stack underflow")?; - let lhs = stack.pop().ok_or("Stack underflow")?; stack.push(Cow::Owned(Value::from( - lhs.try_as_bool()? ^ rhs.try_as_bool()?, + self.registers.b1 ^ rhs.try_as_bool()?, ))); } // tests Op::TestRecortPresent => { - let key = stack.pop().ok_or("Stack underflow")?; - let r = stack.last().ok_or("Stack underflow")?; - let present = r.contains_key(key.try_as_str()?); - stack.push(key); - stack.push(Cow::Owned(Value::from(present))); + let key = stack.last().ok_or("Stack underflow")?; + self.registers.b1 = self.registers.v1.contains_key(key.try_as_str()?); } // record operations on scope Op::TestIsU64 => { - let v = stack.last().ok_or("Stack underflow")?; - let is_u64 = v.is_u64(); - stack.push(Cow::Owned(Value::from(is_u64))); + self.registers.b1 = self.registers.v1.is_u64(); } Op::TestIsI64 => { - let v = stack.last().ok_or("Stack underflow")?; - let is_i64 = v.is_i64(); - stack.push(Cow::Owned(Value::from(is_i64))); + self.registers.b1 = self.registers.v1.is_i64(); } Op::TestIsBytes => { - let v = stack.last().ok_or("Stack underflow")?; - let is_bytes = v.is_bytes(); - stack.push(Cow::Owned(Value::from(is_bytes))); + self.registers.b1 = self.registers.v1.is_bytes(); + } + Op::TestIsRecord => { + self.registers.b1 = self.registers.v1.is_object(); + } + Op::TestIsArray => { + self.registers.b1 = self.registers.v1.is_array(); } + // Records Op::RecordSet => { let value = stack.pop().ok_or("Stack underflow")?; let key = stack.pop().ok_or("Stack underflow")?; - let r = stack.last_mut().ok_or("Stack underflow")?; // FIXME: we can do better than clone here let key = key.into_owned().try_into_string()?; - r.to_mut().insert(key, value.into_owned())?; + self.registers.v1.to_mut().insert(key, value.into_owned())?; } Op::RecordRemove => { let key = stack.pop().ok_or("Stack underflow")?; - let r = stack.last_mut().ok_or("Stack underflow")?; let key = key.try_as_str()?; - let v = r.to_mut().remove(key)?.unwrap_or_default(); + let v = self.registers.v1.to_mut().remove(key)?.unwrap_or_default(); stack.push(Cow::Owned(v)); } Op::RecordGet => { let key = stack.pop().ok_or("Stack underflow")?; - let r = stack.last().ok_or("Stack underflow")?; let key = key.try_as_str()?; // FIXME: can we avoid this clone here - let v = r.get(key).ok_or("not a record")?.clone(); + let v = self.registers.v1.get(key).ok_or("not a record")?.clone(); stack.push(Cow::Owned(v)); } // merge @@ -506,7 +578,7 @@ impl<'run, 'event> Scope<'run, 'event> { } // Path Op::GetKey { key } => { - let key = &self.keys[key as usize]; + let key = &self.program.keys[key as usize]; let v = stack.pop().ok_or("Stack underflow")?; // FIXME: can we avoid this clone here let res = key.lookup(&v).ok_or("Missing Key FIXME")?.clone(); diff --git a/tremor-script/src/vm/compiler.rs b/tremor-script/src/vm/compiler.rs index 54c545d4aa..b2f8257c30 100644 --- a/tremor-script/src/vm/compiler.rs +++ b/tremor-script/src/vm/compiler.rs @@ -1,8 +1,9 @@ use crate::{ ast::{ raw::{BytesDataType, Endian}, - BooleanBinExpr, BooleanBinOpKind, BytesPart, EmitExpr, Expr, Field, ImutExpr, - PatchOperation, Path, Script, Segment, StrLitElement, StringLit, + BaseExpr, BinOpKind, BooleanBinExpr, BooleanBinOpKind, BytesPart, ClauseGroup, + ClausePreCondition, DefaultCase, EmitExpr, Expr, Expression, Field, ImutExpr, Match, + PatchOperation, Path, Pattern, PredicateClause, Script, Segment, StrLitElement, StringLit, }, errors::Result, NodeMeta, @@ -12,16 +13,27 @@ use tremor_value::Value; use super::{Op, Program}; +#[derive(Debug)] /// A compiler for tremor script pub struct Compiler<'script> { opcodes: Vec, meta: Vec, jump_table: Vec, + end_table: Vec, consts: Vec>, keys: Vec>, max_locals: usize, } +trait Compilable<'script>: Sized { + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()>; + fn compile_to_b(self, compiler: &mut Compiler<'script>) -> Result<()> { + self.compile(compiler)?; + compiler.emit(Op::LoadRB, *NodeMeta::dummy()); + Ok(()) + } +} + impl<'script> Compiler<'script> { /// compiles a script into a program /// # Errors @@ -36,7 +48,7 @@ impl<'script> Compiler<'script> { for op in &mut opcodes { match op { - Op::JumpTrue { dst } | Op::JumpFalse { dst } => { + Op::JumpTrue { dst, .. } | Op::JumpFalse { dst, .. } | Op::Jump { dst, .. } => { *dst = self.jump_table[*dst as usize]; } _ => (), @@ -63,17 +75,18 @@ impl<'script> Compiler<'script> { opcodes: Vec::new(), meta: Vec::new(), jump_table: Vec::new(), + end_table: Vec::new(), consts: Vec::new(), keys: Vec::new(), max_locals: 0, } } - #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] - pub(crate) fn last_code_offset(&self) -> u32 { - assert!(!self.opcodes.is_empty()); - self.opcodes.len() as u32 - 1 - } + // #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] + // fn last_code_offset(&self) -> u32 { + // assert!(!self.opcodes.is_empty()); + // self.opcodes.len() as u32 - 1 + // } pub(crate) fn emit(&mut self, op: Op, mid: NodeMeta) { self.opcodes.push(op); @@ -111,9 +124,31 @@ impl<'script> Compiler<'script> { self.jump_table.push(0); self.jump_table.len() as u32 - 1 } + #[deny(clippy::cast_possible_truncation)] + pub(crate) fn set_jump_target(&mut self, jump_point: u32) { + self.jump_table[jump_point as usize] = self.opcodes.len() as u32; + } - pub(crate) fn set_jump(&mut self, jump_point: u32) { - self.jump_table[jump_point as usize] = self.last_code_offset(); + fn new_end_target(&mut self) { + let end = self.new_jump_point(); + self.end_table.push(end); + } + + fn set_end_target(&mut self) -> Result<()> { + if let Some(end) = self.end_table.pop() { + self.set_jump_target(end); + Ok(()) + } else { + Err("No jump destination found".into()) + } + } + + fn end_dst(&self) -> Result { + if let Some(dst) = self.end_table.last() { + Ok(*dst) + } else { + Err("No jump destination found".into()) + } } } @@ -123,20 +158,44 @@ impl<'script> Default for Compiler<'script> { } } -impl<'script> Script<'script> { - pub(crate) fn compile(self, compiler: &mut Compiler<'script>) -> Result { +impl<'script> Compilable<'script> for Script<'script> { + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { for e in self.exprs { e.compile(compiler)?; } - Ok(compiler.last_code_offset()) + Ok(()) } } -impl<'script> Expr<'script> { +impl<'script> Compilable<'script> for Expr<'script> { fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { match self { - Expr::Match(_) => todo!(), - Expr::IfElse(_) => todo!(), + Expr::Match(m) => m.compile(compiler)?, + Expr::IfElse(ie) => { + let mid = *ie.mid.clone(); + // jump point for the else clause + let else_dst = compiler.new_jump_point(); + // jump point for the end of the if expression + compiler.new_end_target(); + // store v1 + compiler.emit(Op::StoreV1, mid.clone()); + + // compile the target and store the result in the B1 register + ie.target.compile(compiler)?; + // load target in register 1; + compiler.emit(Op::LoadV1, mid.clone()); + // compile the if clause + ie.if_clause.compile(compiler)?; + // this is the jump destionaion for the else clause + compiler.set_jump_target(else_dst); + ie.else_clause.compile(compiler)?; + // this is the jump destionaion for the end of the if expression + compiler.set_end_target()?; + // load the result in of the expression to v1 so we can restore the old value + compiler.emit(Op::LoadV1, *ie.mid.clone()); + // restore original value + compiler.emit(Op::SwapV1, *ie.mid.clone()); + } #[allow(clippy::cast_possible_truncation)] Expr::Assign { mid: _, path, expr } => { let Path::Local(p) = path else { @@ -164,7 +223,7 @@ impl<'script> Expr<'script> { } #[allow(clippy::too_many_lines)] -impl<'script> ImutExpr<'script> { +impl<'script> Compilable<'script> for ImutExpr<'script> { fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { match self { #[allow(clippy::cast_possible_truncation)] @@ -197,12 +256,20 @@ impl<'script> ImutExpr<'script> { compiler.emit(Op::Unary { op: u.kind }, *u.mid); } ImutExpr::Patch(p) => { + let mid = *p.mid.clone(); + // Save r1 to ensure we can restore it after the patch operation + compiler.emit(Op::StoreV1, mid.clone()); + // compile the target p.target.compile(compiler)?; + // load the target into the register + compiler.emit(Op::LoadV1, mid.clone()); for op in p.operations { op.compile(compiler)?; } + // restore r1 and ensure the result is on top of the stack + compiler.emit(Op::SwapV1, mid); } - ImutExpr::Match(_) => todo!(), + ImutExpr::Match(m) => m.compile(compiler)?, ImutExpr::Comprehension(_) => todo!(), ImutExpr::Merge(m) => { m.target.compile(compiler)?; @@ -232,10 +299,15 @@ impl<'script> ImutExpr<'script> { #[allow(clippy::cast_possible_truncation)] ImutExpr::Bytes(b) => { let size = b.value.len() as u32; + let mid = *b.mid; + // we modify r1 in the parts so we need to store it + compiler.emit(Op::StoreV1, mid.clone()); for b in b.value { b.compile(compiler)?; } - compiler.emit(Op::Bytes { size }, *b.mid); + compiler.emit(Op::Bytes { size }, mid.clone()); + // once the bytes are crated we restore it + compiler.emit(Op::LoadV1, mid); } #[allow(clippy::cast_possible_truncation)] ImutExpr::ArrayAppend(a) => { @@ -251,17 +323,17 @@ impl<'script> ImutExpr<'script> { } } -impl<'script> Field<'script> { - pub(crate) fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { +impl<'script> Compilable<'script> for Field<'script> { + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { self.name.compile(compiler)?; self.value.compile(compiler)?; Ok(()) } } -impl<'script> StringLit<'script> { +impl<'script> Compilable<'script> for StringLit<'script> { #[allow(clippy::cast_possible_truncation)] - pub(crate) fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { let size = self.elements.len() as u32; for e in self.elements { match e { @@ -278,8 +350,8 @@ impl<'script> StringLit<'script> { } } -impl<'script> EmitExpr<'script> { - pub(crate) fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { +impl<'script> Compilable<'script> for EmitExpr<'script> { + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { self.expr.compile(compiler)?; let dflt = if let Some(port) = self.port { port.compile(compiler)?; @@ -292,9 +364,9 @@ impl<'script> EmitExpr<'script> { } } -impl<'script> PatchOperation<'script> { +impl<'script> Compilable<'script> for PatchOperation<'script> { #[allow(unused_variables)] - pub(crate) fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { match self { PatchOperation::Insert { ident, expr, mid } => { ident.compile(compiler)?; @@ -303,8 +375,7 @@ impl<'script> PatchOperation<'script> { compiler.emit(Op::JumpFalse { dst }, *mid.clone()); compiler.emit_const("Key already present", *mid.clone()); compiler.emit(Op::Error, *mid.clone()); - compiler.set_jump(dst); - compiler.emit(Op::Pop, *mid.clone()); + compiler.set_jump_target(dst); expr.compile(compiler)?; compiler.emit(Op::RecordSet, *mid); } @@ -320,8 +391,7 @@ impl<'script> PatchOperation<'script> { compiler.emit(Op::JumpTrue { dst }, *mid.clone()); compiler.emit_const("Key already present", *mid.clone()); compiler.emit(Op::Error, *mid.clone()); - compiler.set_jump(dst); - compiler.emit(Op::Pop, *mid.clone()); + compiler.set_jump_target(dst); expr.compile(compiler)?; compiler.emit(Op::RecordSet, *mid); } @@ -353,7 +423,7 @@ impl<'script> PatchOperation<'script> { } } -impl<'script> Segment<'script> { +impl<'script> Compilable<'script> for Segment<'script> { fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { match self { Segment::Id { mid, key } => { @@ -394,16 +464,22 @@ impl<'script> Segment<'script> { } } -impl<'script> BooleanBinExpr<'script> { +impl<'script> Compilable<'script> for BooleanBinExpr<'script> { fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { - self.lhs.compile(compiler)?; + let mid = *self.mid.clone(); + self.compile_to_b(compiler)?; + compiler.emit(Op::StoreRB, mid); + Ok(()) + } + fn compile_to_b(self, compiler: &mut Compiler<'script>) -> Result<()> { + self.lhs.compile_to_b(compiler)?; match self.kind { #[allow(clippy::cast_sign_loss)] BooleanBinOpKind::Or => { let dst = compiler.new_jump_point(); - compiler.emit(Op::JumpTrue { dst }, *self.mid); - self.rhs.compile(compiler)?; - compiler.set_jump(dst); + compiler.emit(Op::JumpTrue { dst }, *self.mid.clone()); + self.rhs.compile_to_b(compiler)?; + compiler.set_jump_target(dst); } BooleanBinOpKind::Xor => { self.rhs.compile(compiler)?; @@ -413,8 +489,8 @@ impl<'script> BooleanBinExpr<'script> { BooleanBinOpKind::And => { let dst = compiler.new_jump_point(); compiler.emit(Op::JumpFalse { dst }, *self.mid); - self.rhs.compile(compiler)?; - compiler.set_jump(dst); + self.rhs.compile_to_b(compiler)?; + compiler.set_jump_target(dst); } } Ok(()) @@ -458,7 +534,7 @@ impl From for BytesDataType { } } -impl<'script> BytesPart<'script> { +impl<'script> Compilable<'script> for BytesPart<'script> { fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { let BytesPart { mid, @@ -476,7 +552,8 @@ impl<'script> BytesPart<'script> { let mid = *mid; data.compile(compiler)?; let dst = compiler.new_jump_point(); - + // load the value into r1 so we can test it + compiler.emit(Op::LoadV1, mid.clone()); match data_type { BytesDataType::SignedInteger => compiler.emit(Op::TestIsI64, mid.clone()), BytesDataType::UnsignedInteger => compiler.emit(Op::TestIsU64, mid.clone()), @@ -485,14 +562,15 @@ impl<'script> BytesPart<'script> { compiler.emit(Op::JumpFalse { dst }, mid.clone()); compiler.emit_const("invalid type conversion for binary", mid.clone()); compiler.emit(Op::Error, mid.clone()); - compiler.set_jump(dst); + compiler.set_jump_target(dst); + // save the value back to the stack + compiler.emit(Op::StoreV1, mid.clone()); compiler.emit_const(format, mid); - Ok(()) } } -impl<'script> Path<'script> { +impl<'script> Compilable<'script> for Path<'script> { fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { match self { #[allow(clippy::cast_possible_truncation)] @@ -522,3 +600,214 @@ impl<'script> Path<'script> { Ok(()) } } + +impl<'script, Ex> Compilable<'script> for PredicateClause<'script, Ex> +where + Ex: Expression + Compilable<'script>, +{ + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + let PredicateClause { + mid, + pattern, + guard, + exprs, + last_expr, + } = self; + let end_dst = compiler.end_dst()?; + let dst = compiler.new_jump_point(); + + compiler.emit(Op::CopyV1, *mid.clone()); + pattern.compile_to_b(compiler)?; + compiler.emit(Op::JumpFalse { dst }, *mid.clone()); + if let Some(guard) = guard { + guard.compile_to_b(compiler)?; + compiler.emit(Op::JumpFalse { dst }, *mid.clone()); + } + for e in exprs { + e.compile(compiler)?; + } + last_expr.compile(compiler)?; + // we were successful so we jump to the end + compiler.emit(Op::Jump { dst: end_dst }, *mid); + compiler.set_jump_target(dst); + Ok(()) + } +} + +impl<'script> Compilable<'script> for Pattern<'script> { + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + self.compile_to_b(compiler) + } + /// will return the match state in registers.B + fn compile_to_b(self, compiler: &mut Compiler<'script>) -> Result<()> { + match self { + Pattern::Record(r) => { + let mid = *r.mid; + compiler.emit(Op::TestIsRecord, mid.clone()); + let dst = compiler.new_jump_point(); + compiler.emit(Op::JumpFalse { dst }, mid.clone()); + if r.fields.is_empty() { + } else { + todo!() + } + compiler.set_jump_target(dst); + } + Pattern::Array(a) => { + let mid = *a.mid; + compiler.emit(Op::TestIsArray, mid.clone()); + let dst = compiler.new_jump_point(); + compiler.emit(Op::JumpFalse { dst }, mid.clone()); + if a.exprs.is_empty() { + } else { + todo!() + } + compiler.set_jump_target(dst); + } + Pattern::Expr(e) => { + let mid = e.meta().clone(); + e.compile(compiler)?; + compiler.emit(Op::Binary { op: BinOpKind::Eq }, mid); + compiler.emit(Op::LoadRB, *NodeMeta::dummy()); + } + + Pattern::Assign(_) => todo!(), + Pattern::Tuple(_) => todo!(), + Pattern::Extract(_) => todo!(), + Pattern::DoNotCare => { + compiler.emit(Op::True, *NodeMeta::dummy()); + compiler.emit(Op::LoadRB, *NodeMeta::dummy()); + } + } + Ok(()) + } +} + +impl<'script, Ex> Compilable<'script> for DefaultCase +where + Ex: Compilable<'script> + Expression, +{ + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + match self { + DefaultCase::None => { + compiler.emit(Op::Nop, *NodeMeta::dummy()); + } + DefaultCase::Null => { + compiler.emit(Op::Null, *NodeMeta::dummy()); + } + DefaultCase::Many { exprs, last_expr } => { + for e in exprs { + e.compile(compiler)?; + } + last_expr.compile(compiler)?; + } + DefaultCase::One(e) => e.compile(compiler)?, + } + Ok(()) + } +} +impl<'script, Ex> Compilable<'script> for ClauseGroup<'script, Ex> +where + Ex: Compilable<'script> + Expression, +{ + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + let next = compiler.new_jump_point(); + match self { + ClauseGroup::Simple { + precondition, + patterns, + } => { + if let Some(precondition) = precondition { + precondition.compile_to_b(compiler)?; + compiler.emit(Op::JumpFalse { dst: next }, *NodeMeta::dummy()); + // FIXME + } + for p in patterns { + p.compile(compiler)?; + } + } + ClauseGroup::SearchTree { + precondition, + tree: _, + rest, + } => { + if let Some(precondition) = precondition { + precondition.compile_to_b(compiler)?; + compiler.emit(Op::JumpFalse { dst: next }, *NodeMeta::dummy()); + // FIXME + } + for r in rest { + r.compile(compiler)?; + } + todo!("the tree has to go before therest!"); + } + ClauseGroup::Combined { + precondition, + groups, + } => { + if let Some(precondition) = precondition { + precondition.compile_to_b(compiler)?; + compiler.emit(Op::JumpFalse { dst: next }, *NodeMeta::dummy()); + // FIXME + } + for g in groups { + g.compile(compiler)?; + } + } + ClauseGroup::Single { + precondition, + pattern, + } => { + if let Some(precondition) = precondition { + precondition.compile_to_b(compiler)?; + compiler.emit(Op::JumpFalse { dst: next }, *NodeMeta::dummy()); + } + pattern.compile(compiler)?; + } + } + compiler.set_jump_target(next); + Ok(()) + } +} + +impl<'script> Compilable<'script> for ClausePreCondition<'script> { + fn compile(self, _compiler: &mut Compiler<'script>) -> Result<()> { + todo!() + } + fn compile_to_b(self, _compiler: &mut Compiler<'script>) -> Result<()> { + todo!() + } +} + +impl<'script, Ex> Compilable<'script> for Match<'script, Ex> +where + Ex: Compilable<'script> + Expression, +{ + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + let Match { + mid, + target, + patterns, + default, + } = self; + let mid = *mid; + compiler.new_end_target(); + // Save r1 to ensure we can restore it after the patch operation + compiler.emit(Op::StoreV1, mid.clone()); + // Save r1 to ensure we can restore it after the patch operation + target.compile(compiler)?; + // load target to v1 + compiler.emit(Op::LoadV1, mid.clone()); + + for c in patterns { + c.compile(compiler)?; + } + default.compile(compiler)?; + compiler.set_end_target()?; + // restore r1 and ensure the result is on top of the stack + // Load the result of the match into r1 + compiler.emit(Op::LoadV1, mid.clone()); + // restore r1 and ensure the result is on top of the stack + compiler.emit(Op::SwapV1, mid); + Ok(()) + } +} diff --git a/tremor-script/src/vm/tests.rs b/tremor-script/src/vm/tests.rs index bdd40956a1..bef1ee5832 100644 --- a/tremor-script/src/vm/tests.rs +++ b/tremor-script/src/vm/tests.rs @@ -1,6 +1,6 @@ use tremor_value::literal; -use super::*; +use super::{Op::*, *}; use crate::{ arena::Arena, ast::{Helper, Script}, @@ -63,9 +63,8 @@ fn simple() -> Result<()> { let script = parse("42")?; let p = compiler.compile(script)?; - assert_eq!(p.opcodes.len(), 1); assert_eq!(p.consts.len(), 1); - assert_eq!(p.opcodes, [Op::Const { idx: 0 }]); + assert_eq!(p.opcodes, [Const { idx: 0 }]); assert_eq!(run(&p)?, 42); Ok(()) } @@ -77,14 +76,13 @@ fn simple_add() -> Result<()> { let script = parse("42 + 42")?; let p = compiler.compile(script)?; - assert_eq!(p.opcodes.len(), 3); assert_eq!(p.consts.len(), 1); assert_eq!( p.opcodes, [ - Op::Const { idx: 0 }, - Op::Const { idx: 0 }, - Op::Binary { + Const { idx: 0 }, + Const { idx: 0 }, + Binary { op: crate::ast::BinOpKind::Add } ] @@ -101,18 +99,17 @@ fn simple_add_sub() -> Result<()> { let script = parse("42 + 43 - 44")?; let p = compiler.compile(script)?; - assert_eq!(p.opcodes.len(), 5); assert_eq!(p.consts.len(), 3); assert_eq!( p.opcodes, [ - Op::Const { idx: 0 }, - Op::Const { idx: 1 }, - Op::Binary { + Const { idx: 0 }, + Const { idx: 1 }, + Binary { op: crate::ast::BinOpKind::Add }, - Op::Const { idx: 2 }, - Op::Binary { + Const { idx: 2 }, + Binary { op: crate::ast::BinOpKind::Sub } ] @@ -129,14 +126,16 @@ fn logical_and() -> Result<()> { let script = parse("true and false")?; let p = compiler.compile(script)?; - assert_eq!(p.opcodes.len(), 3); assert_eq!(p.consts.len(), 2); assert_eq!( p.opcodes, [ - Op::Const { idx: 0 }, - Op::JumpFalse { dst: 2 }, - Op::Const { idx: 1 } + Const { idx: 0 }, + LoadRB, + JumpFalse { dst: 5 }, + Const { idx: 1 }, + LoadRB, + StoreRB ] ); assert_eq!(run(&p)?, false); @@ -150,14 +149,17 @@ fn logical_or() -> Result<()> { let script = parse("true or false")?; let p = compiler.compile(script)?; - assert_eq!(p.opcodes.len(), 3); + println!("{p}"); assert_eq!(p.consts.len(), 2); assert_eq!( p.opcodes, [ - Op::Const { idx: 0 }, - Op::JumpTrue { dst: 2 }, - Op::Const { idx: 1 } + Const { idx: 0 }, + LoadRB, + JumpTrue { dst: 5 }, + Const { idx: 1 }, + LoadRB, + StoreRB, ] ); assert_eq!(run(&p)?, true); @@ -171,13 +173,12 @@ fn logical_not() -> Result<()> { let script = parse("not true")?; let p = compiler.compile(script)?; - assert_eq!(p.opcodes.len(), 2); assert_eq!(p.consts.len(), 1); assert_eq!( p.opcodes, [ - Op::Const { idx: 0 }, - Op::Unary { + Const { idx: 0 }, + Unary { op: UnaryOpKind::Not } ] @@ -193,14 +194,13 @@ fn simple_eq() -> Result<()> { let script = parse("42 == 42")?; let p = compiler.compile(script)?; - assert_eq!(p.opcodes.len(), 3); assert_eq!(p.consts.len(), 1); assert_eq!( p.opcodes, [ - Op::Const { idx: 0 }, - Op::Const { idx: 0 }, - Op::Binary { + Const { idx: 0 }, + Const { idx: 0 }, + Binary { op: crate::ast::BinOpKind::Eq } ] @@ -220,18 +220,18 @@ fn patch_insert() -> Result<()> { assert_eq!( p.opcodes, &[ - Op::Record { size: 0 }, - Op::Begin, - Op::Const { idx: 0 }, - Op::String { size: 1 }, - Op::TestRecortPresent, - Op::JumpFalse { dst: 7 }, - Op::Const { idx: 1 }, - Op::Error, - Op::Pop, - Op::Const { idx: 2 }, - Op::RecordSet, - Op::End, + StoreV1, + Record { size: 0 }, + LoadV1, + Const { idx: 0 }, + String { size: 1 }, + TestRecortPresent, + JumpFalse { dst: 9 }, + Const { idx: 1 }, + Error, + Const { idx: 2 }, + RecordSet, + SwapV1, ] ); @@ -255,22 +255,22 @@ fn patch_insert_error() -> Result<()> { assert_eq!( p.opcodes, &[ - Op::Const { idx: 0 }, - Op::String { size: 1 }, - Op::Const { idx: 1 }, - Op::String { size: 1 }, - Op::Record { size: 1 }, - Op::Begin, - Op::Const { idx: 0 }, - Op::String { size: 1 }, - Op::TestRecortPresent, - Op::JumpFalse { dst: 11 }, - Op::Const { idx: 2 }, - Op::Error, - Op::Pop, - Op::Const { idx: 3 }, - Op::RecordSet, - Op::End, + StoreV1, + Const { idx: 0 }, + String { size: 1 }, + Const { idx: 1 }, + String { size: 1 }, + Record { size: 1 }, + LoadV1, + Const { idx: 0 }, + String { size: 1 }, + TestRecortPresent, + JumpFalse { dst: 13 }, + Const { idx: 2 }, + Error, + Const { idx: 3 }, + RecordSet, + SwapV1, ] ); @@ -289,20 +289,22 @@ fn patch_update() -> Result<()> { assert_eq!( p.opcodes, &[ - Op::Const { idx: 0 }, - Op::String { size: 1 }, - Op::Const { idx: 1 }, - Op::String { size: 1 }, - Op::Record { size: 1 }, - Op::Const { idx: 0 }, - Op::String { size: 1 }, - Op::TestRecortPresent, - Op::JumpTrue { dst: 10 }, - Op::Const { idx: 2 }, - Op::Error, - Op::Pop, - Op::Const { idx: 3 }, - Op::RecordSet, + StoreV1, + Const { idx: 0 }, + String { size: 1 }, + Const { idx: 1 }, + String { size: 1 }, + Record { size: 1 }, + LoadV1, + Const { idx: 0 }, + String { size: 1 }, + TestRecortPresent, + JumpTrue { dst: 13 }, + Const { idx: 2 }, + Error, + Const { idx: 3 }, + RecordSet, + SwapV1, ] ); @@ -326,16 +328,18 @@ fn patch_update_error() -> Result<()> { assert_eq!( p.opcodes, &[ - Op::Record { size: 0 }, - Op::Const { idx: 0 }, - Op::String { size: 1 }, - Op::TestRecortPresent, - Op::JumpTrue { dst: 6 }, - Op::Const { idx: 1 }, - Op::Error, - Op::Pop, - Op::Const { idx: 2 }, - Op::RecordSet, + StoreV1, + Record { size: 0 }, + LoadV1, + Const { idx: 0 }, + String { size: 1 }, + TestRecortPresent, + JumpTrue { dst: 9 }, + Const { idx: 1 }, + Error, + Const { idx: 2 }, + RecordSet, + SwapV1, ] ); @@ -354,15 +358,18 @@ fn patch_upsert_1() -> Result<()> { assert_eq!( p.opcodes, &[ - Op::Const { idx: 0 }, - Op::String { size: 1 }, - Op::Const { idx: 1 }, - Op::String { size: 1 }, - Op::Record { size: 1 }, - Op::Const { idx: 0 }, - Op::String { size: 1 }, - Op::Const { idx: 2 }, - Op::RecordSet, + StoreV1, + Const { idx: 0 }, + String { size: 1 }, + Const { idx: 1 }, + String { size: 1 }, + Record { size: 1 }, + LoadV1, + Const { idx: 0 }, + String { size: 1 }, + Const { idx: 2 }, + RecordSet, + SwapV1, ] ); @@ -386,11 +393,14 @@ fn patch_upsert_2() -> Result<()> { assert_eq!( p.opcodes, &[ - Op::Record { size: 0 }, - Op::Const { idx: 0 }, - Op::String { size: 1 }, - Op::Const { idx: 1 }, - Op::RecordSet, + StoreV1, + Record { size: 0 }, + LoadV1, + Const { idx: 0 }, + String { size: 1 }, + Const { idx: 1 }, + RecordSet, + SwapV1, ] ); @@ -417,25 +427,30 @@ fn patch_patch_patch() -> Result<()> { assert_eq!( p.opcodes, &[ - Op::Const { idx: 0 }, - Op::String { size: 1 }, - Op::Const { idx: 1 }, - Op::String { size: 1 }, - Op::Record { size: 1 }, - Op::Const { idx: 1 }, - Op::String { size: 1 }, - Op::Const { idx: 2 }, - Op::String { size: 1 }, - Op::RecordSet, - Op::Const { idx: 2 }, - Op::String { size: 1 }, - Op::TestRecortPresent, - Op::JumpFalse { dst: 15 }, - Op::Const { idx: 3 }, - Op::Error, - Op::Pop, - Op::Const { idx: 4 }, - Op::RecordSet, + StoreV1, + StoreV1, + Const { idx: 0 }, + String { size: 1 }, + Const { idx: 1 }, + String { size: 1 }, + Record { size: 1 }, + LoadV1, + Const { idx: 1 }, + String { size: 1 }, + Const { idx: 2 }, + String { size: 1 }, + RecordSet, + SwapV1, + LoadV1, + Const { idx: 2 }, + String { size: 1 }, + TestRecortPresent, + JumpFalse { dst: 21 }, + Const { idx: 3 }, + Error, + Const { idx: 4 }, + RecordSet, + SwapV1, ] ); @@ -457,15 +472,16 @@ fn array_index_fast() -> Result<()> { let script = parse("[1,2,3][1]")?; let p = compiler.compile(script)?; - assert_eq!(p.consts.len(), 3); + assert_eq!(p.consts.len(), 4); assert_eq!( p.opcodes, &[ - Op::Const { idx: 0 }, - Op::Const { idx: 1 }, - Op::Const { idx: 2 }, - Op::Array { size: 3 }, - Op::IndexFast { idx: 1 }, + Const { idx: 0 }, + Const { idx: 1 }, + Const { idx: 2 }, + Const { idx: 3 }, + Array { size: 3 }, + IndexFast { idx: 1 }, ] ); @@ -480,18 +496,19 @@ fn array_index() -> Result<()> { let script = parse("[1,2,3][1+1]")?; let p = compiler.compile(script)?; - assert_eq!(p.consts.len(), 3); + assert_eq!(p.consts.len(), 4); assert_eq!( p.opcodes, &[ - Op::Const { idx: 0 }, - Op::Const { idx: 1 }, - Op::Const { idx: 2 }, - Op::Array { size: 3 }, - Op::Const { idx: 0 }, - Op::Const { idx: 0 }, - Op::Binary { op: BinOpKind::Add }, - Op::Get, + Const { idx: 0 }, + Const { idx: 1 }, + Const { idx: 2 }, + Const { idx: 3 }, + Array { size: 3 }, + Const { idx: 0 }, + Const { idx: 0 }, + Binary { op: BinOpKind::Add }, + Get, ] ); @@ -510,12 +527,12 @@ fn record_key() -> Result<()> { assert_eq!( p.opcodes, &[ - Op::Const { idx: 0 }, - Op::String { size: 1 }, - Op::Const { idx: 1 }, - Op::String { size: 1 }, - Op::Record { size: 1 }, - Op::GetKey { key: 0 }, + Const { idx: 0 }, + String { size: 1 }, + Const { idx: 1 }, + String { size: 1 }, + Record { size: 1 }, + GetKey { key: 0 }, ] ); @@ -534,14 +551,14 @@ fn record_path() -> Result<()> { assert_eq!( p.opcodes, &[ - Op::Const { idx: 0 }, - Op::String { size: 1 }, - Op::Const { idx: 1 }, - Op::String { size: 1 }, - Op::Record { size: 1 }, - Op::Const { idx: 0 }, - Op::String { size: 1 }, - Op::Get, + Const { idx: 0 }, + String { size: 1 }, + Const { idx: 1 }, + String { size: 1 }, + Record { size: 1 }, + Const { idx: 0 }, + String { size: 1 }, + Get, ] ); @@ -556,17 +573,18 @@ fn array_range() -> Result<()> { let script = parse("[1,2,3][0:2]")?; let p = compiler.compile(script)?; - assert_eq!(p.consts.len(), 4); + assert_eq!(p.consts.len(), 5); assert_eq!( p.opcodes, &[ - Op::Const { idx: 0 }, - Op::Const { idx: 1 }, - Op::Const { idx: 2 }, - Op::Array { size: 3 }, - Op::Const { idx: 3 }, - Op::Const { idx: 1 }, - Op::Range + Const { idx: 0 }, + Const { idx: 1 }, + Const { idx: 2 }, + Const { idx: 3 }, + Array { size: 3 }, + Const { idx: 4 }, + Const { idx: 1 }, + Range ] ); @@ -584,7 +602,7 @@ fn event_key() -> Result<()> { let p = compiler.compile(script)?; assert_eq!(p.consts.len(), 0); - assert_eq!(p.opcodes, &[Op::LoadEvent, Op::GetKey { key: 0 },]); + assert_eq!(p.opcodes, &[LoadEvent, GetKey { key: 0 },]); assert_eq!(run(&p)?, 42); Ok(()) @@ -600,7 +618,7 @@ fn event_nested() -> Result<()> { assert_eq!(p.consts.len(), 0); assert_eq!( p.opcodes, - &[Op::LoadEvent, Op::GetKey { key: 0 }, Op::GetKey { key: 1 }] + &[LoadEvent, GetKey { key: 0 }, GetKey { key: 1 }] ); assert_eq!(run(&p)?, 1); @@ -617,12 +635,7 @@ fn event_nested_index() -> Result<()> { assert_eq!(p.consts.len(), 0); assert_eq!( p.opcodes, - &[ - Op::LoadEvent, - Op::GetKey { key: 5 }, - Op::Const { idx: 0 }, - Op::Get, - ] + &[LoadEvent, GetKey { key: 0 }, IndexFast { idx: 1 },] ); assert_eq!(run(&p)?, 2); @@ -641,9 +654,9 @@ fn test_local() -> Result<()> { assert_eq!( p.opcodes, &[ - Op::Const { idx: 0 }, - Op::StoreLocal { idx: 0 }, - Op::LoadLocal { idx: 0 }, + Const { idx: 0 }, + StoreLocal { idx: 0 }, + LoadLocal { idx: 0 }, ] ); @@ -663,13 +676,88 @@ fn test_local_event() -> Result<()> { assert_eq!( p.opcodes, &[ - Op::LoadEvent, - Op::StoreLocal { idx: 0 }, - Op::LoadLocal { idx: 0 }, - Op::GetKey { key: 0 }, + LoadEvent, + StoreLocal { idx: 0 }, + LoadLocal { idx: 0 }, + GetKey { key: 0 }, + ] + ); + + assert_eq!(run(&p)?, 42); + Ok(()) +} + +#[test] +fn test_match_if_else() -> Result<()> { + let mut compiler: Compiler = Compiler::new(); + + let script = parse("match event.int of case 42 => 42 case _ => 0 end")?; + let p = compiler.compile(script)?; + + println!("{p}"); + assert_eq!(p.consts.len(), 2); + assert_eq!( + p.opcodes, + &[ + StoreV1, + LoadEvent, + GetKey { key: 0 }, + LoadV1, + CopyV1, + Const { idx: 0 }, + Binary { op: BinOpKind::Eq }, + LoadRB, + JumpFalse { dst: 11 }, + Const { idx: 0 }, + Jump { dst: 12 }, + Const { idx: 1 }, + LoadV1, + SwapV1, ] ); assert_eq!(run(&p)?, 42); Ok(()) } + +#[test] +fn test_match_record_type() -> Result<()> { + use BinOpKind::Eq; + let mut compiler: Compiler = Compiler::new(); + + let script = + parse("match event.object of case 42 => 42 case %{} => \"record\" case _ => 0 end")?; + let p = compiler.compile(script)?; + + println!("{p}"); + assert_eq!(p.consts.len(), 3); + assert_eq!( + p.opcodes, + &[ + StoreV1, + LoadEvent, + GetKey { key: 0 }, + LoadV1, + CopyV1, + Const { idx: 0 }, + Binary { op: Eq }, + LoadRB, + JumpFalse { dst: 11 }, + Const { idx: 0 }, + Jump { dst: 19 }, + CopyV1, + TestIsRecord, + JumpFalse { dst: 14 }, + JumpFalse { dst: 18 }, + Const { idx: 1 }, + String { size: 1 }, + Jump { dst: 19 }, + Const { idx: 2 }, + LoadV1, + SwapV1, + ] + ); + + assert_eq!(run(&p)?, "record"); + Ok(()) +} From 217f868f16c30b3b28f10766c8cb5a193f336995 Mon Sep 17 00:00:00 2001 From: "Heinz N. Gies" Date: Sun, 11 Aug 2024 17:24:00 +0200 Subject: [PATCH 03/12] patch default Signed-off-by: Heinz N. Gies --- tremor-script/src/vm/compiler.rs | 10 ++++- tremor-script/src/vm/tests.rs | 70 ++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/tremor-script/src/vm/compiler.rs b/tremor-script/src/vm/compiler.rs index b2f8257c30..0e2a27b053 100644 --- a/tremor-script/src/vm/compiler.rs +++ b/tremor-script/src/vm/compiler.rs @@ -416,7 +416,15 @@ impl<'script> Compilable<'script> for PatchOperation<'script> { } PatchOperation::Merge { ident, expr, mid } => todo!(), PatchOperation::MergeRecord { expr, mid } => todo!(), - PatchOperation::Default { ident, expr, mid } => todo!(), + PatchOperation::Default { ident, expr, mid } => { + ident.compile(compiler)?; + compiler.emit(Op::TestRecortPresent, *mid.clone()); + let dst = compiler.new_jump_point(); + compiler.emit(Op::JumpTrue { dst }, *mid.clone()); + expr.compile(compiler)?; + compiler.emit(Op::RecordSet, *mid); + compiler.set_jump_target(dst); + }, PatchOperation::DefaultRecord { expr, mid } => todo!(), } Ok(()) diff --git a/tremor-script/src/vm/tests.rs b/tremor-script/src/vm/tests.rs index bef1ee5832..100fca1904 100644 --- a/tremor-script/src/vm/tests.rs +++ b/tremor-script/src/vm/tests.rs @@ -244,6 +244,76 @@ fn patch_insert() -> Result<()> { Ok(()) } +#[test] +fn patch_default() -> Result<()> { + let mut compiler: Compiler = Compiler::new(); + + let script = parse(r#"patch {} of default "foo" => 42 end"#)?; + let p = compiler.compile(script)?; + + assert_eq!(p.consts.len(), 2); + assert_eq!( + p.opcodes, + &[ + StoreV1, + Record { size: 0 }, + LoadV1, + Const { idx: 0 }, + String { size: 1 }, + TestRecortPresent, + JumpTrue { dst: 9 }, + Const { idx: 1 }, + RecordSet, + SwapV1, + ] + ); + + assert_eq!( + run(&p)?, + literal!({ + "foo": 42 + }) + ); + Ok(()) +} + +#[test] +fn patch_default_present() -> Result<()> { + let mut compiler: Compiler = Compiler::new(); + + let script = parse(r#"patch {"foo":"bar"} of default "foo" => 42 end"#)?; + let p = compiler.compile(script)?; + + assert_eq!(p.consts.len(), 3); + assert_eq!( + p.opcodes, + &[ + StoreV1, + Const { idx: 0 }, + String { size: 1 }, + Const { idx: 1 }, + String { size: 1 }, + Record { size: 1 }, + LoadV1, + Const { idx: 0 }, + String { size: 1 }, + TestRecortPresent, + JumpTrue { dst: 13 }, + Const { idx: 2 }, + RecordSet, + SwapV1, + ] + ); + + assert_eq!( + run(&p)?, + literal!({ + "foo": "bar" + }) + ); + Ok(()) +} + #[test] fn patch_insert_error() -> Result<()> { let mut compiler: Compiler = Compiler::new(); From fa5fe0c060c22d65852c39e6c172fca60ed3ce08 Mon Sep 17 00:00:00 2001 From: "Heinz N. Gies" Date: Sun, 11 Aug 2024 18:05:36 +0200 Subject: [PATCH 04/12] Restructure Signed-off-by: Heinz N. Gies --- tremor-script/src/vm.rs | 15 +- tremor-script/src/vm/compiler.rs | 686 +----------------- tremor-script/src/vm/compiler/impls.rs | 242 ++++++ .../src/vm/compiler/impls/imut_expr.rs | 451 ++++++++++++ .../src/vm/compiler/impls/mut_expr.rs | 53 ++ tremor-script/src/vm/tests.rs | 321 ++++---- 6 files changed, 908 insertions(+), 860 deletions(-) create mode 100644 tremor-script/src/vm/compiler/impls.rs create mode 100644 tremor-script/src/vm/compiler/impls/imut_expr.rs create mode 100644 tremor-script/src/vm/compiler/impls/mut_expr.rs diff --git a/tremor-script/src/vm.rs b/tremor-script/src/vm.rs index 6be8c3569e..c6ab6b422a 100644 --- a/tremor-script/src/vm.rs +++ b/tremor-script/src/vm.rs @@ -129,7 +129,7 @@ pub(crate) enum Op { RecordRemove, RecordGet, // Merge - Merge, + RecordMerge, TestIsRecord, TestIsArray, } @@ -185,7 +185,7 @@ impl Display for Op { Op::RecordSet => write!(f, "record_set"), Op::RecordRemove => write!(f, "record_remove"), Op::RecordGet => write!(f, "record_get"), - Op::Merge => write!(f, "merge"), + Op::RecordMerge => write!(f, "merge"), } } } @@ -408,7 +408,8 @@ impl<'run, 'event> Scope<'run, 'event> { } Op::Record { size } => { let size = size as usize; - let mut record = Value::object_with_capacity(size); + let mut v = stack.pop().ok_or("Stack underflow")?; + let record = v.to_mut(); for _ in 0..size { let value = stack.pop().ok_or("Stack underflow")?; let key = stack.pop().ok_or("Stack underflow")?; @@ -416,7 +417,7 @@ impl<'run, 'event> Scope<'run, 'event> { let key = key.into_owned().try_into_string()?; record.try_insert(key, value.into_owned()); } - stack.push(Cow::Owned(record)); + stack.push(v); } Op::Array { size } => { let size = size as usize; @@ -570,11 +571,9 @@ impl<'run, 'event> Scope<'run, 'event> { stack.push(Cow::Owned(v)); } // merge - Op::Merge => { + Op::RecordMerge => { let arg = stack.pop().ok_or("Stack underflow")?; - let mut target = stack.pop().ok_or("Stack underflow")?; - merge_values(target.to_mut(), &arg)?; - stack.push(target); + merge_values(self.registers.v1.to_mut(), &arg)?; } // Path Op::GetKey { key } => { diff --git a/tremor-script/src/vm/compiler.rs b/tremor-script/src/vm/compiler.rs index 0e2a27b053..88a20c5bb5 100644 --- a/tremor-script/src/vm/compiler.rs +++ b/tremor-script/src/vm/compiler.rs @@ -1,13 +1,6 @@ -use crate::{ - ast::{ - raw::{BytesDataType, Endian}, - BaseExpr, BinOpKind, BooleanBinExpr, BooleanBinOpKind, BytesPart, ClauseGroup, - ClausePreCondition, DefaultCase, EmitExpr, Expr, Expression, Field, ImutExpr, Match, - PatchOperation, Path, Pattern, PredicateClause, Script, Segment, StrLitElement, StringLit, - }, - errors::Result, - NodeMeta, -}; +mod impls; + +use crate::{ast::Script, errors::Result, NodeMeta}; use std::{collections::HashMap, mem}; use tremor_value::Value; @@ -29,7 +22,7 @@ trait Compilable<'script>: Sized { fn compile(self, compiler: &mut Compiler<'script>) -> Result<()>; fn compile_to_b(self, compiler: &mut Compiler<'script>) -> Result<()> { self.compile(compiler)?; - compiler.emit(Op::LoadRB, *NodeMeta::dummy()); + compiler.emit(Op::LoadRB, &NodeMeta::dummy()); Ok(()) } } @@ -88,9 +81,9 @@ impl<'script> Compiler<'script> { // self.opcodes.len() as u32 - 1 // } - pub(crate) fn emit(&mut self, op: Op, mid: NodeMeta) { + pub(crate) fn emit(&mut self, op: Op, mid: &NodeMeta) { self.opcodes.push(op); - self.meta.push(mid); + self.meta.push(mid.clone()); } #[allow(clippy::cast_possible_truncation)] @@ -103,7 +96,7 @@ impl<'script> Compiler<'script> { self.consts.push(value); (self.consts.len() - 1) as u32 } - pub(crate) fn emit_const>>(&mut self, v: T, mid: NodeMeta) { + pub(crate) fn emit_const>>(&mut self, v: T, mid: &NodeMeta) { let idx = self.add_const(v.into()); self.emit(Op::Const { idx }, mid); } @@ -124,7 +117,8 @@ impl<'script> Compiler<'script> { self.jump_table.push(0); self.jump_table.len() as u32 - 1 } - #[deny(clippy::cast_possible_truncation)] + + #[allow(clippy::cast_possible_truncation)] pub(crate) fn set_jump_target(&mut self, jump_point: u32) { self.jump_table[jump_point as usize] = self.opcodes.len() as u32; } @@ -157,665 +151,3 @@ impl<'script> Default for Compiler<'script> { Self::new() } } - -impl<'script> Compilable<'script> for Script<'script> { - fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { - for e in self.exprs { - e.compile(compiler)?; - } - Ok(()) - } -} - -impl<'script> Compilable<'script> for Expr<'script> { - fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { - match self { - Expr::Match(m) => m.compile(compiler)?, - Expr::IfElse(ie) => { - let mid = *ie.mid.clone(); - // jump point for the else clause - let else_dst = compiler.new_jump_point(); - // jump point for the end of the if expression - compiler.new_end_target(); - // store v1 - compiler.emit(Op::StoreV1, mid.clone()); - - // compile the target and store the result in the B1 register - ie.target.compile(compiler)?; - // load target in register 1; - compiler.emit(Op::LoadV1, mid.clone()); - // compile the if clause - ie.if_clause.compile(compiler)?; - // this is the jump destionaion for the else clause - compiler.set_jump_target(else_dst); - ie.else_clause.compile(compiler)?; - // this is the jump destionaion for the end of the if expression - compiler.set_end_target()?; - // load the result in of the expression to v1 so we can restore the old value - compiler.emit(Op::LoadV1, *ie.mid.clone()); - // restore original value - compiler.emit(Op::SwapV1, *ie.mid.clone()); - } - #[allow(clippy::cast_possible_truncation)] - Expr::Assign { mid: _, path, expr } => { - let Path::Local(p) = path else { - todo!("we only allow local pasth?"); - }; - if !p.segments.is_empty() { - todo!("we only allow non nested asignments pasth?"); - } - expr.compile(compiler)?; - compiler.max_locals = compiler.max_locals.max(p.idx); - compiler.emit(Op::StoreLocal { idx: p.idx as u32 }, *p.mid); - } - Expr::AssignMoveLocal { - mid: _, - path: _, - idx: _, - } => {} - Expr::Comprehension(_) => todo!(), - Expr::Drop { mid } => compiler.emit(Op::Drop, *mid), - Expr::Emit(e) => e.compile(compiler)?, - Expr::Imut(e) => e.compile(compiler)?, - } - Ok(()) - } -} - -#[allow(clippy::too_many_lines)] -impl<'script> Compilable<'script> for ImutExpr<'script> { - fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { - match self { - #[allow(clippy::cast_possible_truncation)] - ImutExpr::Record(r) => { - let size = r.fields.len() as u32; - for f in r.fields { - f.compile(compiler)?; - } - compiler.emit(Op::Record { size }, *r.mid); - } - #[allow(clippy::cast_possible_truncation)] - ImutExpr::List(l) => { - let size = l.exprs.len() as u32; - for e in l.exprs { - e.compile(compiler)?; - } - compiler.emit_const(Value::array(), *l.mid.clone()); - compiler.emit(Op::Array { size }, *l.mid); - } - ImutExpr::Binary(b) => { - b.lhs.compile(compiler)?; - b.rhs.compile(compiler)?; - compiler.emit(Op::Binary { op: b.kind }, *b.mid); - } - ImutExpr::BinaryBoolean(b) => { - b.compile(compiler)?; - } - ImutExpr::Unary(u) => { - u.expr.compile(compiler)?; - compiler.emit(Op::Unary { op: u.kind }, *u.mid); - } - ImutExpr::Patch(p) => { - let mid = *p.mid.clone(); - // Save r1 to ensure we can restore it after the patch operation - compiler.emit(Op::StoreV1, mid.clone()); - // compile the target - p.target.compile(compiler)?; - // load the target into the register - compiler.emit(Op::LoadV1, mid.clone()); - for op in p.operations { - op.compile(compiler)?; - } - // restore r1 and ensure the result is on top of the stack - compiler.emit(Op::SwapV1, mid); - } - ImutExpr::Match(m) => m.compile(compiler)?, - ImutExpr::Comprehension(_) => todo!(), - ImutExpr::Merge(m) => { - m.target.compile(compiler)?; - m.expr.compile(compiler)?; - compiler.emit(Op::Merge, *m.mid); - } - ImutExpr::Path(p) => p.compile(compiler)?, - ImutExpr::String(s) => s.compile(compiler)?, - #[allow(clippy::cast_possible_truncation)] - ImutExpr::Local { idx, mid } => { - compiler.max_locals = compiler.max_locals.max(idx); - compiler.emit(Op::LoadLocal { idx: idx as u32 }, *mid); - } - ImutExpr::Literal(l) => { - compiler.emit_const(l.value, *l.mid); - } - ImutExpr::Present { - path: _path, - mid: _mid, - } => todo!(), - ImutExpr::Invoke1(_) => todo!(), - ImutExpr::Invoke2(_) => todo!(), - ImutExpr::Invoke3(_) => todo!(), - ImutExpr::Invoke(_) => todo!(), - ImutExpr::InvokeAggr(_) => todo!(), - ImutExpr::Recur(_) => todo!(), - #[allow(clippy::cast_possible_truncation)] - ImutExpr::Bytes(b) => { - let size = b.value.len() as u32; - let mid = *b.mid; - // we modify r1 in the parts so we need to store it - compiler.emit(Op::StoreV1, mid.clone()); - for b in b.value { - b.compile(compiler)?; - } - compiler.emit(Op::Bytes { size }, mid.clone()); - // once the bytes are crated we restore it - compiler.emit(Op::LoadV1, mid); - } - #[allow(clippy::cast_possible_truncation)] - ImutExpr::ArrayAppend(a) => { - let size = a.right.len() as u32; - for r in a.right { - r.compile(compiler)?; - } - a.left.compile(compiler)?; - compiler.emit(Op::Array { size }, *a.mid); - } - } - Ok(()) - } -} - -impl<'script> Compilable<'script> for Field<'script> { - fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { - self.name.compile(compiler)?; - self.value.compile(compiler)?; - Ok(()) - } -} - -impl<'script> Compilable<'script> for StringLit<'script> { - #[allow(clippy::cast_possible_truncation)] - fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { - let size = self.elements.len() as u32; - for e in self.elements { - match e { - StrLitElement::Lit(s) => { - compiler.emit_const(s, *self.mid.clone()); - } - StrLitElement::Expr(e) => { - e.compile(compiler)?; - } - } - } - compiler.emit(Op::String { size }, *self.mid); - Ok(()) - } -} - -impl<'script> Compilable<'script> for EmitExpr<'script> { - fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { - self.expr.compile(compiler)?; - let dflt = if let Some(port) = self.port { - port.compile(compiler)?; - false - } else { - true - }; - compiler.emit(Op::Emit { dflt }, *self.mid); - Ok(()) - } -} - -impl<'script> Compilable<'script> for PatchOperation<'script> { - #[allow(unused_variables)] - fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { - match self { - PatchOperation::Insert { ident, expr, mid } => { - ident.compile(compiler)?; - compiler.emit(Op::TestRecortPresent, *mid.clone()); - let dst = compiler.new_jump_point(); - compiler.emit(Op::JumpFalse { dst }, *mid.clone()); - compiler.emit_const("Key already present", *mid.clone()); - compiler.emit(Op::Error, *mid.clone()); - compiler.set_jump_target(dst); - expr.compile(compiler)?; - compiler.emit(Op::RecordSet, *mid); - } - PatchOperation::Upsert { ident, expr, mid } => { - ident.compile(compiler)?; - expr.compile(compiler)?; - compiler.emit(Op::RecordSet, *mid); - } - PatchOperation::Update { ident, expr, mid } => { - ident.compile(compiler)?; - compiler.emit(Op::TestRecortPresent, *mid.clone()); - let dst = compiler.new_jump_point(); - compiler.emit(Op::JumpTrue { dst }, *mid.clone()); - compiler.emit_const("Key already present", *mid.clone()); - compiler.emit(Op::Error, *mid.clone()); - compiler.set_jump_target(dst); - expr.compile(compiler)?; - compiler.emit(Op::RecordSet, *mid); - } - PatchOperation::Erase { ident, mid } => { - ident.compile(compiler)?; - compiler.emit(Op::RecordRemove, *mid.clone()); - compiler.emit(Op::Pop, *mid); - } - PatchOperation::Copy { from, to, mid } => { - from.compile(compiler)?; - compiler.emit(Op::RecordGet, *mid.clone()); - to.compile(compiler)?; - compiler.emit(Op::Swap, *mid.clone()); - compiler.emit(Op::RecordSet, *mid); - } - PatchOperation::Move { from, to, mid } => { - from.compile(compiler)?; - compiler.emit(Op::RecordRemove, *mid.clone()); - to.compile(compiler)?; - compiler.emit(Op::Swap, *mid.clone()); - compiler.emit(Op::RecordSet, *mid); - } - PatchOperation::Merge { ident, expr, mid } => todo!(), - PatchOperation::MergeRecord { expr, mid } => todo!(), - PatchOperation::Default { ident, expr, mid } => { - ident.compile(compiler)?; - compiler.emit(Op::TestRecortPresent, *mid.clone()); - let dst = compiler.new_jump_point(); - compiler.emit(Op::JumpTrue { dst }, *mid.clone()); - expr.compile(compiler)?; - compiler.emit(Op::RecordSet, *mid); - compiler.set_jump_target(dst); - }, - PatchOperation::DefaultRecord { expr, mid } => todo!(), - } - Ok(()) - } -} - -impl<'script> Compilable<'script> for Segment<'script> { - fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { - match self { - Segment::Id { mid, key } => { - let key = compiler.add_key(key); - compiler.emit(Op::GetKey { key }, *mid); - } - Segment::Element { expr, mid } => { - expr.compile(compiler)?; - compiler.emit(Op::Get, *mid); - } - #[allow(clippy::cast_possible_truncation)] - Segment::Idx { idx, mid } => { - if let Ok(idx) = u32::try_from(idx) { - compiler.emit(Op::IndexFast { idx }, *mid); - } else { - compiler.emit_const(idx, *mid.clone()); - compiler.emit(Op::Index, *mid); - } - } - #[allow(clippy::cast_possible_truncation)] - Segment::Range { mid, start, end } => { - if let Some((start, end)) = u16::try_from(start).ok().zip(u16::try_from(end).ok()) { - compiler.emit(Op::RangeFast { start, end }, *mid); - } else { - compiler.emit_const(start, *mid.clone()); - compiler.emit_const(end, *mid.clone()); - compiler.emit(Op::Range, *mid); - } - } - - Segment::RangeExpr { mid, start, end } => { - start.compile(compiler)?; - end.compile(compiler)?; - compiler.emit(Op::Range, *mid); - } - } - Ok(()) - } -} - -impl<'script> Compilable<'script> for BooleanBinExpr<'script> { - fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { - let mid = *self.mid.clone(); - self.compile_to_b(compiler)?; - compiler.emit(Op::StoreRB, mid); - Ok(()) - } - fn compile_to_b(self, compiler: &mut Compiler<'script>) -> Result<()> { - self.lhs.compile_to_b(compiler)?; - match self.kind { - #[allow(clippy::cast_sign_loss)] - BooleanBinOpKind::Or => { - let dst = compiler.new_jump_point(); - compiler.emit(Op::JumpTrue { dst }, *self.mid.clone()); - self.rhs.compile_to_b(compiler)?; - compiler.set_jump_target(dst); - } - BooleanBinOpKind::Xor => { - self.rhs.compile(compiler)?; - compiler.emit(Op::Xor, *self.mid); - } - #[allow(clippy::cast_sign_loss)] - BooleanBinOpKind::And => { - let dst = compiler.new_jump_point(); - compiler.emit(Op::JumpFalse { dst }, *self.mid); - self.rhs.compile_to_b(compiler)?; - compiler.set_jump_target(dst); - } - } - Ok(()) - } -} - -impl From for u8 { - fn from(v: Endian) -> Self { - match v { - Endian::Big => 0, - Endian::Little => 1, - } - } -} - -impl From for Endian { - fn from(v: u8) -> Self { - match v { - 0 => Endian::Big, - _ => Endian::Little, - } - } -} - -impl From for u8 { - fn from(v: BytesDataType) -> Self { - match v { - BytesDataType::SignedInteger => 0, - BytesDataType::UnsignedInteger => 1, - BytesDataType::Binary => 2, - } - } -} -impl From for BytesDataType { - fn from(v: u8) -> Self { - match v { - 0 => BytesDataType::SignedInteger, - 1 => BytesDataType::UnsignedInteger, - _ => BytesDataType::Binary, - } - } -} - -impl<'script> Compilable<'script> for BytesPart<'script> { - fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { - let BytesPart { - mid, - data, - data_type, - endianess, - bits, - }: BytesPart = self; - - // format is bits:48 | data_type:2 |endianess:1 - let mut format = bits; - format = format << 2 | u64::from(u8::from(data_type)) & 0b11; - format = format << 1 | u64::from(u8::from(endianess)) & 0b1; - - let mid = *mid; - data.compile(compiler)?; - let dst = compiler.new_jump_point(); - // load the value into r1 so we can test it - compiler.emit(Op::LoadV1, mid.clone()); - match data_type { - BytesDataType::SignedInteger => compiler.emit(Op::TestIsI64, mid.clone()), - BytesDataType::UnsignedInteger => compiler.emit(Op::TestIsU64, mid.clone()), - BytesDataType::Binary => compiler.emit(Op::TestIsBytes, mid.clone()), - } - compiler.emit(Op::JumpFalse { dst }, mid.clone()); - compiler.emit_const("invalid type conversion for binary", mid.clone()); - compiler.emit(Op::Error, mid.clone()); - compiler.set_jump_target(dst); - // save the value back to the stack - compiler.emit(Op::StoreV1, mid.clone()); - compiler.emit_const(format, mid); - Ok(()) - } -} - -impl<'script> Compilable<'script> for Path<'script> { - fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { - match self { - #[allow(clippy::cast_possible_truncation)] - Path::Local(p) => { - compiler.max_locals = compiler.max_locals.max(p.idx); - compiler.emit(Op::LoadLocal { idx: p.idx as u32 }, *p.mid); - for s in p.segments { - s.compile(compiler)?; - } - } - Path::Event(p) => { - compiler.emit(Op::LoadEvent, *p.mid); - for s in p.segments { - s.compile(compiler)?; - } - } - Path::Expr(p) => { - p.expr.compile(compiler)?; - for s in p.segments { - s.compile(compiler)?; - } - } - Path::Meta(_p) => todo!(), - Path::Reserved(_p) => todo!(), - Path::State(_p) => todo!(), - } - Ok(()) - } -} - -impl<'script, Ex> Compilable<'script> for PredicateClause<'script, Ex> -where - Ex: Expression + Compilable<'script>, -{ - fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { - let PredicateClause { - mid, - pattern, - guard, - exprs, - last_expr, - } = self; - let end_dst = compiler.end_dst()?; - let dst = compiler.new_jump_point(); - - compiler.emit(Op::CopyV1, *mid.clone()); - pattern.compile_to_b(compiler)?; - compiler.emit(Op::JumpFalse { dst }, *mid.clone()); - if let Some(guard) = guard { - guard.compile_to_b(compiler)?; - compiler.emit(Op::JumpFalse { dst }, *mid.clone()); - } - for e in exprs { - e.compile(compiler)?; - } - last_expr.compile(compiler)?; - // we were successful so we jump to the end - compiler.emit(Op::Jump { dst: end_dst }, *mid); - compiler.set_jump_target(dst); - Ok(()) - } -} - -impl<'script> Compilable<'script> for Pattern<'script> { - fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { - self.compile_to_b(compiler) - } - /// will return the match state in registers.B - fn compile_to_b(self, compiler: &mut Compiler<'script>) -> Result<()> { - match self { - Pattern::Record(r) => { - let mid = *r.mid; - compiler.emit(Op::TestIsRecord, mid.clone()); - let dst = compiler.new_jump_point(); - compiler.emit(Op::JumpFalse { dst }, mid.clone()); - if r.fields.is_empty() { - } else { - todo!() - } - compiler.set_jump_target(dst); - } - Pattern::Array(a) => { - let mid = *a.mid; - compiler.emit(Op::TestIsArray, mid.clone()); - let dst = compiler.new_jump_point(); - compiler.emit(Op::JumpFalse { dst }, mid.clone()); - if a.exprs.is_empty() { - } else { - todo!() - } - compiler.set_jump_target(dst); - } - Pattern::Expr(e) => { - let mid = e.meta().clone(); - e.compile(compiler)?; - compiler.emit(Op::Binary { op: BinOpKind::Eq }, mid); - compiler.emit(Op::LoadRB, *NodeMeta::dummy()); - } - - Pattern::Assign(_) => todo!(), - Pattern::Tuple(_) => todo!(), - Pattern::Extract(_) => todo!(), - Pattern::DoNotCare => { - compiler.emit(Op::True, *NodeMeta::dummy()); - compiler.emit(Op::LoadRB, *NodeMeta::dummy()); - } - } - Ok(()) - } -} - -impl<'script, Ex> Compilable<'script> for DefaultCase -where - Ex: Compilable<'script> + Expression, -{ - fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { - match self { - DefaultCase::None => { - compiler.emit(Op::Nop, *NodeMeta::dummy()); - } - DefaultCase::Null => { - compiler.emit(Op::Null, *NodeMeta::dummy()); - } - DefaultCase::Many { exprs, last_expr } => { - for e in exprs { - e.compile(compiler)?; - } - last_expr.compile(compiler)?; - } - DefaultCase::One(e) => e.compile(compiler)?, - } - Ok(()) - } -} -impl<'script, Ex> Compilable<'script> for ClauseGroup<'script, Ex> -where - Ex: Compilable<'script> + Expression, -{ - fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { - let next = compiler.new_jump_point(); - match self { - ClauseGroup::Simple { - precondition, - patterns, - } => { - if let Some(precondition) = precondition { - precondition.compile_to_b(compiler)?; - compiler.emit(Op::JumpFalse { dst: next }, *NodeMeta::dummy()); - // FIXME - } - for p in patterns { - p.compile(compiler)?; - } - } - ClauseGroup::SearchTree { - precondition, - tree: _, - rest, - } => { - if let Some(precondition) = precondition { - precondition.compile_to_b(compiler)?; - compiler.emit(Op::JumpFalse { dst: next }, *NodeMeta::dummy()); - // FIXME - } - for r in rest { - r.compile(compiler)?; - } - todo!("the tree has to go before therest!"); - } - ClauseGroup::Combined { - precondition, - groups, - } => { - if let Some(precondition) = precondition { - precondition.compile_to_b(compiler)?; - compiler.emit(Op::JumpFalse { dst: next }, *NodeMeta::dummy()); - // FIXME - } - for g in groups { - g.compile(compiler)?; - } - } - ClauseGroup::Single { - precondition, - pattern, - } => { - if let Some(precondition) = precondition { - precondition.compile_to_b(compiler)?; - compiler.emit(Op::JumpFalse { dst: next }, *NodeMeta::dummy()); - } - pattern.compile(compiler)?; - } - } - compiler.set_jump_target(next); - Ok(()) - } -} - -impl<'script> Compilable<'script> for ClausePreCondition<'script> { - fn compile(self, _compiler: &mut Compiler<'script>) -> Result<()> { - todo!() - } - fn compile_to_b(self, _compiler: &mut Compiler<'script>) -> Result<()> { - todo!() - } -} - -impl<'script, Ex> Compilable<'script> for Match<'script, Ex> -where - Ex: Compilable<'script> + Expression, -{ - fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { - let Match { - mid, - target, - patterns, - default, - } = self; - let mid = *mid; - compiler.new_end_target(); - // Save r1 to ensure we can restore it after the patch operation - compiler.emit(Op::StoreV1, mid.clone()); - // Save r1 to ensure we can restore it after the patch operation - target.compile(compiler)?; - // load target to v1 - compiler.emit(Op::LoadV1, mid.clone()); - - for c in patterns { - c.compile(compiler)?; - } - default.compile(compiler)?; - compiler.set_end_target()?; - // restore r1 and ensure the result is on top of the stack - // Load the result of the match into r1 - compiler.emit(Op::LoadV1, mid.clone()); - // restore r1 and ensure the result is on top of the stack - compiler.emit(Op::SwapV1, mid); - Ok(()) - } -} diff --git a/tremor-script/src/vm/compiler/impls.rs b/tremor-script/src/vm/compiler/impls.rs new file mode 100644 index 0000000000..c3ed52fa38 --- /dev/null +++ b/tremor-script/src/vm/compiler/impls.rs @@ -0,0 +1,242 @@ +mod imut_expr; +mod mut_expr; + +use crate::{ + ast::{ClauseGroup, DefaultCase, Expression, IfElse, Match, Path, PredicateClause, Script}, + errors::Result, + vm::{ + compiler::{Compilable, Compiler}, + Op, + }, + NodeMeta, +}; +impl<'script> Compilable<'script> for Script<'script> { + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + for e in self.exprs { + e.compile(compiler)?; + } + Ok(()) + } +} + +impl<'script, Ex> Compilable<'script> for PredicateClause<'script, Ex> +where + Ex: Expression + Compilable<'script>, +{ + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + let PredicateClause { + mid, + pattern, + guard, + exprs, + last_expr, + } = self; + let end_dst = compiler.end_dst()?; + let dst = compiler.new_jump_point(); + + compiler.emit(Op::CopyV1, &mid); + pattern.compile_to_b(compiler)?; + compiler.emit(Op::JumpFalse { dst }, &mid); + if let Some(guard) = guard { + guard.compile_to_b(compiler)?; + compiler.emit(Op::JumpFalse { dst }, &mid); + } + for e in exprs { + e.compile(compiler)?; + } + last_expr.compile(compiler)?; + // we were successful so we jump to the end + compiler.emit(Op::Jump { dst: end_dst }, &mid); + compiler.set_jump_target(dst); + Ok(()) + } +} + +impl<'script, Ex> Compilable<'script> for DefaultCase +where + Ex: Compilable<'script> + Expression, +{ + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + match self { + DefaultCase::None => { + compiler.emit(Op::Nop, &NodeMeta::dummy()); + } + DefaultCase::Null => { + compiler.emit(Op::Null, &NodeMeta::dummy()); + } + DefaultCase::Many { exprs, last_expr } => { + for e in exprs { + e.compile(compiler)?; + } + last_expr.compile(compiler)?; + } + DefaultCase::One(e) => e.compile(compiler)?, + } + Ok(()) + } +} +impl<'script, Ex> Compilable<'script> for ClauseGroup<'script, Ex> +where + Ex: Compilable<'script> + Expression, +{ + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + let next = compiler.new_jump_point(); + match self { + ClauseGroup::Simple { + precondition, + patterns, + } => { + if let Some(precondition) = precondition { + precondition.compile_to_b(compiler)?; + compiler.emit(Op::JumpFalse { dst: next }, &NodeMeta::dummy()); + // FIXME + } + for p in patterns { + p.compile(compiler)?; + } + } + ClauseGroup::SearchTree { + precondition, + tree: _, + rest, + } => { + if let Some(precondition) = precondition { + precondition.compile_to_b(compiler)?; + compiler.emit(Op::JumpFalse { dst: next }, &NodeMeta::dummy()); + // FIXME + } + for r in rest { + r.compile(compiler)?; + } + todo!("the tree has to go before therest!"); + } + ClauseGroup::Combined { + precondition, + groups, + } => { + if let Some(precondition) = precondition { + precondition.compile_to_b(compiler)?; + compiler.emit(Op::JumpFalse { dst: next }, &NodeMeta::dummy()); + // FIXME + } + for g in groups { + g.compile(compiler)?; + } + } + ClauseGroup::Single { + precondition, + pattern, + } => { + if let Some(precondition) = precondition { + precondition.compile_to_b(compiler)?; + compiler.emit(Op::JumpFalse { dst: next }, &NodeMeta::dummy()); + } + pattern.compile(compiler)?; + } + } + compiler.set_jump_target(next); + Ok(()) + } +} + +impl<'script, Ex> Compilable<'script> for Match<'script, Ex> +where + Ex: Compilable<'script> + Expression, +{ + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + let Match { + mid, + target, + patterns, + default, + } = self; + compiler.new_end_target(); + // Save r1 to ensure we can restore it after the patch operation + compiler.emit(Op::StoreV1, &mid); + // Save r1 to ensure we can restore it after the patch operation + target.compile(compiler)?; + // load target to v1 + compiler.emit(Op::LoadV1, &mid); + + for c in patterns { + c.compile(compiler)?; + } + default.compile(compiler)?; + compiler.set_end_target()?; + // restore r1 and ensure the result is on top of the stack + // Load the result of the match into r1 + compiler.emit(Op::LoadV1, &mid); + // restore r1 and ensure the result is on top of the stack + compiler.emit(Op::SwapV1, &mid); + Ok(()) + } +} + +impl<'script, Ex> Compilable<'script> for IfElse<'script, Ex> +where + Ex: Compilable<'script> + Expression, +{ + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + let IfElse { + mid, + target, + if_clause, + else_clause, + }: IfElse = self; + // jump point for the else clause + let else_dst = compiler.new_jump_point(); + // jump point for the end of the if expression + compiler.new_end_target(); + // store v1 + compiler.emit(Op::StoreV1, &mid); + + // compile the target and store the result in the B1 register + target.compile(compiler)?; + // load target in register 1; + compiler.emit(Op::LoadV1, &mid); + // compile the if clause + if_clause.compile(compiler)?; + // this is the jump destionaion for the else clause + compiler.set_jump_target(else_dst); + else_clause.compile(compiler)?; + // this is the jump destionaion for the end of the if expression + compiler.set_end_target()?; + // load the result in of the expression to v1 so we can restore the old value + compiler.emit(Op::LoadV1, &mid); + // restore original value + compiler.emit(Op::SwapV1, &mid); + + Ok(()) + } +} + +impl<'script> Compilable<'script> for Path<'script> { + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + match self { + #[allow(clippy::cast_possible_truncation)] + Path::Local(p) => { + compiler.max_locals = compiler.max_locals.max(p.idx); + compiler.emit(Op::LoadLocal { idx: p.idx as u32 }, &p.mid); + for s in p.segments { + s.compile(compiler)?; + } + } + Path::Event(p) => { + compiler.emit(Op::LoadEvent, &p.mid); + for s in p.segments { + s.compile(compiler)?; + } + } + Path::Expr(p) => { + p.expr.compile(compiler)?; + for s in p.segments { + s.compile(compiler)?; + } + } + Path::Meta(_p) => todo!(), + Path::Reserved(_p) => todo!(), + Path::State(_p) => todo!(), + } + Ok(()) + } +} diff --git a/tremor-script/src/vm/compiler/impls/imut_expr.rs b/tremor-script/src/vm/compiler/impls/imut_expr.rs new file mode 100644 index 0000000000..e13d846d4f --- /dev/null +++ b/tremor-script/src/vm/compiler/impls/imut_expr.rs @@ -0,0 +1,451 @@ +use tremor_value::Value; + +use crate::{ + ast::{ + raw::{BytesDataType, Endian}, + BaseExpr, BinOpKind, BooleanBinExpr, BooleanBinOpKind, BytesPart, ClausePreCondition, + Field, ImutExpr, List, Merge, Patch, PatchOperation, Pattern, Record, Segment, + StrLitElement, StringLit, + }, + errors::Result, + vm::{ + compiler::{Compilable, Compiler}, + Op, + }, + NodeMeta, +}; + +#[allow(clippy::too_many_lines)] +impl<'script> Compilable<'script> for ImutExpr<'script> { + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + match self { + ImutExpr::Record(r) => r.compile(compiler)?, + ImutExpr::List(l) => l.compile(compiler)?, + ImutExpr::Binary(b) => { + b.lhs.compile(compiler)?; + b.rhs.compile(compiler)?; + compiler.emit(Op::Binary { op: b.kind }, &b.mid); + } + ImutExpr::BinaryBoolean(b) => { + b.compile(compiler)?; + } + ImutExpr::Unary(u) => { + u.expr.compile(compiler)?; + compiler.emit(Op::Unary { op: u.kind }, &u.mid); + } + ImutExpr::Patch(p) => p.compile(compiler)?, + ImutExpr::Match(m) => m.compile(compiler)?, + ImutExpr::Comprehension(_) => todo!(), + ImutExpr::Merge(m) => m.compile(compiler)?, + ImutExpr::Path(p) => p.compile(compiler)?, + ImutExpr::String(s) => s.compile(compiler)?, + #[allow(clippy::cast_possible_truncation)] + ImutExpr::Local { idx, mid } => { + compiler.max_locals = compiler.max_locals.max(idx); + compiler.emit(Op::LoadLocal { idx: idx as u32 }, &mid); + } + ImutExpr::Literal(l) => { + compiler.emit_const(l.value, &l.mid); + } + ImutExpr::Present { + path: _path, + mid: _mid, + } => todo!(), + ImutExpr::Invoke1(_) => todo!(), + ImutExpr::Invoke2(_) => todo!(), + ImutExpr::Invoke3(_) => todo!(), + ImutExpr::Invoke(_) => todo!(), + ImutExpr::InvokeAggr(_) => todo!(), + ImutExpr::Recur(_) => todo!(), + #[allow(clippy::cast_possible_truncation)] + ImutExpr::Bytes(b) => { + let size = b.value.len() as u32; + let mid = b.mid; + // we modify r1 in the parts so we need to store it + compiler.emit(Op::StoreV1, &mid); + for b in b.value { + b.compile(compiler)?; + } + compiler.emit(Op::Bytes { size }, &mid); + // once the bytes are crated we restore it + compiler.emit(Op::LoadV1, &mid); + } + #[allow(clippy::cast_possible_truncation)] + ImutExpr::ArrayAppend(a) => { + let size = a.right.len() as u32; + for r in a.right { + r.compile(compiler)?; + } + a.left.compile(compiler)?; + compiler.emit(Op::Array { size }, &a.mid); + } + } + Ok(()) + } +} + +impl<'script> Compilable<'script> for Field<'script> { + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + self.name.compile(compiler)?; + self.value.compile(compiler)?; + Ok(()) + } +} + +impl<'script> Compilable<'script> for StringLit<'script> { + #[allow(clippy::cast_possible_truncation)] + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + let size = self.elements.len() as u32; + for e in self.elements { + match e { + StrLitElement::Lit(s) => { + compiler.emit_const(s, &self.mid); + } + StrLitElement::Expr(e) => { + e.compile(compiler)?; + } + } + } + compiler.emit(Op::String { size }, &self.mid); + Ok(()) + } +} + +impl<'script> Compilable<'script> for PatchOperation<'script> { + #[allow(unused_variables)] + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + match self { + PatchOperation::Insert { ident, expr, mid } => { + ident.compile(compiler)?; + compiler.emit(Op::TestRecortPresent, &mid); + let dst = compiler.new_jump_point(); + compiler.emit(Op::JumpFalse { dst }, &mid); + compiler.emit_const("Key already present", &mid); + compiler.emit(Op::Error, &mid); + compiler.set_jump_target(dst); + expr.compile(compiler)?; + compiler.emit(Op::RecordSet, &mid); + } + PatchOperation::Upsert { ident, expr, mid } => { + ident.compile(compiler)?; + expr.compile(compiler)?; + compiler.emit(Op::RecordSet, &mid); + } + PatchOperation::Update { ident, expr, mid } => { + ident.compile(compiler)?; + compiler.emit(Op::TestRecortPresent, &mid); + let dst = compiler.new_jump_point(); + compiler.emit(Op::JumpTrue { dst }, &mid); + compiler.emit_const("Key already present", &mid); + compiler.emit(Op::Error, &mid); + compiler.set_jump_target(dst); + expr.compile(compiler)?; + compiler.emit(Op::RecordSet, &mid); + } + PatchOperation::Erase { ident, mid } => { + ident.compile(compiler)?; + compiler.emit(Op::RecordRemove, &mid); + compiler.emit(Op::Pop, &mid); + } + PatchOperation::Copy { from, to, mid } => { + from.compile(compiler)?; + compiler.emit(Op::RecordGet, &mid); + to.compile(compiler)?; + compiler.emit(Op::Swap, &mid); + compiler.emit(Op::RecordSet, &mid); + } + PatchOperation::Move { from, to, mid } => { + from.compile(compiler)?; + compiler.emit(Op::RecordRemove, &mid); + to.compile(compiler)?; + compiler.emit(Op::Swap, &mid); + compiler.emit(Op::RecordSet, &mid); + } + PatchOperation::Merge { ident, expr, mid } => todo!(), + PatchOperation::MergeRecord { expr, mid } => { + expr.compile(compiler)?; + compiler.emit(Op::RecordMerge, &mid); + } + PatchOperation::Default { ident, expr, mid } => { + ident.compile(compiler)?; + compiler.emit(Op::TestRecortPresent, &mid); + let dst = compiler.new_jump_point(); + compiler.emit(Op::JumpTrue { dst }, &mid); + expr.compile(compiler)?; + compiler.emit(Op::RecordSet, &mid); + compiler.set_jump_target(dst); + } + PatchOperation::DefaultRecord { expr, mid } => todo!(), + } + Ok(()) + } +} + +impl<'script> Compilable<'script> for Segment<'script> { + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + match self { + Segment::Id { mid, key } => { + let key = compiler.add_key(key); + compiler.emit(Op::GetKey { key }, &mid); + } + Segment::Element { expr, mid } => { + expr.compile(compiler)?; + compiler.emit(Op::Get, &mid); + } + #[allow(clippy::cast_possible_truncation)] + Segment::Idx { idx, mid } => { + if let Ok(idx) = u32::try_from(idx) { + compiler.emit(Op::IndexFast { idx }, &mid); + } else { + compiler.emit_const(idx, &mid); + compiler.emit(Op::Index, &mid); + } + } + #[allow(clippy::cast_possible_truncation)] + Segment::Range { mid, start, end } => { + if let Some((start, end)) = u16::try_from(start).ok().zip(u16::try_from(end).ok()) { + compiler.emit(Op::RangeFast { start, end }, &mid); + } else { + compiler.emit_const(start, &mid); + compiler.emit_const(end, &mid); + compiler.emit(Op::Range, &mid); + } + } + + Segment::RangeExpr { mid, start, end } => { + start.compile(compiler)?; + end.compile(compiler)?; + compiler.emit(Op::Range, &mid); + } + } + Ok(()) + } +} + +impl<'script> Compilable<'script> for BooleanBinExpr<'script> { + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + let mid = self.mid.clone(); + self.compile_to_b(compiler)?; + compiler.emit(Op::StoreRB, &mid); + Ok(()) + } + fn compile_to_b(self, compiler: &mut Compiler<'script>) -> Result<()> { + self.lhs.compile_to_b(compiler)?; + match self.kind { + #[allow(clippy::cast_sign_loss)] + BooleanBinOpKind::Or => { + let dst = compiler.new_jump_point(); + compiler.emit(Op::JumpTrue { dst }, &self.mid); + self.rhs.compile_to_b(compiler)?; + compiler.set_jump_target(dst); + } + BooleanBinOpKind::Xor => { + self.rhs.compile(compiler)?; + compiler.emit(Op::Xor, &self.mid); + } + #[allow(clippy::cast_sign_loss)] + BooleanBinOpKind::And => { + let dst = compiler.new_jump_point(); + compiler.emit(Op::JumpFalse { dst }, &self.mid); + self.rhs.compile_to_b(compiler)?; + compiler.set_jump_target(dst); + } + } + Ok(()) + } +} + +impl From for u8 { + fn from(v: Endian) -> Self { + match v { + Endian::Big => 0, + Endian::Little => 1, + } + } +} + +impl From for Endian { + fn from(v: u8) -> Self { + match v { + 0 => Endian::Big, + _ => Endian::Little, + } + } +} + +impl From for u8 { + fn from(v: BytesDataType) -> Self { + match v { + BytesDataType::SignedInteger => 0, + BytesDataType::UnsignedInteger => 1, + BytesDataType::Binary => 2, + } + } +} +impl From for BytesDataType { + fn from(v: u8) -> Self { + match v { + 0 => BytesDataType::SignedInteger, + 1 => BytesDataType::UnsignedInteger, + _ => BytesDataType::Binary, + } + } +} + +impl<'script> Compilable<'script> for BytesPart<'script> { + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + let BytesPart { + mid, + data, + data_type, + endianess, + bits, + }: BytesPart = self; + + // format is bits:48 | data_type:2 |endianess:1 + let mut format = bits; + format = format << 2 | u64::from(u8::from(data_type)) & 0b11; + format = format << 1 | u64::from(u8::from(endianess)) & 0b1; + + data.compile(compiler)?; + let dst = compiler.new_jump_point(); + // load the value into r1 so we can test it + compiler.emit(Op::LoadV1, &mid); + match data_type { + BytesDataType::SignedInteger => compiler.emit(Op::TestIsI64, &mid), + BytesDataType::UnsignedInteger => compiler.emit(Op::TestIsU64, &mid), + BytesDataType::Binary => compiler.emit(Op::TestIsBytes, &mid), + } + compiler.emit(Op::JumpFalse { dst }, &mid); + compiler.emit_const("invalid type conversion for binary", &mid); + compiler.emit(Op::Error, &mid); + compiler.set_jump_target(dst); + // save the value back to the stack + compiler.emit(Op::StoreV1, &mid); + compiler.emit_const(format, &mid); + Ok(()) + } +} + +impl<'script> Compilable<'script> for Pattern<'script> { + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + self.compile_to_b(compiler) + } + /// will return the match state in registers.B + fn compile_to_b(self, compiler: &mut Compiler<'script>) -> Result<()> { + match self { + Pattern::Record(r) => { + compiler.emit(Op::TestIsRecord, &r.mid); + let dst = compiler.new_jump_point(); + compiler.emit(Op::JumpFalse { dst }, &r.mid); + if r.fields.is_empty() { + } else { + todo!() + } + compiler.set_jump_target(dst); + } + Pattern::Array(a) => { + let mid = *a.mid; + compiler.emit(Op::TestIsArray, &mid); + let dst = compiler.new_jump_point(); + compiler.emit(Op::JumpFalse { dst }, &mid); + if a.exprs.is_empty() { + } else { + todo!() + } + compiler.set_jump_target(dst); + } + Pattern::Expr(e) => { + let mid = e.meta().clone(); + e.compile(compiler)?; + compiler.emit(Op::Binary { op: BinOpKind::Eq }, &mid); + compiler.emit(Op::LoadRB, &NodeMeta::dummy()); + } + + Pattern::Assign(_) => todo!(), + Pattern::Tuple(_) => todo!(), + Pattern::Extract(_) => todo!(), + Pattern::DoNotCare => { + compiler.emit(Op::True, &NodeMeta::dummy()); + compiler.emit(Op::LoadRB, &NodeMeta::dummy()); + } + } + Ok(()) + } +} + +impl<'script> Compilable<'script> for ClausePreCondition<'script> { + fn compile(self, _compiler: &mut Compiler<'script>) -> Result<()> { + todo!() + } + fn compile_to_b(self, _compiler: &mut Compiler<'script>) -> Result<()> { + todo!() + } +} + +impl<'script> Compilable<'script> for Patch<'script> { + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + let Patch { + mid, + target, + operations, + } = self; + // Save r1 to ensure we can restore it after the patch operation + compiler.emit(Op::StoreV1, &mid); + // compile the target + target.compile(compiler)?; + // load the target into the register + compiler.emit(Op::LoadV1, &mid); + for op in operations { + op.compile(compiler)?; + } + // restore r1 and ensure the result is on top of the stack + compiler.emit(Op::SwapV1, &mid); + Ok(()) + } +} + +impl<'script> Compilable<'script> for Merge<'script> { + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + let Merge { mid, target, expr } = self; + // Save r1 to ensure we can restore it after the patch operation + compiler.emit(Op::StoreV1, &mid); + // compile the target + target.compile(compiler)?; + // load the target into the register + compiler.emit(Op::LoadV1, &mid); + expr.compile(compiler)?; + compiler.emit(Op::RecordMerge, &mid); + // restore r1 and ensure the result is on top of the stack + compiler.emit(Op::SwapV1, &mid); + Ok(()) + } +} + +impl<'script> Compilable<'script> for List<'script> { + #[allow(clippy::cast_possible_truncation)] + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + let List { mid, exprs } = self; + let size = exprs.len() as u32; + for e in exprs { + e.compile(compiler)?; + } + compiler.emit_const(Value::array(), &mid); + compiler.emit(Op::Array { size }, &mid); + Ok(()) + } +} +impl<'script> Compilable<'script> for Record<'script> { + #[allow(clippy::cast_possible_truncation)] + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + let Record { mid, base, fields } = self; + let size = fields.len() as u32; + for f in fields { + f.compile(compiler)?; + } + compiler.emit_const(Value::from(base), &mid); + compiler.emit(Op::Record { size }, &mid); + + Ok(()) + } +} diff --git a/tremor-script/src/vm/compiler/impls/mut_expr.rs b/tremor-script/src/vm/compiler/impls/mut_expr.rs new file mode 100644 index 0000000000..f261aedec4 --- /dev/null +++ b/tremor-script/src/vm/compiler/impls/mut_expr.rs @@ -0,0 +1,53 @@ +use crate::{ + ast::{EmitExpr, Expr, Path}, + errors::Result, + vm::{ + compiler::{Compilable, Compiler}, + Op, + }, +}; + +impl<'script> Compilable<'script> for Expr<'script> { + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + match self { + Expr::Match(m) => m.compile(compiler)?, + Expr::IfElse(ie) => ie.compile(compiler)?, + #[allow(clippy::cast_possible_truncation)] + Expr::Assign { mid: _, path, expr } => { + let Path::Local(p) = path else { + todo!("we only allow local pasth?"); + }; + if !p.segments.is_empty() { + todo!("we only allow non nested asignments pasth?"); + } + expr.compile(compiler)?; + compiler.max_locals = compiler.max_locals.max(p.idx); + compiler.emit(Op::StoreLocal { idx: p.idx as u32 }, &p.mid); + } + Expr::AssignMoveLocal { + mid: _, + path: _, + idx: _, + } => {} + Expr::Comprehension(_) => todo!(), + Expr::Drop { mid } => compiler.emit(Op::Drop, &mid), + Expr::Emit(e) => e.compile(compiler)?, + Expr::Imut(e) => e.compile(compiler)?, + } + Ok(()) + } +} + +impl<'script> Compilable<'script> for EmitExpr<'script> { + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + self.expr.compile(compiler)?; + let dflt = if let Some(port) = self.port { + port.compile(compiler)?; + false + } else { + true + }; + compiler.emit(Op::Emit { dflt }, &self.mid); + Ok(()) + } +} diff --git a/tremor-script/src/vm/tests.rs b/tremor-script/src/vm/tests.rs index 100fca1904..227a826bee 100644 --- a/tremor-script/src/vm/tests.rs +++ b/tremor-script/src/vm/tests.rs @@ -2,14 +2,12 @@ use tremor_value::literal; use super::{Op::*, *}; use crate::{ - arena::Arena, - ast::{Helper, Script}, - lexer::Lexer, - parser::g::ScriptParser, - registry, AggrRegistry, Compiler, + arena::Arena, ast::Helper, lexer::Lexer, parser::g::ScriptParser, registry, AggrRegistry, + Compiler, }; -fn parse(src: &str) -> Result> { +fn compile(src: &str) -> Result> { + let mut compiler: Compiler = Compiler::new(); let reg = registry::registry(); let fake_aggr_reg = AggrRegistry::default(); let (aid, src) = Arena::insert(src)?; @@ -22,7 +20,9 @@ fn parse(src: &str) -> Result> { // helper.consts.args = args.clone_static(); let script = script_raw.up_script(&mut helper)?; // Optimizer::new(&helper).walk_script(&mut script)?; - Ok(script) + let p = compiler.compile(script)?; + println!("{p}"); + Ok(p) } fn run<'v>(p: &Program<'v>) -> Result> { @@ -58,12 +58,8 @@ fn op_size() { } #[test] fn simple() -> Result<()> { - let mut compiler: Compiler = Compiler::new(); + let p = compile("42")?; - let script = parse("42")?; - let p = compiler.compile(script)?; - - assert_eq!(p.consts.len(), 1); assert_eq!(p.opcodes, [Const { idx: 0 }]); assert_eq!(run(&p)?, 42); Ok(()) @@ -71,12 +67,8 @@ fn simple() -> Result<()> { #[test] fn simple_add() -> Result<()> { - let mut compiler: Compiler = Compiler::new(); + let p = compile("42 + 42")?; - let script = parse("42 + 42")?; - let p = compiler.compile(script)?; - - assert_eq!(p.consts.len(), 1); assert_eq!( p.opcodes, [ @@ -94,12 +86,8 @@ fn simple_add() -> Result<()> { #[test] fn simple_add_sub() -> Result<()> { - let mut compiler: Compiler = Compiler::new(); + let p = compile("42 + 43 - 44")?; - let script = parse("42 + 43 - 44")?; - let p = compiler.compile(script)?; - - assert_eq!(p.consts.len(), 3); assert_eq!( p.opcodes, [ @@ -121,12 +109,8 @@ fn simple_add_sub() -> Result<()> { #[test] fn logical_and() -> Result<()> { - let mut compiler: Compiler = Compiler::new(); + let p = compile("true and false")?; - let script = parse("true and false")?; - let p = compiler.compile(script)?; - - assert_eq!(p.consts.len(), 2); assert_eq!( p.opcodes, [ @@ -144,13 +128,9 @@ fn logical_and() -> Result<()> { #[test] fn logical_or() -> Result<()> { - let mut compiler: Compiler = Compiler::new(); - - let script = parse("true or false")?; - let p = compiler.compile(script)?; + let p = compile("true or false")?; println!("{p}"); - assert_eq!(p.consts.len(), 2); assert_eq!( p.opcodes, [ @@ -168,12 +148,8 @@ fn logical_or() -> Result<()> { #[test] fn logical_not() -> Result<()> { - let mut compiler: Compiler = Compiler::new(); + let p = compile("not true")?; - let script = parse("not true")?; - let p = compiler.compile(script)?; - - assert_eq!(p.consts.len(), 1); assert_eq!( p.opcodes, [ @@ -189,12 +165,8 @@ fn logical_not() -> Result<()> { #[test] fn simple_eq() -> Result<()> { - let mut compiler: Compiler = Compiler::new(); - - let script = parse("42 == 42")?; - let p = compiler.compile(script)?; + let p = compile("42 == 42")?; - assert_eq!(p.consts.len(), 1); assert_eq!( p.opcodes, [ @@ -211,25 +183,22 @@ fn simple_eq() -> Result<()> { #[test] fn patch_insert() -> Result<()> { - let mut compiler: Compiler = Compiler::new(); + let p = compile(r#"patch {} of insert "foo" => 42 end"#)?; - let script = parse(r#"patch {} of insert "foo" => 42 end"#)?; - let p = compiler.compile(script)?; - - assert_eq!(p.consts.len(), 3); assert_eq!( p.opcodes, &[ StoreV1, + Const { idx: 0 }, Record { size: 0 }, LoadV1, - Const { idx: 0 }, + Const { idx: 1 }, String { size: 1 }, TestRecortPresent, - JumpFalse { dst: 9 }, - Const { idx: 1 }, - Error, + JumpFalse { dst: 10 }, Const { idx: 2 }, + Error, + Const { idx: 3 }, RecordSet, SwapV1, ] @@ -246,23 +215,20 @@ fn patch_insert() -> Result<()> { #[test] fn patch_default() -> Result<()> { - let mut compiler: Compiler = Compiler::new(); - - let script = parse(r#"patch {} of default "foo" => 42 end"#)?; - let p = compiler.compile(script)?; + let p = compile(r#"patch {} of default "foo" => 42 end"#)?; - assert_eq!(p.consts.len(), 2); assert_eq!( p.opcodes, &[ StoreV1, + Const { idx: 0 }, Record { size: 0 }, LoadV1, - Const { idx: 0 }, + Const { idx: 1 }, String { size: 1 }, TestRecortPresent, - JumpTrue { dst: 9 }, - Const { idx: 1 }, + JumpTrue { dst: 10 }, + Const { idx: 2 }, RecordSet, SwapV1, ] @@ -279,12 +245,8 @@ fn patch_default() -> Result<()> { #[test] fn patch_default_present() -> Result<()> { - let mut compiler: Compiler = Compiler::new(); - - let script = parse(r#"patch {"foo":"bar"} of default "foo" => 42 end"#)?; - let p = compiler.compile(script)?; + let p = compile(r#"patch {"foo":"bar"} of default "foo" => 42 end"#)?; - assert_eq!(p.consts.len(), 3); assert_eq!( p.opcodes, &[ @@ -293,13 +255,14 @@ fn patch_default_present() -> Result<()> { String { size: 1 }, Const { idx: 1 }, String { size: 1 }, + Const { idx: 2 }, Record { size: 1 }, LoadV1, Const { idx: 0 }, String { size: 1 }, TestRecortPresent, - JumpTrue { dst: 13 }, - Const { idx: 2 }, + JumpTrue { dst: 14 }, + Const { idx: 3 }, RecordSet, SwapV1, ] @@ -316,12 +279,8 @@ fn patch_default_present() -> Result<()> { #[test] fn patch_insert_error() -> Result<()> { - let mut compiler: Compiler = Compiler::new(); - - let script = parse(r#"patch {"foo":"bar"} of insert "foo" => 42 end"#)?; - let p = compiler.compile(script)?; + let p = compile(r#"patch {"foo":"bar"} of insert "foo" => 42 end"#)?; - assert_eq!(p.consts.len(), 4); assert_eq!( p.opcodes, &[ @@ -330,15 +289,16 @@ fn patch_insert_error() -> Result<()> { String { size: 1 }, Const { idx: 1 }, String { size: 1 }, + Const { idx: 2 }, Record { size: 1 }, LoadV1, Const { idx: 0 }, String { size: 1 }, TestRecortPresent, - JumpFalse { dst: 13 }, - Const { idx: 2 }, - Error, + JumpFalse { dst: 14 }, Const { idx: 3 }, + Error, + Const { idx: 4 }, RecordSet, SwapV1, ] @@ -350,12 +310,8 @@ fn patch_insert_error() -> Result<()> { #[test] fn patch_update() -> Result<()> { - let mut compiler: Compiler = Compiler::new(); + let p = compile(r#"patch {"foo":"bar"} of update "foo" => 42 end"#)?; - let script = parse(r#"patch {"foo":"bar"} of update "foo" => 42 end"#)?; - let p = compiler.compile(script)?; - - assert_eq!(p.consts.len(), 4); assert_eq!( p.opcodes, &[ @@ -364,15 +320,16 @@ fn patch_update() -> Result<()> { String { size: 1 }, Const { idx: 1 }, String { size: 1 }, + Const { idx: 2 }, Record { size: 1 }, LoadV1, Const { idx: 0 }, String { size: 1 }, TestRecortPresent, - JumpTrue { dst: 13 }, - Const { idx: 2 }, - Error, + JumpTrue { dst: 14 }, Const { idx: 3 }, + Error, + Const { idx: 4 }, RecordSet, SwapV1, ] @@ -389,25 +346,22 @@ fn patch_update() -> Result<()> { #[test] fn patch_update_error() -> Result<()> { - let mut compiler: Compiler = Compiler::new(); - - let script = parse(r#"patch {} of update "foo" => 42 end"#)?; - let p = compiler.compile(script)?; + let p = compile(r#"patch {} of update "foo" => 42 end"#)?; - assert_eq!(p.consts.len(), 3); assert_eq!( p.opcodes, &[ StoreV1, + Const { idx: 0 }, Record { size: 0 }, LoadV1, - Const { idx: 0 }, + Const { idx: 1 }, String { size: 1 }, TestRecortPresent, - JumpTrue { dst: 9 }, - Const { idx: 1 }, - Error, + JumpTrue { dst: 10 }, Const { idx: 2 }, + Error, + Const { idx: 3 }, RecordSet, SwapV1, ] @@ -419,12 +373,8 @@ fn patch_update_error() -> Result<()> { #[test] fn patch_upsert_1() -> Result<()> { - let mut compiler: Compiler = Compiler::new(); + let p = compile(r#"patch {"foo":"bar"} of upsert "foo" => 42 end"#)?; - let script = parse(r#"patch {"foo":"bar"} of upsert "foo" => 42 end"#)?; - let p = compiler.compile(script)?; - - assert_eq!(p.consts.len(), 3); assert_eq!( p.opcodes, &[ @@ -433,11 +383,12 @@ fn patch_upsert_1() -> Result<()> { String { size: 1 }, Const { idx: 1 }, String { size: 1 }, + Const { idx: 2 }, Record { size: 1 }, LoadV1, Const { idx: 0 }, String { size: 1 }, - Const { idx: 2 }, + Const { idx: 3 }, RecordSet, SwapV1, ] @@ -454,21 +405,18 @@ fn patch_upsert_1() -> Result<()> { #[test] fn patch_upsert_2() -> Result<()> { - let mut compiler: Compiler = Compiler::new(); + let p = compile(r#"patch {} of upsert "foo" => 42 end"#)?; - let script = parse(r#"patch {} of upsert "foo" => 42 end"#)?; - let p = compiler.compile(script)?; - - assert_eq!(p.consts.len(), 2); assert_eq!( p.opcodes, &[ StoreV1, + Const { idx: 0 }, Record { size: 0 }, LoadV1, - Const { idx: 0 }, - String { size: 1 }, Const { idx: 1 }, + String { size: 1 }, + Const { idx: 2 }, RecordSet, SwapV1, ] @@ -485,15 +433,12 @@ fn patch_upsert_2() -> Result<()> { #[test] fn patch_patch_patch() -> Result<()> { - let mut compiler: Compiler = Compiler::new(); - - let script = parse( + let p = compile( r#"patch patch {"foo":"bar"} of upsert "bar" => "baz" end of insert "baz" => 42 end"#, )?; - let p = compiler.compile(script)?; println!("{p}"); - assert_eq!(p.consts.len(), 5); + assert_eq!( p.opcodes, &[ @@ -503,22 +448,23 @@ fn patch_patch_patch() -> Result<()> { String { size: 1 }, Const { idx: 1 }, String { size: 1 }, + Const { idx: 2 }, Record { size: 1 }, LoadV1, Const { idx: 1 }, String { size: 1 }, - Const { idx: 2 }, + Const { idx: 3 }, String { size: 1 }, RecordSet, SwapV1, LoadV1, - Const { idx: 2 }, + Const { idx: 3 }, String { size: 1 }, TestRecortPresent, - JumpFalse { dst: 21 }, - Const { idx: 3 }, - Error, + JumpFalse { dst: 22 }, Const { idx: 4 }, + Error, + Const { idx: 5 }, RecordSet, SwapV1, ] @@ -536,13 +482,81 @@ fn patch_patch_patch() -> Result<()> { } #[test] -fn array_index_fast() -> Result<()> { - let mut compiler: Compiler = Compiler::new(); +fn patch_merge() -> Result<()> { + let p = compile(r#"patch {"snot":"badger"} of merge => {"badger":"snot"} end"#)?; - let script = parse("[1,2,3][1]")?; - let p = compiler.compile(script)?; + assert_eq!( + p.opcodes, + &[ + StoreV1, + Const { idx: 0 }, + String { size: 1 }, + Const { idx: 1 }, + String { size: 1 }, + Const { idx: 2 }, + Record { size: 1 }, + LoadV1, + Const { idx: 1 }, + String { size: 1 }, + Const { idx: 0 }, + String { size: 1 }, + Const { idx: 2 }, + Record { size: 1 }, + RecordMerge, + SwapV1, + ] + ); + + assert_eq!( + run(&p)?, + literal!({ + "snot": "badger", + "badger": "snot" + }) + ); + Ok(()) +} + +#[test] +fn merge() -> Result<()> { + let p = compile(r#"merge {"snot":"badger"} of {"badger":"snot"} end"#)?; + + assert_eq!( + p.opcodes, + &[ + StoreV1, + Const { idx: 0 }, + String { size: 1 }, + Const { idx: 1 }, + String { size: 1 }, + Const { idx: 2 }, + Record { size: 1 }, + LoadV1, + Const { idx: 1 }, + String { size: 1 }, + Const { idx: 0 }, + String { size: 1 }, + Const { idx: 2 }, + Record { size: 1 }, + RecordMerge, + SwapV1, + ] + ); + + assert_eq!( + run(&p)?, + literal!({ + "snot": "badger", + "badger": "snot" + }) + ); + Ok(()) +} + +#[test] +fn array_index_fast() -> Result<()> { + let p = compile("[1,2,3][1]")?; - assert_eq!(p.consts.len(), 4); assert_eq!( p.opcodes, &[ @@ -561,12 +575,8 @@ fn array_index_fast() -> Result<()> { #[test] fn array_index() -> Result<()> { - let mut compiler: Compiler = Compiler::new(); + let p = compile("[1,2,3][1+1]")?; - let script = parse("[1,2,3][1+1]")?; - let p = compiler.compile(script)?; - - assert_eq!(p.consts.len(), 4); assert_eq!( p.opcodes, &[ @@ -588,12 +598,8 @@ fn array_index() -> Result<()> { #[test] fn record_key() -> Result<()> { - let mut compiler: Compiler = Compiler::new(); + let p = compile(r#"{"snot":"badger"}.snot"#)?; - let script = parse(r#"{"snot":"badger"}.snot"#)?; - let p = compiler.compile(script)?; - - assert_eq!(p.consts.len(), 2); assert_eq!( p.opcodes, &[ @@ -601,6 +607,7 @@ fn record_key() -> Result<()> { String { size: 1 }, Const { idx: 1 }, String { size: 1 }, + Const { idx: 2 }, Record { size: 1 }, GetKey { key: 0 }, ] @@ -612,12 +619,8 @@ fn record_key() -> Result<()> { #[test] fn record_path() -> Result<()> { - let mut compiler: Compiler = Compiler::new(); - - let script = parse(r#"{"snot":"badger"}["snot"]"#)?; - let p = compiler.compile(script)?; + let p = compile(r#"{"snot":"badger"}["snot"]"#)?; - assert_eq!(p.consts.len(), 2); assert_eq!( p.opcodes, &[ @@ -625,6 +628,7 @@ fn record_path() -> Result<()> { String { size: 1 }, Const { idx: 1 }, String { size: 1 }, + Const { idx: 2 }, Record { size: 1 }, Const { idx: 0 }, String { size: 1 }, @@ -638,12 +642,8 @@ fn record_path() -> Result<()> { #[test] fn array_range() -> Result<()> { - let mut compiler: Compiler = Compiler::new(); - - let script = parse("[1,2,3][0:2]")?; - let p = compiler.compile(script)?; + let p = compile("[1,2,3][0:2]")?; - assert_eq!(p.consts.len(), 5); assert_eq!( p.opcodes, &[ @@ -666,12 +666,8 @@ fn array_range() -> Result<()> { #[test] fn event_key() -> Result<()> { - let mut compiler: Compiler = Compiler::new(); - - let script = parse("event.int")?; - let p = compiler.compile(script)?; + let p = compile("event.int")?; - assert_eq!(p.consts.len(), 0); assert_eq!(p.opcodes, &[LoadEvent, GetKey { key: 0 },]); assert_eq!(run(&p)?, 42); @@ -680,12 +676,8 @@ fn event_key() -> Result<()> { #[test] fn event_nested() -> Result<()> { - let mut compiler: Compiler = Compiler::new(); - - let script = parse("event.object.a")?; - let p = compiler.compile(script)?; + let p = compile("event.object.a")?; - assert_eq!(p.consts.len(), 0); assert_eq!( p.opcodes, &[LoadEvent, GetKey { key: 0 }, GetKey { key: 1 }] @@ -697,12 +689,8 @@ fn event_nested() -> Result<()> { #[test] fn event_nested_index() -> Result<()> { - let mut compiler: Compiler = Compiler::new(); - - let script = parse("event.array[1]")?; - let p = compiler.compile(script)?; + let p = compile("event.array[1]")?; - assert_eq!(p.consts.len(), 0); assert_eq!( p.opcodes, &[LoadEvent, GetKey { key: 0 }, IndexFast { idx: 1 },] @@ -714,12 +702,8 @@ fn event_nested_index() -> Result<()> { #[test] fn test_local() -> Result<()> { - let mut compiler: Compiler = Compiler::new(); - - let script = parse("let a = 42; a")?; - let p = compiler.compile(script)?; + let p = compile("let a = 42; a")?; - assert_eq!(p.consts.len(), 1); assert_eq!(p.max_locals, 0); assert_eq!( p.opcodes, @@ -736,12 +720,8 @@ fn test_local() -> Result<()> { #[test] fn test_local_event() -> Result<()> { - let mut compiler: Compiler = Compiler::new(); - - let script = parse("let a = event; a.int")?; - let p = compiler.compile(script)?; + let p = compile("let a = event; a.int")?; - assert_eq!(p.consts.len(), 0); assert_eq!(p.max_locals, 0); assert_eq!( p.opcodes, @@ -759,13 +739,9 @@ fn test_local_event() -> Result<()> { #[test] fn test_match_if_else() -> Result<()> { - let mut compiler: Compiler = Compiler::new(); - - let script = parse("match event.int of case 42 => 42 case _ => 0 end")?; - let p = compiler.compile(script)?; + let p = compile("match event.int of case 42 => 42 case _ => 0 end")?; println!("{p}"); - assert_eq!(p.consts.len(), 2); assert_eq!( p.opcodes, &[ @@ -793,14 +769,9 @@ fn test_match_if_else() -> Result<()> { #[test] fn test_match_record_type() -> Result<()> { use BinOpKind::Eq; - let mut compiler: Compiler = Compiler::new(); - let script = - parse("match event.object of case 42 => 42 case %{} => \"record\" case _ => 0 end")?; - let p = compiler.compile(script)?; + let p = compile("match event.object of case 42 => 42 case %{} => \"record\" case _ => 0 end")?; - println!("{p}"); - assert_eq!(p.consts.len(), 3); assert_eq!( p.opcodes, &[ From 15b6c92085f7f41b20bed2da3fdfa8626ef9fa20 Mon Sep 17 00:00:00 2001 From: "Heinz N. Gies" Date: Sun, 11 Aug 2024 20:51:23 +0200 Subject: [PATCH 05/12] nested assignments Signed-off-by: Heinz N. Gies --- tremor-script/src/errors.rs | 12 +- tremor-script/src/vm.rs | 85 +++++++++-- tremor-script/src/vm/compiler/impls.rs | 21 ++- .../src/vm/compiler/impls/imut_expr.rs | 43 +++--- .../src/vm/compiler/impls/mut_expr.rs | 77 ++++++++-- tremor-script/src/vm/tests.rs | 135 +++++++++++++++++- 6 files changed, 320 insertions(+), 53 deletions(-) diff --git a/tremor-script/src/errors.rs b/tremor-script/src/errors.rs index b4f04c5dfa..667af944c7 100644 --- a/tremor-script/src/errors.rs +++ b/tremor-script/src/errors.rs @@ -247,11 +247,11 @@ impl ErrorKind { Msg, NoClauseHit, NoConstsAllowed, NoEventReferencesAllowed, NoLocalsAllowed, NoObjectError, NotConstant, NotFound, Oops, Overflow, ParseIntError, ParserError, PatchKeyExists, PipelineUnknownPort, QueryNodeDuplicateName, QueryNodeReservedName, - QueryStreamNotDefined, RecursionLimit, RuntimeError, TailingHereDoc, TypeConflict, - TypeError, UnexpectedCharacter, UnexpectedEndOfStream, UnexpectedEscapeCode, - UnknownLocal, UnrecognizedToken, UnterminatedExtractor, UnterminatedHereDoc, - UnterminatedIdentLiteral, UnterminatedInterpolation, UnterminatedStringLiteral, - UpdateKeyMissing, Utf8Error, ValueError, WithParamNoArg, + QueryStreamNotDefined, RecursionLimit, RuntimeError, TailingHereDoc, TryFromInt, + TypeConflict, TypeError, UnexpectedCharacter, UnexpectedEndOfStream, + UnexpectedEscapeCode, UnknownLocal, UnrecognizedToken, UnterminatedExtractor, + UnterminatedHereDoc, UnterminatedIdentLiteral, UnterminatedInterpolation, + UnterminatedStringLiteral, UpdateKeyMissing, Utf8Error, ValueError, WithParamNoArg, }; match self { NoClauseHit(outer) @@ -350,6 +350,7 @@ impl ErrorKind { | Self::__Nonexhaustive { .. } | Utf8Error(_) | FromUtf8Error(_) + | TryFromInt(_) | ValueError(_) => (Some(Span::yolo()), None), } } @@ -567,6 +568,7 @@ error_chain! { AccessError(value_trait::AccessError); CodecError(tremor_codec::Error); Common(tremor_common::Error); + TryFromInt(std::num::TryFromIntError); } errors { /* diff --git a/tremor-script/src/vm.rs b/tremor-script/src/vm.rs index c6ab6b422a..cc4274f6a5 100644 --- a/tremor-script/src/vm.rs +++ b/tremor-script/src/vm.rs @@ -44,12 +44,17 @@ pub(crate) enum Op { StoreRB, /// Puts the event on the stack LoadEvent, + /// Takes the top of the stack and stores it in the event + StoreEvent { + elements: u16, + }, /// puts a variable on the stack LoadLocal { idx: u32, }, /// stores a variable from the stack StoreLocal { + elements: u16, idx: u32, }, /// emits an error @@ -152,8 +157,12 @@ impl Display for Op { Op::StoreRB => write!(f, "{:30} B1", "store_reg"), Op::LoadEvent => write!(f, "laod_event"), - Op::LoadLocal { idx } => write!(f, "{:30} {}", "load_local", idx), - Op::StoreLocal { idx } => write!(f, "{:30} {}", "store_local", idx), + Op::StoreEvent { elements } => write!(f, "{:30} {elements}", "store_event"), + Op::LoadLocal { idx } => write!(f, "{:30} {idx:10}", "load_local",), + Op::StoreLocal { elements, idx } => { + write!(f, "{:30} {idx:10} {elements}", "store_local") + } + Op::Emit { dflt } => write!(f, "{:30} {dflt}", "emit"), Op::Drop => write!(f, "drop"), Op::JumpTrue { dst } => write!(f, "{:30} {}", "jump_true", dst), @@ -331,17 +340,37 @@ impl<'run, 'event> Scope<'run, 'event> { self.registers.b1 = stack.pop().ok_or("Stack underflow")?.try_as_bool()?; } Op::StoreRB => stack.push(Cow::Owned(Value::from(self.registers.b1))), - Op::LoadEvent => stack.push(Cow::Borrowed(event)), + Op::LoadEvent => stack.push(Cow::Owned(event.clone())), + Op::StoreEvent { elements } => unsafe { + let mut tmp = event as *mut Value; + nested_assign(elements, &mut stack, &mut tmp, mid)?; + let r: &mut Value = tmp + .as_mut() + .ok_or("this is nasty, we have a null pointer")?; + *r = stack.pop().ok_or("Stack underflow")?.into_owned(); + }, Op::LoadLocal { idx } => { let idx = idx as usize; stack.push(Cow::Owned( self.locals[idx].as_ref().ok_or("Local not set")?.clone(), )); } - Op::StoreLocal { idx } => { + Op::StoreLocal { elements, idx } => unsafe { let idx = idx as usize; - self.locals[idx] = Some(stack.pop().ok_or("Stack underflow")?.into_owned()); - } + if let Some(var) = self.locals[idx].as_mut() { + let mut tmp = var as *mut Value; + nested_assign(elements, &mut stack, &mut tmp, mid)?; + let r: &mut Value = tmp + .as_mut() + .ok_or("this is nasty, we have a null pointer")?; + + *r = stack.pop().ok_or("Stack underflow")?.into_owned(); + } else if elements == 0 { + self.locals[idx] = Some(stack.pop().ok_or("Stack underflow")?.into_owned()); + } else { + return Err("nested assign into unset variable".into()); + } + }, Op::True => stack.push(Cow::Owned(Value::const_true())), Op::False => stack.push(Cow::Owned(Value::const_false())), @@ -387,21 +416,18 @@ impl<'run, 'event> Scope<'run, 'event> { Op::Drop => { return Ok(Return::Drop); } - #[allow(clippy::cast_abs_to_unsigned)] Op::JumpTrue { dst } => { if self.registers.b1 { *pc = dst as usize; continue; } } - #[allow(clippy::cast_abs_to_unsigned)] Op::JumpFalse { dst } => { if !self.registers.b1 { *pc = dst as usize; continue; } } - #[allow(clippy::cast_abs_to_unsigned)] Op::Jump { dst } => { *pc = dst as usize; continue; @@ -645,5 +671,46 @@ impl<'run, 'event> Scope<'run, 'event> { } } +/// This function is unsafe since it works with pointers. +/// It remains safe since we never leak the pointer and just +/// traverse the nested value a pointer at a time. +unsafe fn nested_assign( + elements: u16, + stack: &mut Vec>, + tmp: &mut *mut Value, + mid: &NodeMeta, +) -> Result<()> { + for _ in 0..elements { + let target = stack.pop().ok_or("Stack underflow")?; + if let Some(idx) = target.as_usize() { + let array = tmp + .as_mut() + .ok_or("this is nasty, we have a null pointer")? + .as_array_mut() + .ok_or("needs object")?; + + *tmp = std::ptr::from_mut::(match array.get_mut(idx) { + Some(v) => v, + None => return Err("Index out of bounds".into()), + }); + } else if let Some(key) = target.as_str() { + let map = tmp + .as_mut() + .ok_or("this is nasty, we have a null pointer")? + .as_object_mut() + .ok_or("needs object")?; + *tmp = std::ptr::from_mut::(match map.get_mut(key) { + Some(v) => v, + None => map + .entry(key.to_string().into()) + .or_insert_with(|| Value::object_with_capacity(32)), + }); + } else { + return Err(error_generic(mid, mid, &"Invalid key type")); + } + } + Ok(()) +} + #[cfg(test)] mod tests; diff --git a/tremor-script/src/vm/compiler/impls.rs b/tremor-script/src/vm/compiler/impls.rs index c3ed52fa38..a2576f3edf 100644 --- a/tremor-script/src/vm/compiler/impls.rs +++ b/tremor-script/src/vm/compiler/impls.rs @@ -2,7 +2,10 @@ mod imut_expr; mod mut_expr; use crate::{ - ast::{ClauseGroup, DefaultCase, Expression, IfElse, Match, Path, PredicateClause, Script}, + ast::{ + ClauseGroup, Comprehension, DefaultCase, Expression, IfElse, Match, Path, PredicateClause, + Script, + }, errors::Result, vm::{ compiler::{Compilable, Compiler}, @@ -213,10 +216,14 @@ where impl<'script> Compilable<'script> for Path<'script> { fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { match self { - #[allow(clippy::cast_possible_truncation)] Path::Local(p) => { compiler.max_locals = compiler.max_locals.max(p.idx); - compiler.emit(Op::LoadLocal { idx: p.idx as u32 }, &p.mid); + compiler.emit( + Op::LoadLocal { + idx: u32::try_from(p.idx)?, + }, + &p.mid, + ); for s in p.segments { s.compile(compiler)?; } @@ -240,3 +247,11 @@ impl<'script> Compilable<'script> for Path<'script> { Ok(()) } } +impl<'script, Ex> Compilable<'script> for Comprehension<'script, Ex> +where + Ex: Compilable<'script> + Expression, +{ + fn compile(self, _compiler: &mut Compiler<'script>) -> Result<()> { + todo!() + } +} diff --git a/tremor-script/src/vm/compiler/impls/imut_expr.rs b/tremor-script/src/vm/compiler/impls/imut_expr.rs index e13d846d4f..0cb183de78 100644 --- a/tremor-script/src/vm/compiler/impls/imut_expr.rs +++ b/tremor-script/src/vm/compiler/impls/imut_expr.rs @@ -4,7 +4,7 @@ use crate::{ ast::{ raw::{BytesDataType, Endian}, BaseExpr, BinOpKind, BooleanBinExpr, BooleanBinOpKind, BytesPart, ClausePreCondition, - Field, ImutExpr, List, Merge, Patch, PatchOperation, Pattern, Record, Segment, + Field, ImutExpr, Invoke, List, Merge, Patch, PatchOperation, Pattern, Record, Segment, StrLitElement, StringLit, }, errors::Result, @@ -35,14 +35,18 @@ impl<'script> Compilable<'script> for ImutExpr<'script> { } ImutExpr::Patch(p) => p.compile(compiler)?, ImutExpr::Match(m) => m.compile(compiler)?, - ImutExpr::Comprehension(_) => todo!(), + ImutExpr::Comprehension(c) => c.compile(compiler)?, ImutExpr::Merge(m) => m.compile(compiler)?, ImutExpr::Path(p) => p.compile(compiler)?, ImutExpr::String(s) => s.compile(compiler)?, - #[allow(clippy::cast_possible_truncation)] ImutExpr::Local { idx, mid } => { compiler.max_locals = compiler.max_locals.max(idx); - compiler.emit(Op::LoadLocal { idx: idx as u32 }, &mid); + compiler.emit( + Op::LoadLocal { + idx: u32::try_from(idx)?, + }, + &mid, + ); } ImutExpr::Literal(l) => { compiler.emit_const(l.value, &l.mid); @@ -51,15 +55,14 @@ impl<'script> Compilable<'script> for ImutExpr<'script> { path: _path, mid: _mid, } => todo!(), - ImutExpr::Invoke1(_) => todo!(), - ImutExpr::Invoke2(_) => todo!(), - ImutExpr::Invoke3(_) => todo!(), - ImutExpr::Invoke(_) => todo!(), + ImutExpr::Invoke1(i) => i.compile(compiler)?, + ImutExpr::Invoke2(i) => i.compile(compiler)?, + ImutExpr::Invoke3(i) => i.compile(compiler)?, + ImutExpr::Invoke(i) => i.compile(compiler)?, ImutExpr::InvokeAggr(_) => todo!(), ImutExpr::Recur(_) => todo!(), - #[allow(clippy::cast_possible_truncation)] ImutExpr::Bytes(b) => { - let size = b.value.len() as u32; + let size = u32::try_from(b.value.len())?; let mid = b.mid; // we modify r1 in the parts so we need to store it compiler.emit(Op::StoreV1, &mid); @@ -70,9 +73,8 @@ impl<'script> Compilable<'script> for ImutExpr<'script> { // once the bytes are crated we restore it compiler.emit(Op::LoadV1, &mid); } - #[allow(clippy::cast_possible_truncation)] ImutExpr::ArrayAppend(a) => { - let size = a.right.len() as u32; + let size = u32::try_from(a.right.len())?; for r in a.right { r.compile(compiler)?; } @@ -93,9 +95,8 @@ impl<'script> Compilable<'script> for Field<'script> { } impl<'script> Compilable<'script> for StringLit<'script> { - #[allow(clippy::cast_possible_truncation)] fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { - let size = self.elements.len() as u32; + let size = u32::try_from(self.elements.len())?; for e in self.elements { match e { StrLitElement::Lit(s) => { @@ -192,7 +193,6 @@ impl<'script> Compilable<'script> for Segment<'script> { expr.compile(compiler)?; compiler.emit(Op::Get, &mid); } - #[allow(clippy::cast_possible_truncation)] Segment::Idx { idx, mid } => { if let Ok(idx) = u32::try_from(idx) { compiler.emit(Op::IndexFast { idx }, &mid); @@ -201,7 +201,6 @@ impl<'script> Compilable<'script> for Segment<'script> { compiler.emit(Op::Index, &mid); } } - #[allow(clippy::cast_possible_truncation)] Segment::Range { mid, start, end } => { if let Some((start, end)) = u16::try_from(start).ok().zip(u16::try_from(end).ok()) { compiler.emit(Op::RangeFast { start, end }, &mid); @@ -423,10 +422,9 @@ impl<'script> Compilable<'script> for Merge<'script> { } impl<'script> Compilable<'script> for List<'script> { - #[allow(clippy::cast_possible_truncation)] fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { let List { mid, exprs } = self; - let size = exprs.len() as u32; + let size = u32::try_from(exprs.len())?; for e in exprs { e.compile(compiler)?; } @@ -436,10 +434,9 @@ impl<'script> Compilable<'script> for List<'script> { } } impl<'script> Compilable<'script> for Record<'script> { - #[allow(clippy::cast_possible_truncation)] fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { let Record { mid, base, fields } = self; - let size = fields.len() as u32; + let size = u32::try_from(fields.len())?; for f in fields { f.compile(compiler)?; } @@ -449,3 +446,9 @@ impl<'script> Compilable<'script> for Record<'script> { Ok(()) } } + +impl<'script> Compilable<'script> for Invoke<'script> { + fn compile(self, _compiler: &mut Compiler<'script>) -> Result<()> { + todo!() + } +} diff --git a/tremor-script/src/vm/compiler/impls/mut_expr.rs b/tremor-script/src/vm/compiler/impls/mut_expr.rs index f261aedec4..60bab10675 100644 --- a/tremor-script/src/vm/compiler/impls/mut_expr.rs +++ b/tremor-script/src/vm/compiler/impls/mut_expr.rs @@ -1,10 +1,12 @@ use crate::{ - ast::{EmitExpr, Expr, Path}, - errors::Result, + ast::{EmitExpr, Expr, Path, Segment}, + errors::{err_generic, Result}, + prelude::Ranged as _, vm::{ compiler::{Compilable, Compiler}, Op, }, + NodeMeta, }; impl<'script> Compilable<'script> for Expr<'script> { @@ -12,24 +14,15 @@ impl<'script> Compilable<'script> for Expr<'script> { match self { Expr::Match(m) => m.compile(compiler)?, Expr::IfElse(ie) => ie.compile(compiler)?, - #[allow(clippy::cast_possible_truncation)] - Expr::Assign { mid: _, path, expr } => { - let Path::Local(p) = path else { - todo!("we only allow local pasth?"); - }; - if !p.segments.is_empty() { - todo!("we only allow non nested asignments pasth?"); - } - expr.compile(compiler)?; - compiler.max_locals = compiler.max_locals.max(p.idx); - compiler.emit(Op::StoreLocal { idx: p.idx as u32 }, &p.mid); + Expr::Assign { mid, path, expr } => { + compile_assign(compiler, &mid, path, *expr)?; } Expr::AssignMoveLocal { mid: _, path: _, idx: _, } => {} - Expr::Comprehension(_) => todo!(), + Expr::Comprehension(c) => c.compile(compiler)?, Expr::Drop { mid } => compiler.emit(Op::Drop, &mid), Expr::Emit(e) => e.compile(compiler)?, Expr::Imut(e) => e.compile(compiler)?, @@ -51,3 +44,59 @@ impl<'script> Compilable<'script> for EmitExpr<'script> { Ok(()) } } + +fn compile_segment_path<'script>( + compiler: &mut Compiler<'script>, + segmetn: Segment<'script>, +) -> Result<()> { + match segmetn { + Segment::Id { mid, key } => compiler.emit_const(key.key().to_string(), &mid), + Segment::Element { expr, mid: _ } => expr.compile(compiler)?, + Segment::Idx { idx, mid } => compiler.emit_const(idx, &mid), + Segment::Range { mid, .. } | Segment::RangeExpr { mid, .. } => { + return err_generic( + &mid.extent(), + &mid.extent(), + &"range segment can't be assigned", + ) + } + } + Ok(()) +} +#[allow(clippy::cast_possible_truncation)] +fn compile_assign<'script>( + compiler: &mut Compiler<'script>, + _mid: &NodeMeta, + path: Path<'script>, + expr: Expr<'script>, +) -> Result<()> { + expr.compile(compiler)?; + match path { + Path::Local(p) => { + let elements: u16 = u16::try_from(p.segments.len())?; + for s in p.segments.into_iter().rev() { + compile_segment_path(compiler, s)?; + } + compiler.max_locals = compiler.max_locals.max(p.idx); + compiler.emit( + Op::StoreLocal { + idx: u32::try_from(p.idx)?, + elements, + }, + &p.mid, + ); + } + Path::Event(p) => { + let elements: u16 = p.segments.len() as u16; + for s in p.segments.into_iter().rev() { + compile_segment_path(compiler, s)?; + } + compiler.emit(Op::StoreEvent { elements }, &p.mid); + } + Path::State(_p) => todo!(), + Path::Meta(_p) => todo!(), + Path::Expr(_p) => todo!(), + Path::Reserved(_p) => todo!(), + } + Ok(()) +} diff --git a/tremor-script/src/vm/tests.rs b/tremor-script/src/vm/tests.rs index 227a826bee..b9650a91bd 100644 --- a/tremor-script/src/vm/tests.rs +++ b/tremor-script/src/vm/tests.rs @@ -709,7 +709,10 @@ fn test_local() -> Result<()> { p.opcodes, &[ Const { idx: 0 }, - StoreLocal { idx: 0 }, + StoreLocal { + idx: 0, + elements: 0 + }, LoadLocal { idx: 0 }, ] ); @@ -727,7 +730,10 @@ fn test_local_event() -> Result<()> { p.opcodes, &[ LoadEvent, - StoreLocal { idx: 0 }, + StoreLocal { + idx: 0, + elements: 0 + }, LoadLocal { idx: 0 }, GetKey { key: 0 }, ] @@ -802,3 +808,128 @@ fn test_match_record_type() -> Result<()> { assert_eq!(run(&p)?, "record"); Ok(()) } + +#[test] +fn test_event_assign_nested() -> Result<()> { + let p = compile("let event.string = \"snot\"; event.string")?; + + assert_eq!(p.max_locals, 0); + assert_eq!( + p.opcodes, + &[ + Const { idx: 0 }, + String { size: 1 }, + Const { idx: 1 }, + StoreEvent { elements: 1 }, + LoadEvent, + GetKey { key: 0 }, + ] + ); + + assert_eq!(run(&p)?, "snot"); + Ok(()) +} + +#[test] +fn test_event_assign_nested_new() -> Result<()> { + let p = compile("let event.badger = \"snot\"; event.badger")?; + + assert_eq!(p.max_locals, 0); + assert_eq!( + p.opcodes, + &[ + Const { idx: 0 }, + String { size: 1 }, + Const { idx: 1 }, + StoreEvent { elements: 1 }, + LoadEvent, + GetKey { key: 0 }, + ] + ); + + assert_eq!(run(&p)?, "snot"); + Ok(()) +} + +#[test] +fn test_event_array_assign_nested() -> Result<()> { + let p = compile("let event.array[1] = 42; event.array")?; + + assert_eq!(p.max_locals, 0); + assert_eq!( + p.opcodes, + &[ + Const { idx: 0 }, + Const { idx: 1 }, + Const { idx: 2 }, + StoreEvent { elements: 2 }, + LoadEvent, + GetKey { key: 0 }, + ] + ); + + assert_eq!(run(&p)?, literal!([1, 42, 3])); + Ok(()) +} + +#[test] +fn test_local_assign_nested() -> Result<()> { + let p = compile("let a = {}; let a.b = 1; a.b")?; + + assert_eq!(p.max_locals, 0); + assert_eq!( + p.opcodes, + &[ + Const { idx: 0 }, + Record { size: 0 }, + StoreLocal { + elements: 0, + idx: 0, + }, + Const { idx: 1 }, + Const { idx: 2 }, + StoreLocal { + elements: 1, + idx: 0, + }, + LoadLocal { idx: 0 }, + GetKey { key: 0 }, + ] + ); + + assert_eq!(run(&p)?, 1); + Ok(()) +} + +#[test] +fn test_local_array_assign_nested() -> Result<()> { + let p = compile("let a = [1,2]; let a[0]=-1; a")?; + + assert_eq!(p.max_locals, 0); + assert_eq!( + p.opcodes, + &[ + Const { idx: 0 }, + Const { idx: 1 }, + Const { idx: 2 }, + Array { size: 2 }, + StoreLocal { + elements: 0, + idx: 0, + }, + Const { idx: 0 }, + Unary { + op: UnaryOpKind::Minus, + }, + Const { idx: 3 }, + StoreLocal { + elements: 1, + idx: 0, + }, + LoadLocal { idx: 0 }, + ] + ); + + assert_eq!(run(&p)?, literal!([-1, 2])); + Ok(()) +} From 4f01ec144a2090a71e4ebe8d7d2033f47bfdd850 Mon Sep 17 00:00:00 2001 From: "Heinz N. Gies" Date: Sun, 11 Aug 2024 21:25:06 +0200 Subject: [PATCH 06/12] patch mergee key Signed-off-by: Heinz N. Gies --- tremor-script/src/vm.rs | 24 +++++++++++- .../src/vm/compiler/impls/imut_expr.rs | 6 ++- tremor-script/src/vm/tests.rs | 38 ++++++++++++++++++- 3 files changed, 64 insertions(+), 4 deletions(-) diff --git a/tremor-script/src/vm.rs b/tremor-script/src/vm.rs index cc4274f6a5..c63965eaa5 100644 --- a/tremor-script/src/vm.rs +++ b/tremor-script/src/vm.rs @@ -137,6 +137,7 @@ pub(crate) enum Op { RecordMerge, TestIsRecord, TestIsArray, + RecordMergeKey, } impl Display for Op { @@ -185,16 +186,19 @@ impl Display for Op { Op::IndexFast { idx } => write!(f, "{:30} {}", "idx_fast", idx), Op::Range => write!(f, "range"), Op::RangeFast { start, end } => write!(f, "{:30} {} {}", "range_fast", start, end), + Op::TestRecortPresent => write!(f, "test_record_present"), Op::TestIsU64 => write!(f, "test_is_u64"), Op::TestIsI64 => write!(f, "test_is_i64"), Op::TestIsBytes => write!(f, "test_is_bytes"), Op::TestIsRecord => write!(f, "test_is_record"), Op::TestIsArray => write!(f, "test_is_array"), + Op::RecordSet => write!(f, "record_set"), Op::RecordRemove => write!(f, "record_remove"), Op::RecordGet => write!(f, "record_get"), - Op::RecordMerge => write!(f, "merge"), + Op::RecordMergeKey => write!(f, "record_merge_key"), + Op::RecordMerge => write!(f, "record_merge"), } } } @@ -596,7 +600,23 @@ impl<'run, 'event> Scope<'run, 'event> { let v = self.registers.v1.get(key).ok_or("not a record")?.clone(); stack.push(Cow::Owned(v)); } - // merge + Op::RecordMergeKey => { + let key_val = stack.pop().ok_or("Stack underflow")?; + let key = key_val.try_as_str()?; + let arg = stack.pop().ok_or("Stack underflow")?; + + let obj = self + .registers + .v1 + .to_mut() + .as_object_mut() + .ok_or("needs object")?; + + let target = obj + .entry(key.to_string().into()) + .or_insert_with(|| Value::object_with_capacity(32)); + merge_values(target, &arg)?; + } Op::RecordMerge => { let arg = stack.pop().ok_or("Stack underflow")?; merge_values(self.registers.v1.to_mut(), &arg)?; diff --git a/tremor-script/src/vm/compiler/impls/imut_expr.rs b/tremor-script/src/vm/compiler/impls/imut_expr.rs index 0cb183de78..37ac8fec3f 100644 --- a/tremor-script/src/vm/compiler/impls/imut_expr.rs +++ b/tremor-script/src/vm/compiler/impls/imut_expr.rs @@ -162,7 +162,11 @@ impl<'script> Compilable<'script> for PatchOperation<'script> { compiler.emit(Op::Swap, &mid); compiler.emit(Op::RecordSet, &mid); } - PatchOperation::Merge { ident, expr, mid } => todo!(), + PatchOperation::Merge { ident, expr, mid } => { + expr.compile(compiler)?; + ident.compile(compiler)?; + compiler.emit(Op::RecordMergeKey, &mid); + } PatchOperation::MergeRecord { expr, mid } => { expr.compile(compiler)?; compiler.emit(Op::RecordMerge, &mid); diff --git a/tremor-script/src/vm/tests.rs b/tremor-script/src/vm/tests.rs index b9650a91bd..a5c7fbfb0c 100644 --- a/tremor-script/src/vm/tests.rs +++ b/tremor-script/src/vm/tests.rs @@ -483,7 +483,7 @@ fn patch_patch_patch() -> Result<()> { #[test] fn patch_merge() -> Result<()> { - let p = compile(r#"patch {"snot":"badger"} of merge => {"badger":"snot"} end"#)?; + let p = compile(r#"patch {"snot":"badger"} of merge => {"badger":"snot"} end"#)?; assert_eq!( p.opcodes, @@ -517,6 +517,42 @@ fn patch_merge() -> Result<()> { Ok(()) } +#[test] +fn patch_merge_key() -> Result<()> { + let p = compile(r#"(patch event of merge "object" => {"badger":"snot"} end).object"#)?; + + assert_eq!( + p.opcodes, + &[ + StoreV1, + LoadEvent, + LoadV1, + Const { idx: 0 }, + String { size: 1 }, + Const { idx: 1 }, + String { size: 1 }, + Const { idx: 2 }, + Record { size: 1 }, + Const { idx: 3 }, + String { size: 1 }, + RecordMergeKey, + SwapV1, + GetKey { key: 0 }, + ] + ); + + assert_eq!( + run(&p)?, + literal!({ + "a": 1, + "b": 2, + "c": 3, + "badger": "snot", + }) + ); + Ok(()) +} + #[test] fn merge() -> Result<()> { let p = compile(r#"merge {"snot":"badger"} of {"badger":"snot"} end"#)?; From a93a42439f82ae9eb4a152497ffb13bbd4026bde Mon Sep 17 00:00:00 2001 From: "Heinz N. Gies" Date: Sun, 11 Aug 2024 23:32:55 +0200 Subject: [PATCH 07/12] Add support for default and a first loop Signed-off-by: Heinz N. Gies --- tremor-script/src/vm.rs | 456 +++++------------- tremor-script/src/vm/compiler.rs | 61 ++- .../src/vm/compiler/impls/imut_expr.rs | 39 +- tremor-script/src/vm/op.rs | 196 ++++++++ tremor-script/src/vm/tests.rs | 65 ++- 5 files changed, 488 insertions(+), 329 deletions(-) create mode 100644 tremor-script/src/vm/op.rs diff --git a/tremor-script/src/vm.rs b/tremor-script/src/vm.rs index c63965eaa5..3c0f34eebd 100644 --- a/tremor-script/src/vm.rs +++ b/tremor-script/src/vm.rs @@ -1,5 +1,6 @@ -use std::{borrow::Cow, collections::HashMap, fmt::Display}; +use std::{borrow::Cow, mem}; +use compiler::Program; use simd_json::{prelude::*, ValueBuilder}; use tremor_value::Value; @@ -7,7 +8,6 @@ use crate::{ ast::{ binary::write_bits, raw::{BytesDataType, Endian}, - BinOpKind, UnaryOpKind, }, errors::{error_generic, Result}, interpreter::{exec_binary, exec_unary, merge_values}, @@ -16,249 +16,8 @@ use crate::{ }; pub(super) mod compiler; - -#[derive(Debug, PartialEq, Copy, Clone, Default, Eq)] -pub(crate) enum Op { - /// do absolutely nothing - #[default] - Nop, - /// take the top most value from the stack and delete it - Pop, - /// swap the top two values on the stack - Swap, - /// duplicate the top of the stack - #[allow(dead_code)] - Duplicate, - /// Load V1, pops the stack and stores the value in V1 - LoadV1, - /// Stores the value in V1 on the stack and sets it to null - StoreV1, - /// Swaps the value in V1 with the top of the stack - SwapV1, - /// Copies the content of V1 to the top of the stack - CopyV1, - /// Load boolean register from the top of the stack - LoadRB, - /// Store boolean register to the top of the stack - #[allow(dead_code)] - StoreRB, - /// Puts the event on the stack - LoadEvent, - /// Takes the top of the stack and stores it in the event - StoreEvent { - elements: u16, - }, - /// puts a variable on the stack - LoadLocal { - idx: u32, - }, - /// stores a variable from the stack - StoreLocal { - elements: u16, - idx: u32, - }, - /// emits an error - Error, - /// emits the top of the stack - Emit { - dflt: bool, - }, - /// drops the event - Drop, - /// jumps to the given offset if the top of the stack is true does not op the stack - JumpTrue { - dst: u32, - }, - /// jumps to the given offset if the top of the stack is true does not op the stack - JumpFalse { - dst: u32, - }, - /// jumps to the given offset if the top of the stack is true does not op the stack - Jump { - dst: u32, - }, - Const { - idx: u32, - }, - - // Values - #[allow(dead_code)] - True, - #[allow(dead_code)] - False, - Null, - Record { - size: u32, - }, - Array { - size: u32, - }, - String { - size: u32, - }, - Bytes { - size: u32, - }, - // Logical XOP - Xor, - - Binary { - op: BinOpKind, - }, - Unary { - op: UnaryOpKind, - }, - - GetKey { - key: u32, - }, - Get, - Index, - IndexFast { - idx: u32, - }, - Range, - RangeFast { - start: u16, - end: u16, - }, - - // Tests - TestRecortPresent, - TestIsU64, - TestIsI64, - TestIsBytes, - - // Patch - RecordSet, - RecordRemove, - RecordGet, - // Merge - RecordMerge, - TestIsRecord, - TestIsArray, - RecordMergeKey, -} - -impl Display for Op { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Op::Nop => write!(f, "nop"), - Op::Pop => write!(f, "pop"), - Op::Swap => write!(f, "swap"), - Op::Duplicate => write!(f, "duplicate"), - Op::Error => write!(f, "error"), - - Op::LoadV1 => write!(f, "{:30} V1", "load_reg"), - Op::StoreV1 => write!(f, "{:30} V1", "store_reg"), - Op::SwapV1 => write!(f, "{:30} V1", "swap_reg"), - Op::CopyV1 => write!(f, "{:30} V1", "copy_reg"), - - Op::LoadRB => write!(f, "{:30} B1", "load_reg"), - Op::StoreRB => write!(f, "{:30} B1", "store_reg"), - - Op::LoadEvent => write!(f, "laod_event"), - Op::StoreEvent { elements } => write!(f, "{:30} {elements}", "store_event"), - Op::LoadLocal { idx } => write!(f, "{:30} {idx:10}", "load_local",), - Op::StoreLocal { elements, idx } => { - write!(f, "{:30} {idx:10} {elements}", "store_local") - } - - Op::Emit { dflt } => write!(f, "{:30} {dflt}", "emit"), - Op::Drop => write!(f, "drop"), - Op::JumpTrue { dst } => write!(f, "{:30} {}", "jump_true", dst), - Op::JumpFalse { dst } => write!(f, "{:30} {}", "jump_false", dst), - Op::Jump { dst } => write!(f, "{:30} {}", "jump", dst), - Op::True => write!(f, "true"), - Op::False => write!(f, "false"), - Op::Null => write!(f, "null"), - Op::Const { idx } => write!(f, "{:30} {}", "const", idx), - Op::Record { size } => write!(f, "{:30} {}", "record", size), - Op::Array { size } => write!(f, "{:30} {}", "array", size), - Op::String { size } => write!(f, "{:30} {}", "string", size), - Op::Bytes { size } => write!(f, "{:30} {}", "bytes", size), - Op::Xor => write!(f, "xor"), - Op::Binary { op } => write!(f, "{:30} {:?}", "binary", op), - Op::Unary { op } => write!(f, "{:30} {:?}", "unary", op), - Op::GetKey { key } => write!(f, "{:30} {}", "lookup_key", key), - Op::Get => write!(f, "lookup"), - Op::Index => write!(f, "idx"), - Op::IndexFast { idx } => write!(f, "{:30} {}", "idx_fast", idx), - Op::Range => write!(f, "range"), - Op::RangeFast { start, end } => write!(f, "{:30} {} {}", "range_fast", start, end), - - Op::TestRecortPresent => write!(f, "test_record_present"), - Op::TestIsU64 => write!(f, "test_is_u64"), - Op::TestIsI64 => write!(f, "test_is_i64"), - Op::TestIsBytes => write!(f, "test_is_bytes"), - Op::TestIsRecord => write!(f, "test_is_record"), - Op::TestIsArray => write!(f, "test_is_array"), - - Op::RecordSet => write!(f, "record_set"), - Op::RecordRemove => write!(f, "record_remove"), - Op::RecordGet => write!(f, "record_get"), - Op::RecordMergeKey => write!(f, "record_merge_key"), - Op::RecordMerge => write!(f, "record_merge"), - } - } -} - -#[derive(Debug, PartialEq, Clone, Default, Eq)] -/// A compiler for tremor script -pub struct Program<'script> { - opcodes: Vec, - meta: Vec, - jump_table: HashMap, - consts: Vec>, - keys: Vec>, - max_locals: usize, -} - -impl Display for Program<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - for (idx, op) in self.opcodes.iter().enumerate() { - if let Some(dst) = self.jump_table.get(&idx).copied() { - writeln!(f, "JMP<{dst:03}>")?; - } - match op { - Op::JumpTrue { dst } => writeln!( - f, - " {idx:04}: {:30} JMP<{:03}>", - "jump_true", - self.jump_table - .get(&(*dst as usize)) - .copied() - .unwrap_or_default() - )?, - Op::JumpFalse { dst } => writeln!( - f, - " {idx:04}: {:30} JMP<{:03}>", - "jump_false", - self.jump_table - .get(&(*dst as usize)) - .copied() - .unwrap_or_default() - )?, - Op::Jump { dst } => writeln!( - f, - " {idx:04}: {:30} JMP<{:03}>", - "jump", - self.jump_table - .get(&(*dst as usize)) - .copied() - .unwrap_or_default() - )?, - _ => writeln!(f, " {idx:04}: {op}")?, - } - } - for (i, dst) in &self.jump_table { - if *i > self.opcodes.len() { - writeln!(f, "JMP<{dst:03}>")?; - } - } - Ok(()) - } -} +mod op; +use op::Op; #[allow(dead_code)] pub struct Vm {} @@ -273,7 +32,7 @@ struct Registers<'run, 'event> { pub struct Scope<'run, 'event> { // value: &'run mut Value<'event>, program: &'run Program<'event>, - registers: &'run mut Registers<'run, 'event>, + reg: &'run mut Registers<'run, 'event>, locals: &'run mut [Option>], } #[allow(dead_code)] @@ -305,13 +64,14 @@ impl Vm { let mut root = Scope { program, locals: &mut locals, - registers: &mut registers, + reg: &mut registers, }; let mut pc = 0; + let mut cc = 0; // ensure that the opcodes and meta are the same length assert_eq!(program.opcodes.len(), program.meta.len()); - root.run(event, &mut pc) + root.run(event, &mut pc, &mut cc) } } @@ -321,6 +81,7 @@ impl<'run, 'event> Scope<'run, 'event> { &mut self, event: &'run mut Value<'event>, pc: &mut usize, + cc: &mut usize, ) -> Result> where 'prog: 'event, @@ -328,30 +89,30 @@ impl<'run, 'event> Scope<'run, 'event> { let mut stack: Vec> = Vec::with_capacity(8); while *pc < self.program.opcodes.len() { + *cc += 1; let mid = &self.program.meta[*pc]; // ALLOW: we test that pc is always in bounds in the while loop above match unsafe { *self.program.opcodes.get_unchecked(*pc) } { Op::Nop => continue, // Loads - Op::LoadV1 => self.registers.v1 = stack.pop().ok_or("Stack underflow")?, - Op::StoreV1 => stack.push(std::mem::take(&mut self.registers.v1)), - Op::CopyV1 => stack.push(self.registers.v1.clone()), - Op::SwapV1 => std::mem::swap( - &mut self.registers.v1, - stack.last_mut().ok_or("Stack underflow")?, - ), + Op::LoadV1 => self.reg.v1 = pop(&mut stack, *pc, *cc)?, + Op::StoreV1 => stack.push(mem::take(&mut self.reg.v1)), + Op::CopyV1 => stack.push(self.reg.v1.clone()), + Op::SwapV1 => { + mem::swap(&mut self.reg.v1, last_mut(&mut stack, *pc, *cc)?); + } Op::LoadRB => { - self.registers.b1 = stack.pop().ok_or("Stack underflow")?.try_as_bool()?; + self.reg.b1 = pop(&mut stack, *pc, *cc)?.try_as_bool()?; } - Op::StoreRB => stack.push(Cow::Owned(Value::from(self.registers.b1))), + Op::StoreRB => stack.push(Cow::Owned(Value::from(self.reg.b1))), Op::LoadEvent => stack.push(Cow::Owned(event.clone())), Op::StoreEvent { elements } => unsafe { let mut tmp = event as *mut Value; - nested_assign(elements, &mut stack, &mut tmp, mid)?; + nested_assign(elements, &mut stack, &mut tmp, mid, *pc, *cc)?; let r: &mut Value = tmp .as_mut() .ok_or("this is nasty, we have a null pointer")?; - *r = stack.pop().ok_or("Stack underflow")?.into_owned(); + *r = pop(&mut stack, *pc, *cc)?.into_owned(); }, Op::LoadLocal { idx } => { let idx = idx as usize; @@ -363,14 +124,14 @@ impl<'run, 'event> Scope<'run, 'event> { let idx = idx as usize; if let Some(var) = self.locals[idx].as_mut() { let mut tmp = var as *mut Value; - nested_assign(elements, &mut stack, &mut tmp, mid)?; + nested_assign(elements, &mut stack, &mut tmp, mid, *pc, *cc)?; let r: &mut Value = tmp .as_mut() .ok_or("this is nasty, we have a null pointer")?; - *r = stack.pop().ok_or("Stack underflow")?.into_owned(); + *r = pop(&mut stack, *pc, *cc)?.into_owned(); } else if elements == 0 { - self.locals[idx] = Some(stack.pop().ok_or("Stack underflow")?.into_owned()); + self.locals[idx] = Some(pop(&mut stack, *pc, *cc)?.into_owned()); } else { return Err("nested assign into unset variable".into()); } @@ -381,20 +142,20 @@ impl<'run, 'event> Scope<'run, 'event> { Op::Null => stack.push(Cow::Owned(Value::null())), Op::Const { idx } => stack.push(Cow::Borrowed(&self.program.consts[idx as usize])), Op::Pop => { - stack.pop().ok_or("Stack underflow")?; + pop(&mut stack, *pc, *cc)?; } Op::Swap => { - let a = stack.pop().ok_or("Stack underflow")?; - let b = stack.pop().ok_or("Stack underflow")?; + let a = pop(&mut stack, *pc, *cc)?; + let b = pop(&mut stack, *pc, *cc)?; stack.push(a); stack.push(b); } Op::Duplicate => { - let a = stack.last().ok_or("Stack underflow")?; + let a = last(&stack, *pc, *cc)?; stack.push(a.clone()); } Op::Error => { - let msg = stack.pop().ok_or("Stack underflow")?; + let msg = pop(&mut stack, *pc, *cc)?; let mid = mid.clone(); return Err(error_generic( &mid.extent(), @@ -403,15 +164,15 @@ impl<'run, 'event> Scope<'run, 'event> { )); } Op::Emit { dflt: true } => { - let value = stack.pop().ok_or("Stack underflow")?; + let value = pop(&mut stack, *pc, *cc)?; return Ok(Return::Emit { value: value.into_owned(), port: None, }); } Op::Emit { dflt: false } => { - let port = stack.pop().ok_or("Stack underflow")?; - let value = stack.pop().ok_or("Stack underflow")?; + let port = pop(&mut stack, *pc, *cc)?; + let value = pop(&mut stack, *pc, *cc)?; return Ok(Return::Emit { value: value.into_owned(), port: Some(port.try_as_str()?.to_string().into()), @@ -421,13 +182,13 @@ impl<'run, 'event> Scope<'run, 'event> { return Ok(Return::Drop); } Op::JumpTrue { dst } => { - if self.registers.b1 { + if self.reg.b1 { *pc = dst as usize; continue; } } Op::JumpFalse { dst } => { - if !self.registers.b1 { + if !self.reg.b1 { *pc = dst as usize; continue; } @@ -438,11 +199,11 @@ impl<'run, 'event> Scope<'run, 'event> { } Op::Record { size } => { let size = size as usize; - let mut v = stack.pop().ok_or("Stack underflow")?; + let mut v = pop(&mut stack, *pc, *cc)?; let record = v.to_mut(); for _ in 0..size { - let value = stack.pop().ok_or("Stack underflow")?; - let key = stack.pop().ok_or("Stack underflow")?; + let value = pop(&mut stack, *pc, *cc)?; + let key = pop(&mut stack, *pc, *cc)?; // FIXME: we can do better than clone here let key = key.into_owned().try_into_string()?; record.try_insert(key, value.into_owned()); @@ -451,7 +212,7 @@ impl<'run, 'event> Scope<'run, 'event> { } Op::Array { size } => { let size = size as usize; - let mut v = stack.pop().ok_or("Stack underflow")?; + let mut v = pop(&mut stack, *pc, *cc)?; let array = v.to_mut().as_array_mut().ok_or("Not an array")?; array.reserve(size); for value in stack.drain(stack.len() - size..) { @@ -479,12 +240,12 @@ impl<'run, 'event> Scope<'run, 'event> { let mut pending = 0; let mut buf = 0; for _ in 0..size { - let mut format = stack.pop().ok_or("Stack underflow")?.try_as_i64()?; + let mut format = pop(&mut stack, *pc, *cc)?.try_as_i64()?; let endianess = Endian::from(format as u8 & 0b1); format >>= 1; let data_type = BytesDataType::from(format as u8 & 0b11); let bits = (format >> 2) as u8; - let value = stack.pop().ok_or("Stack underflow")?; + let value = pop(&mut stack, *pc, *cc)?; match data_type { BytesDataType::UnsignedInteger => write_bits( &mut bytes, @@ -539,78 +300,82 @@ impl<'run, 'event> Scope<'run, 'event> { // | BinOpKind::Lte // | BinOpKind::Lt), // } => { - // let rhs = stack.pop().ok_or("Stack underflow")?; - // let lhs = stack.pop().ok_or("Stack underflow")?; + // let rhs = pop(&mut stack, *pc, *cc)?; + // let lhs = pop(&mut stack, *pc, *cc)?; // self.registers.b1 = exec_binary(mid, mid, op, &lhs, &rhs)?.try_as_bool()?; // } Op::Binary { op } => { - let rhs = stack.pop().ok_or("Stack underflow")?; - let lhs = stack.pop().ok_or("Stack underflow")?; + let rhs = pop(&mut stack, *pc, *cc)?; + let lhs = pop(&mut stack, *pc, *cc)?; stack.push(exec_binary(mid, mid, op, &lhs, &rhs)?); } Op::Unary { op } => { - let value = stack.pop().ok_or("Stack underflow")?; + let value = pop(&mut stack, *pc, *cc)?; stack.push(exec_unary(mid, mid, op, &value)?); } Op::Xor => { - let rhs = stack.pop().ok_or("Stack underflow")?; - stack.push(Cow::Owned(Value::from( - self.registers.b1 ^ rhs.try_as_bool()?, - ))); + let rhs = pop(&mut stack, *pc, *cc)?; + stack.push(Cow::Owned(Value::from(self.reg.b1 ^ rhs.try_as_bool()?))); } // tests Op::TestRecortPresent => { - let key = stack.last().ok_or("Stack underflow")?; - self.registers.b1 = self.registers.v1.contains_key(key.try_as_str()?); + let key = last(&stack, *pc, *cc)?; + self.reg.b1 = self.reg.v1.contains_key(key.try_as_str()?); } // record operations on scope Op::TestIsU64 => { - self.registers.b1 = self.registers.v1.is_u64(); + self.reg.b1 = self.reg.v1.is_u64(); } Op::TestIsI64 => { - self.registers.b1 = self.registers.v1.is_i64(); + self.reg.b1 = self.reg.v1.is_i64(); } Op::TestIsBytes => { - self.registers.b1 = self.registers.v1.is_bytes(); + self.reg.b1 = self.reg.v1.is_bytes(); } Op::TestIsRecord => { - self.registers.b1 = self.registers.v1.is_object(); + self.reg.b1 = self.reg.v1.is_object(); } Op::TestIsArray => { - self.registers.b1 = self.registers.v1.is_array(); + self.reg.b1 = self.reg.v1.is_array(); + } + Op::TestArrayIsEmpty => { + self.reg.b1 = self.reg.v1.as_array().map_or(true, Vec::is_empty); + } + Op::TestRecordIsEmpty => { + self.reg.b1 = self + .reg + .v1 + .as_object() + .map_or(true, halfbrown::SizedHashMap::is_empty); } + // Records Op::RecordSet => { - let value = stack.pop().ok_or("Stack underflow")?; - let key = stack.pop().ok_or("Stack underflow")?; + let value = pop(&mut stack, *pc, *cc)?; + let key = pop(&mut stack, *pc, *cc)?; // FIXME: we can do better than clone here let key = key.into_owned().try_into_string()?; - self.registers.v1.to_mut().insert(key, value.into_owned())?; + self.reg.v1.to_mut().insert(key, value.into_owned())?; } Op::RecordRemove => { - let key = stack.pop().ok_or("Stack underflow")?; + let key = pop(&mut stack, *pc, *cc)?; let key = key.try_as_str()?; - let v = self.registers.v1.to_mut().remove(key)?.unwrap_or_default(); + let v = self.reg.v1.to_mut().remove(key)?.unwrap_or_default(); stack.push(Cow::Owned(v)); } Op::RecordGet => { - let key = stack.pop().ok_or("Stack underflow")?; + let key = pop(&mut stack, *pc, *cc)?; let key = key.try_as_str()?; // FIXME: can we avoid this clone here - let v = self.registers.v1.get(key).ok_or("not a record")?.clone(); + let v = self.reg.v1.get(key).ok_or("not a record")?.clone(); stack.push(Cow::Owned(v)); } Op::RecordMergeKey => { - let key_val = stack.pop().ok_or("Stack underflow")?; + let key_val = pop(&mut stack, *pc, *cc)?; let key = key_val.try_as_str()?; - let arg = stack.pop().ok_or("Stack underflow")?; + let arg = pop(&mut stack, *pc, *cc)?; - let obj = self - .registers - .v1 - .to_mut() - .as_object_mut() - .ok_or("needs object")?; + let obj = self.reg.v1.to_mut().as_object_mut().ok_or("needs object")?; let target = obj .entry(key.to_string().into()) @@ -618,20 +383,31 @@ impl<'run, 'event> Scope<'run, 'event> { merge_values(target, &arg)?; } Op::RecordMerge => { - let arg = stack.pop().ok_or("Stack underflow")?; - merge_values(self.registers.v1.to_mut(), &arg)?; + let arg = pop(&mut stack, *pc, *cc)?; + merge_values(self.reg.v1.to_mut(), &arg)?; + } + // FIXME: this is kind akeward, we use the stack here instead of the register + Op::RecordPop => { + let obj = last_mut(&mut stack, *pc, *cc)? + .to_mut() + .as_object_mut() + .ok_or("needs object")?; + let key = obj.keys().next().ok_or("Empty object")?.clone(); + let v = obj.remove(&key).unwrap_or_default(); + stack.push(Cow::Owned(v)); + stack.push(Cow::Owned(key.into())); } // Path Op::GetKey { key } => { let key = &self.program.keys[key as usize]; - let v = stack.pop().ok_or("Stack underflow")?; + let v = pop(&mut stack, *pc, *cc)?; // FIXME: can we avoid this clone here let res = key.lookup(&v).ok_or("Missing Key FIXME")?.clone(); stack.push(Cow::Owned(res)); } Op::Get => { - let key = stack.pop().ok_or("Stack underflow")?; - let v = stack.pop().ok_or("Stack underflow")?; + let key = pop(&mut stack, *pc, *cc)?; + let v = pop(&mut stack, *pc, *cc)?; let v = if let Some(key) = key.as_str() { v.get(key).ok_or("not a record")?.clone() } else if let Some(idx) = key.as_usize() { @@ -643,24 +419,24 @@ impl<'run, 'event> Scope<'run, 'event> { stack.push(Cow::Owned(v)); } Op::Index => { - let idx = stack.pop().ok_or("Stack underflow")?; + let idx = pop(&mut stack, *pc, *cc)?; let idx = idx.try_as_usize()?; - let v = stack.pop().ok_or("Stack underflow")?; + let v = pop(&mut stack, *pc, *cc)?; let v = v.get_idx(idx).ok_or("Index out of bounds")?.clone(); stack.push(Cow::Owned(v)); } Op::IndexFast { idx } => { let idx = idx as usize; - let v = stack.pop().ok_or("Stack underflow")?; + let v = pop(&mut stack, *pc, *cc)?; let v = v.get_idx(idx).ok_or("Index out of bounds")?.clone(); stack.push(Cow::Owned(v)); } Op::Range => { - let end = stack.pop().ok_or("Stack underflow")?; - let start = stack.pop().ok_or("Stack underflow")?; + let end = pop(&mut stack, *pc, *cc)?; + let start = pop(&mut stack, *pc, *cc)?; let end = end.try_as_usize()?; let start = start.try_as_usize()?; - let v = stack.pop().ok_or("Stack underflow")?; + let v = pop(&mut stack, *pc, *cc)?; let v = v .try_as_array()? .get(start..end) @@ -671,7 +447,7 @@ impl<'run, 'event> Scope<'run, 'event> { Op::RangeFast { start, end } => { let start = start as usize; let end = end as usize; - let v = stack.pop().ok_or("Stack underflow")?; + let v = pop(&mut stack, *pc, *cc)?; let v = v .try_as_array()? .get(start..end) @@ -683,7 +459,7 @@ impl<'run, 'event> Scope<'run, 'event> { *pc += 1; } - let value = stack.pop().ok_or("Stack underflow")?; + let value = pop(&mut stack, *pc, *cc)?; Ok(Return::Emit { value: value.into_owned(), port: None, @@ -699,9 +475,11 @@ unsafe fn nested_assign( stack: &mut Vec>, tmp: &mut *mut Value, mid: &NodeMeta, + pc: usize, + cc: usize, ) -> Result<()> { for _ in 0..elements { - let target = stack.pop().ok_or("Stack underflow")?; + let target = pop(stack, pc, cc)?; if let Some(idx) = target.as_usize() { let array = tmp .as_mut() @@ -732,5 +510,37 @@ unsafe fn nested_assign( Ok(()) } +#[inline] +fn pop<'run, 'event>( + stack: &mut Vec>>, + pc: usize, + cc: usize, +) -> Result>> { + Ok(stack + .pop() + .ok_or_else(|| format!("Stack underflow @{pc}:{cc}"))?) +} + +#[inline] +fn last<'call, 'run, 'event>( + stack: &'call [Cow<'run, Value<'event>>], + pc: usize, + cc: usize, +) -> Result<&'call Cow<'run, Value<'event>>> { + Ok(stack + .last() + .ok_or_else(|| format!("Stack underflow @{pc}:{cc}"))?) +} +#[inline] +fn last_mut<'call, 'run, 'event>( + stack: &'call mut [Cow<'run, Value<'event>>], + pc: usize, + cc: usize, +) -> Result<&'call mut Cow<'run, Value<'event>>> { + Ok(stack + .last_mut() + .ok_or_else(|| format!("Stack underflow @{pc}:{cc}",))?) +} + #[cfg(test)] mod tests; diff --git a/tremor-script/src/vm/compiler.rs b/tremor-script/src/vm/compiler.rs index 88a20c5bb5..087542d445 100644 --- a/tremor-script/src/vm/compiler.rs +++ b/tremor-script/src/vm/compiler.rs @@ -1,10 +1,10 @@ mod impls; use crate::{ast::Script, errors::Result, NodeMeta}; -use std::{collections::HashMap, mem}; +use std::{collections::HashMap, fmt::Display, mem}; use tremor_value::Value; -use super::{Op, Program}; +use super::Op; #[derive(Debug)] /// A compiler for tremor script @@ -151,3 +151,60 @@ impl<'script> Default for Compiler<'script> { Self::new() } } + +#[derive(Debug, PartialEq, Clone, Default, Eq)] +/// A compiler for tremor script +pub struct Program<'script> { + pub(crate) opcodes: Vec, + pub(crate) meta: Vec, + pub(crate) jump_table: HashMap, + pub(crate) consts: Vec>, + pub(crate) keys: Vec>, + pub(crate) max_locals: usize, +} + +impl Display for Program<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for (idx, op) in self.opcodes.iter().enumerate() { + if let Some(dst) = self.jump_table.get(&idx).copied() { + writeln!(f, "JMP<{dst:03}>")?; + } + match op { + Op::JumpTrue { dst } => writeln!( + f, + " {idx:04}: {:30} JMP<{:03}>", + "jump_true", + self.jump_table + .get(&(*dst as usize)) + .copied() + .unwrap_or_default() + )?, + Op::JumpFalse { dst } => writeln!( + f, + " {idx:04}: {:30} JMP<{:03}>", + "jump_false", + self.jump_table + .get(&(*dst as usize)) + .copied() + .unwrap_or_default() + )?, + Op::Jump { dst } => writeln!( + f, + " {idx:04}: {:30} JMP<{:03}>", + "jump", + self.jump_table + .get(&(*dst as usize)) + .copied() + .unwrap_or_default() + )?, + _ => writeln!(f, " {idx:04}: {op}")?, + } + } + for (i, dst) in &self.jump_table { + if *i > self.opcodes.len() { + writeln!(f, "JMP<{dst:03}>")?; + } + } + Ok(()) + } +} diff --git a/tremor-script/src/vm/compiler/impls/imut_expr.rs b/tremor-script/src/vm/compiler/impls/imut_expr.rs index 37ac8fec3f..369b9158f5 100644 --- a/tremor-script/src/vm/compiler/impls/imut_expr.rs +++ b/tremor-script/src/vm/compiler/impls/imut_expr.rs @@ -180,7 +180,44 @@ impl<'script> Compilable<'script> for PatchOperation<'script> { compiler.emit(Op::RecordSet, &mid); compiler.set_jump_target(dst); } - PatchOperation::DefaultRecord { expr, mid } => todo!(), + PatchOperation::DefaultRecord { expr, mid } => { + expr.compile(compiler)?; + let empty_dst = compiler.new_jump_point(); + let start_dst = compiler.new_jump_point(); + let skip_dst = compiler.new_jump_point(); + + // start of the loop + compiler.set_jump_target(start_dst); + // pull the record from the stack in v1 to test for empy + // FIXME: this is not great + compiler.emit(Op::SwapV1, &mid); + compiler.emit(Op::TestRecordIsEmpty, &mid); + // return the original v1 + compiler.emit(Op::SwapV1, &mid); + // if the record is empty we are done + compiler.emit(Op::JumpTrue { dst: empty_dst }, &mid); + // take the top record + compiler.emit(Op::RecordPop, &mid); + + // Test if the key is present + compiler.emit(Op::TestRecortPresent, &mid); + // if it is we don't need to set r + compiler.emit(Op::JumpTrue { dst: skip_dst }, &mid); + // we insert the missing key + // ne need to swap the elements as RecordSewt expects the value on top of the stack + compiler.emit(Op::Swap, &mid); + compiler.emit(Op::RecordSet, &mid); + // next itteration + compiler.emit(Op::Jump { dst: start_dst }, &mid); + + // If we skip we need to pop the key and value + compiler.set_jump_target(skip_dst); + compiler.emit(Op::Pop, &mid); + compiler.emit(Op::Pop, &mid); + // next itteration + compiler.emit(Op::Jump { dst: start_dst }, &mid); + compiler.set_jump_target(empty_dst); + } } Ok(()) } diff --git a/tremor-script/src/vm/op.rs b/tremor-script/src/vm/op.rs new file mode 100644 index 0000000000..f0d82cc9c3 --- /dev/null +++ b/tremor-script/src/vm/op.rs @@ -0,0 +1,196 @@ +use std::fmt::Display; + +use crate::ast::{BinOpKind, UnaryOpKind}; + +#[derive(Debug, PartialEq, Copy, Clone, Default, Eq)] +pub(crate) enum Op { + /// do absolutely nothing + #[default] + Nop, + /// take the top most value from the stack and delete it + Pop, + /// swap the top two values on the stack + Swap, + /// duplicate the top of the stack + #[allow(dead_code)] + Duplicate, + /// Load V1, pops the stack and stores the value in V1 + LoadV1, + /// Stores the value in V1 on the stack and sets it to null + StoreV1, + /// Swaps the value in V1 with the top of the stack + SwapV1, + /// Copies the content of V1 to the top of the stack + CopyV1, + /// Load boolean register from the top of the stack + LoadRB, + /// Store boolean register to the top of the stack + #[allow(dead_code)] + StoreRB, + /// Puts the event on the stack + LoadEvent, + /// Takes the top of the stack and stores it in the event + StoreEvent { + elements: u16, + }, + /// puts a variable on the stack + LoadLocal { + idx: u32, + }, + /// stores a variable from the stack + StoreLocal { + elements: u16, + idx: u32, + }, + /// emits an error + Error, + /// emits the top of the stack + Emit { + dflt: bool, + }, + /// drops the event + Drop, + /// jumps to the given offset if the top of the stack is true does not op the stack + JumpTrue { + dst: u32, + }, + /// jumps to the given offset if the top of the stack is true does not op the stack + JumpFalse { + dst: u32, + }, + /// jumps to the given offset if the top of the stack is true does not op the stack + Jump { + dst: u32, + }, + Const { + idx: u32, + }, + + // Values + #[allow(dead_code)] + True, + #[allow(dead_code)] + False, + Null, + Record { + size: u32, + }, + Array { + size: u32, + }, + String { + size: u32, + }, + Bytes { + size: u32, + }, + // Logical XOP + Xor, + + Binary { + op: BinOpKind, + }, + Unary { + op: UnaryOpKind, + }, + + GetKey { + key: u32, + }, + Get, + Index, + IndexFast { + idx: u32, + }, + Range, + RangeFast { + start: u16, + end: u16, + }, + + // Tests + TestRecortPresent, + TestIsU64, + TestIsI64, + TestIsBytes, + #[allow(dead_code)] + TestArrayIsEmpty, + TestRecordIsEmpty, + + // Patch + RecordSet, + RecordRemove, + RecordGet, + // Merge + RecordMerge, + TestIsRecord, + TestIsArray, + RecordMergeKey, + RecordPop, +} + +impl Display for Op { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Op::Nop => write!(f, "nop"), + Op::Pop => write!(f, "pop"), + Op::Swap => write!(f, "swap"), + Op::Duplicate => write!(f, "duplicate"), + Op::Error => write!(f, "error"), + + Op::LoadV1 => write!(f, "{:30} V1", "load_reg"), + Op::StoreV1 => write!(f, "{:30} V1", "store_reg"), + Op::SwapV1 => write!(f, "{:30} V1", "swap_reg"), + Op::CopyV1 => write!(f, "{:30} V1", "copy_reg"), + + Op::LoadRB => write!(f, "{:30} B1", "load_reg"), + Op::StoreRB => write!(f, "{:30} B1", "store_reg"), + + Op::LoadEvent => write!(f, "laod_event"), + Op::StoreEvent { elements } => write!(f, "{:30} {elements}", "store_event"), + Op::LoadLocal { idx } => write!(f, "{:30} {idx:10}", "load_local",), + Op::StoreLocal { elements, idx } => { + write!(f, "{:30} {idx:10} {elements}", "store_local") + } + + Op::Emit { dflt } => write!(f, "{:30} {dflt}", "emit"), + Op::Drop => write!(f, "drop"), + Op::JumpTrue { dst } => write!(f, "{:30} {}", "jump_true", dst), + Op::JumpFalse { dst } => write!(f, "{:30} {}", "jump_false", dst), + Op::Jump { dst } => write!(f, "{:30} {}", "jump", dst), + Op::True => write!(f, "true"), + Op::False => write!(f, "false"), + Op::Null => write!(f, "null"), + Op::Const { idx } => write!(f, "{:30} {}", "const", idx), + Op::Record { size } => write!(f, "{:30} {}", "record", size), + Op::Array { size } => write!(f, "{:30} {}", "array", size), + Op::String { size } => write!(f, "{:30} {}", "string", size), + Op::Bytes { size } => write!(f, "{:30} {}", "bytes", size), + Op::Xor => write!(f, "xor"), + Op::Binary { op } => write!(f, "{:30} {:?}", "binary", op), + Op::Unary { op } => write!(f, "{:30} {:?}", "unary", op), + Op::GetKey { key } => write!(f, "{:30} {}", "lookup_key", key), + Op::Get => write!(f, "lookup"), + Op::Index => write!(f, "idx"), + Op::IndexFast { idx } => write!(f, "{:30} {}", "idx_fast", idx), + Op::Range => write!(f, "range"), + Op::RangeFast { start, end } => write!(f, "{:30} {} {}", "range_fast", start, end), + + Op::TestRecortPresent => write!(f, "test_record_present"), + Op::TestIsU64 => write!(f, "test_is_u64"), + Op::TestIsI64 => write!(f, "test_is_i64"), + Op::TestIsBytes => write!(f, "test_is_bytes"), + Op::TestIsRecord => write!(f, "test_is_record"), + Op::TestIsArray => write!(f, "test_is_array"), + Op::TestArrayIsEmpty => write!(f, "test_array_is_empty"), + Op::TestRecordIsEmpty => write!(f, "test_record_is_empty"), + + Op::RecordSet => write!(f, "record_set"), + Op::RecordRemove => write!(f, "record_remove"), + Op::RecordGet => write!(f, "record_get"), + Op::RecordMergeKey => write!(f, "record_merge_key"), + Op::RecordMerge => write!(f, "record_merge"), + Op::RecordPop => write!(f, "record_pop"), + } + } +} diff --git a/tremor-script/src/vm/tests.rs b/tremor-script/src/vm/tests.rs index a5c7fbfb0c..019def6b0d 100644 --- a/tremor-script/src/vm/tests.rs +++ b/tremor-script/src/vm/tests.rs @@ -2,8 +2,11 @@ use tremor_value::literal; use super::{Op::*, *}; use crate::{ - arena::Arena, ast::Helper, lexer::Lexer, parser::g::ScriptParser, registry, AggrRegistry, - Compiler, + arena::Arena, + ast::{BinOpKind, Helper, UnaryOpKind}, + lexer::Lexer, + parser::g::ScriptParser, + registry, AggrRegistry, Compiler, }; fn compile(src: &str) -> Result> { @@ -214,7 +217,7 @@ fn patch_insert() -> Result<()> { } #[test] -fn patch_default() -> Result<()> { +fn patch_default_key() -> Result<()> { let p = compile(r#"patch {} of default "foo" => 42 end"#)?; assert_eq!( @@ -243,6 +246,62 @@ fn patch_default() -> Result<()> { Ok(()) } +#[test] +fn patch_default() -> Result<()> { + let p = compile( + r#"patch {"snot": 42} of default => {"snot": 23, "badger": 23, "cake": "cookie"} end"#, + )?; + + assert_eq!( + p.opcodes, + &[ + StoreV1, + Const { idx: 0 }, + String { size: 1 }, + Const { idx: 1 }, + Const { idx: 2 }, + Record { size: 1 }, + LoadV1, + Const { idx: 0 }, + String { size: 1 }, + Const { idx: 3 }, + Const { idx: 4 }, + String { size: 1 }, + Const { idx: 3 }, + Const { idx: 5 }, + String { size: 1 }, + Const { idx: 6 }, + String { size: 1 }, + Const { idx: 2 }, + Record { size: 3 }, + SwapV1, + TestRecordIsEmpty, + SwapV1, + JumpTrue { dst: 32 }, + RecordPop, + TestRecortPresent, + JumpTrue { dst: 29 }, + Swap, + RecordSet, + Jump { dst: 19 }, + Pop, + Pop, + Jump { dst: 19 }, + SwapV1, + ] + ); + + assert_eq!( + run(&p)?, + literal!({ + "snot": 42, + "badger": 23, + "cake": "cookie", + }) + ); + Ok(()) +} + #[test] fn patch_default_present() -> Result<()> { let p = compile(r#"patch {"foo":"bar"} of default "foo" => 42 end"#)?; From bcd7223035594e5f54439c467b3df8ed6bdc44ef Mon Sep 17 00:00:00 2001 From: "Heinz N. Gies" Date: Mon, 12 Aug 2024 21:48:42 +0200 Subject: [PATCH 08/12] More pattern matching Signed-off-by: Heinz N. Gies --- tremor-script/src/vm.rs | 30 +++++++++ .../src/vm/compiler/impls/imut_expr.rs | 64 ++++++++++++++++--- tremor-script/src/vm/op.rs | 13 +++- 3 files changed, 96 insertions(+), 11 deletions(-) diff --git a/tremor-script/src/vm.rs b/tremor-script/src/vm.rs index 3c0f34eebd..6bb1420805 100644 --- a/tremor-script/src/vm.rs +++ b/tremor-script/src/vm.rs @@ -348,6 +348,18 @@ impl<'run, 'event> Scope<'run, 'event> { .as_object() .map_or(true, halfbrown::SizedHashMap::is_empty); } + // Inspect + Op::InspectLen => { + let v = last(&mut stack, *pc, *cc)?; + let len = if let Some(v) = v.as_array() { + v.len() + } else if let Some(v) = v.as_object() { + v.len() + } else { + return Err("Not an array or object".into()); + }; + stack.push(Cow::Owned(Value::from(len))); + } // Records Op::RecordSet => { @@ -455,6 +467,24 @@ impl<'run, 'event> Scope<'run, 'event> { .to_vec(); stack.push(Cow::Owned(v.into())); } + + // Array + // FIXME: this is kind akeward, we use the stack here instead of the register + Op::ArrayPop => { + let arr = last_mut(&mut stack, *pc, *cc)? + .to_mut() + .as_array_mut() + .ok_or("needs array")?; + let v = arr + .pop() + .ok_or_else(|| error_generic(mid, mid, &"Empty array"))?; + stack.push(Cow::Owned(v)); + } + Op::ArrayReverse => { + let v = last_mut(&mut stack, *pc, *cc)?; + let arr = v.to_mut().as_array_mut().ok_or("needs array")?; + arr.reverse(); + } } *pc += 1; } diff --git a/tremor-script/src/vm/compiler/impls/imut_expr.rs b/tremor-script/src/vm/compiler/impls/imut_expr.rs index 369b9158f5..8898fe48c0 100644 --- a/tremor-script/src/vm/compiler/impls/imut_expr.rs +++ b/tremor-script/src/vm/compiler/impls/imut_expr.rs @@ -3,9 +3,9 @@ use tremor_value::Value; use crate::{ ast::{ raw::{BytesDataType, Endian}, - BaseExpr, BinOpKind, BooleanBinExpr, BooleanBinOpKind, BytesPart, ClausePreCondition, - Field, ImutExpr, Invoke, List, Merge, Patch, PatchOperation, Pattern, Record, Segment, - StrLitElement, StringLit, + ArrayPredicatePattern, BaseExpr, BinOpKind, BooleanBinExpr, BooleanBinOpKind, BytesPart, + ClausePreCondition, Field, ImutExpr, Invoke, List, Merge, Patch, PatchOperation, Pattern, + PredicatePattern, Record, Segment, StrLitElement, StringLit, }, errors::Result, vm::{ @@ -367,6 +367,17 @@ impl<'script> Compilable<'script> for BytesPart<'script> { } } +impl<'script> Compilable<'script> for PredicatePattern<'script> { + fn compile(self, _compiler: &mut Compiler<'script>) -> Result<()> { + todo!() + } +} +impl<'script> Compilable<'script> for ArrayPredicatePattern<'script> { + fn compile(self, _compiler: &mut Compiler<'script>) -> Result<()> { + todo!() + } +} + impl<'script> Compilable<'script> for Pattern<'script> { fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { self.compile_to_b(compiler) @@ -378,10 +389,11 @@ impl<'script> Compilable<'script> for Pattern<'script> { compiler.emit(Op::TestIsRecord, &r.mid); let dst = compiler.new_jump_point(); compiler.emit(Op::JumpFalse { dst }, &r.mid); - if r.fields.is_empty() { - } else { - todo!() + + for f in r.fields { + f.compile(compiler)?; } + compiler.set_jump_target(dst); } Pattern::Array(a) => { @@ -389,9 +401,9 @@ impl<'script> Compilable<'script> for Pattern<'script> { compiler.emit(Op::TestIsArray, &mid); let dst = compiler.new_jump_point(); compiler.emit(Op::JumpFalse { dst }, &mid); - if a.exprs.is_empty() { - } else { - todo!() + for e in a.exprs { + e.compile(compiler)?; + todo!("we need to look at all the array elements :sob:") } compiler.set_jump_target(dst); } @@ -403,7 +415,39 @@ impl<'script> Compilable<'script> for Pattern<'script> { } Pattern::Assign(_) => todo!(), - Pattern::Tuple(_) => todo!(), + Pattern::Tuple(t) => { + let mid = *t.mid; + compiler.emit(Op::TestIsArray, &mid); + let dst = compiler.new_jump_point(); + compiler.emit(Op::JumpFalse { dst }, &mid); + // copy the item to the stack so we can itterate + compiler.emit(Op::CopyV1, &mid); + compiler.emit(Op::InspectLen, &mid); + compiler.emit_const(t.exprs.len(), &mid); + + if t.open { + compiler.emit(Op::Binary { op: BinOpKind::Gte }, &mid); + } else { + compiler.emit(Op::Binary { op: BinOpKind::Eq }, &mid); + } + let end_and_pop = compiler.new_jump_point(); + + compiler.emit(Op::JumpFalse { dst: end_and_pop }, &mid); + + // reverse the array so we can pop in the right order + compiler.emit(Op::ArrayReverse, &mid); + for e in t.exprs { + compiler.emit(Op::ArrayPop, &mid); + e.compile(compiler)?; + compiler.emit(Op::JumpFalse { dst: end_and_pop }, &mid); + todo!("we need to look at all the array elements :sob:") + } + compiler.emit(Op::LoadRB, &mid); + compiler.set_jump_target(end_and_pop); + // remove the array from the stack + compiler.emit(Op::Pop, &mid); + compiler.set_jump_target(dst); + } Pattern::Extract(_) => todo!(), Pattern::DoNotCare => { compiler.emit(Op::True, &NodeMeta::dummy()); diff --git a/tremor-script/src/vm/op.rs b/tremor-script/src/vm/op.rs index f0d82cc9c3..8e81bdf56d 100644 --- a/tremor-script/src/vm/op.rs +++ b/tremor-script/src/vm/op.rs @@ -108,7 +108,7 @@ pub(crate) enum Op { end: u16, }, - // Tests + // Tests - does not pop the stack result is stored in the b register TestRecortPresent, TestIsU64, TestIsI64, @@ -117,6 +117,10 @@ pub(crate) enum Op { TestArrayIsEmpty, TestRecordIsEmpty, + // Inspect - does not pop the stack result is stored on the stack + //// returns the lenght of an array, object or 1 for scalar values + InspectLen, + // Patch RecordSet, RecordRemove, @@ -127,6 +131,8 @@ pub(crate) enum Op { TestIsArray, RecordMergeKey, RecordPop, + ArrayPop, + ArrayReverse, } impl Display for Op { @@ -185,12 +191,17 @@ impl Display for Op { Op::TestArrayIsEmpty => write!(f, "test_array_is_empty"), Op::TestRecordIsEmpty => write!(f, "test_record_is_empty"), + Op::InspectLen => write!(f, "inspect_len"), + Op::RecordSet => write!(f, "record_set"), Op::RecordRemove => write!(f, "record_remove"), Op::RecordGet => write!(f, "record_get"), Op::RecordMergeKey => write!(f, "record_merge_key"), Op::RecordMerge => write!(f, "record_merge"), + Op::RecordPop => write!(f, "record_pop"), + Op::ArrayPop => write!(f, "array_pop"), + Op::ArrayReverse => write!(f, "array_reverse"), } } } From bc8d9480d311bcee64b10c33352ac3804ed39f06 Mon Sep 17 00:00:00 2001 From: "Heinz N. Gies" Date: Tue, 13 Aug 2024 19:46:01 +0200 Subject: [PATCH 09/12] Touple expressions and compiler comments Signed-off-by: Heinz N. Gies --- tremor-script/src/vm.rs | 2 +- tremor-script/src/vm/compiler.rs | 29 ++++- tremor-script/src/vm/compiler/impls.rs | 15 ++- .../src/vm/compiler/impls/imut_expr.rs | 38 ++++-- tremor-script/src/vm/op.rs | 32 +++--- tremor-script/src/vm/tests.rs | 108 +++++++++++++++++- 6 files changed, 188 insertions(+), 36 deletions(-) diff --git a/tremor-script/src/vm.rs b/tremor-script/src/vm.rs index 6bb1420805..f7f2581cb3 100644 --- a/tremor-script/src/vm.rs +++ b/tremor-script/src/vm.rs @@ -350,7 +350,7 @@ impl<'run, 'event> Scope<'run, 'event> { } // Inspect Op::InspectLen => { - let v = last(&mut stack, *pc, *cc)?; + let v = last(&stack, *pc, *cc)?; let len = if let Some(v) = v.as_array() { v.len() } else if let Some(v) = v.as_object() { diff --git a/tremor-script/src/vm/compiler.rs b/tremor-script/src/vm/compiler.rs index 087542d445..614675cf38 100644 --- a/tremor-script/src/vm/compiler.rs +++ b/tremor-script/src/vm/compiler.rs @@ -1,7 +1,12 @@ mod impls; use crate::{ast::Script, errors::Result, NodeMeta}; -use std::{collections::HashMap, fmt::Display, mem}; +use simd_json_derive::Serialize; +use std::{ + collections::{BTreeMap, HashMap}, + fmt::Display, + mem, +}; use tremor_value::Value; use super::Op; @@ -16,6 +21,7 @@ pub struct Compiler<'script> { consts: Vec>, keys: Vec>, max_locals: usize, + pub(crate) comments: BTreeMap, } trait Compilable<'script>: Sized { @@ -38,6 +44,7 @@ impl<'script> Compiler<'script> { let consts = mem::take(&mut self.consts); let mut jump_table = HashMap::with_capacity(self.jump_table.len()); let keys = mem::take(&mut self.keys); + let comments = mem::take(&mut self.comments); for op in &mut opcodes { match op { @@ -57,6 +64,7 @@ impl<'script> Compiler<'script> { jump_table, consts, keys, + comments, max_locals: self.max_locals, }) } @@ -72,6 +80,7 @@ impl<'script> Compiler<'script> { consts: Vec::new(), keys: Vec::new(), max_locals: 0, + comments: BTreeMap::new(), } } @@ -81,6 +90,13 @@ impl<'script> Compiler<'script> { // self.opcodes.len() as u32 - 1 // } + pub(crate) fn comment(&mut self, comment: S) + where + S: Into, + { + self.comments.insert(self.opcodes.len(), comment.into()); + } + pub(crate) fn emit(&mut self, op: Op, mid: &NodeMeta) { self.opcodes.push(op); self.meta.push(mid.clone()); @@ -161,11 +177,16 @@ pub struct Program<'script> { pub(crate) consts: Vec>, pub(crate) keys: Vec>, pub(crate) max_locals: usize, + pub(crate) comments: BTreeMap, } impl Display for Program<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "Program: ")?; for (idx, op) in self.opcodes.iter().enumerate() { + if let Some(comment) = self.comments.get(&idx) { + writeln!(f, "\n # {comment}")?; + } if let Some(dst) = self.jump_table.get(&idx).copied() { writeln!(f, "JMP<{dst:03}>")?; } @@ -197,6 +218,12 @@ impl Display for Program<'_> { .copied() .unwrap_or_default() )?, + Op::Const { idx: c } => writeln!( + f, + " {idx:04}: {:30} {c:<15} {}", + "const", + self.consts[*c as usize].json_string().unwrap_or_default(), + )?, _ => writeln!(f, " {idx:04}: {op}")?, } } diff --git a/tremor-script/src/vm/compiler/impls.rs b/tremor-script/src/vm/compiler/impls.rs index a2576f3edf..b163afda25 100644 --- a/tremor-script/src/vm/compiler/impls.rs +++ b/tremor-script/src/vm/compiler/impls.rs @@ -36,19 +36,23 @@ where } = self; let end_dst = compiler.end_dst()?; let dst = compiler.new_jump_point(); - + compiler.comment("Predicate Clause"); compiler.emit(Op::CopyV1, &mid); pattern.compile_to_b(compiler)?; + compiler.comment("Jump to the next pattern"); compiler.emit(Op::JumpFalse { dst }, &mid); if let Some(guard) = guard { + compiler.comment("Predicate Clause Guard"); guard.compile_to_b(compiler)?; compiler.emit(Op::JumpFalse { dst }, &mid); } + compiler.comment("Predicate Clause Body"); for e in exprs { e.compile(compiler)?; } last_expr.compile(compiler)?; // we were successful so we jump to the end + compiler.comment("Jump to the end of the matching statement since we were successful"); compiler.emit(Op::Jump { dst: end_dst }, &mid); compiler.set_jump_target(dst); Ok(()) @@ -90,7 +94,9 @@ where patterns, } => { if let Some(precondition) = precondition { + compiler.comment("Match Group Preconditions"); precondition.compile_to_b(compiler)?; + compiler.comment("Jump to next case if precondition is false"); compiler.emit(Op::JumpFalse { dst: next }, &NodeMeta::dummy()); // FIXME } @@ -104,7 +110,9 @@ where rest, } => { if let Some(precondition) = precondition { + compiler.comment("Match Tree Preconditions"); precondition.compile_to_b(compiler)?; + compiler.comment("Jump to next case if precondition is false"); compiler.emit(Op::JumpFalse { dst: next }, &NodeMeta::dummy()); // FIXME } @@ -118,7 +126,9 @@ where groups, } => { if let Some(precondition) = precondition { + compiler.comment("Match Combined Preconditions"); precondition.compile_to_b(compiler)?; + compiler.comment("Jump to next case if precondition is false"); compiler.emit(Op::JumpFalse { dst: next }, &NodeMeta::dummy()); // FIXME } @@ -131,7 +141,9 @@ where pattern, } => { if let Some(precondition) = precondition { + compiler.comment("Match Single Preconditions"); precondition.compile_to_b(compiler)?; + compiler.comment("Jump to next case if precondition is false"); compiler.emit(Op::JumpFalse { dst: next }, &NodeMeta::dummy()); } pattern.compile(compiler)?; @@ -153,6 +165,7 @@ where patterns, default, } = self; + compiler.comment("Match statement"); compiler.new_end_target(); // Save r1 to ensure we can restore it after the patch operation compiler.emit(Op::StoreV1, &mid); diff --git a/tremor-script/src/vm/compiler/impls/imut_expr.rs b/tremor-script/src/vm/compiler/impls/imut_expr.rs index 8898fe48c0..24167631d5 100644 --- a/tremor-script/src/vm/compiler/impls/imut_expr.rs +++ b/tremor-script/src/vm/compiler/impls/imut_expr.rs @@ -373,8 +373,19 @@ impl<'script> Compilable<'script> for PredicatePattern<'script> { } } impl<'script> Compilable<'script> for ArrayPredicatePattern<'script> { - fn compile(self, _compiler: &mut Compiler<'script>) -> Result<()> { - todo!() + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + match self { + ArrayPredicatePattern::Expr(e) => { + let mid = e.meta().clone(); + e.compile(compiler)?; + compiler.emit(Op::Binary { op: BinOpKind::Eq }, &mid); + compiler.emit(Op::LoadRB, &mid); + } + ArrayPredicatePattern::Tilde(_) => todo!(), + ArrayPredicatePattern::Record(_) => todo!(), + ArrayPredicatePattern::Ignore => todo!(), + } + Ok(()) } } @@ -416,12 +427,12 @@ impl<'script> Compilable<'script> for Pattern<'script> { Pattern::Assign(_) => todo!(), Pattern::Tuple(t) => { + compiler.comment("Tuple pattern"); let mid = *t.mid; compiler.emit(Op::TestIsArray, &mid); - let dst = compiler.new_jump_point(); - compiler.emit(Op::JumpFalse { dst }, &mid); - // copy the item to the stack so we can itterate - compiler.emit(Op::CopyV1, &mid); + let dst_next = compiler.new_jump_point(); + compiler.emit(Op::JumpFalse { dst: dst_next }, &mid); + compiler.comment("Check if the array is long enough"); compiler.emit(Op::InspectLen, &mid); compiler.emit_const(t.exprs.len(), &mid); @@ -430,23 +441,26 @@ impl<'script> Compilable<'script> for Pattern<'script> { } else { compiler.emit(Op::Binary { op: BinOpKind::Eq }, &mid); } + compiler.emit(Op::LoadRB, &mid); let end_and_pop = compiler.new_jump_point(); compiler.emit(Op::JumpFalse { dst: end_and_pop }, &mid); - // reverse the array so we can pop in the right order + compiler.comment("Save array for itteration and reverse it"); + compiler.emit(Op::CopyV1, &mid); compiler.emit(Op::ArrayReverse, &mid); - for e in t.exprs { + for (i, e) in t.exprs.into_iter().enumerate() { + compiler.comment(&format!("Test tuple element {}", i)); compiler.emit(Op::ArrayPop, &mid); e.compile(compiler)?; + compiler.comment("Jump on no match"); compiler.emit(Op::JumpFalse { dst: end_and_pop }, &mid); - todo!("we need to look at all the array elements :sob:") } - compiler.emit(Op::LoadRB, &mid); - compiler.set_jump_target(end_and_pop); // remove the array from the stack + compiler.comment("Remove the array from the stack"); + compiler.set_jump_target(end_and_pop); compiler.emit(Op::Pop, &mid); - compiler.set_jump_target(dst); + compiler.set_jump_target(dst_next); } Pattern::Extract(_) => todo!(), Pattern::DoNotCare => { diff --git a/tremor-script/src/vm/op.rs b/tremor-script/src/vm/op.rs index 8e81bdf56d..31481cc55f 100644 --- a/tremor-script/src/vm/op.rs +++ b/tremor-script/src/vm/op.rs @@ -153,34 +153,34 @@ impl Display for Op { Op::StoreRB => write!(f, "{:30} B1", "store_reg"), Op::LoadEvent => write!(f, "laod_event"), - Op::StoreEvent { elements } => write!(f, "{:30} {elements}", "store_event"), - Op::LoadLocal { idx } => write!(f, "{:30} {idx:10}", "load_local",), + Op::StoreEvent { elements } => write!(f, "{:30} {elements:<5}", "store_event"), + Op::LoadLocal { idx } => write!(f, "{:30} {idx:<5}", "load_local",), Op::StoreLocal { elements, idx } => { - write!(f, "{:30} {idx:10} {elements}", "store_local") + write!(f, "{:30} {idx:<5} {elements}", "store_local") } - Op::Emit { dflt } => write!(f, "{:30} {dflt}", "emit"), + Op::Emit { dflt } => write!(f, "{:30} {dflt:<5}", "emit"), Op::Drop => write!(f, "drop"), - Op::JumpTrue { dst } => write!(f, "{:30} {}", "jump_true", dst), - Op::JumpFalse { dst } => write!(f, "{:30} {}", "jump_false", dst), - Op::Jump { dst } => write!(f, "{:30} {}", "jump", dst), + Op::JumpTrue { dst } => write!(f, "{:30} {:<5}", "jump_true", dst), + Op::JumpFalse { dst } => write!(f, "{:30} {:<5}", "jump_false", dst), + Op::Jump { dst } => write!(f, "{:30} {:<5}", "jump", dst), Op::True => write!(f, "true"), Op::False => write!(f, "false"), Op::Null => write!(f, "null"), - Op::Const { idx } => write!(f, "{:30} {}", "const", idx), - Op::Record { size } => write!(f, "{:30} {}", "record", size), - Op::Array { size } => write!(f, "{:30} {}", "array", size), - Op::String { size } => write!(f, "{:30} {}", "string", size), - Op::Bytes { size } => write!(f, "{:30} {}", "bytes", size), + Op::Const { idx } => write!(f, "{:30} {idx:<5}", "const"), + Op::Record { size } => write!(f, "{:30} {size:<5}", "record",), + Op::Array { size } => write!(f, "{:30} {size:<5}", "array",), + Op::String { size } => write!(f, "{:30} {size:<5}", "string",), + Op::Bytes { size } => write!(f, "{:30} {size:<5}", "bytes",), Op::Xor => write!(f, "xor"), - Op::Binary { op } => write!(f, "{:30} {:?}", "binary", op), - Op::Unary { op } => write!(f, "{:30} {:?}", "unary", op), + Op::Binary { op } => write!(f, "{:30} {:<5?}", "binary", op), + Op::Unary { op } => write!(f, "{:30} {:<5?}", "unary", op), Op::GetKey { key } => write!(f, "{:30} {}", "lookup_key", key), Op::Get => write!(f, "lookup"), Op::Index => write!(f, "idx"), - Op::IndexFast { idx } => write!(f, "{:30} {}", "idx_fast", idx), + Op::IndexFast { idx } => write!(f, "{:30} {:<5}", "idx_fast", idx), Op::Range => write!(f, "range"), - Op::RangeFast { start, end } => write!(f, "{:30} {} {}", "range_fast", start, end), + Op::RangeFast { start, end } => write!(f, "{:30} {:<5} {}", "range_fast", start, end), Op::TestRecortPresent => write!(f, "test_record_present"), Op::TestIsU64 => write!(f, "test_is_u64"), diff --git a/tremor-script/src/vm/tests.rs b/tremor-script/src/vm/tests.rs index 019def6b0d..127cdc357c 100644 --- a/tremor-script/src/vm/tests.rs +++ b/tremor-script/src/vm/tests.rs @@ -3,7 +3,7 @@ use tremor_value::literal; use super::{Op::*, *}; use crate::{ arena::Arena, - ast::{BinOpKind, Helper, UnaryOpKind}, + ast::{BinOpKind::*, Helper, UnaryOpKind}, lexer::Lexer, parser::g::ScriptParser, registry, AggrRegistry, Compiler, @@ -682,7 +682,7 @@ fn array_index() -> Result<()> { Array { size: 3 }, Const { idx: 0 }, Const { idx: 0 }, - Binary { op: BinOpKind::Add }, + Binary { op: Add }, Get, ] ); @@ -852,7 +852,7 @@ fn test_match_if_else() -> Result<()> { LoadV1, CopyV1, Const { idx: 0 }, - Binary { op: BinOpKind::Eq }, + Binary { op: Eq }, LoadRB, JumpFalse { dst: 11 }, Const { idx: 0 }, @@ -869,8 +869,6 @@ fn test_match_if_else() -> Result<()> { #[test] fn test_match_record_type() -> Result<()> { - use BinOpKind::Eq; - let p = compile("match event.object of case 42 => 42 case %{} => \"record\" case _ => 0 end")?; assert_eq!( @@ -1028,3 +1026,103 @@ fn test_local_array_assign_nested() -> Result<()> { assert_eq!(run(&p)?, literal!([-1, 2])); Ok(()) } + +#[test] +fn test_match_touple() -> Result<()> { + let p = compile("match [42, 23] of case %(42, 24) => 24 case %(42, 23, 7) => 7 case %(42, 23) => 42 case _ => 0 end")?; + + assert_eq!( + p.opcodes, + &[ + StoreV1, + Const { idx: 0 }, + Const { idx: 1 }, + Const { idx: 2 }, + Array { size: 2 }, + LoadV1, + CopyV1, + TestIsArray, + JumpFalse { dst: 27 }, + InspectLen, + Const { idx: 3 }, + Binary { op: Eq }, + LoadRB, + JumpFalse { dst: 26 }, + CopyV1, + ArrayReverse, + ArrayPop, + Const { idx: 0 }, + Binary { op: Eq }, + LoadRB, + JumpFalse { dst: 26 }, + ArrayPop, + Const { idx: 4 }, + Binary { op: Eq }, + LoadRB, + JumpFalse { dst: 26 }, + Pop, + JumpFalse { dst: 30 }, + Const { idx: 4 }, + Jump { dst: 84 }, + CopyV1, + TestIsArray, + JumpFalse { dst: 56 }, + InspectLen, + Const { idx: 5 }, + Binary { op: Eq }, + LoadRB, + JumpFalse { dst: 55 }, + CopyV1, + ArrayReverse, + ArrayPop, + Const { idx: 0 }, + Binary { op: Eq }, + LoadRB, + JumpFalse { dst: 55 }, + ArrayPop, + Const { idx: 1 }, + Binary { op: Eq }, + LoadRB, + JumpFalse { dst: 55 }, + ArrayPop, + Const { idx: 6 }, + Binary { op: Eq }, + LoadRB, + JumpFalse { dst: 55 }, + Pop, + JumpFalse { dst: 59 }, + Const { idx: 6 }, + Jump { dst: 84 }, + CopyV1, + TestIsArray, + JumpFalse { dst: 80 }, + InspectLen, + Const { idx: 3 }, + Binary { op: Eq }, + LoadRB, + JumpFalse { dst: 79 }, + CopyV1, + ArrayReverse, + ArrayPop, + Const { idx: 0 }, + Binary { op: Eq }, + LoadRB, + JumpFalse { dst: 79 }, + ArrayPop, + Const { idx: 1 }, + Binary { op: Eq }, + LoadRB, + JumpFalse { dst: 79 }, + Pop, + JumpFalse { dst: 83 }, + Const { idx: 0 }, + Jump { dst: 84 }, + Const { idx: 7 }, + LoadV1, + SwapV1, + ] + ); + + assert_eq!(run(&p)?, 42); + Ok(()) +} From e4d61f8b18935038d1f6d187b0229509954fe02f Mon Sep 17 00:00:00 2001 From: "Heinz N. Gies" Date: Wed, 14 Aug 2024 21:53:31 +0200 Subject: [PATCH 10/12] Tree search pattern matcher Signed-off-by: Heinz N. Gies --- tremor-script/src/script.rs | 1 - tremor-script/src/vm.rs | 41 +++- tremor-script/src/vm/compiler/impls.rs | 63 +++++- .../src/vm/compiler/impls/imut_expr.rs | 5 +- tremor-script/src/vm/op.rs | 17 ++ tremor-script/src/vm/tests.rs | 189 +++++++++++------- 6 files changed, 230 insertions(+), 86 deletions(-) diff --git a/tremor-script/src/script.rs b/tremor-script/src/script.rs index 9cf87d2af4..95f543a99e 100644 --- a/tremor-script/src/script.rs +++ b/tremor-script/src/script.rs @@ -131,7 +131,6 @@ impl Script { // helper.consts.args = args.clone_static(); let mut script = script_raw.up_script(&mut helper)?; Optimizer::new(&helper).walk_script(&mut script)?; - let script = script; Ok(Self { script, diff --git a/tremor-script/src/vm.rs b/tremor-script/src/vm.rs index f7f2581cb3..bc33ba9cb1 100644 --- a/tremor-script/src/vm.rs +++ b/tremor-script/src/vm.rs @@ -341,6 +341,42 @@ impl<'run, 'event> Scope<'run, 'event> { Op::TestArrayIsEmpty => { self.reg.b1 = self.reg.v1.as_array().map_or(true, Vec::is_empty); } + Op::TestEq => { + let rhs = last(&stack, *pc, *cc)?; + self.reg.b1 = + exec_binary(mid, mid, crate::ast::BinOpKind::Eq, &self.reg.v1, rhs)? + .try_as_bool()?; + } + Op::TestNeq => { + let rhs = last(&stack, *pc, *cc)?; + self.reg.b1 = + exec_binary(mid, mid, crate::ast::BinOpKind::NotEq, &self.reg.v1, rhs)? + .try_as_bool()?; + } + Op::TestGt => { + let rhs = last(&stack, *pc, *cc)?; + self.reg.b1 = + exec_binary(mid, mid, crate::ast::BinOpKind::Gt, &self.reg.v1, rhs)? + .try_as_bool()?; + } + Op::TestLt => { + let rhs = last(&stack, *pc, *cc)?; + self.reg.b1 = + exec_binary(mid, mid, crate::ast::BinOpKind::Lt, &self.reg.v1, rhs)? + .try_as_bool()?; + } + Op::TestGte => { + let rhs = last(&stack, *pc, *cc)?; + self.reg.b1 = + exec_binary(mid, mid, crate::ast::BinOpKind::Gte, &self.reg.v1, rhs)? + .try_as_bool()?; + } + Op::TestLte => { + let rhs = last(&stack, *pc, *cc)?; + self.reg.b1 = + exec_binary(mid, mid, crate::ast::BinOpKind::Gte, &self.reg.v1, rhs)? + .try_as_bool()?; + } Op::TestRecordIsEmpty => { self.reg.b1 = self .reg @@ -350,10 +386,9 @@ impl<'run, 'event> Scope<'run, 'event> { } // Inspect Op::InspectLen => { - let v = last(&stack, *pc, *cc)?; - let len = if let Some(v) = v.as_array() { + let len = if let Some(v) = self.reg.v1.as_array() { v.len() - } else if let Some(v) = v.as_object() { + } else if let Some(v) = self.reg.v1.as_object() { v.len() } else { return Err("Not an array or object".into()); diff --git a/tremor-script/src/vm/compiler/impls.rs b/tremor-script/src/vm/compiler/impls.rs index b163afda25..e67912cde7 100644 --- a/tremor-script/src/vm/compiler/impls.rs +++ b/tremor-script/src/vm/compiler/impls.rs @@ -1,6 +1,8 @@ mod imut_expr; mod mut_expr; +use tremor_value::Value; + use crate::{ ast::{ ClauseGroup, Comprehension, DefaultCase, Expression, IfElse, Match, Path, PredicateClause, @@ -37,7 +39,6 @@ where let end_dst = compiler.end_dst()?; let dst = compiler.new_jump_point(); compiler.comment("Predicate Clause"); - compiler.emit(Op::CopyV1, &mid); pattern.compile_to_b(compiler)?; compiler.comment("Jump to the next pattern"); compiler.emit(Op::JumpFalse { dst }, &mid); @@ -106,7 +107,7 @@ where } ClauseGroup::SearchTree { precondition, - tree: _, + tree, rest, } => { if let Some(precondition) = precondition { @@ -114,12 +115,18 @@ where precondition.compile_to_b(compiler)?; compiler.comment("Jump to next case if precondition is false"); compiler.emit(Op::JumpFalse { dst: next }, &NodeMeta::dummy()); - // FIXME + } + compiler.comment("Encode binary search tree"); + + let tree = tree.into_iter().collect::>(); + traverse_tree(compiler, &tree)?; + + if !rest.is_empty() { + compiler.comment("Test remaining matches"); } for r in rest { r.compile(compiler)?; } - todo!("the tree has to go before therest!"); } ClauseGroup::Combined { precondition, @@ -154,6 +161,54 @@ where } } +fn traverse_tree<'script, Ex>( + compiler: &mut Compiler<'script>, + tree: &[(Value<'script>, (Vec, Ex))], +) -> Result<()> +where + Ex: Compilable<'script> + Expression, +{ + let mid = NodeMeta::dummy(); + let (lower, upper) = tree.split_at(tree.len() / 2); + + assert!(lower.len() <= upper.len()); + let Some(((val, (exprs, last)), upper_rest)) = upper.split_first() else { + // we know that in the case of a odd number the upper half is always larger or equal to the lower half + // so if it is empty we have nothing left to do in the tree + return Ok(()); + }; + let gt = compiler.new_jump_point(); + let lt = compiler.new_jump_point(); + compiler.comment("Binary search tree node"); + compiler.emit_const(val.clone(), &mid); + compiler.comment("Test if value is greater than the current node"); + compiler.emit(Op::TestGt, &mid); + compiler.emit(Op::JumpTrue { dst: gt }, &mid); + compiler.comment("Test if value is lesser than the current node"); + compiler.emit(Op::TestLt, &mid); + compiler.emit(Op::JumpTrue { dst: lt }, &mid); + compiler.comment("Value is equal to the current node"); + for e in exprs { + // FIXME: kins of annoying to clone but eh + e.clone().compile(compiler)?; + } // FIXME: kins of annoying to clone but eh + + last.clone().compile(compiler)?; + compiler.comment("We found and executed a match, jump to the end of the match statement"); + compiler.emit( + Op::Jump { + dst: compiler.end_dst()?, + }, + &mid, + ); + compiler.comment("Llesser half of the tree"); + compiler.set_jump_target(lt); + traverse_tree(compiler, lower)?; + compiler.set_jump_target(gt); + traverse_tree(compiler, upper_rest)?; + Ok(()) +} + impl<'script, Ex> Compilable<'script> for Match<'script, Ex> where Ex: Compilable<'script> + Expression, diff --git a/tremor-script/src/vm/compiler/impls/imut_expr.rs b/tremor-script/src/vm/compiler/impls/imut_expr.rs index 24167631d5..a7eeb027cd 100644 --- a/tremor-script/src/vm/compiler/impls/imut_expr.rs +++ b/tremor-script/src/vm/compiler/impls/imut_expr.rs @@ -421,8 +421,7 @@ impl<'script> Compilable<'script> for Pattern<'script> { Pattern::Expr(e) => { let mid = e.meta().clone(); e.compile(compiler)?; - compiler.emit(Op::Binary { op: BinOpKind::Eq }, &mid); - compiler.emit(Op::LoadRB, &NodeMeta::dummy()); + compiler.emit(Op::TestEq, &mid); } Pattern::Assign(_) => todo!(), @@ -444,7 +443,7 @@ impl<'script> Compilable<'script> for Pattern<'script> { compiler.emit(Op::LoadRB, &mid); let end_and_pop = compiler.new_jump_point(); - compiler.emit(Op::JumpFalse { dst: end_and_pop }, &mid); + compiler.emit(Op::JumpFalse { dst: dst_next }, &mid); compiler.comment("Save array for itteration and reverse it"); compiler.emit(Op::CopyV1, &mid); diff --git a/tremor-script/src/vm/op.rs b/tremor-script/src/vm/op.rs index 31481cc55f..f1544d6b25 100644 --- a/tremor-script/src/vm/op.rs +++ b/tremor-script/src/vm/op.rs @@ -113,6 +113,17 @@ pub(crate) enum Op { TestIsU64, TestIsI64, TestIsBytes, + #[allow(dead_code)] + TestEq, + #[allow(dead_code)] + TestNeq, + TestGt, + #[allow(dead_code)] + TestGte, + TestLt, + #[allow(dead_code)] + TestLte, + #[allow(dead_code)] TestArrayIsEmpty, TestRecordIsEmpty, @@ -190,6 +201,12 @@ impl Display for Op { Op::TestIsArray => write!(f, "test_is_array"), Op::TestArrayIsEmpty => write!(f, "test_array_is_empty"), Op::TestRecordIsEmpty => write!(f, "test_record_is_empty"), + Op::TestEq => write!(f, "test_eq"), + Op::TestNeq => write!(f, "test_neq"), + Op::TestGt => write!(f, "test_gt"), + Op::TestGte => write!(f, "test_gte"), + Op::TestLt => write!(f, "test_lt"), + Op::TestLte => write!(f, "test_lte"), Op::InspectLen => write!(f, "inspect_len"), diff --git a/tremor-script/src/vm/tests.rs b/tremor-script/src/vm/tests.rs index 127cdc357c..1bb4dd65d7 100644 --- a/tremor-script/src/vm/tests.rs +++ b/tremor-script/src/vm/tests.rs @@ -3,13 +3,13 @@ use tremor_value::literal; use super::{Op::*, *}; use crate::{ arena::Arena, - ast::{BinOpKind::*, Helper, UnaryOpKind}, + ast::{optimizer::Optimizer, BinOpKind::*, Helper, UnaryOpKind}, lexer::Lexer, parser::g::ScriptParser, registry, AggrRegistry, Compiler, }; -fn compile(src: &str) -> Result> { +fn compile(optimize: bool, src: &str) -> Result> { let mut compiler: Compiler = Compiler::new(); let reg = registry::registry(); let fake_aggr_reg = AggrRegistry::default(); @@ -21,8 +21,10 @@ fn compile(src: &str) -> Result> { let script_raw = ScriptParser::new().parse(filtered_tokens)?; let mut helper = Helper::new(®, &fake_aggr_reg); // helper.consts.args = args.clone_static(); - let script = script_raw.up_script(&mut helper)?; - // Optimizer::new(&helper).walk_script(&mut script)?; + let mut script = script_raw.up_script(&mut helper)?; + if optimize { + Optimizer::new(&helper).walk_script(&mut script)?; + } let p = compiler.compile(script)?; println!("{p}"); Ok(p) @@ -61,7 +63,7 @@ fn op_size() { } #[test] fn simple() -> Result<()> { - let p = compile("42")?; + let p = compile(false, "42")?; assert_eq!(p.opcodes, [Const { idx: 0 }]); assert_eq!(run(&p)?, 42); @@ -70,7 +72,7 @@ fn simple() -> Result<()> { #[test] fn simple_add() -> Result<()> { - let p = compile("42 + 42")?; + let p = compile(false, "42 + 42")?; assert_eq!( p.opcodes, @@ -89,7 +91,7 @@ fn simple_add() -> Result<()> { #[test] fn simple_add_sub() -> Result<()> { - let p = compile("42 + 43 - 44")?; + let p = compile(false, "42 + 43 - 44")?; assert_eq!( p.opcodes, @@ -112,7 +114,7 @@ fn simple_add_sub() -> Result<()> { #[test] fn logical_and() -> Result<()> { - let p = compile("true and false")?; + let p = compile(false, "true and false")?; assert_eq!( p.opcodes, @@ -131,7 +133,7 @@ fn logical_and() -> Result<()> { #[test] fn logical_or() -> Result<()> { - let p = compile("true or false")?; + let p = compile(false, "true or false")?; println!("{p}"); assert_eq!( @@ -151,7 +153,7 @@ fn logical_or() -> Result<()> { #[test] fn logical_not() -> Result<()> { - let p = compile("not true")?; + let p = compile(false, "not true")?; assert_eq!( p.opcodes, @@ -168,7 +170,7 @@ fn logical_not() -> Result<()> { #[test] fn simple_eq() -> Result<()> { - let p = compile("42 == 42")?; + let p = compile(false, "42 == 42")?; assert_eq!( p.opcodes, @@ -186,7 +188,7 @@ fn simple_eq() -> Result<()> { #[test] fn patch_insert() -> Result<()> { - let p = compile(r#"patch {} of insert "foo" => 42 end"#)?; + let p = compile(false, r#"patch {} of insert "foo" => 42 end"#)?; assert_eq!( p.opcodes, @@ -218,7 +220,7 @@ fn patch_insert() -> Result<()> { #[test] fn patch_default_key() -> Result<()> { - let p = compile(r#"patch {} of default "foo" => 42 end"#)?; + let p = compile(false, r#"patch {} of default "foo" => 42 end"#)?; assert_eq!( p.opcodes, @@ -249,6 +251,7 @@ fn patch_default_key() -> Result<()> { #[test] fn patch_default() -> Result<()> { let p = compile( + false, r#"patch {"snot": 42} of default => {"snot": 23, "badger": 23, "cake": "cookie"} end"#, )?; @@ -304,7 +307,7 @@ fn patch_default() -> Result<()> { #[test] fn patch_default_present() -> Result<()> { - let p = compile(r#"patch {"foo":"bar"} of default "foo" => 42 end"#)?; + let p = compile(false, r#"patch {"foo":"bar"} of default "foo" => 42 end"#)?; assert_eq!( p.opcodes, @@ -338,7 +341,7 @@ fn patch_default_present() -> Result<()> { #[test] fn patch_insert_error() -> Result<()> { - let p = compile(r#"patch {"foo":"bar"} of insert "foo" => 42 end"#)?; + let p = compile(false, r#"patch {"foo":"bar"} of insert "foo" => 42 end"#)?; assert_eq!( p.opcodes, @@ -369,7 +372,7 @@ fn patch_insert_error() -> Result<()> { #[test] fn patch_update() -> Result<()> { - let p = compile(r#"patch {"foo":"bar"} of update "foo" => 42 end"#)?; + let p = compile(false, r#"patch {"foo":"bar"} of update "foo" => 42 end"#)?; assert_eq!( p.opcodes, @@ -405,7 +408,7 @@ fn patch_update() -> Result<()> { #[test] fn patch_update_error() -> Result<()> { - let p = compile(r#"patch {} of update "foo" => 42 end"#)?; + let p = compile(false, r#"patch {} of update "foo" => 42 end"#)?; assert_eq!( p.opcodes, @@ -432,7 +435,7 @@ fn patch_update_error() -> Result<()> { #[test] fn patch_upsert_1() -> Result<()> { - let p = compile(r#"patch {"foo":"bar"} of upsert "foo" => 42 end"#)?; + let p = compile(false, r#"patch {"foo":"bar"} of upsert "foo" => 42 end"#)?; assert_eq!( p.opcodes, @@ -464,7 +467,7 @@ fn patch_upsert_1() -> Result<()> { #[test] fn patch_upsert_2() -> Result<()> { - let p = compile(r#"patch {} of upsert "foo" => 42 end"#)?; + let p = compile(false, r#"patch {} of upsert "foo" => 42 end"#)?; assert_eq!( p.opcodes, @@ -493,6 +496,7 @@ fn patch_upsert_2() -> Result<()> { #[test] fn patch_patch_patch() -> Result<()> { let p = compile( + false, r#"patch patch {"foo":"bar"} of upsert "bar" => "baz" end of insert "baz" => 42 end"#, )?; @@ -542,7 +546,10 @@ fn patch_patch_patch() -> Result<()> { #[test] fn patch_merge() -> Result<()> { - let p = compile(r#"patch {"snot":"badger"} of merge => {"badger":"snot"} end"#)?; + let p = compile( + false, + r#"patch {"snot":"badger"} of merge => {"badger":"snot"} end"#, + )?; assert_eq!( p.opcodes, @@ -578,7 +585,10 @@ fn patch_merge() -> Result<()> { #[test] fn patch_merge_key() -> Result<()> { - let p = compile(r#"(patch event of merge "object" => {"badger":"snot"} end).object"#)?; + let p = compile( + false, + r#"(patch event of merge "object" => {"badger":"snot"} end).object"#, + )?; assert_eq!( p.opcodes, @@ -614,7 +624,7 @@ fn patch_merge_key() -> Result<()> { #[test] fn merge() -> Result<()> { - let p = compile(r#"merge {"snot":"badger"} of {"badger":"snot"} end"#)?; + let p = compile(false, r#"merge {"snot":"badger"} of {"badger":"snot"} end"#)?; assert_eq!( p.opcodes, @@ -650,7 +660,7 @@ fn merge() -> Result<()> { #[test] fn array_index_fast() -> Result<()> { - let p = compile("[1,2,3][1]")?; + let p = compile(false, "[1,2,3][1]")?; assert_eq!( p.opcodes, @@ -670,7 +680,7 @@ fn array_index_fast() -> Result<()> { #[test] fn array_index() -> Result<()> { - let p = compile("[1,2,3][1+1]")?; + let p = compile(false, "[1,2,3][1+1]")?; assert_eq!( p.opcodes, @@ -693,7 +703,7 @@ fn array_index() -> Result<()> { #[test] fn record_key() -> Result<()> { - let p = compile(r#"{"snot":"badger"}.snot"#)?; + let p = compile(false, r#"{"snot":"badger"}.snot"#)?; assert_eq!( p.opcodes, @@ -714,7 +724,7 @@ fn record_key() -> Result<()> { #[test] fn record_path() -> Result<()> { - let p = compile(r#"{"snot":"badger"}["snot"]"#)?; + let p = compile(false, r#"{"snot":"badger"}["snot"]"#)?; assert_eq!( p.opcodes, @@ -737,7 +747,7 @@ fn record_path() -> Result<()> { #[test] fn array_range() -> Result<()> { - let p = compile("[1,2,3][0:2]")?; + let p = compile(false, "[1,2,3][0:2]")?; assert_eq!( p.opcodes, @@ -761,7 +771,7 @@ fn array_range() -> Result<()> { #[test] fn event_key() -> Result<()> { - let p = compile("event.int")?; + let p = compile(false, "event.int")?; assert_eq!(p.opcodes, &[LoadEvent, GetKey { key: 0 },]); @@ -771,7 +781,7 @@ fn event_key() -> Result<()> { #[test] fn event_nested() -> Result<()> { - let p = compile("event.object.a")?; + let p = compile(false, "event.object.a")?; assert_eq!( p.opcodes, @@ -784,7 +794,7 @@ fn event_nested() -> Result<()> { #[test] fn event_nested_index() -> Result<()> { - let p = compile("event.array[1]")?; + let p = compile(false, "event.array[1]")?; assert_eq!( p.opcodes, @@ -797,7 +807,7 @@ fn event_nested_index() -> Result<()> { #[test] fn test_local() -> Result<()> { - let p = compile("let a = 42; a")?; + let p = compile(false, "let a = 42; a")?; assert_eq!(p.max_locals, 0); assert_eq!( @@ -818,7 +828,7 @@ fn test_local() -> Result<()> { #[test] fn test_local_event() -> Result<()> { - let p = compile("let a = event; a.int")?; + let p = compile(false, "let a = event; a.int")?; assert_eq!(p.max_locals, 0); assert_eq!( @@ -840,7 +850,7 @@ fn test_local_event() -> Result<()> { #[test] fn test_match_if_else() -> Result<()> { - let p = compile("match event.int of case 42 => 42 case _ => 0 end")?; + let p = compile(false, "match event.int of case 42 => 42 case _ => 0 end")?; println!("{p}"); assert_eq!( @@ -850,13 +860,11 @@ fn test_match_if_else() -> Result<()> { LoadEvent, GetKey { key: 0 }, LoadV1, - CopyV1, Const { idx: 0 }, - Binary { op: Eq }, - LoadRB, - JumpFalse { dst: 11 }, + TestEq, + JumpFalse { dst: 9 }, Const { idx: 0 }, - Jump { dst: 12 }, + Jump { dst: 10 }, Const { idx: 1 }, LoadV1, SwapV1, @@ -869,7 +877,10 @@ fn test_match_if_else() -> Result<()> { #[test] fn test_match_record_type() -> Result<()> { - let p = compile("match event.object of case 42 => 42 case %{} => \"record\" case _ => 0 end")?; + let p = compile( + false, + "match event.object of case 42 => 42 case %{} => \"record\" case _ => 0 end", + )?; assert_eq!( p.opcodes, @@ -878,20 +889,17 @@ fn test_match_record_type() -> Result<()> { LoadEvent, GetKey { key: 0 }, LoadV1, - CopyV1, Const { idx: 0 }, - Binary { op: Eq }, - LoadRB, - JumpFalse { dst: 11 }, + TestEq, + JumpFalse { dst: 9 }, Const { idx: 0 }, - Jump { dst: 19 }, - CopyV1, + Jump { dst: 16 }, TestIsRecord, - JumpFalse { dst: 14 }, - JumpFalse { dst: 18 }, + JumpFalse { dst: 11 }, + JumpFalse { dst: 15 }, Const { idx: 1 }, String { size: 1 }, - Jump { dst: 19 }, + Jump { dst: 16 }, Const { idx: 2 }, LoadV1, SwapV1, @@ -904,7 +912,7 @@ fn test_match_record_type() -> Result<()> { #[test] fn test_event_assign_nested() -> Result<()> { - let p = compile("let event.string = \"snot\"; event.string")?; + let p = compile(false, "let event.string = \"snot\"; event.string")?; assert_eq!(p.max_locals, 0); assert_eq!( @@ -925,7 +933,7 @@ fn test_event_assign_nested() -> Result<()> { #[test] fn test_event_assign_nested_new() -> Result<()> { - let p = compile("let event.badger = \"snot\"; event.badger")?; + let p = compile(false, "let event.badger = \"snot\"; event.badger")?; assert_eq!(p.max_locals, 0); assert_eq!( @@ -946,7 +954,7 @@ fn test_event_assign_nested_new() -> Result<()> { #[test] fn test_event_array_assign_nested() -> Result<()> { - let p = compile("let event.array[1] = 42; event.array")?; + let p = compile(false, "let event.array[1] = 42; event.array")?; assert_eq!(p.max_locals, 0); assert_eq!( @@ -967,7 +975,7 @@ fn test_event_array_assign_nested() -> Result<()> { #[test] fn test_local_assign_nested() -> Result<()> { - let p = compile("let a = {}; let a.b = 1; a.b")?; + let p = compile(false, "let a = {}; let a.b = 1; a.b")?; assert_eq!(p.max_locals, 0); assert_eq!( @@ -996,7 +1004,7 @@ fn test_local_assign_nested() -> Result<()> { #[test] fn test_local_array_assign_nested() -> Result<()> { - let p = compile("let a = [1,2]; let a[0]=-1; a")?; + let p = compile(false, "let a = [1,2]; let a[0]=-1; a")?; assert_eq!(p.max_locals, 0); assert_eq!( @@ -1029,7 +1037,16 @@ fn test_local_array_assign_nested() -> Result<()> { #[test] fn test_match_touple() -> Result<()> { - let p = compile("match [42, 23] of case %(42, 24) => 24 case %(42, 23, 7) => 7 case %(42, 23) => 42 case _ => 0 end")?; + let p = compile( + false, + r" + match [42, 23] of + case %(42, 24) => 24 + case %(42, 23, 7) => 7 + case %(42, 23) => 42 + case _ => 0 + end", + )?; assert_eq!( p.opcodes, @@ -1040,9 +1057,8 @@ fn test_match_touple() -> Result<()> { Const { idx: 2 }, Array { size: 2 }, LoadV1, - CopyV1, TestIsArray, - JumpFalse { dst: 27 }, + JumpFalse { dst: 26 }, InspectLen, Const { idx: 3 }, Binary { op: Eq }, @@ -1054,69 +1070,67 @@ fn test_match_touple() -> Result<()> { Const { idx: 0 }, Binary { op: Eq }, LoadRB, - JumpFalse { dst: 26 }, + JumpFalse { dst: 25 }, ArrayPop, Const { idx: 4 }, Binary { op: Eq }, LoadRB, - JumpFalse { dst: 26 }, + JumpFalse { dst: 25 }, Pop, - JumpFalse { dst: 30 }, + JumpFalse { dst: 29 }, Const { idx: 4 }, - Jump { dst: 84 }, - CopyV1, + Jump { dst: 81 }, TestIsArray, - JumpFalse { dst: 56 }, + JumpFalse { dst: 54 }, InspectLen, Const { idx: 5 }, Binary { op: Eq }, LoadRB, - JumpFalse { dst: 55 }, + JumpFalse { dst: 54 }, CopyV1, ArrayReverse, ArrayPop, Const { idx: 0 }, Binary { op: Eq }, LoadRB, - JumpFalse { dst: 55 }, + JumpFalse { dst: 53 }, ArrayPop, Const { idx: 1 }, Binary { op: Eq }, LoadRB, - JumpFalse { dst: 55 }, + JumpFalse { dst: 53 }, ArrayPop, Const { idx: 6 }, Binary { op: Eq }, LoadRB, - JumpFalse { dst: 55 }, + JumpFalse { dst: 53 }, Pop, - JumpFalse { dst: 59 }, + JumpFalse { dst: 57 }, Const { idx: 6 }, - Jump { dst: 84 }, - CopyV1, + Jump { dst: 81 }, TestIsArray, - JumpFalse { dst: 80 }, + JumpFalse { dst: 77 }, InspectLen, Const { idx: 3 }, Binary { op: Eq }, LoadRB, - JumpFalse { dst: 79 }, + JumpFalse { dst: 77 }, CopyV1, ArrayReverse, ArrayPop, Const { idx: 0 }, Binary { op: Eq }, LoadRB, - JumpFalse { dst: 79 }, + JumpFalse { dst: 76 }, ArrayPop, Const { idx: 1 }, Binary { op: Eq }, LoadRB, - JumpFalse { dst: 79 }, + JumpFalse { dst: 76 }, Pop, - JumpFalse { dst: 83 }, + JumpFalse { dst: 80 }, Const { idx: 0 }, - Jump { dst: 84 }, + Jump { dst: 81 }, Const { idx: 7 }, LoadV1, SwapV1, @@ -1126,3 +1140,28 @@ fn test_match_touple() -> Result<()> { assert_eq!(run(&p)?, 42); Ok(()) } +#[test] +fn test_match_search_tree() -> Result<()> { + let p = compile( + true, + r" + match event.int of + case 1 => 1 + case 2 => 2 + case 3 => 3 + case 4 => 4 + case 5 => 5 + case 6 => 6 + case 7 => 7 + case 8 => 8 + case 9 => 9 + case 42 => 42 + case _ => 23 + end", + )?; + + // assert_eq!(p.opcodes, &[]); + + assert_eq!(run(&p)?, 42); + Ok(()) +} From 5e1dfff0de3365e38b80e672991ee0e928f76916 Mon Sep 17 00:00:00 2001 From: "Heinz N. Gies" Date: Thu, 15 Aug 2024 18:21:08 +0200 Subject: [PATCH 11/12] Allow assignments in match patterns Signed-off-by: Heinz N. Gies --- tremor-script/src/vm.rs | 4 +- .../src/vm/compiler/impls/imut_expr.rs | 42 ++++- tremor-script/src/vm/tests.rs | 178 ++++++++++++++---- 3 files changed, 182 insertions(+), 42 deletions(-) diff --git a/tremor-script/src/vm.rs b/tremor-script/src/vm.rs index bc33ba9cb1..47a73c9b8c 100644 --- a/tremor-script/src/vm.rs +++ b/tremor-script/src/vm.rs @@ -342,9 +342,9 @@ impl<'run, 'event> Scope<'run, 'event> { self.reg.b1 = self.reg.v1.as_array().map_or(true, Vec::is_empty); } Op::TestEq => { - let rhs = last(&stack, *pc, *cc)?; + let rhs = pop(&mut stack, *pc, *cc)?; self.reg.b1 = - exec_binary(mid, mid, crate::ast::BinOpKind::Eq, &self.reg.v1, rhs)? + exec_binary(mid, mid, crate::ast::BinOpKind::Eq, &self.reg.v1, &rhs)? .try_as_bool()?; } Op::TestNeq => { diff --git a/tremor-script/src/vm/compiler/impls/imut_expr.rs b/tremor-script/src/vm/compiler/impls/imut_expr.rs index a7eeb027cd..7b51a136e7 100644 --- a/tremor-script/src/vm/compiler/impls/imut_expr.rs +++ b/tremor-script/src/vm/compiler/impls/imut_expr.rs @@ -3,9 +3,10 @@ use tremor_value::Value; use crate::{ ast::{ raw::{BytesDataType, Endian}, - ArrayPredicatePattern, BaseExpr, BinOpKind, BooleanBinExpr, BooleanBinOpKind, BytesPart, - ClausePreCondition, Field, ImutExpr, Invoke, List, Merge, Patch, PatchOperation, Pattern, - PredicatePattern, Record, Segment, StrLitElement, StringLit, + ArrayPredicatePattern, AssignPattern, BaseExpr, BinOpKind, BooleanBinExpr, + BooleanBinOpKind, BytesPart, ClausePreCondition, Field, ImutExpr, Invoke, List, Merge, + Patch, PatchOperation, Pattern, PredicatePattern, Record, Segment, StrLitElement, + StringLit, }, errors::Result, vm::{ @@ -378,8 +379,7 @@ impl<'script> Compilable<'script> for ArrayPredicatePattern<'script> { ArrayPredicatePattern::Expr(e) => { let mid = e.meta().clone(); e.compile(compiler)?; - compiler.emit(Op::Binary { op: BinOpKind::Eq }, &mid); - compiler.emit(Op::LoadRB, &mid); + compiler.emit(Op::TestEq, &mid); } ArrayPredicatePattern::Tilde(_) => todo!(), ArrayPredicatePattern::Record(_) => todo!(), @@ -424,7 +424,31 @@ impl<'script> Compilable<'script> for Pattern<'script> { compiler.emit(Op::TestEq, &mid); } - Pattern::Assign(_) => todo!(), + #[allow(clippy::cast_possible_truncation)] + Pattern::Assign(p) => { + let AssignPattern { + id: _, + idx, + pattern, + } = p; + // FIXME: want a MID + let mid = NodeMeta::dummy(); + compiler.comment("Assign pattern"); + pattern.compile(compiler)?; + let dst = compiler.new_jump_point(); + compiler.comment("Jump on no match"); + compiler.emit(Op::JumpFalse { dst }, &mid); + compiler.comment("Store the value in the local"); + compiler.emit(Op::CopyV1, &mid); + compiler.emit( + Op::StoreLocal { + idx: idx as u32, + elements: 0, + }, + &mid, + ); + compiler.set_jump_target(dst); + } Pattern::Tuple(t) => { compiler.comment("Tuple pattern"); let mid = *t.mid; @@ -449,9 +473,13 @@ impl<'script> Compilable<'script> for Pattern<'script> { compiler.emit(Op::CopyV1, &mid); compiler.emit(Op::ArrayReverse, &mid); for (i, e) in t.exprs.into_iter().enumerate() { - compiler.comment(&format!("Test tuple element {}", i)); + compiler.comment(&format!("Test tuple element {i}")); compiler.emit(Op::ArrayPop, &mid); + compiler.comment("Load value in register to test"); + compiler.emit(Op::SwapV1, &mid); e.compile(compiler)?; + compiler.comment("restore original test value"); + compiler.emit(Op::LoadV1, &mid); compiler.comment("Jump on no match"); compiler.emit(Op::JumpFalse { dst: end_and_pop }, &mid); } diff --git a/tremor-script/src/vm/tests.rs b/tremor-script/src/vm/tests.rs index 1bb4dd65d7..a575d78fc9 100644 --- a/tremor-script/src/vm/tests.rs +++ b/tremor-script/src/vm/tests.rs @@ -1036,6 +1036,7 @@ fn test_local_array_assign_nested() -> Result<()> { } #[test] +#[allow(clippy::too_many_lines)] fn test_match_touple() -> Result<()> { let p = compile( false, @@ -1058,79 +1059,86 @@ fn test_match_touple() -> Result<()> { Array { size: 2 }, LoadV1, TestIsArray, - JumpFalse { dst: 26 }, + JumpFalse { dst: 28 }, InspectLen, Const { idx: 3 }, Binary { op: Eq }, LoadRB, - JumpFalse { dst: 26 }, + JumpFalse { dst: 28 }, CopyV1, ArrayReverse, ArrayPop, + SwapV1, Const { idx: 0 }, - Binary { op: Eq }, - LoadRB, - JumpFalse { dst: 25 }, + TestEq, + LoadV1, + JumpFalse { dst: 27 }, ArrayPop, + SwapV1, Const { idx: 4 }, - Binary { op: Eq }, - LoadRB, - JumpFalse { dst: 25 }, + TestEq, + LoadV1, + JumpFalse { dst: 27 }, Pop, - JumpFalse { dst: 29 }, + JumpFalse { dst: 31 }, Const { idx: 4 }, - Jump { dst: 81 }, + Jump { dst: 88 }, TestIsArray, - JumpFalse { dst: 54 }, + JumpFalse { dst: 59 }, InspectLen, Const { idx: 5 }, Binary { op: Eq }, LoadRB, - JumpFalse { dst: 54 }, + JumpFalse { dst: 59 }, CopyV1, ArrayReverse, ArrayPop, + SwapV1, Const { idx: 0 }, - Binary { op: Eq }, - LoadRB, - JumpFalse { dst: 53 }, + TestEq, + LoadV1, + JumpFalse { dst: 58 }, ArrayPop, + SwapV1, Const { idx: 1 }, - Binary { op: Eq }, - LoadRB, - JumpFalse { dst: 53 }, + TestEq, + LoadV1, + JumpFalse { dst: 58 }, ArrayPop, + SwapV1, Const { idx: 6 }, - Binary { op: Eq }, - LoadRB, - JumpFalse { dst: 53 }, + TestEq, + LoadV1, + JumpFalse { dst: 58 }, Pop, - JumpFalse { dst: 57 }, + JumpFalse { dst: 62 }, Const { idx: 6 }, - Jump { dst: 81 }, + Jump { dst: 88 }, TestIsArray, - JumpFalse { dst: 77 }, + JumpFalse { dst: 84 }, InspectLen, Const { idx: 3 }, Binary { op: Eq }, LoadRB, - JumpFalse { dst: 77 }, + JumpFalse { dst: 84 }, CopyV1, ArrayReverse, ArrayPop, + SwapV1, Const { idx: 0 }, - Binary { op: Eq }, - LoadRB, - JumpFalse { dst: 76 }, + TestEq, + LoadV1, + JumpFalse { dst: 83 }, ArrayPop, + SwapV1, Const { idx: 1 }, - Binary { op: Eq }, - LoadRB, - JumpFalse { dst: 76 }, + TestEq, + LoadV1, + JumpFalse { dst: 83 }, Pop, - JumpFalse { dst: 80 }, + JumpFalse { dst: 87 }, Const { idx: 0 }, - Jump { dst: 81 }, + Jump { dst: 88 }, Const { idx: 7 }, LoadV1, SwapV1, @@ -1165,3 +1173,107 @@ fn test_match_search_tree() -> Result<()> { assert_eq!(run(&p)?, 42); Ok(()) } + +#[test] +fn test_match_assign() -> Result<()> { + let p = compile( + false, + r" + match 42 of + case 24 => 24 + case 7 => 7 + case a = 42 => a + case _ => 0 + end", + )?; + + assert_eq!( + p.opcodes, + &[ + StoreV1, + Const { idx: 0 }, + LoadV1, + Const { idx: 1 }, + TestEq, + JumpFalse { dst: 8 }, + Const { idx: 1 }, + Jump { dst: 22 }, + Const { idx: 0 }, + TestEq, + JumpFalse { dst: 13 }, + CopyV1, + StoreLocal { + elements: 0, + idx: 0, + }, + JumpFalse { dst: 16 }, + LoadLocal { idx: 0 }, + Jump { dst: 22 }, + Const { idx: 2 }, + TestEq, + JumpFalse { dst: 21 }, + Const { idx: 2 }, + Jump { dst: 22 }, + Const { idx: 3 }, + LoadV1, + SwapV1, + ] + ); + + assert_eq!(run(&p)?, 42); + Ok(()) +} + +#[test] +fn test_match_assign_nested() -> Result<()> { + let p = compile( + false, + r" + match [42] of + case a = %(42) => a + case _ => 0 + end", + )?; + + assert_eq!( + p.opcodes, + &[ + StoreV1, + Const { idx: 0 }, + Const { idx: 1 }, + Array { size: 1 }, + LoadV1, + TestIsArray, + JumpFalse { dst: 21 }, + InspectLen, + Const { idx: 2 }, + Binary { op: Eq }, + LoadRB, + JumpFalse { dst: 21 }, + CopyV1, + ArrayReverse, + ArrayPop, + SwapV1, + Const { idx: 0 }, + TestEq, + LoadV1, + JumpFalse { dst: 20 }, + Pop, + JumpFalse { dst: 24 }, + CopyV1, + StoreLocal { + elements: 0, + idx: 0, + }, + JumpFalse { dst: 27 }, + LoadLocal { idx: 0 }, + Jump { dst: 28 }, + Const { idx: 3 }, + LoadV1, + SwapV1, + ] + ); + + assert_eq!(run(&p)?, literal!([42])); + Ok(()) +} From 14219797f9fc615ffc241eb41f28b239130b0ac7 Mon Sep 17 00:00:00 2001 From: "Heinz N. Gies" Date: Thu, 15 Aug 2024 22:42:15 +0200 Subject: [PATCH 12/12] Add support for record patterns Signed-off-by: Heinz N. Gies --- tremor-script/src/vm.rs | 22 +- tremor-script/src/vm/compiler.rs | 13 + tremor-script/src/vm/compiler/impls.rs | 13 + .../src/vm/compiler/impls/imut_expr.rs | 332 +++++--- .../src/vm/compiler/impls/mut_expr.rs | 13 + tremor-script/src/vm/op.rs | 35 +- tremor-script/src/vm/tests.rs | 767 +----------------- tremor-script/src/vm/tests/match_stmt.rs | 521 ++++++++++++ tremor-script/src/vm/tests/patch.rs | 454 +++++++++++ 9 files changed, 1321 insertions(+), 849 deletions(-) create mode 100644 tremor-script/src/vm/tests/match_stmt.rs create mode 100644 tremor-script/src/vm/tests/patch.rs diff --git a/tremor-script/src/vm.rs b/tremor-script/src/vm.rs index 47a73c9b8c..06d1ed6436 100644 --- a/tremor-script/src/vm.rs +++ b/tremor-script/src/vm.rs @@ -22,6 +22,7 @@ use op::Op; #[allow(dead_code)] pub struct Vm {} +#[derive(Debug)] struct Registers<'run, 'event> { /// Value register 1 v1: Cow<'run, Value<'event>>, @@ -92,7 +93,13 @@ impl<'run, 'event> Scope<'run, 'event> { *cc += 1; let mid = &self.program.meta[*pc]; // ALLOW: we test that pc is always in bounds in the while loop above - match unsafe { *self.program.opcodes.get_unchecked(*pc) } { + let op = unsafe { *self.program.opcodes.get_unchecked(*pc) }; + // if let Some(comment) = self.program.comments.get(pc) { + // println!("# {comment}"); + // } + // dbg!(stack.last(), &self.reg); + // println!("{pc:3}: {op}"); + match op { Op::Nop => continue, // Loads Op::LoadV1 => self.reg.v1 = pop(&mut stack, *pc, *cc)?, @@ -105,6 +112,9 @@ impl<'run, 'event> Scope<'run, 'event> { self.reg.b1 = pop(&mut stack, *pc, *cc)?.try_as_bool()?; } Op::StoreRB => stack.push(Cow::Owned(Value::from(self.reg.b1))), + Op::NotRB => self.reg.b1 = !self.reg.b1, + Op::TrueRB => self.reg.b1 = true, + Op::FalseRB => self.reg.b1 = false, Op::LoadEvent => stack.push(Cow::Owned(event.clone())), Op::StoreEvent { elements } => unsafe { let mut tmp = event as *mut Value; @@ -384,6 +394,10 @@ impl<'run, 'event> Scope<'run, 'event> { .as_object() .map_or(true, halfbrown::SizedHashMap::is_empty); } + Op::TestRecordContainsKey { key } => { + let key = &self.program.keys[key as usize]; + self.reg.b1 = key.lookup(&self.reg.v1).is_some(); + } // Inspect Op::InspectLen => { let len = if let Some(v) = self.reg.v1.as_array() { @@ -452,6 +466,12 @@ impl<'run, 'event> Scope<'run, 'event> { let res = key.lookup(&v).ok_or("Missing Key FIXME")?.clone(); stack.push(Cow::Owned(res)); } + Op::GetKeyRegV1 { key } => { + let key = &self.program.keys[key as usize]; + // FIXME: can we avoid this clone here + let res = key.lookup(&self.reg.v1).ok_or("Missing Key FIXME")?.clone(); + stack.push(Cow::Owned(res)); + } Op::Get => { let key = pop(&mut stack, *pc, *cc)?; let v = pop(&mut stack, *pc, *cc)?; diff --git a/tremor-script/src/vm/compiler.rs b/tremor-script/src/vm/compiler.rs index 614675cf38..e159b44a49 100644 --- a/tremor-script/src/vm/compiler.rs +++ b/tremor-script/src/vm/compiler.rs @@ -1,3 +1,16 @@ +// Copyright 2020-2024, The Tremor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. mod impls; use crate::{ast::Script, errors::Result, NodeMeta}; diff --git a/tremor-script/src/vm/compiler/impls.rs b/tremor-script/src/vm/compiler/impls.rs index e67912cde7..f2af333bc7 100644 --- a/tremor-script/src/vm/compiler/impls.rs +++ b/tremor-script/src/vm/compiler/impls.rs @@ -1,3 +1,16 @@ +// Copyright 2020-2024, The Tremor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. mod imut_expr; mod mut_expr; diff --git a/tremor-script/src/vm/compiler/impls/imut_expr.rs b/tremor-script/src/vm/compiler/impls/imut_expr.rs index 7b51a136e7..60bd0ba4b0 100644 --- a/tremor-script/src/vm/compiler/impls/imut_expr.rs +++ b/tremor-script/src/vm/compiler/impls/imut_expr.rs @@ -1,12 +1,25 @@ +// Copyright 2020-2024, The Tremor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. use tremor_value::Value; use crate::{ ast::{ raw::{BytesDataType, Endian}, - ArrayPredicatePattern, AssignPattern, BaseExpr, BinOpKind, BooleanBinExpr, + ArrayPattern, ArrayPredicatePattern, AssignPattern, BaseExpr, BinOpKind, BooleanBinExpr, BooleanBinOpKind, BytesPart, ClausePreCondition, Field, ImutExpr, Invoke, List, Merge, - Patch, PatchOperation, Pattern, PredicatePattern, Record, Segment, StrLitElement, - StringLit, + Patch, PatchOperation, Pattern, PredicatePattern, Record, RecordPattern, Segment, + StrLitElement, StringLit, TestExpr, TuplePattern, }, errors::Result, vm::{ @@ -369,8 +382,91 @@ impl<'script> Compilable<'script> for BytesPart<'script> { } impl<'script> Compilable<'script> for PredicatePattern<'script> { - fn compile(self, _compiler: &mut Compiler<'script>) -> Result<()> { - todo!() + #[allow(unused_variables)] + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + let key = compiler.add_key(self.key().clone()); + let mid = NodeMeta::dummy(); // FIXME + compiler.comment("Test if the record contains the key"); + compiler.emit(Op::TestRecordContainsKey { key }, &mid); + // handle the absent patter ndifferently + if let PredicatePattern::FieldAbsent { key: _, lhs: _ } = self { + compiler.emit(Op::NotRB, &mid); + } else if let PredicatePattern::FieldPresent { key: _, lhs: _ } = self { + } else { + let dst = compiler.new_jump_point(); + compiler.emit(Op::JumpFalse { dst }, &mid); + compiler.comment("Fetch key to test"); + compiler.emit(Op::GetKeyRegV1 { key }, &mid); + compiler.comment("Swap value in the register"); + compiler.emit(Op::SwapV1, &mid); + match self { + PredicatePattern::TildeEq { + assign, + lhs, + key, + test, + } => todo!(), + PredicatePattern::Bin { + lhs: _, + key: _, + rhs, + kind, + } => { + compiler.comment("Run binary pattern"); + rhs.compile(compiler)?; + match kind { + BinOpKind::Eq => compiler.emit(Op::TestEq, &mid), + BinOpKind::NotEq => compiler.emit(Op::TestNeq, &mid), + BinOpKind::Gte => compiler.emit(Op::TestGte, &mid), + BinOpKind::Gt => compiler.emit(Op::TestGt, &mid), + BinOpKind::Lte => compiler.emit(Op::TestLte, &mid), + BinOpKind::Lt => compiler.emit(Op::TestLt, &mid), + BinOpKind::BitXor + | BinOpKind::BitAnd + | BinOpKind::RBitShiftSigned + | BinOpKind::RBitShiftUnsigned + | BinOpKind::LBitShift + | BinOpKind::Add + | BinOpKind::Sub + | BinOpKind::Mul + | BinOpKind::Div + | BinOpKind::Mod => { + return Err(format!("Invalid operator {kind:?} in predicate").into()); + } + } + compiler.comment("Remove the value from the stack"); + compiler.emit(Op::Pop, &mid); + } + PredicatePattern::RecordPatternEq { + lhs, + key: _, + pattern, + } => { + compiler.comment("Run record pattern"); + pattern.compile(compiler)?; + } + PredicatePattern::ArrayPatternEq { lhs, key, pattern } => { + compiler.comment("Run array pattern"); + pattern.compile(compiler)?; + } + PredicatePattern::TuplePatternEq { + lhs, + key: _, + pattern, + } => { + compiler.comment("Run tuple pattern"); + pattern.compile(compiler)?; + } + PredicatePattern::FieldPresent { .. } | PredicatePattern::FieldAbsent { .. } => { + unreachable!("FieldAbsent and FieldPresent should be handled earlier"); + } + } + compiler.comment("Restore the register"); + compiler.emit(Op::LoadV1, &mid); + compiler.set_jump_target(dst); + } + + Ok(()) } } impl<'script> Compilable<'script> for ArrayPredicatePattern<'script> { @@ -382,8 +478,8 @@ impl<'script> Compilable<'script> for ArrayPredicatePattern<'script> { compiler.emit(Op::TestEq, &mid); } ArrayPredicatePattern::Tilde(_) => todo!(), - ArrayPredicatePattern::Record(_) => todo!(), - ArrayPredicatePattern::Ignore => todo!(), + ArrayPredicatePattern::Record(p) => p.compile(compiler)?, + ArrayPredicatePattern::Ignore => compiler.emit(Op::TrueRB, &NodeMeta::dummy()), } Ok(()) } @@ -396,114 +492,36 @@ impl<'script> Compilable<'script> for Pattern<'script> { /// will return the match state in registers.B fn compile_to_b(self, compiler: &mut Compiler<'script>) -> Result<()> { match self { - Pattern::Record(r) => { - compiler.emit(Op::TestIsRecord, &r.mid); - let dst = compiler.new_jump_point(); - compiler.emit(Op::JumpFalse { dst }, &r.mid); - - for f in r.fields { - f.compile(compiler)?; - } - - compiler.set_jump_target(dst); - } - Pattern::Array(a) => { - let mid = *a.mid; - compiler.emit(Op::TestIsArray, &mid); - let dst = compiler.new_jump_point(); - compiler.emit(Op::JumpFalse { dst }, &mid); - for e in a.exprs { - e.compile(compiler)?; - todo!("we need to look at all the array elements :sob:") - } - compiler.set_jump_target(dst); - } + Pattern::Record(p) => p.compile(compiler)?, + Pattern::Array(p) => p.compile(compiler)?, Pattern::Expr(e) => { let mid = e.meta().clone(); e.compile(compiler)?; compiler.emit(Op::TestEq, &mid); } - - #[allow(clippy::cast_possible_truncation)] - Pattern::Assign(p) => { - let AssignPattern { - id: _, - idx, - pattern, - } = p; - // FIXME: want a MID - let mid = NodeMeta::dummy(); - compiler.comment("Assign pattern"); - pattern.compile(compiler)?; - let dst = compiler.new_jump_point(); - compiler.comment("Jump on no match"); - compiler.emit(Op::JumpFalse { dst }, &mid); - compiler.comment("Store the value in the local"); - compiler.emit(Op::CopyV1, &mid); - compiler.emit( - Op::StoreLocal { - idx: idx as u32, - elements: 0, - }, - &mid, - ); - compiler.set_jump_target(dst); - } - Pattern::Tuple(t) => { - compiler.comment("Tuple pattern"); - let mid = *t.mid; - compiler.emit(Op::TestIsArray, &mid); - let dst_next = compiler.new_jump_point(); - compiler.emit(Op::JumpFalse { dst: dst_next }, &mid); - compiler.comment("Check if the array is long enough"); - compiler.emit(Op::InspectLen, &mid); - compiler.emit_const(t.exprs.len(), &mid); - - if t.open { - compiler.emit(Op::Binary { op: BinOpKind::Gte }, &mid); - } else { - compiler.emit(Op::Binary { op: BinOpKind::Eq }, &mid); - } - compiler.emit(Op::LoadRB, &mid); - let end_and_pop = compiler.new_jump_point(); - - compiler.emit(Op::JumpFalse { dst: dst_next }, &mid); - - compiler.comment("Save array for itteration and reverse it"); - compiler.emit(Op::CopyV1, &mid); - compiler.emit(Op::ArrayReverse, &mid); - for (i, e) in t.exprs.into_iter().enumerate() { - compiler.comment(&format!("Test tuple element {i}")); - compiler.emit(Op::ArrayPop, &mid); - compiler.comment("Load value in register to test"); - compiler.emit(Op::SwapV1, &mid); - e.compile(compiler)?; - compiler.comment("restore original test value"); - compiler.emit(Op::LoadV1, &mid); - compiler.comment("Jump on no match"); - compiler.emit(Op::JumpFalse { dst: end_and_pop }, &mid); - } - // remove the array from the stack - compiler.comment("Remove the array from the stack"); - compiler.set_jump_target(end_and_pop); - compiler.emit(Op::Pop, &mid); - compiler.set_jump_target(dst_next); - } - Pattern::Extract(_) => todo!(), - Pattern::DoNotCare => { - compiler.emit(Op::True, &NodeMeta::dummy()); - compiler.emit(Op::LoadRB, &NodeMeta::dummy()); - } + Pattern::Assign(p) => p.compile(compiler)?, + Pattern::Tuple(p) => p.compile(compiler)?, + Pattern::Extract(p) => p.compile(compiler)?, + Pattern::DoNotCare => compiler.emit(Op::TrueRB, &NodeMeta::dummy()), } Ok(()) } } impl<'script> Compilable<'script> for ClausePreCondition<'script> { - fn compile(self, _compiler: &mut Compiler<'script>) -> Result<()> { - todo!() + fn compile_to_b(self, compiler: &mut Compiler<'script>) -> Result<()> { + let ClausePreCondition { mut path } = self; + assert!(path.segments().len() == 1); + if let Some(Segment::Id { key, mid }) = path.segments_mut().pop() { + let key = compiler.add_key(key); + compiler.emit(Op::TestRecordContainsKey { key }, &mid); + Ok(()) + } else { + Err("Invalid path in pre condition".into()) + } } - fn compile_to_b(self, _compiler: &mut Compiler<'script>) -> Result<()> { + + fn compile(self, _compiler: &mut Compiler<'script>) -> Result<()> { todo!() } } @@ -578,3 +596,111 @@ impl<'script> Compilable<'script> for Invoke<'script> { todo!() } } + +impl<'script> Compilable<'script> for RecordPattern<'script> { + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + let RecordPattern { mid, fields } = self; + compiler.emit(Op::TestIsRecord, &mid); + let dst = compiler.new_jump_point(); + compiler.emit(Op::JumpFalse { dst }, &mid); + for f in fields { + f.compile(compiler)?; + } + compiler.set_jump_target(dst); + Ok(()) + } +} + +impl<'script> Compilable<'script> for ArrayPattern<'script> { + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + let ArrayPattern { mid, exprs } = self; + compiler.emit(Op::TestIsArray, &mid); + let dst = compiler.new_jump_point(); + compiler.emit(Op::JumpFalse { dst }, &mid); + for e in exprs { + e.compile(compiler)?; + } + compiler.set_jump_target(dst); + todo!("we need to look at all the array elements :sob:"); + // Ok(()) + } +} + +impl<'script> Compilable<'script> for AssignPattern<'script> { + #[allow(clippy::cast_possible_truncation)] + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + let AssignPattern { + id: _, + idx, + pattern, + } = self; + // FIXME: want a MID + let mid = NodeMeta::dummy(); + compiler.comment("Assign pattern"); + pattern.compile(compiler)?; + let dst = compiler.new_jump_point(); + compiler.comment("Jump on no match"); + compiler.emit(Op::JumpFalse { dst }, &mid); + compiler.comment("Store the value in the local"); + compiler.emit(Op::CopyV1, &mid); + compiler.emit( + Op::StoreLocal { + idx: idx as u32, + elements: 0, + }, + &mid, + ); + compiler.set_jump_target(dst); + Ok(()) + } +} + +impl<'script> Compilable<'script> for TuplePattern<'script> { + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + let TuplePattern { mid, exprs, open } = self; + compiler.comment("Tuple pattern"); + compiler.emit(Op::TestIsArray, &mid); + let dst_next = compiler.new_jump_point(); + compiler.emit(Op::JumpFalse { dst: dst_next }, &mid); + compiler.comment("Check if the array is long enough"); + compiler.emit(Op::InspectLen, &mid); + compiler.emit_const(exprs.len(), &mid); + + if open { + compiler.emit(Op::Binary { op: BinOpKind::Gte }, &mid); + } else { + compiler.emit(Op::Binary { op: BinOpKind::Eq }, &mid); + } + compiler.emit(Op::LoadRB, &mid); + let end_and_pop = compiler.new_jump_point(); + + compiler.emit(Op::JumpFalse { dst: dst_next }, &mid); + + compiler.comment("Save array for itteration and reverse it"); + compiler.emit(Op::CopyV1, &mid); + compiler.emit(Op::ArrayReverse, &mid); + for (i, e) in exprs.into_iter().enumerate() { + compiler.comment(&format!("Test tuple element {i}")); + compiler.emit(Op::ArrayPop, &mid); + compiler.comment("Load value in register to test"); + compiler.emit(Op::SwapV1, &mid); + e.compile(compiler)?; + compiler.comment("restore original test value"); + compiler.emit(Op::LoadV1, &mid); + compiler.comment("Jump on no match"); + compiler.emit(Op::JumpFalse { dst: end_and_pop }, &mid); + } + // remove the array from the stack + compiler.comment("Remove the array from the stack"); + compiler.set_jump_target(end_and_pop); + compiler.emit(Op::Pop, &mid); + compiler.set_jump_target(dst_next); + Ok(()) + } +} + +impl<'script> Compilable<'script> for TestExpr { + fn compile(self, _compiler: &mut Compiler<'script>) -> Result<()> { + todo!() + } +} diff --git a/tremor-script/src/vm/compiler/impls/mut_expr.rs b/tremor-script/src/vm/compiler/impls/mut_expr.rs index 60bab10675..21953c9fdb 100644 --- a/tremor-script/src/vm/compiler/impls/mut_expr.rs +++ b/tremor-script/src/vm/compiler/impls/mut_expr.rs @@ -1,3 +1,16 @@ +// Copyright 2020-2024, The Tremor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. use crate::{ ast::{EmitExpr, Expr, Path, Segment}, errors::{err_generic, Result}, diff --git a/tremor-script/src/vm/op.rs b/tremor-script/src/vm/op.rs index f1544d6b25..41acc447a1 100644 --- a/tremor-script/src/vm/op.rs +++ b/tremor-script/src/vm/op.rs @@ -1,3 +1,16 @@ +// Copyright 2020-2024, The Tremor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. use std::fmt::Display; use crate::ast::{BinOpKind, UnaryOpKind}; @@ -27,6 +40,13 @@ pub(crate) enum Op { /// Store boolean register to the top of the stack #[allow(dead_code)] StoreRB, + /// Negates the boolean register + NotRB, + /// Sets the boolean register to true + TrueRB, + /// Sets the boolean register to false + #[allow(dead_code)] + FalseRB, /// Puts the event on the stack LoadEvent, /// Takes the top of the stack and stores it in the event @@ -97,6 +117,9 @@ pub(crate) enum Op { GetKey { key: u32, }, + GetKeyRegV1 { + key: u32, + }, Get, Index, IndexFast { @@ -127,6 +150,9 @@ pub(crate) enum Op { #[allow(dead_code)] TestArrayIsEmpty, TestRecordIsEmpty, + TestRecordContainsKey { + key: u32, + }, // Inspect - does not pop the stack result is stored on the stack //// returns the lenght of an array, object or 1 for scalar values @@ -162,6 +188,9 @@ impl Display for Op { Op::LoadRB => write!(f, "{:30} B1", "load_reg"), Op::StoreRB => write!(f, "{:30} B1", "store_reg"), + Op::NotRB => write!(f, "{:30} B1", "not_reg"), + Op::TrueRB => write!(f, "{:30} B1", "true_reg"), + Op::FalseRB => write!(f, "{:30} B1", "false_reg"), Op::LoadEvent => write!(f, "laod_event"), Op::StoreEvent { elements } => write!(f, "{:30} {elements:<5}", "store_event"), @@ -186,7 +215,8 @@ impl Display for Op { Op::Xor => write!(f, "xor"), Op::Binary { op } => write!(f, "{:30} {:<5?}", "binary", op), Op::Unary { op } => write!(f, "{:30} {:<5?}", "unary", op), - Op::GetKey { key } => write!(f, "{:30} {}", "lookup_key", key), + Op::GetKey { key } => write!(f, "{:30} {:<5?} stack", "lookup_key", key), + Op::GetKeyRegV1 { key } => write!(f, "{:30} {:<5?} V1", "lookup_key", key), Op::Get => write!(f, "lookup"), Op::Index => write!(f, "idx"), Op::IndexFast { idx } => write!(f, "{:30} {:<5}", "idx_fast", idx), @@ -201,6 +231,9 @@ impl Display for Op { Op::TestIsArray => write!(f, "test_is_array"), Op::TestArrayIsEmpty => write!(f, "test_array_is_empty"), Op::TestRecordIsEmpty => write!(f, "test_record_is_empty"), + Op::TestRecordContainsKey { key } => { + write!(f, "{:32} {:<5}", "test_record_contains_key", key) + } Op::TestEq => write!(f, "test_eq"), Op::TestNeq => write!(f, "test_neq"), Op::TestGt => write!(f, "test_gt"), diff --git a/tremor-script/src/vm/tests.rs b/tremor-script/src/vm/tests.rs index a575d78fc9..54cf9a0579 100644 --- a/tremor-script/src/vm/tests.rs +++ b/tremor-script/src/vm/tests.rs @@ -1,3 +1,16 @@ +// Copyright 2020-2024, The Tremor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. use tremor_value::literal; use super::{Op::*, *}; @@ -9,6 +22,9 @@ use crate::{ registry, AggrRegistry, Compiler, }; +mod match_stmt; +mod patch; + fn compile(optimize: bool, src: &str) -> Result> { let mut compiler: Compiler = Compiler::new(); let reg = registry::registry(); @@ -40,9 +56,13 @@ fn run<'v>(p: &Program<'v>) -> Result> { "string": "string", "array": [1, 2, 3], "object": { - "a": 1, + "a": [1, 2, 3], "b": 2, - "c": 3 + "c": 3, + "o": { + "d": 4, + "e": 5, + } } }); if let Return::Emit { value, .. } = vm.run(&mut event, p)? { @@ -186,442 +206,6 @@ fn simple_eq() -> Result<()> { Ok(()) } -#[test] -fn patch_insert() -> Result<()> { - let p = compile(false, r#"patch {} of insert "foo" => 42 end"#)?; - - assert_eq!( - p.opcodes, - &[ - StoreV1, - Const { idx: 0 }, - Record { size: 0 }, - LoadV1, - Const { idx: 1 }, - String { size: 1 }, - TestRecortPresent, - JumpFalse { dst: 10 }, - Const { idx: 2 }, - Error, - Const { idx: 3 }, - RecordSet, - SwapV1, - ] - ); - - assert_eq!( - run(&p)?, - literal!({ - "foo": 42 - }) - ); - Ok(()) -} - -#[test] -fn patch_default_key() -> Result<()> { - let p = compile(false, r#"patch {} of default "foo" => 42 end"#)?; - - assert_eq!( - p.opcodes, - &[ - StoreV1, - Const { idx: 0 }, - Record { size: 0 }, - LoadV1, - Const { idx: 1 }, - String { size: 1 }, - TestRecortPresent, - JumpTrue { dst: 10 }, - Const { idx: 2 }, - RecordSet, - SwapV1, - ] - ); - - assert_eq!( - run(&p)?, - literal!({ - "foo": 42 - }) - ); - Ok(()) -} - -#[test] -fn patch_default() -> Result<()> { - let p = compile( - false, - r#"patch {"snot": 42} of default => {"snot": 23, "badger": 23, "cake": "cookie"} end"#, - )?; - - assert_eq!( - p.opcodes, - &[ - StoreV1, - Const { idx: 0 }, - String { size: 1 }, - Const { idx: 1 }, - Const { idx: 2 }, - Record { size: 1 }, - LoadV1, - Const { idx: 0 }, - String { size: 1 }, - Const { idx: 3 }, - Const { idx: 4 }, - String { size: 1 }, - Const { idx: 3 }, - Const { idx: 5 }, - String { size: 1 }, - Const { idx: 6 }, - String { size: 1 }, - Const { idx: 2 }, - Record { size: 3 }, - SwapV1, - TestRecordIsEmpty, - SwapV1, - JumpTrue { dst: 32 }, - RecordPop, - TestRecortPresent, - JumpTrue { dst: 29 }, - Swap, - RecordSet, - Jump { dst: 19 }, - Pop, - Pop, - Jump { dst: 19 }, - SwapV1, - ] - ); - - assert_eq!( - run(&p)?, - literal!({ - "snot": 42, - "badger": 23, - "cake": "cookie", - }) - ); - Ok(()) -} - -#[test] -fn patch_default_present() -> Result<()> { - let p = compile(false, r#"patch {"foo":"bar"} of default "foo" => 42 end"#)?; - - assert_eq!( - p.opcodes, - &[ - StoreV1, - Const { idx: 0 }, - String { size: 1 }, - Const { idx: 1 }, - String { size: 1 }, - Const { idx: 2 }, - Record { size: 1 }, - LoadV1, - Const { idx: 0 }, - String { size: 1 }, - TestRecortPresent, - JumpTrue { dst: 14 }, - Const { idx: 3 }, - RecordSet, - SwapV1, - ] - ); - - assert_eq!( - run(&p)?, - literal!({ - "foo": "bar" - }) - ); - Ok(()) -} - -#[test] -fn patch_insert_error() -> Result<()> { - let p = compile(false, r#"patch {"foo":"bar"} of insert "foo" => 42 end"#)?; - - assert_eq!( - p.opcodes, - &[ - StoreV1, - Const { idx: 0 }, - String { size: 1 }, - Const { idx: 1 }, - String { size: 1 }, - Const { idx: 2 }, - Record { size: 1 }, - LoadV1, - Const { idx: 0 }, - String { size: 1 }, - TestRecortPresent, - JumpFalse { dst: 14 }, - Const { idx: 3 }, - Error, - Const { idx: 4 }, - RecordSet, - SwapV1, - ] - ); - - assert!(run(&p).is_err(),); - Ok(()) -} - -#[test] -fn patch_update() -> Result<()> { - let p = compile(false, r#"patch {"foo":"bar"} of update "foo" => 42 end"#)?; - - assert_eq!( - p.opcodes, - &[ - StoreV1, - Const { idx: 0 }, - String { size: 1 }, - Const { idx: 1 }, - String { size: 1 }, - Const { idx: 2 }, - Record { size: 1 }, - LoadV1, - Const { idx: 0 }, - String { size: 1 }, - TestRecortPresent, - JumpTrue { dst: 14 }, - Const { idx: 3 }, - Error, - Const { idx: 4 }, - RecordSet, - SwapV1, - ] - ); - - assert_eq!( - run(&p)?, - literal!({ - "foo": 42 - }) - ); - Ok(()) -} - -#[test] -fn patch_update_error() -> Result<()> { - let p = compile(false, r#"patch {} of update "foo" => 42 end"#)?; - - assert_eq!( - p.opcodes, - &[ - StoreV1, - Const { idx: 0 }, - Record { size: 0 }, - LoadV1, - Const { idx: 1 }, - String { size: 1 }, - TestRecortPresent, - JumpTrue { dst: 10 }, - Const { idx: 2 }, - Error, - Const { idx: 3 }, - RecordSet, - SwapV1, - ] - ); - - assert!(run(&p).is_err(),); - Ok(()) -} - -#[test] -fn patch_upsert_1() -> Result<()> { - let p = compile(false, r#"patch {"foo":"bar"} of upsert "foo" => 42 end"#)?; - - assert_eq!( - p.opcodes, - &[ - StoreV1, - Const { idx: 0 }, - String { size: 1 }, - Const { idx: 1 }, - String { size: 1 }, - Const { idx: 2 }, - Record { size: 1 }, - LoadV1, - Const { idx: 0 }, - String { size: 1 }, - Const { idx: 3 }, - RecordSet, - SwapV1, - ] - ); - - assert_eq!( - run(&p)?, - literal!({ - "foo": 42 - }) - ); - Ok(()) -} - -#[test] -fn patch_upsert_2() -> Result<()> { - let p = compile(false, r#"patch {} of upsert "foo" => 42 end"#)?; - - assert_eq!( - p.opcodes, - &[ - StoreV1, - Const { idx: 0 }, - Record { size: 0 }, - LoadV1, - Const { idx: 1 }, - String { size: 1 }, - Const { idx: 2 }, - RecordSet, - SwapV1, - ] - ); - - assert_eq!( - run(&p)?, - literal!({ - "foo": 42 - }) - ); - Ok(()) -} - -#[test] -fn patch_patch_patch() -> Result<()> { - let p = compile( - false, - r#"patch patch {"foo":"bar"} of upsert "bar" => "baz" end of insert "baz" => 42 end"#, - )?; - - println!("{p}"); - - assert_eq!( - p.opcodes, - &[ - StoreV1, - StoreV1, - Const { idx: 0 }, - String { size: 1 }, - Const { idx: 1 }, - String { size: 1 }, - Const { idx: 2 }, - Record { size: 1 }, - LoadV1, - Const { idx: 1 }, - String { size: 1 }, - Const { idx: 3 }, - String { size: 1 }, - RecordSet, - SwapV1, - LoadV1, - Const { idx: 3 }, - String { size: 1 }, - TestRecortPresent, - JumpFalse { dst: 22 }, - Const { idx: 4 }, - Error, - Const { idx: 5 }, - RecordSet, - SwapV1, - ] - ); - - assert_eq!( - run(&p)?, - literal!({ - "foo": "bar", - "bar": "baz", - "baz": 42 - }) - ); - Ok(()) -} - -#[test] -fn patch_merge() -> Result<()> { - let p = compile( - false, - r#"patch {"snot":"badger"} of merge => {"badger":"snot"} end"#, - )?; - - assert_eq!( - p.opcodes, - &[ - StoreV1, - Const { idx: 0 }, - String { size: 1 }, - Const { idx: 1 }, - String { size: 1 }, - Const { idx: 2 }, - Record { size: 1 }, - LoadV1, - Const { idx: 1 }, - String { size: 1 }, - Const { idx: 0 }, - String { size: 1 }, - Const { idx: 2 }, - Record { size: 1 }, - RecordMerge, - SwapV1, - ] - ); - - assert_eq!( - run(&p)?, - literal!({ - "snot": "badger", - "badger": "snot" - }) - ); - Ok(()) -} - -#[test] -fn patch_merge_key() -> Result<()> { - let p = compile( - false, - r#"(patch event of merge "object" => {"badger":"snot"} end).object"#, - )?; - - assert_eq!( - p.opcodes, - &[ - StoreV1, - LoadEvent, - LoadV1, - Const { idx: 0 }, - String { size: 1 }, - Const { idx: 1 }, - String { size: 1 }, - Const { idx: 2 }, - Record { size: 1 }, - Const { idx: 3 }, - String { size: 1 }, - RecordMergeKey, - SwapV1, - GetKey { key: 0 }, - ] - ); - - assert_eq!( - run(&p)?, - literal!({ - "a": 1, - "b": 2, - "c": 3, - "badger": "snot", - }) - ); - Ok(()) -} - #[test] fn merge() -> Result<()> { let p = compile(false, r#"merge {"snot":"badger"} of {"badger":"snot"} end"#)?; @@ -788,7 +372,7 @@ fn event_nested() -> Result<()> { &[LoadEvent, GetKey { key: 0 }, GetKey { key: 1 }] ); - assert_eq!(run(&p)?, 1); + assert_eq!(run(&p)?, literal!([1, 2, 3])); Ok(()) } @@ -848,68 +432,6 @@ fn test_local_event() -> Result<()> { Ok(()) } -#[test] -fn test_match_if_else() -> Result<()> { - let p = compile(false, "match event.int of case 42 => 42 case _ => 0 end")?; - - println!("{p}"); - assert_eq!( - p.opcodes, - &[ - StoreV1, - LoadEvent, - GetKey { key: 0 }, - LoadV1, - Const { idx: 0 }, - TestEq, - JumpFalse { dst: 9 }, - Const { idx: 0 }, - Jump { dst: 10 }, - Const { idx: 1 }, - LoadV1, - SwapV1, - ] - ); - - assert_eq!(run(&p)?, 42); - Ok(()) -} - -#[test] -fn test_match_record_type() -> Result<()> { - let p = compile( - false, - "match event.object of case 42 => 42 case %{} => \"record\" case _ => 0 end", - )?; - - assert_eq!( - p.opcodes, - &[ - StoreV1, - LoadEvent, - GetKey { key: 0 }, - LoadV1, - Const { idx: 0 }, - TestEq, - JumpFalse { dst: 9 }, - Const { idx: 0 }, - Jump { dst: 16 }, - TestIsRecord, - JumpFalse { dst: 11 }, - JumpFalse { dst: 15 }, - Const { idx: 1 }, - String { size: 1 }, - Jump { dst: 16 }, - Const { idx: 2 }, - LoadV1, - SwapV1, - ] - ); - - assert_eq!(run(&p)?, "record"); - Ok(()) -} - #[test] fn test_event_assign_nested() -> Result<()> { let p = compile(false, "let event.string = \"snot\"; event.string")?; @@ -1034,246 +556,3 @@ fn test_local_array_assign_nested() -> Result<()> { assert_eq!(run(&p)?, literal!([-1, 2])); Ok(()) } - -#[test] -#[allow(clippy::too_many_lines)] -fn test_match_touple() -> Result<()> { - let p = compile( - false, - r" - match [42, 23] of - case %(42, 24) => 24 - case %(42, 23, 7) => 7 - case %(42, 23) => 42 - case _ => 0 - end", - )?; - - assert_eq!( - p.opcodes, - &[ - StoreV1, - Const { idx: 0 }, - Const { idx: 1 }, - Const { idx: 2 }, - Array { size: 2 }, - LoadV1, - TestIsArray, - JumpFalse { dst: 28 }, - InspectLen, - Const { idx: 3 }, - Binary { op: Eq }, - LoadRB, - JumpFalse { dst: 28 }, - CopyV1, - ArrayReverse, - ArrayPop, - SwapV1, - Const { idx: 0 }, - TestEq, - LoadV1, - JumpFalse { dst: 27 }, - ArrayPop, - SwapV1, - Const { idx: 4 }, - TestEq, - LoadV1, - JumpFalse { dst: 27 }, - Pop, - JumpFalse { dst: 31 }, - Const { idx: 4 }, - Jump { dst: 88 }, - TestIsArray, - JumpFalse { dst: 59 }, - InspectLen, - Const { idx: 5 }, - Binary { op: Eq }, - LoadRB, - JumpFalse { dst: 59 }, - CopyV1, - ArrayReverse, - ArrayPop, - SwapV1, - Const { idx: 0 }, - TestEq, - LoadV1, - JumpFalse { dst: 58 }, - ArrayPop, - SwapV1, - Const { idx: 1 }, - TestEq, - LoadV1, - JumpFalse { dst: 58 }, - ArrayPop, - SwapV1, - Const { idx: 6 }, - TestEq, - LoadV1, - JumpFalse { dst: 58 }, - Pop, - JumpFalse { dst: 62 }, - Const { idx: 6 }, - Jump { dst: 88 }, - TestIsArray, - JumpFalse { dst: 84 }, - InspectLen, - Const { idx: 3 }, - Binary { op: Eq }, - LoadRB, - JumpFalse { dst: 84 }, - CopyV1, - ArrayReverse, - ArrayPop, - SwapV1, - Const { idx: 0 }, - TestEq, - LoadV1, - JumpFalse { dst: 83 }, - ArrayPop, - SwapV1, - Const { idx: 1 }, - TestEq, - LoadV1, - JumpFalse { dst: 83 }, - Pop, - JumpFalse { dst: 87 }, - Const { idx: 0 }, - Jump { dst: 88 }, - Const { idx: 7 }, - LoadV1, - SwapV1, - ] - ); - - assert_eq!(run(&p)?, 42); - Ok(()) -} -#[test] -fn test_match_search_tree() -> Result<()> { - let p = compile( - true, - r" - match event.int of - case 1 => 1 - case 2 => 2 - case 3 => 3 - case 4 => 4 - case 5 => 5 - case 6 => 6 - case 7 => 7 - case 8 => 8 - case 9 => 9 - case 42 => 42 - case _ => 23 - end", - )?; - - // assert_eq!(p.opcodes, &[]); - - assert_eq!(run(&p)?, 42); - Ok(()) -} - -#[test] -fn test_match_assign() -> Result<()> { - let p = compile( - false, - r" - match 42 of - case 24 => 24 - case 7 => 7 - case a = 42 => a - case _ => 0 - end", - )?; - - assert_eq!( - p.opcodes, - &[ - StoreV1, - Const { idx: 0 }, - LoadV1, - Const { idx: 1 }, - TestEq, - JumpFalse { dst: 8 }, - Const { idx: 1 }, - Jump { dst: 22 }, - Const { idx: 0 }, - TestEq, - JumpFalse { dst: 13 }, - CopyV1, - StoreLocal { - elements: 0, - idx: 0, - }, - JumpFalse { dst: 16 }, - LoadLocal { idx: 0 }, - Jump { dst: 22 }, - Const { idx: 2 }, - TestEq, - JumpFalse { dst: 21 }, - Const { idx: 2 }, - Jump { dst: 22 }, - Const { idx: 3 }, - LoadV1, - SwapV1, - ] - ); - - assert_eq!(run(&p)?, 42); - Ok(()) -} - -#[test] -fn test_match_assign_nested() -> Result<()> { - let p = compile( - false, - r" - match [42] of - case a = %(42) => a - case _ => 0 - end", - )?; - - assert_eq!( - p.opcodes, - &[ - StoreV1, - Const { idx: 0 }, - Const { idx: 1 }, - Array { size: 1 }, - LoadV1, - TestIsArray, - JumpFalse { dst: 21 }, - InspectLen, - Const { idx: 2 }, - Binary { op: Eq }, - LoadRB, - JumpFalse { dst: 21 }, - CopyV1, - ArrayReverse, - ArrayPop, - SwapV1, - Const { idx: 0 }, - TestEq, - LoadV1, - JumpFalse { dst: 20 }, - Pop, - JumpFalse { dst: 24 }, - CopyV1, - StoreLocal { - elements: 0, - idx: 0, - }, - JumpFalse { dst: 27 }, - LoadLocal { idx: 0 }, - Jump { dst: 28 }, - Const { idx: 3 }, - LoadV1, - SwapV1, - ] - ); - - assert_eq!(run(&p)?, literal!([42])); - Ok(()) -} diff --git a/tremor-script/src/vm/tests/match_stmt.rs b/tremor-script/src/vm/tests/match_stmt.rs new file mode 100644 index 0000000000..ead5363d2d --- /dev/null +++ b/tremor-script/src/vm/tests/match_stmt.rs @@ -0,0 +1,521 @@ +// Copyright 2020-2024, The Tremor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use super::*; + +#[test] +fn test_match_if_else() -> Result<()> { + let p = compile(false, "match event.int of case 42 => 42 case _ => 0 end")?; + + println!("{p}"); + assert_eq!( + p.opcodes, + &[ + StoreV1, + LoadEvent, + GetKey { key: 0 }, + LoadV1, + Const { idx: 0 }, + TestEq, + JumpFalse { dst: 9 }, + Const { idx: 0 }, + Jump { dst: 10 }, + Const { idx: 1 }, + LoadV1, + SwapV1, + ] + ); + + assert_eq!(run(&p)?, 42); + Ok(()) +} + +#[test] +fn test_match_record_type() -> Result<()> { + let p = compile( + false, + "match event.object of case 42 => 42 case %{} => \"record\" case _ => 0 end", + )?; + + assert_eq!( + p.opcodes, + &[ + StoreV1, + LoadEvent, + GetKey { key: 0 }, + LoadV1, + Const { idx: 0 }, + TestEq, + JumpFalse { dst: 9 }, + Const { idx: 0 }, + Jump { dst: 16 }, + TestIsRecord, + JumpFalse { dst: 11 }, + JumpFalse { dst: 15 }, + Const { idx: 1 }, + String { size: 1 }, + Jump { dst: 16 }, + Const { idx: 2 }, + LoadV1, + SwapV1, + ] + ); + + assert_eq!(run(&p)?, "record"); + Ok(()) +} + +#[test] +#[allow(clippy::too_many_lines)] +fn test_match_touple() -> Result<()> { + let p = compile( + false, + r" + match [42, 23] of + case %(42, 24) => 24 + case %(42, 23, 7) => 7 + case %(42, 23) => 42 + case _ => 0 + end", + )?; + + assert_eq!( + p.opcodes, + &[ + StoreV1, + Const { idx: 0 }, + Const { idx: 1 }, + Const { idx: 2 }, + Array { size: 2 }, + LoadV1, + TestIsArray, + JumpFalse { dst: 28 }, + InspectLen, + Const { idx: 3 }, + Binary { op: Eq }, + LoadRB, + JumpFalse { dst: 28 }, + CopyV1, + ArrayReverse, + ArrayPop, + SwapV1, + Const { idx: 0 }, + TestEq, + LoadV1, + JumpFalse { dst: 27 }, + ArrayPop, + SwapV1, + Const { idx: 4 }, + TestEq, + LoadV1, + JumpFalse { dst: 27 }, + Pop, + JumpFalse { dst: 31 }, + Const { idx: 4 }, + Jump { dst: 88 }, + TestIsArray, + JumpFalse { dst: 59 }, + InspectLen, + Const { idx: 5 }, + Binary { op: Eq }, + LoadRB, + JumpFalse { dst: 59 }, + CopyV1, + ArrayReverse, + ArrayPop, + SwapV1, + Const { idx: 0 }, + TestEq, + LoadV1, + JumpFalse { dst: 58 }, + ArrayPop, + SwapV1, + Const { idx: 1 }, + TestEq, + LoadV1, + JumpFalse { dst: 58 }, + ArrayPop, + SwapV1, + Const { idx: 6 }, + TestEq, + LoadV1, + JumpFalse { dst: 58 }, + Pop, + JumpFalse { dst: 62 }, + Const { idx: 6 }, + Jump { dst: 88 }, + TestIsArray, + JumpFalse { dst: 84 }, + InspectLen, + Const { idx: 3 }, + Binary { op: Eq }, + LoadRB, + JumpFalse { dst: 84 }, + CopyV1, + ArrayReverse, + ArrayPop, + SwapV1, + Const { idx: 0 }, + TestEq, + LoadV1, + JumpFalse { dst: 83 }, + ArrayPop, + SwapV1, + Const { idx: 1 }, + TestEq, + LoadV1, + JumpFalse { dst: 83 }, + Pop, + JumpFalse { dst: 87 }, + Const { idx: 0 }, + Jump { dst: 88 }, + Const { idx: 7 }, + LoadV1, + SwapV1, + ] + ); + + assert_eq!(run(&p)?, 42); + Ok(()) +} +#[test] +fn test_match_search_tree() -> Result<()> { + let p = compile( + true, + r" + match event.int of + case 1 => 1 + case 2 => 2 + case 3 => 3 + case 4 => 4 + case 5 => 5 + case 6 => 6 + case 7 => 7 + case 8 => 8 + case 9 => 9 + case 42 => 42 + case _ => 23 + end", + )?; + + // assert_eq!(p.opcodes, &[]); + + assert_eq!(run(&p)?, 42); + Ok(()) +} + +#[test] +fn test_match_assign() -> Result<()> { + let p = compile( + false, + r" + match 42 of + case 24 => 24 + case 7 => 7 + case a = 42 => a + case _ => 0 + end", + )?; + + assert_eq!( + p.opcodes, + &[ + StoreV1, + Const { idx: 0 }, + LoadV1, + Const { idx: 1 }, + TestEq, + JumpFalse { dst: 8 }, + Const { idx: 1 }, + Jump { dst: 22 }, + Const { idx: 0 }, + TestEq, + JumpFalse { dst: 13 }, + CopyV1, + StoreLocal { + elements: 0, + idx: 0, + }, + JumpFalse { dst: 16 }, + LoadLocal { idx: 0 }, + Jump { dst: 22 }, + Const { idx: 2 }, + TestEq, + JumpFalse { dst: 21 }, + Const { idx: 2 }, + Jump { dst: 22 }, + Const { idx: 3 }, + LoadV1, + SwapV1, + ] + ); + + assert_eq!(run(&p)?, 42); + Ok(()) +} + +#[test] +fn test_match_assign_nested() -> Result<()> { + let p = compile( + false, + r" + match [42] of + case a = %(42) => a + case _ => 0 + end", + )?; + + assert_eq!( + p.opcodes, + &[ + StoreV1, + Const { idx: 0 }, + Const { idx: 1 }, + Array { size: 1 }, + LoadV1, + TestIsArray, + JumpFalse { dst: 21 }, + InspectLen, + Const { idx: 2 }, + Binary { op: Eq }, + LoadRB, + JumpFalse { dst: 21 }, + CopyV1, + ArrayReverse, + ArrayPop, + SwapV1, + Const { idx: 0 }, + TestEq, + LoadV1, + JumpFalse { dst: 20 }, + Pop, + JumpFalse { dst: 24 }, + CopyV1, + StoreLocal { + elements: 0, + idx: 0, + }, + JumpFalse { dst: 27 }, + LoadLocal { idx: 0 }, + Jump { dst: 28 }, + Const { idx: 3 }, + LoadV1, + SwapV1, + ] + ); + + assert_eq!(run(&p)?, literal!([42])); + Ok(()) +} + +#[test] +fn test_match_record_key_present() -> Result<()> { + let p = compile( + false, + r" + match event of + case %{present obj} => 1 + case %{present object} =>2 + case _ => 3 + end", + )?; + + assert_eq!( + p.opcodes, + &[ + StoreV1, + LoadEvent, + LoadV1, + TestIsRecord, + JumpFalse { dst: 6 }, + TestRecordContainsKey { key: 0 }, + JumpFalse { dst: 9 }, + Const { idx: 0 }, + Jump { dst: 16 }, + TestIsRecord, + JumpFalse { dst: 12 }, + TestRecordContainsKey { key: 1 }, + JumpFalse { dst: 15 }, + Const { idx: 1 }, + Jump { dst: 16 }, + Const { idx: 2 }, + LoadV1, + SwapV1, + ] + ); + + assert_eq!(run(&p)?, 2); + Ok(()) +} + +#[test] +fn test_match_record_key_absent() -> Result<()> { + let p = compile( + false, + r" + match event of + case %{absent obj} => 1 + case %{absent object} =>2 + case _ => 3 + end", + )?; + + assert_eq!( + p.opcodes, + &[ + StoreV1, + LoadEvent, + LoadV1, + TestIsRecord, + JumpFalse { dst: 7 }, + TestRecordContainsKey { key: 0 }, + NotRB, + JumpFalse { dst: 10 }, + Const { idx: 0 }, + Jump { dst: 18 }, + TestIsRecord, + JumpFalse { dst: 14 }, + TestRecordContainsKey { key: 1 }, + NotRB, + JumpFalse { dst: 17 }, + Const { idx: 1 }, + Jump { dst: 18 }, + Const { idx: 2 }, + LoadV1, + SwapV1, + ] + ); + + assert_eq!(run(&p)?, 1); + Ok(()) +} + +#[test] +fn test_record_binary() -> Result<()> { + let p = compile( + false, + r" + match event of + case %{ int > 23 } => 1 + case %{ bool == true } =>2 + case _ => 3 + end", + )?; + assert_eq!( + p.opcodes, + &[ + StoreV1, + LoadEvent, + LoadV1, + TestIsRecord, + JumpFalse { dst: 13 }, + TestRecordContainsKey { key: 0 }, + JumpFalse { dst: 13 }, + GetKeyRegV1 { key: 0 }, + SwapV1, + Const { idx: 0 }, + TestGt, + Pop, + LoadV1, + JumpFalse { dst: 16 }, + Const { idx: 1 }, + Jump { dst: 24 }, + TestRecordContainsKey { key: 1 }, + JumpFalse { dst: 23 }, + Const { idx: 2 }, + TestEq, + JumpFalse { dst: 23 }, + Const { idx: 3 }, + Jump { dst: 24 }, + Const { idx: 4 }, + LoadV1, + SwapV1, + ] + ); + assert_eq!(run(&p)?, 1); + Ok(()) +} + +#[test] +fn test_record_tuple() -> Result<()> { + let p = compile( + false, + r#" + match {"array": [1], "int": 30} of + case %{ int < 23 } => 1 + case %{ array ~= %(1) } => 2 + case _ => 3 + end"#, + )?; + assert_eq!( + p.opcodes, + &[ + StoreV1, + Const { idx: 0 }, + String { size: 1 }, + Const { idx: 1 }, + Const { idx: 2 }, + Array { size: 1 }, + Const { idx: 3 }, + String { size: 1 }, + Const { idx: 4 }, + Const { idx: 5 }, + Record { size: 2 }, + LoadV1, + TestIsRecord, + JumpFalse { dst: 22 }, + TestRecordContainsKey { key: 0 }, + JumpFalse { dst: 22 }, + GetKeyRegV1 { key: 0 }, + SwapV1, + Const { idx: 6 }, + TestLt, + Pop, + LoadV1, + JumpFalse { dst: 25 }, + Const { idx: 1 }, + Jump { dst: 52 }, + TestIsRecord, + JumpFalse { dst: 48 }, + TestRecordContainsKey { key: 1 }, + JumpFalse { dst: 48 }, + GetKeyRegV1 { key: 1 }, + SwapV1, + TestIsArray, + JumpFalse { dst: 47 }, + InspectLen, + Const { idx: 1 }, + Binary { op: Eq }, + LoadRB, + JumpFalse { dst: 47 }, + CopyV1, + ArrayReverse, + ArrayPop, + SwapV1, + Const { idx: 1 }, + TestEq, + LoadV1, + JumpFalse { dst: 46 }, + Pop, + LoadV1, + JumpFalse { dst: 51 }, + Const { idx: 7 }, + Jump { dst: 52 }, + Const { idx: 8 }, + LoadV1, + SwapV1, + ] + ); + assert_eq!(run(&p)?, 2); + Ok(()) +} diff --git a/tremor-script/src/vm/tests/patch.rs b/tremor-script/src/vm/tests/patch.rs new file mode 100644 index 0000000000..2d3e073766 --- /dev/null +++ b/tremor-script/src/vm/tests/patch.rs @@ -0,0 +1,454 @@ +// Copyright 2020-2024, The Tremor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use super::*; + +#[test] +fn patch_insert() -> Result<()> { + let p = compile(false, r#"patch {} of insert "foo" => 42 end"#)?; + + assert_eq!( + p.opcodes, + &[ + StoreV1, + Const { idx: 0 }, + Record { size: 0 }, + LoadV1, + Const { idx: 1 }, + String { size: 1 }, + TestRecortPresent, + JumpFalse { dst: 10 }, + Const { idx: 2 }, + Error, + Const { idx: 3 }, + RecordSet, + SwapV1, + ] + ); + + assert_eq!( + run(&p)?, + literal!({ + "foo": 42 + }) + ); + Ok(()) +} + +#[test] +fn patch_default_key() -> Result<()> { + let p = compile(false, r#"patch {} of default "foo" => 42 end"#)?; + + assert_eq!( + p.opcodes, + &[ + StoreV1, + Const { idx: 0 }, + Record { size: 0 }, + LoadV1, + Const { idx: 1 }, + String { size: 1 }, + TestRecortPresent, + JumpTrue { dst: 10 }, + Const { idx: 2 }, + RecordSet, + SwapV1, + ] + ); + + assert_eq!( + run(&p)?, + literal!({ + "foo": 42 + }) + ); + Ok(()) +} + +#[test] +fn patch_default() -> Result<()> { + let p = compile( + false, + r#"patch {"snot": 42} of default => {"snot": 23, "badger": 23, "cake": "cookie"} end"#, + )?; + + assert_eq!( + p.opcodes, + &[ + StoreV1, + Const { idx: 0 }, + String { size: 1 }, + Const { idx: 1 }, + Const { idx: 2 }, + Record { size: 1 }, + LoadV1, + Const { idx: 0 }, + String { size: 1 }, + Const { idx: 3 }, + Const { idx: 4 }, + String { size: 1 }, + Const { idx: 3 }, + Const { idx: 5 }, + String { size: 1 }, + Const { idx: 6 }, + String { size: 1 }, + Const { idx: 2 }, + Record { size: 3 }, + SwapV1, + TestRecordIsEmpty, + SwapV1, + JumpTrue { dst: 32 }, + RecordPop, + TestRecortPresent, + JumpTrue { dst: 29 }, + Swap, + RecordSet, + Jump { dst: 19 }, + Pop, + Pop, + Jump { dst: 19 }, + SwapV1, + ] + ); + + assert_eq!( + run(&p)?, + literal!({ + "snot": 42, + "badger": 23, + "cake": "cookie", + }) + ); + Ok(()) +} + +#[test] +fn patch_default_present() -> Result<()> { + let p = compile(false, r#"patch {"foo":"bar"} of default "foo" => 42 end"#)?; + + assert_eq!( + p.opcodes, + &[ + StoreV1, + Const { idx: 0 }, + String { size: 1 }, + Const { idx: 1 }, + String { size: 1 }, + Const { idx: 2 }, + Record { size: 1 }, + LoadV1, + Const { idx: 0 }, + String { size: 1 }, + TestRecortPresent, + JumpTrue { dst: 14 }, + Const { idx: 3 }, + RecordSet, + SwapV1, + ] + ); + + assert_eq!( + run(&p)?, + literal!({ + "foo": "bar" + }) + ); + Ok(()) +} + +#[test] +fn patch_insert_error() -> Result<()> { + let p = compile(false, r#"patch {"foo":"bar"} of insert "foo" => 42 end"#)?; + + assert_eq!( + p.opcodes, + &[ + StoreV1, + Const { idx: 0 }, + String { size: 1 }, + Const { idx: 1 }, + String { size: 1 }, + Const { idx: 2 }, + Record { size: 1 }, + LoadV1, + Const { idx: 0 }, + String { size: 1 }, + TestRecortPresent, + JumpFalse { dst: 14 }, + Const { idx: 3 }, + Error, + Const { idx: 4 }, + RecordSet, + SwapV1, + ] + ); + + assert!(run(&p).is_err(),); + Ok(()) +} + +#[test] +fn patch_update() -> Result<()> { + let p = compile(false, r#"patch {"foo":"bar"} of update "foo" => 42 end"#)?; + + assert_eq!( + p.opcodes, + &[ + StoreV1, + Const { idx: 0 }, + String { size: 1 }, + Const { idx: 1 }, + String { size: 1 }, + Const { idx: 2 }, + Record { size: 1 }, + LoadV1, + Const { idx: 0 }, + String { size: 1 }, + TestRecortPresent, + JumpTrue { dst: 14 }, + Const { idx: 3 }, + Error, + Const { idx: 4 }, + RecordSet, + SwapV1, + ] + ); + + assert_eq!( + run(&p)?, + literal!({ + "foo": 42 + }) + ); + Ok(()) +} + +#[test] +fn patch_update_error() -> Result<()> { + let p = compile(false, r#"patch {} of update "foo" => 42 end"#)?; + + assert_eq!( + p.opcodes, + &[ + StoreV1, + Const { idx: 0 }, + Record { size: 0 }, + LoadV1, + Const { idx: 1 }, + String { size: 1 }, + TestRecortPresent, + JumpTrue { dst: 10 }, + Const { idx: 2 }, + Error, + Const { idx: 3 }, + RecordSet, + SwapV1, + ] + ); + + assert!(run(&p).is_err(),); + Ok(()) +} + +#[test] +fn patch_upsert_1() -> Result<()> { + let p = compile(false, r#"patch {"foo":"bar"} of upsert "foo" => 42 end"#)?; + + assert_eq!( + p.opcodes, + &[ + StoreV1, + Const { idx: 0 }, + String { size: 1 }, + Const { idx: 1 }, + String { size: 1 }, + Const { idx: 2 }, + Record { size: 1 }, + LoadV1, + Const { idx: 0 }, + String { size: 1 }, + Const { idx: 3 }, + RecordSet, + SwapV1, + ] + ); + + assert_eq!( + run(&p)?, + literal!({ + "foo": 42 + }) + ); + Ok(()) +} + +#[test] +fn patch_upsert_2() -> Result<()> { + let p = compile(false, r#"patch {} of upsert "foo" => 42 end"#)?; + + assert_eq!( + p.opcodes, + &[ + StoreV1, + Const { idx: 0 }, + Record { size: 0 }, + LoadV1, + Const { idx: 1 }, + String { size: 1 }, + Const { idx: 2 }, + RecordSet, + SwapV1, + ] + ); + + assert_eq!( + run(&p)?, + literal!({ + "foo": 42 + }) + ); + Ok(()) +} + +#[test] +fn patch_patch_patch() -> Result<()> { + let p = compile( + false, + r#"patch patch {"foo":"bar"} of upsert "bar" => "baz" end of insert "baz" => 42 end"#, + )?; + + println!("{p}"); + + assert_eq!( + p.opcodes, + &[ + StoreV1, + StoreV1, + Const { idx: 0 }, + String { size: 1 }, + Const { idx: 1 }, + String { size: 1 }, + Const { idx: 2 }, + Record { size: 1 }, + LoadV1, + Const { idx: 1 }, + String { size: 1 }, + Const { idx: 3 }, + String { size: 1 }, + RecordSet, + SwapV1, + LoadV1, + Const { idx: 3 }, + String { size: 1 }, + TestRecortPresent, + JumpFalse { dst: 22 }, + Const { idx: 4 }, + Error, + Const { idx: 5 }, + RecordSet, + SwapV1, + ] + ); + + assert_eq!( + run(&p)?, + literal!({ + "foo": "bar", + "bar": "baz", + "baz": 42 + }) + ); + Ok(()) +} + +#[test] +fn patch_merge() -> Result<()> { + let p = compile( + false, + r#"patch {"snot":"badger"} of merge => {"badger":"snot"} end"#, + )?; + + assert_eq!( + p.opcodes, + &[ + StoreV1, + Const { idx: 0 }, + String { size: 1 }, + Const { idx: 1 }, + String { size: 1 }, + Const { idx: 2 }, + Record { size: 1 }, + LoadV1, + Const { idx: 1 }, + String { size: 1 }, + Const { idx: 0 }, + String { size: 1 }, + Const { idx: 2 }, + Record { size: 1 }, + RecordMerge, + SwapV1, + ] + ); + + assert_eq!( + run(&p)?, + literal!({ + "snot": "badger", + "badger": "snot" + }) + ); + Ok(()) +} + +#[test] +fn patch_merge_key() -> Result<()> { + let p = compile( + false, + r#"(patch event of merge "object" => {"badger":"snot"} end).object"#, + )?; + + assert_eq!( + p.opcodes, + &[ + StoreV1, + LoadEvent, + LoadV1, + Const { idx: 0 }, + String { size: 1 }, + Const { idx: 1 }, + String { size: 1 }, + Const { idx: 2 }, + Record { size: 1 }, + Const { idx: 3 }, + String { size: 1 }, + RecordMergeKey, + SwapV1, + GetKey { key: 0 }, + ] + ); + + assert_eq!( + run(&p)?, + literal!({ + "a": [1, 2, 3], + "b": 2, + "c": 3, + "o": { + "d": 4, + "e": 5, + }, + "badger": "snot", + }) + ); + Ok(()) +}