From f2fcb95ad90fdecf4ff12f8969dc0cf1ed86e6bf Mon Sep 17 00:00:00 2001 From: stefan-gorules <127550877+stefan-gorules@users.noreply.github.com> Date: Wed, 18 Jun 2025 10:43:01 +0200 Subject: [PATCH] feat: assignment (#366) * feat: assignment * add tests --- core/expression/src/compiler/compiler.rs | 20 +- core/expression/src/compiler/error.rs | 3 + core/expression/src/compiler/opcode.rs | 6 + core/expression/src/functions/defs.rs | 6 +- core/expression/src/intellisense/mod.rs | 19 +- core/expression/src/intellisense/scope.rs | 8 +- .../src/intellisense/types/provider.rs | 65 ++- .../src/intellisense/types/type_info.rs | 8 +- core/expression/src/lexer/codes.rs | 2 +- core/expression/src/lexer/lexer.rs | 34 ++ core/expression/src/lexer/token.rs | 6 + core/expression/src/parser/ast.rs | 41 ++ core/expression/src/parser/parser.rs | 521 ++++++++++++------ core/expression/src/parser/standard.rs | 12 +- core/expression/src/parser/unary.rs | 17 +- core/expression/src/variable/mod.rs | 76 +++ core/expression/src/variable/types/conv.rs | 7 +- core/expression/src/variable/types/mod.rs | 4 +- core/expression/src/variable/types/util.rs | 148 ++++- core/expression/src/vm/vm.rs | 51 +- core/expression/tests/data/standard.csv | 60 +- 21 files changed, 867 insertions(+), 247 deletions(-) diff --git a/core/expression/src/compiler/compiler.rs b/core/expression/src/compiler/compiler.rs index daf6c681..b03fae0a 100644 --- a/core/expression/src/compiler/compiler.rs +++ b/core/expression/src/compiler/compiler.rs @@ -117,7 +117,7 @@ impl<'arena, 'bytecode_ref> CompilerInner<'arena, 'bytecode_ref> { match node { Node::Root => Some(vec![FetchFastTarget::Root]), Node::Identifier(v) => Some(vec![ - FetchFastTarget::Root, + FetchFastTarget::Begin, FetchFastTarget::String(Arc::from(*v)), ]), Node::Member { node, property } => { @@ -171,6 +171,24 @@ impl<'arena, 'bytecode_ref> CompilerInner<'arena, 'bytecode_ref> { self.emit(Opcode::PushNumber(Decimal::from(v.len()))); Ok(self.emit(Opcode::Object)) } + Node::Assignments { list, output } => { + self.emit(Opcode::AssignedObjectBegin); + list.iter().try_for_each(|&(key, value)| { + self.compile_node(key).map(|_| ())?; + self.compile_node(value).map(|_| ())?; + self.emit(Opcode::AssignedObjectStep); + + Ok(()) + })?; + + if let Some(output) = output { + self.compile_node(output).map(|_| ())?; + } + + Ok(self.emit(Opcode::AssignedObjectEnd { + with_return: output.is_some(), + })) + } Node::Identifier(v) => Ok(self.emit(Opcode::FetchEnv(Arc::from(*v)))), Node::Closure(v) => self.compile_node(v), Node::Parenthesized(v) => self.compile_node(v), diff --git a/core/expression/src/compiler/error.rs b/core/expression/src/compiler/error.rs index 1d97bb36..803df13b 100644 --- a/core/expression/src/compiler/error.rs +++ b/core/expression/src/compiler/error.rs @@ -22,6 +22,9 @@ pub enum CompilerError { #[error("Invalid method call `{name}`: {message}")] InvalidMethodCall { name: String, message: String }, + + #[error("Unexpected assigned object")] + UnexpectedAssignedObject, } pub(crate) type CompilerResult = Result; diff --git a/core/expression/src/compiler/opcode.rs b/core/expression/src/compiler/opcode.rs index c7e17939..a08ffedd 100644 --- a/core/expression/src/compiler/opcode.rs +++ b/core/expression/src/compiler/opcode.rs @@ -7,6 +7,7 @@ use strum_macros::Display; #[derive(Debug, Clone, PartialEq, Eq)] pub enum FetchFastTarget { Root, + Begin, String(Arc), Number(u32), } @@ -40,6 +41,11 @@ pub enum Opcode { Slice, Array, Object, + AssignedObjectBegin, + AssignedObjectStep, + AssignedObjectEnd { + with_return: bool, + }, Len, IncrementIt, IncrementCount, diff --git a/core/expression/src/functions/defs.rs b/core/expression/src/functions/defs.rs index e692a283..2e7ef090 100644 --- a/core/expression/src/functions/defs.rs +++ b/core/expression/src/functions/defs.rs @@ -7,7 +7,7 @@ use std::rc::Rc; pub trait FunctionDefinition { fn required_parameters(&self) -> usize; fn optional_parameters(&self) -> usize; - fn check_types(&self, args: &[Rc]) -> FunctionTypecheck; + fn check_types(&self, args: &[VariableType]) -> FunctionTypecheck; fn call(&self, args: Arguments) -> anyhow::Result; fn param_type(&self, index: usize) -> Option; fn param_type_str(&self, index: usize) -> String; @@ -52,7 +52,7 @@ impl FunctionDefinition for StaticFunction { 0 } - fn check_types(&self, args: &[Rc]) -> FunctionTypecheck { + fn check_types(&self, args: &[VariableType]) -> FunctionTypecheck { let mut typecheck = FunctionTypecheck::default(); typecheck.return_type = self.signature.return_type.clone(); @@ -135,7 +135,7 @@ impl FunctionDefinition for CompositeFunction { max - required_params } - fn check_types(&self, args: &[Rc]) -> FunctionTypecheck { + fn check_types(&self, args: &[VariableType]) -> FunctionTypecheck { let mut typecheck = FunctionTypecheck::default(); if self.signatures.is_empty() { typecheck.general = Some("No implementation".to_string()); diff --git a/core/expression/src/intellisense/mod.rs b/core/expression/src/intellisense/mod.rs index e84f7503..b43e24a9 100644 --- a/core/expression/src/intellisense/mod.rs +++ b/core/expression/src/intellisense/mod.rs @@ -6,7 +6,6 @@ use crate::parser::{Node, Parser}; use crate::variable::VariableType; use serde::Serialize; use std::cell::RefCell; -use std::rc::Rc; mod scope; mod types; @@ -15,7 +14,7 @@ mod types; #[serde(rename_all = "camelCase")] pub struct IntelliSenseToken { pub span: (u32, u32), - pub kind: Rc, + pub kind: VariableType, pub node_kind: &'static str, pub error: Option, } @@ -50,9 +49,9 @@ impl<'arena> IntelliSense<'arena> { let type_data = TypesProvider::generate( ast, IntelliSenseScope { - pointer_data: data, - root_data: data, - current_data: data, + pointer_data: data.shallow_clone(), + root_data: data.shallow_clone(), + current_data: data.shallow_clone(), }, ); @@ -71,7 +70,7 @@ impl<'arena> IntelliSense<'arena> { error: typ.map(|t| t.error.clone()).flatten(), kind: typ .map(|t| t.kind.clone()) - .unwrap_or_else(|| Rc::new(VariableType::Any)), + .unwrap_or_else(|| VariableType::Any), }); }); @@ -96,9 +95,9 @@ impl<'arena> IntelliSense<'arena> { let type_data = TypesProvider::generate( ast, IntelliSenseScope { - pointer_data: data, - root_data: data, - current_data: data, + pointer_data: data.shallow_clone(), + root_data: data.shallow_clone(), + current_data: data.shallow_clone(), }, ); @@ -114,7 +113,7 @@ impl<'arena> IntelliSense<'arena> { error: typ.map(|t| t.error.clone()).flatten(), kind: typ .map(|t| t.kind.clone()) - .unwrap_or_else(|| Rc::new(VariableType::Any)), + .unwrap_or_else(|| VariableType::Any), }); }); diff --git a/core/expression/src/intellisense/scope.rs b/core/expression/src/intellisense/scope.rs index 8cf40777..07860d29 100644 --- a/core/expression/src/intellisense/scope.rs +++ b/core/expression/src/intellisense/scope.rs @@ -1,8 +1,8 @@ use crate::variable::VariableType; #[derive(Clone, Debug)] -pub struct IntelliSenseScope<'a> { - pub root_data: &'a VariableType, - pub current_data: &'a VariableType, - pub pointer_data: &'a VariableType, +pub struct IntelliSenseScope { + pub root_data: VariableType, + pub current_data: VariableType, + pub pointer_data: VariableType, } diff --git a/core/expression/src/intellisense/types/provider.rs b/core/expression/src/intellisense/types/provider.rs index 4b6d819d..bcea1f4e 100644 --- a/core/expression/src/intellisense/types/provider.rs +++ b/core/expression/src/intellisense/types/provider.rs @@ -5,6 +5,7 @@ use crate::intellisense::types::type_info::TypeInfo; use crate::lexer::{ArithmeticOperator, ComparisonOperator, LogicalOperator, Operator}; use crate::parser::Node; use crate::variable::VariableType; +use std::cell::RefCell; use std::collections::HashMap; use std::iter::once; use std::ops::Deref; @@ -52,14 +53,14 @@ impl TypesProvider { } #[cfg_attr(feature = "stack-protection", recursive::recursive)] - fn determine(&mut self, node: &Node, scope: IntelliSenseScope) -> TypeInfo { + fn determine(&mut self, node: &Node, mut scope: IntelliSenseScope) -> TypeInfo { #[allow(non_snake_case)] let V = |vt: VariableType| TypeInfo::from(vt); #[allow(non_snake_case)] let Const = |v: &str| TypeInfo::from(VariableType::Const(Rc::from(v))); #[allow(non_snake_case)] let Error = |error: String| TypeInfo { - kind: Rc::from(VariableType::Any), + kind: VariableType::Any, error: Some(error), }; @@ -111,7 +112,7 @@ impl TypesProvider { } Node::Array(items) => { - let mut type_list: Vec> = items + let mut type_list: Vec = items .iter() .map(|n| self.determine(n, scope.clone()).kind) .collect(); @@ -119,7 +120,7 @@ impl TypesProvider { let all_same = type_list.iter().all(|t| Some(t) == first.as_ref()); match (first, all_same) { - (Some(typ), true) => V(VariableType::Array(typ)), + (Some(typ), true) => V(VariableType::Array(Rc::new(typ))), _ => V(VariableType::Array(Rc::new(VariableType::Any))), } } @@ -136,14 +137,41 @@ impl TypesProvider { }) .collect(); - V(VariableType::Object(obj_type)) + V(VariableType::Object(Rc::new(RefCell::new(obj_type)))) } + + Node::Assignments { list, output } => { + let obj_type: HashMap, VariableType> = list + .iter() + .filter_map(|(k, v)| { + let key_type = self.determine(k, scope.clone()).as_const_str()?; + let value_type = self.determine(v, scope.clone()); + + if let Some(new_var) = scope + .root_data + .dot_insert_detached(key_type.as_ref(), value_type.kind.shallow_clone()) + { + println!("NewVar: {new_var:?}"); + scope.root_data = new_var; + }; + + Some((key_type, value_type.kind)) + }) + .collect(); + + if let Some(output) = output { + self.determine(output, scope.clone()) + } else { + V(VariableType::Object(Rc::new(RefCell::new(obj_type)))) + } + } + Node::Identifier(i) => TypeInfo::from(scope.root_data.get(i)), Node::Member { node, property } => { let node_type = self.determine(node, scope.clone()); let property_type = self.determine(property, scope.clone()); - match node_type.kind.as_ref() { + match &node_type.kind { VariableType::Any => V(VariableType::Any), VariableType::Null => V(VariableType::Null), VariableType::Array(inner) => { @@ -164,10 +192,11 @@ impl TypesProvider { ); } + let obj = obj.borrow(); match property_type.as_const_str() { None => V(VariableType::Any), Some(key) => TypeInfo::from( - obj.get(&key).cloned().unwrap_or(Rc::new(VariableType::Any)), + obj.get(&key).cloned().unwrap_or(VariableType::Any), ), } } @@ -287,7 +316,7 @@ impl TypesProvider { let true_type = self.determine(on_true, scope.clone()); let false_type = self.determine(on_false, scope.clone()); - V(true_type.kind.merge(false_type.kind.as_ref())) + V(true_type.kind.merge(&false_type.kind)) } Node::Unary { node, operator } => { let node_type = self.determine(node, scope.clone()); @@ -325,7 +354,9 @@ impl TypesProvider { | Operator::Comma | Operator::Slice | Operator::Dot - | Operator::QuestionMark => Error("Unsupported operator".to_string()), + | Operator::QuestionMark + | Operator::Assign + | Operator::Semi => Error("Unsupported operator".to_string()), } } Node::Interval { left, right, .. } => { @@ -352,7 +383,7 @@ impl TypesProvider { V(VariableType::Interval) } Node::FunctionCall { arguments, kind } => { - let mut type_list: Vec> = arguments + let mut type_list: Vec = arguments .iter() .map(|n| self.determine(n, scope.clone()).kind) .collect(); @@ -362,7 +393,7 @@ impl TypesProvider { let new_type = self.determine( arguments[1], IntelliSenseScope { - pointer_data: &ptr_type, + pointer_data: ptr_type.deref().clone(), current_data: scope.current_data, root_data: scope.root_data, }, @@ -383,7 +414,7 @@ impl TypesProvider { } TypeInfo { - kind: Rc::new(typecheck.return_type), + kind: typecheck.return_type, error: typecheck.general, } } @@ -423,7 +454,9 @@ impl TypesProvider { ClosureFunction::One => V(VariableType::Bool), ClosureFunction::Filter => TypeInfo::from(type_list[0].clone()), ClosureFunction::Count => V(VariableType::Number), - ClosureFunction::Map => V(VariableType::Array(type_list[1].clone())), + ClosureFunction::Map => { + V(VariableType::Array(Rc::new(type_list[1].clone()))) + } ClosureFunction::FlatMap => V(VariableType::Any), } } @@ -435,7 +468,7 @@ impl TypesProvider { kind, } => { let this_type = self.determine(this, scope.clone()); - let type_list: Vec> = once(this_type.kind) + let type_list: Vec = once(this_type.kind) .chain( arguments .iter() @@ -457,7 +490,7 @@ impl TypesProvider { } TypeInfo { - kind: Rc::new(typecheck.return_type), + kind: typecheck.return_type, error: typecheck.general, } } @@ -465,7 +498,7 @@ impl TypesProvider { Node::Parenthesized(c) => self.determine(c, scope.clone()), Node::Error { node, error } => match node { None => TypeInfo { - kind: Rc::new(VariableType::Any), + kind: VariableType::Any, error: Some(error.to_string()), }, Some(n) => { diff --git a/core/expression/src/intellisense/types/type_info.rs b/core/expression/src/intellisense/types/type_info.rs index b1e028e0..19b35e7e 100644 --- a/core/expression/src/intellisense/types/type_info.rs +++ b/core/expression/src/intellisense/types/type_info.rs @@ -5,7 +5,7 @@ use std::rc::Rc; #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct TypeInfo { - pub(crate) kind: Rc, + pub(crate) kind: VariableType, pub(crate) error: Option, } @@ -26,7 +26,7 @@ impl Display for TypeInfo { impl Default for TypeInfo { fn default() -> Self { Self { - kind: Rc::new(VariableType::Any), + kind: VariableType::Any, error: None, } } @@ -35,7 +35,7 @@ impl Default for TypeInfo { impl From for TypeInfo { fn from(value: VariableType) -> Self { Self { - kind: Rc::new(value), + kind: value, error: None, } } @@ -44,7 +44,7 @@ impl From for TypeInfo { impl From> for TypeInfo { fn from(value: Rc) -> Self { Self { - kind: value, + kind: value.deref().clone(), error: None, } } diff --git a/core/expression/src/lexer/codes.rs b/core/expression/src/lexer/codes.rs index f464439d..0f7a0cc5 100644 --- a/core/expression/src/lexer/codes.rs +++ b/core/expression/src/lexer/codes.rs @@ -2,7 +2,7 @@ macro_rules! token_type { ("space") => { ' ' | '\n' | '\t' }; ("digit") => { '0'..='9' }; ("bracket") => { '(' | ')' | '[' | ']' | '{' | '}' }; - ("cmp_operator") => { '>' | '<' | '!' | '=' }; + ("cmp_operator") => { '>' | '<' | '!' }; ("operator") => { ',' | ':' | '+' | '-' | '/' | '*' | '^' | '%' }; ("alpha") => { 'A'..='Z' | 'a'..='z' | '$' | '_' | '#' }; ("alphanumeric") => { 'A'..='Z' | 'a'..='z' | '0'..='9' | '$' | '_' | '#' }; diff --git a/core/expression/src/lexer/lexer.rs b/core/expression/src/lexer/lexer.rs index c9d60466..2c2a9812 100644 --- a/core/expression/src/lexer/lexer.rs +++ b/core/expression/src/lexer/lexer.rs @@ -63,8 +63,10 @@ impl<'arena, 'self_ref> Scanner<'arena, 'self_ref> { token_type!("cmp_operator") => self.cmp_operator(), token_type!("operator") => self.operator(), token_type!("question_mark") => self.question_mark(), + '=' => self.equals(), '`' => self.template_string(), '.' => self.dot(), + ';' => self.semi(), token_type!("alpha") => self.identifier(), _ => Err(LexerError::UnmatchedSymbol { symbol: s, @@ -317,6 +319,38 @@ impl<'arena, 'self_ref> Scanner<'arena, 'self_ref> { Ok(()) } + fn semi(&mut self) -> LexerResult<()> { + let (start, _) = self.next()?; + self.push(Token { + kind: TokenKind::Operator(Operator::Semi), + span: (start as u32, (start + 1) as u32), + value: &self.source[start..=start], + }); + + Ok(()) + } + + fn equals(&mut self) -> LexerResult<()> { + let (start, _) = self.next()?; + let Some((end, _)) = self.cursor.next_if(|c| c == '=') else { + self.push(Token { + kind: TokenKind::Operator(Operator::Assign), + span: (start as u32, (start + 1) as u32), + value: &self.source[start..=start], + }); + + return Ok(()); + }; + + self.push(Token { + kind: TokenKind::Operator(Operator::Comparison(ComparisonOperator::Equal)), + span: (start as u32, (end + 1) as u32), + value: &self.source[start..=end], + }); + + Ok(()) + } + fn question_mark(&mut self) -> LexerResult<()> { let (start, _) = self.next()?; let mut kind = TokenKind::Operator(Operator::QuestionMark); diff --git a/core/expression/src/lexer/token.rs b/core/expression/src/lexer/token.rs index eec29659..bc13971e 100644 --- a/core/expression/src/lexer/token.rs +++ b/core/expression/src/lexer/token.rs @@ -75,6 +75,8 @@ pub enum Operator { Slice, // : Dot, // . QuestionMark, // ? + Assign, // = + Semi, // ; } impl Display for Operator { @@ -88,6 +90,8 @@ impl Display for Operator { Operator::Slice => write!(f, ":"), Operator::Dot => write!(f, "."), Operator::QuestionMark => write!(f, "?"), + Operator::Assign => write!(f, "="), + Operator::Semi => write!(f, ";"), } } } @@ -219,6 +223,8 @@ impl Operator { Operator::Slice => 21, Operator::Dot => 22, Operator::QuestionMark => 23, + Operator::Assign => 24, + Operator::Semi => 25, } } } diff --git a/core/expression/src/parser/ast.rs b/core/expression/src/parser/ast.rs index bb0e30cf..42681a8e 100644 --- a/core/expression/src/parser/ast.rs +++ b/core/expression/src/parser/ast.rs @@ -1,5 +1,6 @@ use crate::functions::{FunctionKind, MethodKind}; use crate::lexer::{Bracket, Operator}; +use bumpalo::collections::Vec as BumpVec; use rust_decimal::Decimal; use std::cell::Cell; use strum_macros::IntoStaticStr; @@ -15,6 +16,10 @@ pub enum Node<'a> { Pointer, Array(&'a [&'a Node<'a>]), Object(&'a [(&'a Node<'a>, &'a Node<'a>)]), + Assignments { + list: &'a [(&'a Node<'a>, &'a Node<'a>)], + output: Option<&'a Node<'a>>, + }, Identifier(&'a str), Closure(&'a Node<'a>), Parenthesized(&'a Node<'a>), @@ -91,6 +96,16 @@ impl<'a> Node<'a> { k.walk(func.clone()); v.walk(func.clone()); }), + Node::Assignments { list, output } => { + list.iter().for_each(|(k, v)| { + k.walk(func.clone()); + v.walk(func.clone()); + }); + + if let Some(output) = output { + output.walk(func.clone()); + } + } Node::Closure(closure) => closure.walk(func.clone()), Node::Parenthesized(c) => c.walk(func.clone()), Node::Member { node, property } => { @@ -172,6 +187,32 @@ impl<'a> Node<'a> { _ => None, } } + + pub(crate) fn member_key(&self, vec: &mut BumpVec<&'a str>) -> Option<()> { + match self { + Node::Member { node, property } => { + node.member_key(vec)?; + + let property_key = match property { + Node::String(key) => Some(*key), + Node::Root => Some("$root"), + _ => None, + }?; + + vec.push(property_key); + Some(()) + } + Node::Identifier(name) => { + vec.push(name); + Some(()) + } + Node::Root => { + vec.push("$root"); + Some(()) + } + _ => None, + } + } } #[derive(Debug, PartialEq, Eq, Clone, Error)] diff --git a/core/expression/src/parser/parser.rs b/core/expression/src/parser/parser.rs index e42e40c0..7c303504 100644 --- a/core/expression/src/parser/parser.rs +++ b/core/expression/src/parser/parser.rs @@ -316,7 +316,7 @@ impl<'arena, 'token_ref, Flavor> Parser<'arena, 'token_ref, Flavor> { }) } - pub(crate) fn template_string(&self, expression_parser: F) -> &'arena Node<'arena> + pub(crate) fn template_string(&self, expression_parser: &F) -> &'arena Node<'arena> where F: Fn(ParserContext) -> &'arena Node<'arena>, { @@ -384,7 +384,7 @@ impl<'arena, 'token_ref, Flavor> Parser<'arena, 'token_ref, Flavor> { &self, node: &'arena Node, method_name: &Token, - expression_parser: F, + expression_parser: &F, ) -> &'arena Node<'arena> where F: Fn(ParserContext) -> &'arena Node<'arena>, @@ -433,187 +433,212 @@ impl<'arena, 'token_ref, Flavor> Parser<'arena, 'token_ref, Flavor> { }, ); - self.with_postfix(method_node, expression_parser) + let (node, _) = self.with_postfix(method_node, expression_parser); + node + } + + pub(crate) fn with_member_access( + &self, + node: &'arena Node<'arena>, + expression_parser: &F, + ) -> &'arena Node<'arena> + where + F: Fn(ParserContext) -> &'arena Node<'arena>, + { + self.next(); + let property_token = self.current(); + self.next(); + + let property = match property_token { + None => self.error_with_node( + AstNodeError::Custom { + message: afmt!(self, "Expected a property"), + span: (self.prev_token_end(), self.prev_token_end()), + }, + node, + ), + Some(t) => match is_valid_property(t) { + true => { + if self.current_kind() == Some(&TokenKind::Bracket(Bracket::LeftParenthesis)) { + return self.method_call(node, t, expression_parser); + } + + self.node(Node::String(t.value), |_| NodeMetadata { span: t.span }) + } + false => { + self.set_position(self.position() - 1); + self.error_with_node( + AstNodeError::InvalidProperty { + property: afmt!(self, "{}", t.value), + span: t.span, + }, + node, + ) + } + }, + }; + + self.node(Node::Member { node, property }, |h| NodeMetadata { + span: h.span(node, property).unwrap_or_default(), + }) + } + + pub(crate) fn with_property_access( + &self, + node: &'arena Node<'arena>, + expression_parser: &F, + ) -> &'arena Node<'arena> + where + F: Fn(ParserContext) -> &'arena Node<'arena>, + { + self.next(); + let mut from: Option<&'arena Node<'arena>> = None; + let mut to: Option<&'arena Node<'arena>> = None; + + let Some(mut c) = self.current() else { + return self.error_with_node( + AstNodeError::Custom { + message: afmt!(self, "Expected a property"), + span: (self.prev_token_end(), self.prev_token_end()), + }, + node, + ); + }; + + if c.kind == TokenKind::Operator(Operator::Slice) { + self.next(); + + let Some(cc) = self.current() else { + return self.error_with_node( + AstNodeError::Custom { + message: afmt!(self, "Unexpected token"), + span: (self.prev_token_end(), self.prev_token_end()), + }, + node, + ); + }; + c = cc; + + if c.kind != TokenKind::Bracket(Bracket::RightSquareBracket) { + to = Some(expression_parser(ParserContext::Global)); + } + + expect!(self, TokenKind::Bracket(Bracket::RightSquareBracket)); + self.node(Node::Slice { node, to, from }, |h| NodeMetadata { + span: ( + h.metadata(node).map(|m| m.span.0).unwrap_or_default(), + self.prev_token_end(), + ), + }) + } else { + let from_node = expression_parser(ParserContext::Global); + from = Some(from_node); + let Some(cc) = self.current() else { + return self.error_with_node( + AstNodeError::Custom { + message: afmt!(self, "Unexpected token"), + span: (self.prev_token_end(), self.prev_token_end()), + }, + self.node( + Node::Member { + node, + property: from_node, + }, + |h| NodeMetadata { + span: h.span(node, from_node).unwrap_or_default(), + }, + ), + ); + }; + c = cc; + + if c.kind == TokenKind::Operator(Operator::Slice) { + self.next(); + let Some(cc) = self.current() else { + return self.error_with_node( + AstNodeError::Custom { + message: afmt!(self, "Invalid slice syntax"), + span: (self.prev_token_end(), self.prev_token_end()), + }, + self.node(Node::Slice { node, from, to }, |h| NodeMetadata { + span: h.span(node, from_node).unwrap_or_default(), + }), + ); + }; + c = cc; + + if c.kind != TokenKind::Bracket(Bracket::RightSquareBracket) { + to = Some(expression_parser(ParserContext::Global)); + } + + expect!(self, TokenKind::Bracket(Bracket::RightSquareBracket)); + self.node(Node::Slice { node, from, to }, |h| NodeMetadata { + span: ( + h.metadata(node).map(|m| m.span.0).unwrap_or_default(), + self.prev_token_end(), + ), + }) + } else { + // Slice operator [:] was not found, + // it should be just an index node. + expect!(self, TokenKind::Bracket(Bracket::RightSquareBracket)); + self.node( + Node::Member { + node, + property: from.unwrap_or_else(|| { + return self.error_with_node( + AstNodeError::Custom { + message: afmt!(self, "Invalid index property"), + span: (self.prev_token_end(), self.prev_token_end()), + }, + node, + ); + }), + }, + |h| NodeMetadata { + span: ( + h.metadata(node).map(|m| m.span.0).unwrap_or_default(), + self.prev_token_end(), + ), + }, + ) + } + } } pub(crate) fn with_postfix( &self, node: &'arena Node<'arena>, - expression_parser: F, - ) -> &'arena Node<'arena> + expression_parser: &F, + ) -> (&'arena Node<'arena>, PostfixCombination) where F: Fn(ParserContext) -> &'arena Node<'arena>, { let Some(postfix_token) = self.current() else { - return node; + return (node, PostfixCombination::Mixed); }; let postfix_kind = PostfixKind::from(postfix_token); - let processed_token = match postfix_kind { - PostfixKind::Other => return node, - PostfixKind::MemberAccess => { - self.next(); - let property_token = self.current(); - self.next(); - - let property = match property_token { - None => self.error_with_node( - AstNodeError::Custom { - message: afmt!(self, "Expected a property"), - span: (self.prev_token_end(), self.prev_token_end()), - }, - node, - ), - Some(t) => match is_valid_property(t) { - true => { - if self.current_kind() - == Some(&TokenKind::Bracket(Bracket::LeftParenthesis)) - { - return self.method_call(node, t, expression_parser); - } - - self.node(Node::String(t.value), |_| NodeMetadata { span: t.span }) - } - false => { - self.set_position(self.position() - 1); - self.error_with_node( - AstNodeError::InvalidProperty { - property: afmt!(self, "{}", t.value), - span: t.span, - }, - node, - ) - } - }, - }; - - self.node(Node::Member { node, property }, |h| NodeMetadata { - span: h.span(node, property).unwrap_or_default(), - }) - } - PostfixKind::PropertyAccess => { - self.next(); - let mut from: Option<&'arena Node<'arena>> = None; - let mut to: Option<&'arena Node<'arena>> = None; - - let Some(mut c) = self.current() else { - return self.error_with_node( - AstNodeError::Custom { - message: afmt!(self, "Expected a property"), - span: (self.prev_token_end(), self.prev_token_end()), - }, - node, - ); - }; - - if c.kind == TokenKind::Operator(Operator::Slice) { - self.next(); - - let Some(cc) = self.current() else { - return self.error_with_node( - AstNodeError::Custom { - message: afmt!(self, "Unexpected token"), - span: (self.prev_token_end(), self.prev_token_end()), - }, - node, - ); - }; - c = cc; - - if c.kind != TokenKind::Bracket(Bracket::RightSquareBracket) { - to = Some(expression_parser(ParserContext::Global)); - } - - expect!(self, TokenKind::Bracket(Bracket::RightSquareBracket)); - self.node(Node::Slice { node, to, from }, |h| NodeMetadata { - span: ( - h.metadata(node).map(|m| m.span.0).unwrap_or_default(), - self.prev_token_end(), - ), - }) - } else { - let from_node = expression_parser(ParserContext::Global); - from = Some(from_node); - let Some(cc) = self.current() else { - return self.error_with_node( - AstNodeError::Custom { - message: afmt!(self, "Unexpected token"), - span: (self.prev_token_end(), self.prev_token_end()), - }, - self.node( - Node::Member { - node, - property: from_node, - }, - |h| NodeMetadata { - span: h.span(node, from_node).unwrap_or_default(), - }, - ), - ); - }; - c = cc; - - if c.kind == TokenKind::Operator(Operator::Slice) { - self.next(); - let Some(cc) = self.current() else { - return self.error_with_node( - AstNodeError::Custom { - message: afmt!(self, "Invalid slice syntax"), - span: (self.prev_token_end(), self.prev_token_end()), - }, - self.node(Node::Slice { node, from, to }, |h| NodeMetadata { - span: h.span(node, from_node).unwrap_or_default(), - }), - ); - }; - c = cc; - - if c.kind != TokenKind::Bracket(Bracket::RightSquareBracket) { - to = Some(expression_parser(ParserContext::Global)); - } - - expect!(self, TokenKind::Bracket(Bracket::RightSquareBracket)); - self.node(Node::Slice { node, from, to }, |h| NodeMetadata { - span: ( - h.metadata(node).map(|m| m.span.0).unwrap_or_default(), - self.prev_token_end(), - ), - }) - } else { - // Slice operator [:] was not found, - // it should be just an index node. - expect!(self, TokenKind::Bracket(Bracket::RightSquareBracket)); - self.node( - Node::Member { - node, - property: from.unwrap_or_else(|| { - return self.error_with_node( - AstNodeError::Custom { - message: afmt!(self, "Invalid index property"), - span: (self.prev_token_end(), self.prev_token_end()), - }, - node, - ); - }), - }, - |h| NodeMetadata { - span: ( - h.metadata(node).map(|m| m.span.0).unwrap_or_default(), - self.prev_token_end(), - ), - }, - ) - } - } - } + let (processed_token, processed_combination) = match postfix_kind { + PostfixKind::Other => return (node, PostfixCombination::Inherit), + PostfixKind::PropertyAccess => ( + self.with_member_access(node, expression_parser), + PostfixCombination::Property, + ), + PostfixKind::ComputedAccess => ( + self.with_property_access(node, expression_parser), + PostfixCombination::Computed, + ), }; - self.with_postfix(processed_token, expression_parser) + let (node, combination) = self.with_postfix(processed_token, expression_parser); + + (node, combination.and(processed_combination)) } /// Closure - pub(crate) fn closure(&self, expression_parser: F) -> &'arena Node<'arena> + pub(crate) fn closure(&self, expression_parser: &F) -> &'arena Node<'arena> where F: Fn(ParserContext) -> &'arena Node<'arena>, { @@ -630,7 +655,7 @@ impl<'arena, 'token_ref, Flavor> Parser<'arena, 'token_ref, Flavor> { /// Identifier expression /// Either or - pub(crate) fn identifier(&self, expression_parser: F) -> &'arena Node<'arena> + pub(crate) fn identifier(&self, expression_parser: &F) -> &'arena Node<'arena> where F: Fn(ParserContext) -> &'arena Node<'arena>, { @@ -674,7 +699,25 @@ impl<'arena, 'token_ref, Flavor> Parser<'arena, 'token_ref, Flavor> { }), }; - return self.with_postfix(identifier_node, expression_parser); + let (identifier_with_postfix, combination) = + self.with_postfix(identifier_node, &expression_parser); + if let Some(&TokenKind::Operator(Operator::Assign)) = self.current_kind() { + if combination != PostfixCombination::Property + && combination != PostfixCombination::Inherit + { + return self.error_with_node( + AstNodeError::Custom { + span: identifier_with_postfix.span().unwrap_or_default(), + message: "Only property access is allowed during assignment", + }, + identifier_with_postfix, + ); + } + + return self.assigned_object(identifier_with_postfix, expression_parser); + } + + return identifier_with_postfix; } // Potentially it might be a built-in expression @@ -734,16 +777,17 @@ impl<'arena, 'token_ref, Flavor> Parser<'arena, 'token_ref, Flavor> { } }; - self.with_postfix( + let (node, _) = self.with_postfix( self.node(function_node, |_| NodeMetadata { span: (identifier_token.span.0, self.prev_token_end()), }), expression_parser, - ) + ); + node } /// Interval node - pub(crate) fn interval(&self, expression_parser: F) -> Option<&'arena Node<'arena>> + pub(crate) fn interval(&self, expression_parser: &F) -> Option<&'arena Node<'arena>> where F: Fn(ParserContext) -> &'arena Node<'arena>, { @@ -799,11 +843,12 @@ impl<'arena, 'token_ref, Flavor> Parser<'arena, 'token_ref, Flavor> { }, ); - Some(self.with_postfix(interval_node, expression_parser)) + let (node, _) = self.with_postfix(interval_node, &expression_parser); + Some(node) } /// Array nodes - pub(crate) fn array(&self, expression_parser: F) -> &'arena Node<'arena> + pub(crate) fn array(&self, expression_parser: &F) -> &'arena Node<'arena> where F: Fn(ParserContext) -> &'arena Node<'arena>, { @@ -843,15 +888,110 @@ impl<'arena, 'token_ref, Flavor> Parser<'arena, 'token_ref, Flavor> { let node = Node::Array(nodes.into_bump_slice()); - self.with_postfix( + let (node, _) = self.with_postfix( self.node(node, |_| NodeMetadata { span: (current_token.span.0, self.prev_token_end()), }), - expression_parser, + &expression_parser, + ); + node + } + + pub(crate) fn assigned_object( + &self, + starting_key: &'arena Node<'arena>, + expression_parser: &F, + ) -> &'arena Node<'arena> + where + F: Fn(ParserContext) -> &'arena Node<'arena>, + { + let transform_key = |n: &'arena Node<'arena>| -> &'arena Node<'arena> { + let mut keys = BumpVec::new_in(&self.bump); + if n.member_key(&mut keys).is_none() { + return self.error_with_node( + AstNodeError::Custom { + span: n.span().unwrap_or_default(), + message: self.bump.alloc_str("Failed to resolve"), + }, + n, + ); + } + + self.node( + Node::String(self.bump.alloc_str(keys.join(".").as_str())), + |_| NodeMetadata { + span: n.span().unwrap_or_default(), + }, + ) + }; + + let span_start = self.token_start(); + expect!(self, TokenKind::Operator(Operator::Assign)); + + let mut key_value_pairs = BumpVec::new_in(self.bump); + let value = expression_parser(ParserContext::Global); + + key_value_pairs.push((transform_key(starting_key), value)); + let mut checkpoint_for_return = None; + + loop { + if let None = self.current() { + break; + } + + expect!(self, TokenKind::Operator(Operator::Semi)); + let Some(identifier_token) = self.current() else { + break; + }; + + let checkpoint = self.position(); + let identifier_node = + self.node(Node::Identifier(identifier_token.value), |_| NodeMetadata { + span: identifier_token.span, + }); + self.next(); + + let (key_node, combination) = self.with_postfix(identifier_node, expression_parser); + if let Some(_) = self.expect(TokenKind::Operator(Operator::Assign)) { + checkpoint_for_return = Some(checkpoint); + break; + }; + + if combination != PostfixCombination::Property + && combination != PostfixCombination::Inherit + { + return self.error_with_node( + AstNodeError::Custom { + span: key_node.span().unwrap_or_default(), + message: "Only property access is allowed during assignment", + }, + key_node, + ); + } + + let value = expression_parser(ParserContext::Global); + key_value_pairs.push((transform_key(key_node), value)); + } + + let mut output = None; + if let Some(starting_position) = checkpoint_for_return { + self.set_position(starting_position); + let value = expression_parser(ParserContext::Global); + output.replace(value); + } + + self.node( + Node::Assignments { + list: key_value_pairs.into_bump_slice(), + output, + }, + |_| NodeMetadata { + span: (span_start, self.prev_token_end()), + }, ) } - pub(crate) fn object(&self, expression_parser: F) -> &'arena Node<'arena> + pub(crate) fn object(&self, expression_parser: &F) -> &'arena Node<'arena> where F: Fn(ParserContext) -> &'arena Node<'arena>, { @@ -910,7 +1050,7 @@ impl<'arena, 'token_ref, Flavor> Parser<'arena, 'token_ref, Flavor> { }) } - pub(crate) fn object_key(&self, expression_parser: F) -> &'arena Node<'arena> + pub(crate) fn object_key(&self, expression_parser: &F) -> &'arena Node<'arena> where F: Fn(ParserContext) -> &'arena Node<'arena>, { @@ -1007,7 +1147,7 @@ impl<'arena, 'token_ref, Flavor> Parser<'arena, 'token_ref, Flavor> { pub(crate) fn conditional( &self, condition: &'arena Node<'arena>, - expression_parser: F, + expression_parser: &F, ) -> Option<&'arena Node<'arena>> where F: Fn(ParserContext) -> &'arena Node<'arena>, @@ -1040,7 +1180,7 @@ impl<'arena, 'token_ref, Flavor> Parser<'arena, 'token_ref, Flavor> { } /// Literal - number, string, array etc. - pub(crate) fn literal(&self, expression_parser: F) -> &'arena Node<'arena> + pub(crate) fn literal(&self, expression_parser: &F) -> &'arena Node<'arena> where F: Fn(ParserContext) -> &'arena Node<'arena>, { @@ -1112,21 +1252,40 @@ fn is_valid_property(token: &Token) -> bool { #[derive(Debug)] enum PostfixKind { - MemberAccess, PropertyAccess, + ComputedAccess, Other, } impl From<&Token<'_>> for PostfixKind { fn from(token: &Token) -> Self { match &token.kind { - TokenKind::Bracket(Bracket::LeftSquareBracket) => Self::PropertyAccess, - TokenKind::Operator(Operator::Dot) => Self::MemberAccess, + TokenKind::Bracket(Bracket::LeftSquareBracket) => Self::ComputedAccess, + TokenKind::Operator(Operator::Dot) => Self::PropertyAccess, _ => Self::Other, } } } +#[derive(Debug, PartialEq)] +pub(crate) enum PostfixCombination { + Mixed, + Computed, + Property, + Inherit, +} + +impl PostfixCombination { + fn and(self, other: PostfixCombination) -> Self { + match (self, other) { + (Self::Computed, Self::Computed) => Self::Computed, + (Self::Property, Self::Property) => Self::Property, + (Self::Inherit, other) => other, + _ => Self::Mixed, + } + } +} + pub(crate) struct MetadataHelper<'a, 'arena> { node_metadata: &'a HashMap>, arena: PhantomData<&'arena ()>, diff --git a/core/expression/src/parser/standard.rs b/core/expression/src/parser/standard.rs index b2c23980..6a87f56e 100644 --- a/core/expression/src/parser/standard.rs +++ b/core/expression/src/parser/standard.rs @@ -64,7 +64,7 @@ impl<'arena, 'token_ref> Parser<'arena, 'token_ref, Standard> { if precedence == 0 { if let Some(conditional_node) = - self.conditional(node_left, |_| self.binary_expression(0)) + self.conditional(node_left, &|_| self.binary_expression(0)) { node_left = conditional_node; } @@ -85,7 +85,8 @@ impl<'arena, 'token_ref> Parser<'arena, 'token_ref, Standard> { self.next(); let node = self.node(Node::Pointer, |_| NodeMetadata { span: token.span }); - return self.with_postfix(node, |_| self.binary_expression(0)); + let (n, _) = self.with_postfix(node, &|_| self.binary_expression(0)); + return n; } if let TokenKind::Operator(operator) = &token.kind { @@ -115,7 +116,7 @@ impl<'arena, 'token_ref> Parser<'arena, 'token_ref, Standard> { return node; } - if let Some(interval_node) = self.interval(|_| self.binary_expression(0)) { + if let Some(interval_node) = self.interval(&|_| self.binary_expression(0)) { return interval_node; } @@ -132,9 +133,10 @@ impl<'arena, 'token_ref> Parser<'arena, 'token_ref, Standard> { span: (p_start.unwrap_or_default(), self.prev_token_end()), }); - return self.with_postfix(expr, |_| self.binary_expression(0)); + let (n, _) = self.with_postfix(expr, &|_| self.binary_expression(0)); + return n; } - self.literal(|_| self.binary_expression(0)) + self.literal(&|_| self.binary_expression(0)) } } diff --git a/core/expression/src/parser/unary.rs b/core/expression/src/parser/unary.rs index fe4ef43c..c807167b 100644 --- a/core/expression/src/parser/unary.rs +++ b/core/expression/src/parser/unary.rs @@ -190,7 +190,7 @@ impl<'arena, 'token_ref> Parser<'arena, 'token_ref, Unary> { if precedence == 0 { if let Some(conditional_node) = - self.conditional(node_left, |c| self.binary_expression(0, c)) + self.conditional(node_left, &|c| self.binary_expression(0, c)) { node_left = conditional_node; } @@ -201,14 +201,15 @@ impl<'arena, 'token_ref> Parser<'arena, 'token_ref, Unary> { fn unary_expression(&self) -> &'arena Node<'arena> { let Some(token) = self.current() else { - return self.literal(|c| self.binary_expression(0, c)); + return self.literal(&|c| self.binary_expression(0, c)); }; if self.depth() > 0 && token.kind == TokenKind::Identifier(Identifier::CallbackReference) { self.next(); let node = self.node(Node::Pointer, |_| NodeMetadata { span: token.span }); - return self.with_postfix(node, |c| self.binary_expression(0, c)); + let (n, _) = self.with_postfix(node, &|c| self.binary_expression(0, c)); + return n; } if let TokenKind::Operator(operator) = &token.kind { @@ -238,7 +239,7 @@ impl<'arena, 'token_ref> Parser<'arena, 'token_ref, Unary> { return node; } - if let Some(interval_node) = self.interval(|c| self.binary_expression(0, c)) { + if let Some(interval_node) = self.interval(&|c| self.binary_expression(0, c)) { return interval_node; } @@ -255,10 +256,11 @@ impl<'arena, 'token_ref> Parser<'arena, 'token_ref, Unary> { span: (p_start.unwrap_or_default(), self.prev_token_end()), }); - return self.with_postfix(expr, |c| self.binary_expression(0, c)); + let (n, _) = self.with_postfix(expr, &|c| self.binary_expression(0, c)); + return n; } - self.literal(|c| self.binary_expression(0, c)) + self.literal(&|c| self.binary_expression(0, c)) } } @@ -293,6 +295,7 @@ impl From<&Node<'_>> for UnaryNodeBehaviour { Node::String(_) => CompareWithReference(Equal), Node::TemplateString(_) => CompareWithReference(Equal), Node::Object(_) => CompareWithReference(Equal), + Node::Assignments { .. } => CompareWithReference(Equal), Node::Pointer => AsBoolean, Node::Array(_) => CompareWithReference(In), Node::Identifier(_) => CompareWithReference(Equal), @@ -336,6 +339,8 @@ impl From<&Node<'_>> for UnaryNodeBehaviour { Operator::Comma => AsBoolean, Operator::Dot => AsBoolean, Operator::QuestionMark => AsBoolean, + Operator::Assign => AsBoolean, + Operator::Semi => AsBoolean, }, Node::FunctionCall { kind, .. } => match kind { FunctionKind::Internal(i) => match i { diff --git a/core/expression/src/variable/mod.rs b/core/expression/src/variable/mod.rs index d87581b0..e2410ef4 100644 --- a/core/expression/src/variable/mod.rs +++ b/core/expression/src/variable/mod.rs @@ -161,6 +161,37 @@ impl Variable { _ => None, }) } + + fn dot_head_detach(&self, key: &str) -> (Variable, Option) { + let mut parts = Vec::from_iter(key.split('.')); + parts.pop(); + + let cloned_self = self.depth_clone(1); + let head = parts + .iter() + .try_fold(cloned_self.shallow_clone(), |var, part| match var { + Variable::Object(obj) => { + let mut obj_ref = obj.borrow_mut(); + Some(match obj_ref.entry(Rc::from(*part)) { + Entry::Occupied(mut occ) => { + let var = occ.get(); + let new_obj = match var { + Variable::Object(_) => var.depth_clone(1), + _ => Variable::empty_object(), + }; + + occ.insert(new_obj.shallow_clone()); + new_obj + } + Entry::Vacant(vac) => vac.insert(Self::empty_object()).shallow_clone(), + }) + } + _ => None, + }); + + (cloned_self, head) + } + pub fn dot_remove(&self, key: &str) -> Option { let last_part = key.split('.').last()?; let head = self.dot_head(key)?; @@ -183,6 +214,19 @@ impl Variable { object.insert(Rc::from(last_part), variable) } + pub fn dot_insert_detached(&self, key: &str, variable: Variable) -> Option { + let last_part = key.split('.').last()?; + let (new_var, head_opt) = self.dot_head_detach(key); + let head = head_opt?; + let Variable::Object(object_ref) = head else { + return None; + }; + + let mut object = object_ref.borrow_mut(); + object.insert(Rc::from(last_part), variable); + Some(new_var) + } + pub fn merge(&mut self, patch: &Variable) -> Variable { let _ = merge_variables(self, patch, true, MergeStrategy::InPlace); @@ -403,3 +447,35 @@ impl PartialEq for Variable { } impl Eq for Variable {} + +#[cfg(test)] +mod tests { + use crate::Variable; + use rust_decimal_macros::dec; + use serde_json::json; + + #[test] + fn insert_detached() { + let some_data: Variable = json!({ "customer": { "firstName": "John" }}).into(); + + let a_a = some_data + .dot_insert_detached("a.a", Variable::Number(dec!(1))) + .unwrap(); + let a_b = a_a + .dot_insert_detached("a.b", Variable::Number(dec!(2))) + .unwrap(); + let a_c = a_b + .dot_insert_detached("a.c", Variable::Number(dec!(3))) + .unwrap(); + + assert_eq!(a_a.dot("a"), Some(Variable::from(json!({ "a": 1 })))); + assert_eq!( + a_b.dot("a"), + Some(Variable::from(json!({ "a": 1, "b": 2 }))) + ); + assert_eq!( + a_c.dot("a"), + Some(Variable::from(json!({ "a": 1, "b": 2, "c": 3 }))) + ); + } +} diff --git a/core/expression/src/variable/types/conv.rs b/core/expression/src/variable/types/conv.rs index 692e9afc..761d6a1f 100644 --- a/core/expression/src/variable/types/conv.rs +++ b/core/expression/src/variable/types/conv.rs @@ -1,6 +1,7 @@ use crate::variable::types::VariableType; use serde_json::Value; use std::borrow::Cow; +use std::cell::RefCell; use std::ops::Deref; use std::rc::Rc; @@ -23,11 +24,11 @@ impl<'a> From> for VariableType { panic!("unexpected type of value, expected object"); }; - VariableType::Object( + VariableType::Object(Rc::new(RefCell::new( obj.into_iter() - .map(|(k, v)| (Rc::from(k.as_str()), Rc::new(v.into()))) + .map(|(k, v)| (Rc::from(k.as_str()), v.into())) .collect(), - ) + ))) } } } diff --git a/core/expression/src/variable/types/mod.rs b/core/expression/src/variable/types/mod.rs index 7ab0c4bb..bdf0c446 100644 --- a/core/expression/src/variable/types/mod.rs +++ b/core/expression/src/variable/types/mod.rs @@ -1,6 +1,7 @@ mod conv; mod util; +use crate::variable::RcCell; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fmt::{Display, Write}; @@ -17,7 +18,7 @@ pub enum VariableType { Date, Interval, Array(Rc), - Object(HashMap, Rc>), + Object(RcCell, VariableType>>), Const(Rc), Enum(Option>, Vec>), @@ -97,6 +98,7 @@ impl Hash for VariableType { VariableType::Object(obj) => { 10.hash(state); + let obj = obj.borrow(); let mut pairs: Vec<_> = obj.iter().collect(); pairs.sort_by_key(|i| i.0); diff --git a/core/expression/src/variable/types/util.rs b/core/expression/src/variable/types/util.rs index d48356d5..031f4857 100644 --- a/core/expression/src/variable/types/util.rs +++ b/core/expression/src/variable/types/util.rs @@ -1,4 +1,6 @@ use crate::variable::types::VariableType; +use rust_decimal::prelude::Zero; +use std::cell::RefCell; use std::collections::hash_map::Entry; use std::collections::HashMap; use std::rc::Rc; @@ -19,12 +21,13 @@ impl VariableType { } } - pub fn get(&self, key: &str) -> Rc { + pub fn get(&self, key: &str) -> VariableType { match self { VariableType::Object(obj) => { - obj.get(key).cloned().unwrap_or(Rc::new(VariableType::Any)) + let obj = obj.borrow(); + obj.get(key).cloned().unwrap_or(VariableType::Any) } - _ => Rc::from(VariableType::Null), + _ => VariableType::Null, } } @@ -40,9 +43,13 @@ impl VariableType { (_, VariableType::Date) if self.widen().is_string() => true, (VariableType::Interval, VariableType::Interval) => true, (VariableType::Array(a1), VariableType::Array(a2)) => a1.satisfies(a2), - (VariableType::Object(o1), VariableType::Object(o2)) => o1 - .iter() - .all(|(k, v)| o2.get(k).is_some_and(|tv| v.satisfies(tv))), + (VariableType::Object(o1), VariableType::Object(o2)) => { + let o1 = o1.borrow(); + let o2 = o2.borrow(); + + o1.iter() + .all(|(k, v)| o2.get(k).is_some_and(|tv| v.satisfies(tv))) + } (VariableType::Const(c1), VariableType::Const(c2)) => c1 == c2, (VariableType::Const(c), VariableType::Enum(_, e)) => e.iter().any(|e| e == c), @@ -120,6 +127,9 @@ impl VariableType { } (VariableType::Object(o1), VariableType::Object(o2)) => { + let o1 = o1.borrow(); + let o2 = o2.borrow(); + let mut merged = HashMap::with_capacity(o1.len().max(o2.len())); for (k, v) in o1.iter() { merged.insert(k.clone(), v.clone()); @@ -129,8 +139,8 @@ impl VariableType { match merged.entry(k.clone()) { Entry::Occupied(mut entry) => { let current = entry.get(); - let merged_value = current.as_ref().merge(v.as_ref()); - entry.insert(Rc::new(merged_value)); + let merged_value = current.merge(v); + entry.insert(merged_value); } Entry::Vacant(entry) => { entry.insert(v.clone()); @@ -138,7 +148,7 @@ impl VariableType { } } - VariableType::Object(merged) + VariableType::Object(Rc::new(RefCell::new(merged))) } (VariableType::Const(c), VariableType::Enum(_, values)) => { @@ -189,6 +199,126 @@ impl VariableType { (_, _) => VariableType::Any, } } + + pub fn shallow_clone(&self) -> Self { + match self { + VariableType::Any => VariableType::Any, + VariableType::Null => VariableType::Null, + VariableType::Bool => VariableType::Bool, + VariableType::String => VariableType::String, + VariableType::Number => VariableType::Number, + VariableType::Date => VariableType::Date, + VariableType::Interval => VariableType::Interval, + VariableType::Array(arr) => VariableType::Array(arr.clone()), + VariableType::Object(obj) => VariableType::Object(obj.clone()), + VariableType::Const(c) => VariableType::Const(c.clone()), + VariableType::Enum(name, options) => VariableType::Enum(name.clone(), options.clone()), + } + } + + pub fn dot_head(&self, key: &str) -> Option { + let mut parts = Vec::from_iter(key.split('.')); + parts.pop(); + + parts + .iter() + .try_fold(self.shallow_clone(), |var, part| match var { + VariableType::Object(obj) => { + let mut obj_ref = obj.borrow_mut(); + Some(match obj_ref.entry(Rc::from(*part)) { + Entry::Occupied(occ) => occ.get().shallow_clone(), + Entry::Vacant(vac) => vac.insert(Self::empty_object()).shallow_clone(), + }) + } + _ => None, + }) + } + + pub fn dot_head_detach(&self, key: &str) -> (Self, Option) { + let mut parts = Vec::from_iter(key.split('.')); + parts.pop(); + + let cloned_self = self.depth_clone(1); + let head = parts + .iter() + .try_fold(cloned_self.shallow_clone(), |var, part| match var { + VariableType::Object(obj) => { + let mut obj_ref = obj.borrow_mut(); + Some(match obj_ref.entry(Rc::from(*part)) { + Entry::Occupied(mut occ) => { + let var = occ.get(); + let new_obj = match var { + VariableType::Object(_) => var.depth_clone(1), + _ => VariableType::empty_object(), + }; + + occ.insert(new_obj.shallow_clone()); + new_obj + } + Entry::Vacant(vac) => vac.insert(Self::empty_object()).shallow_clone(), + }) + } + _ => None, + }); + + (cloned_self, head) + } + + pub fn depth_clone(&self, depth: usize) -> Self { + match depth.is_zero() { + true => self.shallow_clone(), + false => match self { + VariableType::Object(o) => { + let obj = o.borrow(); + VariableType::Object(Rc::new(RefCell::new( + obj.iter() + .map(|(k, v)| (k.clone(), v.depth_clone(depth - 1))) + .collect(), + ))) + } + _ => self.shallow_clone(), + }, + } + } + + pub fn empty_object() -> Self { + VariableType::Object(Rc::new(RefCell::new(HashMap::new()))) + } + + pub fn dot_insert_detached(&self, key: &str, variable: Self) -> Option { + let last_part = key.split('.').last()?; + let (new_var, head_opt) = self.dot_head_detach(key); + let head = head_opt?; + let VariableType::Object(object_ref) = head else { + return None; + }; + + let mut object = object_ref.borrow_mut(); + object.insert(Rc::from(last_part), variable); + Some(new_var) + } + + pub fn dot_insert(&self, key: &str, variable: Self) -> Option { + let last_part = key.split('.').last()?; + let head = self.dot_head(key)?; + let Self::Object(object_ref) = head else { + return None; + }; + + let mut object = object_ref.borrow_mut(); + object.insert(Rc::from(last_part), variable) + } + + pub fn dot(&self, key: &str) -> Option { + key.split('.') + .try_fold(self.shallow_clone(), |var, part| match var { + Self::Object(obj) => { + let reference = obj.borrow(); + reference.get(part).map(|v| v.shallow_clone()) + } + _ => None, + }) + } } #[cfg(test)] diff --git a/core/expression/src/vm/vm.rs b/core/expression/src/vm/vm.rs index 3c7d7659..79a50f16 100644 --- a/core/expression/src/vm/vm.rs +++ b/core/expression/src/vm/vm.rs @@ -75,7 +75,9 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'parent_ref, 'bytecode_ref> { }) } - pub fn run(&mut self, env: Variable) -> VMResult { + pub fn run(&mut self, root_env: Variable) -> VMResult { + let mut env = root_env.clone(); + let mut assigned_object: Option = None; if self.ip != 0 { self.ip = 0; } @@ -136,7 +138,8 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'parent_ref, 'bytecode_ref> { } Opcode::FetchFast(path) => { let variable = path.iter().fold(Null, |v, p| match p { - FetchFastTarget::Root => env.clone(), + FetchFastTarget::Root => root_env.clone(), + FetchFastTarget::Begin => env.clone(), FetchFastTarget::String(key) => match v { Object(obj) => { let obj_ref = obj.borrow(); @@ -727,6 +730,50 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'parent_ref, 'bytecode_ref> { self.push(Variable::from_object(map)); } + Opcode::AssignedObjectBegin => { + assigned_object = Some(Variable::empty_object()); + } + Opcode::AssignedObjectStep => { + let value = self.pop()?; + let String(key) = self.pop()? else { + return Err(OpcodeErr { + opcode: "AssignedObjectStep".into(), + message: "Unexpected key value".to_string(), + }); + }; + + let Some(assigned_object) = &assigned_object else { + return Err(OpcodeErr { + opcode: "AssignedObjectStep".into(), + message: "Assigned object scope must be set".to_owned(), + }); + }; + + let Some(new_env) = env.dot_insert_detached(key.as_ref(), value.clone()) else { + return Err(OpcodeErr { + opcode: "AssignedObjectStep".into(), + message: "Failed to mutate existing env".to_owned(), + }); + }; + + env = new_env; + assigned_object.dot_insert(key.as_ref(), value); + } + Opcode::AssignedObjectEnd { with_return } => { + let Some(assigned_object) = assigned_object.take() else { + return Err(OpcodeErr { + opcode: "AssignedObjectEnd".into(), + message: "Assigned object scope must be set".to_owned(), + }); + }; + + if *with_return { + let output = self.pop()?; + self.push(output); + } else { + self.push(assigned_object); + } + } Opcode::Len => { let current = self.stack.last().ok_or_else(|| OpcodeErr { opcode: "Len".into(), diff --git a/core/expression/tests/data/standard.csv b/core/expression/tests/data/standard.csv index 5b16f533..3b1fb172 100644 --- a/core/expression/tests/data/standard.csv +++ b/core/expression/tests/data/standard.csv @@ -555,4 +555,62 @@ round(1.234e2);;123 # Scientific notation - template strings `Value: ${1e3}`;; 'Value: 1000' `Scientific: ${2.5e2}`;; 'Scientific: 250' -`Negative: ${-1e2}`;; 'Negative: -100' \ No newline at end of file +`Negative: ${-1e2}`;; 'Negative: -100' + +### Assignment + +# Basic assignment +"a = 5";{};{"a": 5} +"name = 'John'";{};{"name": "John"} +"active = true";{};{"active": true} +"items = [1, 2, 3]";{};{"items": [1, 2, 3]} +"config = {debug: true}";{};{"config": {"debug": true}} + +# Property assignment +"user.name = 'Alice'";{};{"user": {"name": "Alice"}} +"user.age = 30";{};{"user": {"age": 30}} +"settings.theme = 'dark'";{};{"settings": {"theme": "dark"}} + +# Nested property assignment +"user.profile.bio = 'Developer'";{};{"user": {"profile": {"bio": "Developer"}}} +"app.config.database.host = 'localhost'";{};{"app": {"config": {"database": {"host": "localhost"}}}} +"system.cache.redis.port = 6379";{};{"system": {"cache": {"redis": {"port": 6379}}}} + +# Multiple assignments +"a = 1; b = 2";{};{"a": 1, "b": 2} +"user.name = 'Charlie'; user.age = 35";{};{"user": {"name": "Charlie", "age": 35}} +"x = 10; y = 20; z = 30";{};{"x": 10, "y": 20, "z": 30} + +# Assignment with expressions +"counter = counter + 1";{"counter": 5};{"counter": 6} +"total = price * quantity";{"price": 15, "quantity": 3};{"total": 45} +"fullName = firstName + ' ' + lastName";{"firstName": "John", "lastName": "Doe"};{"fullName": "John Doe"} + +# Assignment with function calls +"timestamp = date('2023-10-15')";{};{"timestamp": 1697328000} +"userName = upper(name)";{"name": "alice"};{"userName": "ALICE"} +"itemCount = len(items)";{"items": [1, 2, 3, 4]};{"itemCount": 4} + +# Assignment with array operations +"doubled = map(numbers, # * 2)";{"numbers": [1, 2, 3]};{"doubled": [2, 4, 6]} +"filtered = filter(items, # > 10)";{"items": [5, 15, 8, 20]};{"filtered": [15, 20]} +"total = sum(values)";{"values": [10, 20, 30]};{"total": 60} + +# Assignment with conditional expressions +"status = score > 70 ? 'pass' : 'fail'";{"score": 85};{"status": "pass"} +"message = user.isActive ? 'Welcome!' : 'Please activate'";{"user": {"isActive": true}};{"message": "Welcome!"} +"category = age >= 18 ? 'adult' : 'minor'";{"age": 25};{"category": "adult"} + +# Assignment with object creation +"person = {name: firstName, age: userAge}";{"firstName": "Diana", "userAge": 28};{"person": {"name": "Diana", "age": 28}} +"point = {x: posX, y: posY}";{"posX": 10, "posY": 15};{"point": {"x": 10, "y": 15}} + +# Assignment with template strings +"greeting = `Hello, ${name}!`";{"name": "World"};{"greeting": "Hello, World!"} +"summary = `Total: ${count} items`";{"count": 42};{"summary": "Total: 42 items"} + +# Assignment with return value +"user.name = 'Eve'; user.name";{};'Eve' +"config.debug = true; config.env = 'dev'; config";{};{"debug": true, "env": "dev"} +"config.debug = true; config.env = 'dev'; $root";{};{"config": {"debug": true, "env": "dev"}} +"a = 5; b = 10; a + b";{};15 \ No newline at end of file