mirror of
https://github.com/gorules/zen.git
synced 2026-10-04 00:02:18 +00:00
@@ -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),
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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' | '$' | '_' | '#' };
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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 ()>,
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 })))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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.
|
Reference in New Issue
Block a user