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(()) +}