feat: assignment (#366)

* feat: assignment

* add tests
This commit is contained in:
stefan-gorules
2025-06-18 10:43:01 +02:00
committed by GitHub
parent 21a1238efd
commit f2fcb95ad9
21 changed files with 867 additions and 247 deletions
+19 -1
View File
@@ -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),
+3
View File
@@ -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<T> = Result<T, CompilerError>;
+6
View File
@@ -7,6 +7,7 @@ use strum_macros::Display;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FetchFastTarget {
Root,
Begin,
String(Arc<str>),
Number(u32),
}
@@ -40,6 +41,11 @@ pub enum Opcode {
Slice,
Array,
Object,
AssignedObjectBegin,
AssignedObjectStep,
AssignedObjectEnd {
with_return: bool,
},
Len,
IncrementIt,
IncrementCount,
+3 -3
View File
@@ -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<VariableType>]) -> FunctionTypecheck;
fn check_types(&self, args: &[VariableType]) -> FunctionTypecheck;
fn call(&self, args: Arguments) -> anyhow::Result<Variable>;
fn param_type(&self, index: usize) -> Option<VariableType>;
fn param_type_str(&self, index: usize) -> String;
@@ -52,7 +52,7 @@ impl FunctionDefinition for StaticFunction {
0
}
fn check_types(&self, args: &[Rc<VariableType>]) -> 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<VariableType>]) -> 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());
+9 -10
View File
@@ -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<VariableType>,
pub kind: VariableType,
pub node_kind: &'static str,
pub error: Option<String>,
}
@@ -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),
});
});
+4 -4
View File
@@ -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,
}
@@ -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<Rc<VariableType>> = items
let mut type_list: Vec<VariableType> = 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<Rc<str>, 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<Rc<VariableType>> = arguments
let mut type_list: Vec<VariableType> = 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<Rc<VariableType>> = once(this_type.kind)
let type_list: Vec<VariableType> = 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) => {
@@ -5,7 +5,7 @@ use std::rc::Rc;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct TypeInfo {
pub(crate) kind: Rc<VariableType>,
pub(crate) kind: VariableType,
pub(crate) error: Option<String>,
}
@@ -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<VariableType> for TypeInfo {
fn from(value: VariableType) -> Self {
Self {
kind: Rc::new(value),
kind: value,
error: None,
}
}
@@ -44,7 +44,7 @@ impl From<VariableType> for TypeInfo {
impl From<Rc<VariableType>> for TypeInfo {
fn from(value: Rc<VariableType>) -> Self {
Self {
kind: value,
kind: value.deref().clone(),
error: None,
}
}
+1 -1
View File
@@ -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' | '$' | '_' | '#' };
+34
View File
@@ -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);
+6
View File
@@ -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,
}
}
}
+41
View File
@@ -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)]
+340 -181
View File
@@ -316,7 +316,7 @@ impl<'arena, 'token_ref, Flavor> Parser<'arena, 'token_ref, Flavor> {
})
}
pub(crate) fn template_string<F>(&self, expression_parser: F) -> &'arena Node<'arena>
pub(crate) fn template_string<F>(&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<F>(
&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<F>(
&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<F>(
&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<F>(&self, expression_parser: F) -> &'arena Node<'arena>
pub(crate) fn closure<F>(&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 <Identifier> or <Identifier Expression>
pub(crate) fn identifier<F>(&self, expression_parser: F) -> &'arena Node<'arena>
pub(crate) fn identifier<F>(&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<F>(&self, expression_parser: F) -> Option<&'arena Node<'arena>>
pub(crate) fn interval<F>(&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<F>(&self, expression_parser: F) -> &'arena Node<'arena>
pub(crate) fn array<F>(&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<F>(
&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<F>(&self, expression_parser: F) -> &'arena Node<'arena>
pub(crate) fn object<F>(&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<F>(&self, expression_parser: F) -> &'arena Node<'arena>
pub(crate) fn object_key<F>(&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<F>(
&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<F>(&self, expression_parser: F) -> &'arena Node<'arena>
pub(crate) fn literal<F>(&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<usize, NodeMetadata, BuildNoHashHasher<usize>>,
arena: PhantomData<&'arena ()>,
+7 -5
View File
@@ -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))
}
}
+11 -6
View File
@@ -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 {
+76
View File
@@ -161,6 +161,37 @@ impl Variable {
_ => None,
})
}
fn dot_head_detach(&self, key: &str) -> (Variable, Option<Variable>) {
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<Variable> {
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<Variable> {
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 })))
);
}
}
+4 -3
View File
@@ -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<Cow<'a, Value>> 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(),
)
)))
}
}
}
+3 -1
View File
@@ -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<VariableType>),
Object(HashMap<Rc<str>, Rc<VariableType>>),
Object(RcCell<HashMap<Rc<str>, VariableType>>),
Const(Rc<str>),
Enum(Option<Rc<str>>, Vec<Rc<str>>),
@@ -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);
+139 -9
View File
@@ -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<VariableType> {
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<Self> {
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<Self>) {
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<Self> {
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<Self> {
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<Self> {
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)]
+49 -2
View File
@@ -75,7 +75,9 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'parent_ref, 'bytecode_ref> {
})
}
pub fn run(&mut self, env: Variable) -> VMResult<Variable> {
pub fn run(&mut self, root_env: Variable) -> VMResult<Variable> {
let mut env = root_env.clone();
let mut assigned_object: Option<Variable> = 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(),
+59 -1
View File
@@ -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'
`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
Can't render this file because it contains an unexpected character in line 14 and column 13.