diff --git a/tremor-script/src/ast.rs b/tremor-script/src/ast.rs index 7a0522dea0..fb70914732 100644 --- a/tremor-script/src/ast.rs +++ b/tremor-script/src/ast.rs @@ -1673,8 +1673,6 @@ impl<'script> Pattern<'script> { pub enum PredicatePattern<'script> { /// Structural application TildeEq { - /// Assignment bind point - assign: Cow<'script, str>, /// Lhs lhs: Cow<'script, str>, /// Key @@ -1974,6 +1972,11 @@ pub enum Path<'script> { } impl<'script> Path<'script> { + /// Get the length of the path + #[must_use] + pub(crate) fn len(&self) -> usize { + self.segments().len() + } /// Get segments as slice #[must_use] pub(crate) fn segments(&self) -> &Segments<'script> { diff --git a/tremor-script/src/ast/eq.rs b/tremor-script/src/ast/eq.rs index 5cdca4e5bb..6134cd1c38 100644 --- a/tremor-script/src/ast/eq.rs +++ b/tremor-script/src/ast/eq.rs @@ -437,19 +437,13 @@ impl<'script> AstEq for PredicatePattern<'script> { use PredicatePattern::{ArrayPatternEq, Bin, RecordPatternEq, TildeEq}; match (self, other) { ( + TildeEq { lhs, key, test }, TildeEq { - assign, - lhs, - key, - test, - }, - TildeEq { - assign: a2, lhs: l2, key: k2, test: t2, }, - ) => assign == a2 && lhs == l2 && key == k2 && test.ast_eq(t2.as_ref()), + ) => lhs == l2 && key == k2 && test.ast_eq(t2.as_ref()), ( Bin { lhs, diff --git a/tremor-script/src/ast/eq/tests.rs b/tremor-script/src/ast/eq/tests.rs index c9ee0ba610..16f7b796cc 100644 --- a/tremor-script/src/ast/eq/tests.rs +++ b/tremor-script/src/ast/eq/tests.rs @@ -468,7 +468,6 @@ fn imut_expr() -> ImutExpr<'static> { #[test] fn predicate_pattern() { assert!(PredicatePattern::TildeEq { - assign: "assign".into(), lhs: "lhs".into(), key: "key".into(), test: Box::new(TestExpr { @@ -479,7 +478,6 @@ fn predicate_pattern() { }), } .ast_eq(&PredicatePattern::TildeEq { - assign: "assign".into(), lhs: "lhs".into(), key: "key".into(), test: Box::new(TestExpr { diff --git a/tremor-script/src/ast/raw.rs b/tremor-script/src/ast/raw.rs index cb01f2269e..79c6c14c7a 100644 --- a/tremor-script/src/ast/raw.rs +++ b/tremor-script/src/ast/raw.rs @@ -1396,8 +1396,6 @@ impl<'script> Upable<'script> for PatternRaw<'script> { pub enum PredicatePatternRaw<'script> { /// we're forced to make this pub because of lalrpop TildeEq { - /// we're forced to make this pub because of lalrpop - assign: Cow<'script, str>, /// we're forced to make this pub because of lalrpop lhs: Cow<'script, str>, /// we're forced to make this pub because of lalrpop @@ -1453,8 +1451,7 @@ impl<'script> Upable<'script> for PredicatePatternRaw<'script> { TuplePatternEq, }; Ok(match self { - TildeEq { assign, lhs, test } => PredicatePattern::TildeEq { - assign, + TildeEq { lhs, test } => PredicatePattern::TildeEq { key: KnownKey::from(lhs.clone()), lhs, test: Box::new(test.up(helper)?), diff --git a/tremor-script/src/ast/test.rs b/tremor-script/src/ast/test.rs index 2e8bb5e856..85e200d25c 100644 --- a/tremor-script/src/ast/test.rs +++ b/tremor-script/src/ast/test.rs @@ -354,7 +354,6 @@ fn pp_is_exclusive() { ); let teq = PredicatePattern::TildeEq { lhs: "k1".into(), - assign: "k".into(), key: "k1".into(), test: Box::new(TestExpr { mid: NodeMeta::dummy(), @@ -365,7 +364,6 @@ fn pp_is_exclusive() { }; let test_eq2 = PredicatePattern::TildeEq { lhs: "k2".into(), - assign: "k".into(), key: "k2".into(), test: Box::new(TestExpr { mid: NodeMeta::dummy(), @@ -376,7 +374,6 @@ fn pp_is_exclusive() { }; let teq3 = PredicatePattern::TildeEq { lhs: "k1".into(), - assign: "k".into(), key: "k1".into(), test: Box::new(TestExpr { mid: NodeMeta::dummy(), diff --git a/tremor-script/src/errors.rs b/tremor-script/src/errors.rs index 667af944c7..ef9b135667 100644 --- a/tremor-script/src/errors.rs +++ b/tremor-script/src/errors.rs @@ -16,6 +16,7 @@ #![allow(clippy::large_enum_variant)] #![allow(deprecated)] #![allow(missing_docs)] +#![allow(non_snake_case)] use crate::errors::ErrorKind::InvalidBinaryBoolean; pub use crate::prelude::ValueType; diff --git a/tremor-script/src/extractor/re.rs b/tremor-script/src/extractor/re.rs index c555d0bdf3..606ae55d68 100644 --- a/tremor-script/src/extractor/re.rs +++ b/tremor-script/src/extractor/re.rs @@ -138,7 +138,8 @@ use tremor_value::Value; use value_trait::prelude::*; pub(crate) fn execute(s: &str, result_needed: bool, compiled: &Regex) -> Result<'static> { - compiled.captures(s).map_or(Result::NoMatch, |caps| { + let res = compiled.captures(s); + res.map_or(Result::NoMatch, |caps| { if result_needed { let matches: HashMap, Value, _> = compiled .capture_names() diff --git a/tremor-script/src/grammar.lalrpop b/tremor-script/src/grammar.lalrpop index 30bde330a4..50c2f48792 100644 --- a/tremor-script/src/grammar.lalrpop +++ b/tremor-script/src/grammar.lalrpop @@ -1425,8 +1425,9 @@ WhenClause: Option> = { /// Predicate Patterns (aka tests) for record pattrens PredicateFieldPattern: PredicatePatternRaw<'input> = { - "~=" => PredicatePatternRaw::TildeEq { assign: lhs.id.clone(), lhs: lhs.id, test: expr}, - "=" "~=" => PredicatePatternRaw::TildeEq { assign: assign.id, lhs: lhs.id, test: expr} , + "~=" => PredicatePatternRaw::TildeEq { lhs: lhs.id, test: expr}, + // FIXME: why was assign never used in here? + <_assign:Ident> "=" "~=" => PredicatePatternRaw::TildeEq { lhs: lhs.id, test: expr} , "~=" => PredicatePatternRaw::RecordPatternEq { lhs: lhs.id, pattern: rp }, "~=" => PredicatePatternRaw::ArrayPatternEq { lhs: lhs.id, pattern: ap }, "~=" => PredicatePatternRaw::TuplePatternEq { lhs: lhs.id, pattern: ap }, diff --git a/tremor-script/src/vm.rs b/tremor-script/src/vm.rs index 06d1ed6436..3092b55521 100644 --- a/tremor-script/src/vm.rs +++ b/tremor-script/src/vm.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, mem}; +use std::{borrow::Cow, mem, ptr}; use compiler::Program; use simd_json::{prelude::*, ValueBuilder}; @@ -10,9 +10,10 @@ use crate::{ raw::{BytesDataType, Endian}, }, errors::{error_generic, Result}, + extractor, interpreter::{exec_binary, exec_unary, merge_values}, prelude::Ranged, - NodeMeta, Return, + EventContext, NodeMeta, Return, }; pub(super) mod compiler; @@ -46,6 +47,7 @@ impl Vm { pub fn run<'run, 'prog, 'event>( &self, event: &mut Value<'event>, + ctx: &EventContext, program: &'run Program<'prog>, ) -> Result> where @@ -72,7 +74,7 @@ impl Vm { // ensure that the opcodes and meta are the same length assert_eq!(program.opcodes.len(), program.meta.len()); - root.run(event, &mut pc, &mut cc) + root.run(event, ctx, &mut pc, &mut cc) } } @@ -81,6 +83,7 @@ impl<'run, 'event> Scope<'run, 'event> { pub fn run<'prog>( &mut self, event: &'run mut Value<'event>, + ctx: &EventContext, pc: &mut usize, cc: &mut usize, ) -> Result> @@ -118,7 +121,13 @@ impl<'run, 'event> Scope<'run, '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, *pc, *cc)?; + if stack.len() < elements as usize { + return Err(format!("Stack underflow @{pc}:{cc}").into()); + } + let path = stack + .drain(stack.len() - (elements as usize)..) + .collect::>(); + nested_assign(path, &mut tmp, mid)?; let r: &mut Value = tmp .as_mut() .ok_or("this is nasty, we have a null pointer")?; @@ -134,7 +143,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, *pc, *cc)?; + if stack.len() < elements as usize { + return Err(format!("Stack underflow @{pc}:{cc}").into()); + } + let path = stack + .drain(stack.len() - (elements as usize)..) + .collect::>(); + + nested_assign(path, &mut tmp, mid)?; let r: &mut Value = tmp .as_mut() .ok_or("this is nasty, we have a null pointer")?; @@ -147,6 +163,7 @@ impl<'run, 'event> Scope<'run, 'event> { } }, + // Others Op::True => stack.push(Cow::Owned(Value::const_true())), Op::False => stack.push(Cow::Owned(Value::const_false())), Op::Null => stack.push(Cow::Owned(Value::null())), @@ -300,20 +317,6 @@ 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 = 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 = pop(&mut stack, *pc, *cc)?; let lhs = pop(&mut stack, *pc, *cc)?; @@ -333,6 +336,35 @@ impl<'run, 'event> Scope<'run, 'event> { self.reg.b1 = self.reg.v1.contains_key(key.try_as_str()?); } // record operations on scope + Op::TestExtractor { extractor } => { + let extractor: &_ = &self.program.extractors[extractor as usize]; + // FIXME: We don't always need the result + let extracted = extractor.extract(true, self.reg.v1.as_ref(), ctx); + match extracted { + extractor::Result::MatchNull => { + self.reg.b1 = true; + stack.push(Cow::Owned(Value::null())); + } + extractor::Result::Match(v) => { + self.reg.b1 = true; + stack.push(Cow::Owned(v)); + } + extractor::Result::NoMatch => self.reg.b1 = false, + extractor::Result::Err(e) => { + return Err(format!("extractor error: {e}").into()) + } + } + } + Op::TestPresent { elements } => { + if stack.len() < elements as usize { + return Err(format!("Stack underflow @{pc}:{cc}").into()); + } + let path = stack + .drain(stack.len() - (elements as usize)..) + .collect::>(); + self.reg.b1 = present(path, &self.reg.v1); + } + Op::TestIsU64 => { self.reg.b1 = self.reg.v1.is_u64(); } @@ -555,34 +587,24 @@ 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, - pc: usize, - cc: usize, -) -> Result<()> { - for _ in 0..elements { - let target = pop(stack, pc, cc)?; +fn nested_assign(path: Vec>, tmp: &mut *mut Value, mid: &NodeMeta) -> Result<()> { + for target in path { if let Some(idx) = target.as_usize() { - let array = tmp - .as_mut() + let array = unsafe { 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) { + *tmp = 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() + let map = unsafe { 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) { + *tmp = ptr::from_mut::(match map.get_mut(key) { Some(v) => v, None => map .entry(key.to_string().into()) @@ -595,6 +617,36 @@ unsafe fn nested_assign( Ok(()) } +/// 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. +fn present(keys: Vec>, val: &Value) -> bool { + let mut tmp = ptr::from_ref(val); + // the path is stored reversed on the stack so we have to approach it from back to front + for target in keys { + if let Some(idx) = target.as_usize() { + let Some(array) = unsafe { tmp.as_ref() }.and_then(|v| v.as_array()) else { + return false; + }; + let Some(v) = array.get(idx) else { + return false; + }; + tmp = ptr::from_ref(v); + } else if let Some(key) = target.as_str() { + let Some(map) = unsafe { tmp.as_ref() }.and_then(|v| v.as_object()) else { + return false; + }; + let Some(v) = map.get(key) else { + return false; + }; + tmp = ptr::from_ref(v); + } else { + return false; + } + } + true +} + #[inline] fn pop<'run, 'event>( stack: &mut Vec>>, diff --git a/tremor-script/src/vm/compiler.rs b/tremor-script/src/vm/compiler.rs index e159b44a49..caaaca933b 100644 --- a/tremor-script/src/vm/compiler.rs +++ b/tremor-script/src/vm/compiler.rs @@ -13,7 +13,7 @@ // limitations under the License. mod impls; -use crate::{ast::Script, errors::Result, NodeMeta}; +use crate::{ast::Script, errors::Result, extractor::Extractor, NodeMeta}; use simd_json_derive::Serialize; use std::{ collections::{BTreeMap, HashMap}, @@ -32,6 +32,7 @@ pub struct Compiler<'script> { jump_table: Vec, end_table: Vec, consts: Vec>, + extractors: Vec, keys: Vec>, max_locals: usize, pub(crate) comments: BTreeMap, @@ -58,6 +59,7 @@ impl<'script> Compiler<'script> { 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); + let extractors = mem::take(&mut self.extractors); for op in &mut opcodes { match op { @@ -78,6 +80,7 @@ impl<'script> Compiler<'script> { consts, keys, comments, + extractors, max_locals: self.max_locals, }) } @@ -92,6 +95,7 @@ impl<'script> Compiler<'script> { end_table: Vec::new(), consts: Vec::new(), keys: Vec::new(), + extractors: Vec::new(), max_locals: 0, comments: BTreeMap::new(), } @@ -140,6 +144,16 @@ impl<'script> Compiler<'script> { self.keys.push(key); (self.keys.len() - 1) as u32 } + #[allow(clippy::cast_possible_truncation)] + fn add_extractor(&mut self, extractor: Extractor) -> u32 { + for (idx, v) in self.extractors.iter().enumerate() { + if v == &extractor { + return idx as u32; + } + } + self.extractors.push(extractor); + (self.extractors.len() - 1) as u32 + } #[allow(clippy::cast_possible_truncation)] pub(crate) fn new_jump_point(&mut self) -> u32 { @@ -181,7 +195,7 @@ impl<'script> Default for Compiler<'script> { } } -#[derive(Debug, PartialEq, Clone, Default, Eq)] +#[derive(Debug, Clone, Default)] /// A compiler for tremor script pub struct Program<'script> { pub(crate) opcodes: Vec, @@ -189,6 +203,7 @@ pub struct Program<'script> { pub(crate) jump_table: HashMap, pub(crate) consts: Vec>, pub(crate) keys: Vec>, + pub(crate) extractors: Vec, pub(crate) max_locals: usize, pub(crate) comments: BTreeMap, } diff --git a/tremor-script/src/vm/compiler/impls.rs b/tremor-script/src/vm/compiler/impls.rs index f2af333bc7..60a78133a9 100644 --- a/tremor-script/src/vm/compiler/impls.rs +++ b/tremor-script/src/vm/compiler/impls.rs @@ -19,9 +19,10 @@ use tremor_value::Value; use crate::{ ast::{ ClauseGroup, Comprehension, DefaultCase, Expression, IfElse, Match, Path, PredicateClause, - Script, + Script, Segment, }, - errors::Result, + errors::{err_generic, Result}, + prelude::Ranged as _, vm::{ compiler::{Compilable, Compiler}, Op, @@ -336,3 +337,22 @@ where todo!() } } + +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(()) +} diff --git a/tremor-script/src/vm/compiler/impls/imut_expr.rs b/tremor-script/src/vm/compiler/impls/imut_expr.rs index 60bd0ba4b0..58b149297f 100644 --- a/tremor-script/src/vm/compiler/impls/imut_expr.rs +++ b/tremor-script/src/vm/compiler/impls/imut_expr.rs @@ -17,11 +17,11 @@ use crate::{ ast::{ raw::{BytesDataType, Endian}, ArrayPattern, ArrayPredicatePattern, AssignPattern, BaseExpr, BinOpKind, BooleanBinExpr, - BooleanBinOpKind, BytesPart, ClausePreCondition, Field, ImutExpr, Invoke, List, Merge, - Patch, PatchOperation, Pattern, PredicatePattern, Record, RecordPattern, Segment, - StrLitElement, StringLit, TestExpr, TuplePattern, + BooleanBinOpKind, BytesPart, ClausePreCondition, EventPath, Field, ImutExpr, Invoke, + InvokeAggr, List, LocalPath, Merge, Patch, PatchOperation, Path, Pattern, PredicatePattern, + Record, RecordPattern, Segment, StrLitElement, StringLit, TestExpr, TuplePattern, }, - errors::Result, + errors::{err_generic, Result}, vm::{ compiler::{Compilable, Compiler}, Op, @@ -29,6 +29,8 @@ use crate::{ NodeMeta, }; +use super::compile_segment_path; + #[allow(clippy::too_many_lines)] impl<'script> Compilable<'script> for ImutExpr<'script> { fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { @@ -65,16 +67,15 @@ impl<'script> Compilable<'script> for ImutExpr<'script> { ImutExpr::Literal(l) => { compiler.emit_const(l.value, &l.mid); } - ImutExpr::Present { - path: _path, - mid: _mid, - } => todo!(), + ImutExpr::Present { path, mid } => { + compile_present(compiler, &mid, path)?; + } 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!(), + ImutExpr::InvokeAggr(a) => a.compile(compiler)?, + ImutExpr::Recur(_r) => todo!(), ImutExpr::Bytes(b) => { let size = u32::try_from(b.value.len())?; let mid = b.mid; @@ -401,11 +402,18 @@ impl<'script> Compilable<'script> for PredicatePattern<'script> { compiler.emit(Op::SwapV1, &mid); match self { PredicatePattern::TildeEq { - assign, - lhs, - key, + lhs: _, + key: _, test, - } => todo!(), + } => { + let dst = compiler.new_jump_point(); + compiler.comment("Run tilde pattern"); + test.compile(compiler)?; + compiler.emit(Op::JumpFalse { dst }, &mid); + compiler.comment("Swap the value of the tilde pattern and the stored register"); + compiler.emit(Op::Swap, &mid); + compiler.set_jump_target(dst); + } PredicatePattern::Bin { lhs: _, key: _, @@ -477,7 +485,7 @@ impl<'script> Compilable<'script> for ArrayPredicatePattern<'script> { e.compile(compiler)?; compiler.emit(Op::TestEq, &mid); } - ArrayPredicatePattern::Tilde(_) => todo!(), + ArrayPredicatePattern::Tilde(p) => p.compile(compiler)?, ArrayPredicatePattern::Record(p) => p.compile(compiler)?, ArrayPredicatePattern::Ignore => compiler.emit(Op::TrueRB, &NodeMeta::dummy()), } @@ -511,7 +519,7 @@ impl<'script> Compilable<'script> for Pattern<'script> { impl<'script> Compilable<'script> for ClausePreCondition<'script> { fn compile_to_b(self, compiler: &mut Compiler<'script>) -> Result<()> { let ClausePreCondition { mut path } = self; - assert!(path.segments().len() == 1); + assert!(path.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); @@ -522,7 +530,11 @@ impl<'script> Compilable<'script> for ClausePreCondition<'script> { } fn compile(self, _compiler: &mut Compiler<'script>) -> Result<()> { - todo!() + err_generic( + &self.path, + &self.path, + &"Pre conditions are not supported in this context", + ) } } @@ -596,6 +608,11 @@ impl<'script> Compilable<'script> for Invoke<'script> { todo!() } } +impl<'script> Compilable<'script> for InvokeAggr { + fn compile(self, _compiler: &mut Compiler<'script>) -> Result<()> { + todo!() + } +} impl<'script> Compilable<'script> for RecordPattern<'script> { fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { @@ -627,7 +644,6 @@ impl<'script> Compilable<'script> for ArrayPattern<'script> { } impl<'script> Compilable<'script> for AssignPattern<'script> { - #[allow(clippy::cast_possible_truncation)] fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { let AssignPattern { id: _, @@ -637,15 +653,19 @@ impl<'script> Compilable<'script> for AssignPattern<'script> { // FIXME: want a MID let mid = NodeMeta::dummy(); compiler.comment("Assign pattern"); + let is_extract = matches!(pattern.as_ref(), &Pattern::Extract(_)); 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); + // extract patterns will store the value in the local stack if they match + if !is_extract { + compiler.comment("Store the value in the local"); + compiler.emit(Op::CopyV1, &mid); + } compiler.emit( Op::StoreLocal { - idx: idx as u32, + idx: u32::try_from(idx)?, elements: 0, }, &mid, @@ -700,7 +720,61 @@ impl<'script> Compilable<'script> for TuplePattern<'script> { } impl<'script> Compilable<'script> for TestExpr { - fn compile(self, _compiler: &mut Compiler<'script>) -> Result<()> { - todo!() + fn compile(self, compiler: &mut Compiler<'script>) -> Result<()> { + let TestExpr { + mid, + id: _, + test: _, + extractor, + } = self; + let extractor = compiler.add_extractor(extractor); + compiler.emit(Op::TestExtractor { extractor }, &mid); + Ok(()) + } +} + +#[allow(clippy::cast_possible_truncation)] +fn compile_present<'script>( + compiler: &mut Compiler<'script>, + mid: &NodeMeta, + path: Path<'script>, +) -> Result<()> { + let elements = u32::try_from(path.len())?; + let segments = match path { + Path::Local(LocalPath { idx, mid, segments }) => { + compiler.comment("Store local on stack"); + compiler.emit( + Op::LoadLocal { + idx: u32::try_from(idx)?, + }, + &mid, + ); + segments + } + Path::Event(EventPath { mid, segments }) => { + compiler.comment("Store event on stack"); + compiler.emit(Op::LoadEvent, &mid); + segments + } + Path::State(_p) => todo!(), + Path::Meta(_p) => todo!(), + Path::Expr(_p) => todo!(), + Path::Reserved(_p) => todo!(), + }; + dbg!(&segments); + compiler.comment("Swap in V1 to save the current value and load the base for the test"); + compiler.emit(Op::SwapV1, mid); + if !segments.is_empty() { + compiler.comment("Load the path"); + } + for s in segments { + compile_segment_path(compiler, s)?; } + compiler.comment("Test if the value is present"); + compiler.emit(Op::TestPresent { elements }, mid); + compiler.comment("Restore V1"); + compiler.emit(Op::LoadV1, mid); + compiler.comment("Save result"); + compiler.emit(Op::StoreRB, mid); + Ok(()) } diff --git a/tremor-script/src/vm/compiler/impls/mut_expr.rs b/tremor-script/src/vm/compiler/impls/mut_expr.rs index 21953c9fdb..3e7ceadc64 100644 --- a/tremor-script/src/vm/compiler/impls/mut_expr.rs +++ b/tremor-script/src/vm/compiler/impls/mut_expr.rs @@ -12,23 +12,23 @@ // 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}, - prelude::Ranged as _, + ast::{EmitExpr, Expr, Path}, + errors::Result, vm::{ compiler::{Compilable, Compiler}, Op, }, - NodeMeta, }; +use super::compile_segment_path; + 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)?, - Expr::Assign { mid, path, expr } => { - compile_assign(compiler, &mid, path, *expr)?; + Expr::Assign { mid: _, path, expr } => { + compile_assign(compiler, path, *expr)?; } Expr::AssignMoveLocal { mid: _, @@ -58,38 +58,19 @@ impl<'script> Compilable<'script> for EmitExpr<'script> { } } -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>, + mut path: Path<'script>, expr: Expr<'script>, ) -> Result<()> { expr.compile(compiler)?; + let elements = u16::try_from(path.len())?; + for s in path.segments_mut().drain(..) { + compile_segment_path(compiler, s)?; + } 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 { @@ -100,10 +81,6 @@ fn compile_assign<'script>( ); } 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!(), diff --git a/tremor-script/src/vm/op.rs b/tremor-script/src/vm/op.rs index 41acc447a1..ff28038d65 100644 --- a/tremor-script/src/vm/op.rs +++ b/tremor-script/src/vm/op.rs @@ -154,6 +154,13 @@ pub(crate) enum Op { key: u32, }, + TestExtractor { + extractor: u32, + }, + TestPresent { + elements: 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 InspectLen, @@ -240,6 +247,10 @@ impl Display for Op { Op::TestGte => write!(f, "test_gte"), Op::TestLt => write!(f, "test_lt"), Op::TestLte => write!(f, "test_lte"), + Op::TestExtractor { extractor } => { + write!(f, "{:30} {:<5}", "test_extractor", extractor) + } + Op::TestPresent { elements: depth } => write!(f, "{:30} {:<5}", "test_present", depth), Op::InspectLen => write!(f, "inspect_len"), diff --git a/tremor-script/src/vm/tests.rs b/tremor-script/src/vm/tests.rs index 54cf9a0579..001c7e0db6 100644 --- a/tremor-script/src/vm/tests.rs +++ b/tremor-script/src/vm/tests.rs @@ -65,7 +65,8 @@ fn run<'v>(p: &Program<'v>) -> Result> { } } }); - if let Return::Emit { value, .. } = vm.run(&mut event, p)? { + let ctx = EventContext::new(1, None); + if let Return::Emit { value, .. } = vm.run(&mut event, &ctx, p)? { Ok(value) } else { Err("Expected Emit".into()) @@ -556,3 +557,27 @@ fn test_local_array_assign_nested() -> Result<()> { assert_eq!(run(&p)?, literal!([-1, 2])); Ok(()) } + +#[test] +fn test_present() -> Result<()> { + let p = compile(false, r#"present event.object["a"][1] "#)?; + + assert_eq!(p.max_locals, 0); + assert_eq!( + p.opcodes, + &[ + LoadEvent, + SwapV1, + Const { idx: 0 }, + Const { idx: 1 }, + String { size: 1 }, + Const { idx: 2 }, + TestPresent { elements: 3 }, + LoadV1, + StoreRB + ] + ); + assert_eq!(run(&p)?, true); + + Ok(()) +} diff --git a/tremor-script/src/vm/tests/match_stmt.rs b/tremor-script/src/vm/tests/match_stmt.rs index ead5363d2d..fdd63b6d16 100644 --- a/tremor-script/src/vm/tests/match_stmt.rs +++ b/tremor-script/src/vm/tests/match_stmt.rs @@ -519,3 +519,102 @@ fn test_record_tuple() -> Result<()> { assert_eq!(run(&p)?, 2); Ok(()) } + +#[test] +fn extraxtor_top() -> Result<()> { + let p = compile( + false, + r#" + match "badger" of + case %{ } => 1 + case ~re|.*er.*| => 2 + case _ => 3 + end"#, + )?; + assert_eq!( + p.opcodes, + &[ + StoreV1, + Const { idx: 0 }, + String { size: 1 }, + LoadV1, + TestIsRecord, + JumpFalse { dst: 6 }, + JumpFalse { dst: 9 }, + Const { idx: 1 }, + Jump { dst: 14 }, + TestExtractor { extractor: 0 }, + JumpFalse { dst: 13 }, + Const { idx: 2 }, + Jump { dst: 14 }, + Const { idx: 3 }, + LoadV1, + SwapV1, + ] + ); + assert_eq!(run(&p)?, 2); + Ok(()) +} + +#[test] +fn extraxtor_nested() -> Result<()> { + let p = compile( + false, + r" + match event of + case r = %{string ~= re|snot|} => 1 + case r = %{string ~= re|ing|} => 2 + end", + )?; + assert_eq!( + p.opcodes, + &[ + StoreV1, + LoadEvent, + LoadV1, + TestIsRecord, + JumpFalse { dst: 13 }, + TestRecordContainsKey { key: 0 }, + JumpFalse { dst: 13 }, + GetKeyRegV1 { key: 0 }, + SwapV1, + TestExtractor { extractor: 0 }, + JumpFalse { dst: 12 }, + Swap, + LoadV1, + JumpFalse { dst: 16 }, + CopyV1, + StoreLocal { + elements: 0, + idx: 0, + }, + JumpFalse { dst: 19 }, + Const { idx: 0 }, + Jump { dst: 36 }, + TestIsRecord, + JumpFalse { dst: 29 }, + TestRecordContainsKey { key: 0 }, + JumpFalse { dst: 29 }, + GetKeyRegV1 { key: 0 }, + SwapV1, + TestExtractor { extractor: 1 }, + JumpFalse { dst: 28 }, + Swap, + LoadV1, + JumpFalse { dst: 32 }, + CopyV1, + StoreLocal { + elements: 0, + idx: 0, + }, + JumpFalse { dst: 35 }, + Const { idx: 1 }, + Jump { dst: 36 }, + Nop, + LoadV1, + SwapV1, + ] + ); + assert_eq!(run(&p)?, 2); + Ok(()) +}