diff --git a/.github/workflows/latest.yml b/.github/workflows/latest.yml index 2849812..2e6baf6 100644 --- a/.github/workflows/latest.yml +++ b/.github/workflows/latest.yml @@ -17,7 +17,11 @@ jobs: toolchain: stable # targets: x86_64-unknown-linux-musl - run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh - - run: cargo test --verbose -- --nocapture + - run: | + cargo build --release --manifest-path ./adana-script/dynamic_lib/example_lib_src/Cargo.toml + rm ./adana-script/dynamic_lib/libplugin_example.so + cp ./adana-script/dynamic_lib/example_lib_src/target/release/libplugin_example.so ./adana-script/dynamic_lib/libplugin_example.so + - run: cargo test #--verbose -- --nocapture - run: wasm-pack test --headless --firefox ./adana-script-wasm - run: wasm-pack test --headless --chrome ./adana-script-wasm docker: diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 8ce2959..55c9e50 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -19,8 +19,15 @@ jobs: - run: git checkout -B pr_check - run: rustup component add clippy - run: cargo install cargo-release + - run: | + cargo build --release --manifest-path ./adana-script/dynamic_lib/example_lib_src/Cargo.toml + rm ./adana-script/dynamic_lib/libplugin_example.so + cp ./adana-script/dynamic_lib/example_lib_src/target/release/libplugin_example.so ./adana-script/dynamic_lib/libplugin_example.so + git config --global user.email "you@example.com" + git config --global user.name "GH" + git add . && git commit -m "we need to build libpluginexample.so with the proper glibc" - run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh - - run: cargo test --verbose -- --nocapture + - run: cargo test #--verbose -- --nocapture - run: wasm-pack test --headless --firefox ./adana-script-wasm - run: wasm-pack test --headless --chrome ./adana-script-wasm - run: cargo clippy --all-targets --all-features -- -D warnings diff --git a/Cargo.toml b/Cargo.toml index b39b3cd..a065999 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,8 +32,8 @@ libloading = "0.8.5" nu-ansi-term = "0.50.1" rustyline = "14.0.0" rustyline-derive = "0.10.0" -serde = { version = "1.0.208", features = ['serde_derive', 'rc'] } -serde_json = "1.0.125" +serde = { version = "1.0.209", features = ['serde_derive', 'rc'] } +serde_json = "1.0.127" slab_tree = "0.3.2" strum = { version = "0.26.3", features = ["derive"] } ctrlc = "3.4.5" diff --git a/README.md b/README.md index 62b6edf..94673e1 100644 --- a/README.md +++ b/README.md @@ -540,7 +540,10 @@ Example of defining a struct: ```javascript person = struct { name: "hello", - age: 20 + age: 20, + headers: struct { + "Content-Type": "application/json" + } } person_service = struct { @@ -780,6 +783,8 @@ Here is a list of built-in functions available: | match | match regex | `match("AaAaAbbBBBb", "(?i)a+(?-i)b+")` | | replace | replace | `replace("AaAaAbbBBBb", "(?i)a+(?-i)b+", "xxx")` | | replace_all | replace all | `replace_all("AaAaAbbBBBb", "A", "b")` | +| jsonify | jsonify value | `jsonify(struct {a: 9})` | +| parse_json | parse a json string | `parse_json("""{"a": 9}""")` | #### Matching regexes diff --git a/adana-script-core/Cargo.toml b/adana-script-core/Cargo.toml index a1162ea..26cae88 100644 --- a/adana-script-core/Cargo.toml +++ b/adana-script-core/Cargo.toml @@ -17,12 +17,17 @@ exclude.workspace = true anyhow.workspace = true serde.workspace = true strum.workspace = true -regex = { workspace = true, features = [ +regex = { workspace = true, default-features = false, features = [ + "std", "unicode", "unicode-age", "unicode-bool", "unicode-case", "unicode-perl", + "unicode-gencat", + "unicode-script", + "unicode-segment", ] } +serde_json.workspace = true [target.'cfg(not(target_arch = "wasm32"))'.dependencies] libloading.workspace = true diff --git a/adana-script-core/src/lib.rs b/adana-script-core/src/lib.rs index fd587eb..e989e62 100644 --- a/adana-script-core/src/lib.rs +++ b/adana-script-core/src/lib.rs @@ -4,12 +4,12 @@ use std::collections::BTreeMap; use constants::{ BREAK, CAPITALIZE, CEIL, DROP, ELSE, EULER_NUMBER, FALSE, FLOOR, FOR, IF, IN, IS_ARRAY, IS_BOOL, IS_DOUBLE, IS_ERROR, IS_FUNCTION, IS_I8, IS_INT, - IS_MATCH, IS_STRUCT, IS_U8, MAKE_ERROR, MATCH, MULTILINE, NULL, PI, - REPLACE, REPLACE_ALL, REQUIRE, RETURN, ROUND, STRUCT, TAU, TO_BINARY, - TO_HEX, TO_LOWER, TO_UPPER, TRUE, WHILE, + IS_MATCH, IS_STRUCT, IS_U8, JSONIFY, MAKE_ERROR, MATCH, MULTILINE, NULL, + PARSE_JSON, PI, REPLACE, REPLACE_ALL, REQUIRE, RETURN, ROUND, STRUCT, TAU, + TO_BINARY, TO_HEX, TO_LOWER, TO_UPPER, TRUE, WHILE, }; +pub use primitive::Primitive; -use primitive::Primitive; use serde::{Deserialize, Serialize}; use strum::EnumCount; @@ -68,6 +68,8 @@ pub mod constants { pub const IS_FUNCTION: &str = "is_function"; pub const IS_ARRAY: &str = "is_array"; pub const IS_STRUCT: &str = "is_struct"; + pub const JSONIFY: &str = "jsonify"; + pub const PARSE_JSON: &str = "parse_json"; pub const MAKE_ERROR: &str = "make_err"; pub const ABS: &str = "abs"; pub const LENGTH: &str = "length"; @@ -123,6 +125,7 @@ impl MathConstants { #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub enum Value { Break, + Primitive(Primitive), EarlyReturn(Box>), Drop(Box), Expression(Vec), @@ -180,16 +183,20 @@ pub enum Value { exprs: Vec, }, Array(Vec), - ArrayAccess { - arr: Box, - index: Box, - }, Struct(BTreeMap), - StructAccess { - struc: Box, - key: String, + + MultiDepthAccess { + root: Box, + next_keys: Vec, }, } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum KeyAccess { + Index(Primitive), + Key(Primitive), + Variable(Value), + FunctionCall { key: Box, parameters: Value }, +} #[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] pub enum BuiltInFunctionType { Sqrt, @@ -232,6 +239,8 @@ pub enum BuiltInFunctionType { IsDouble, IsFunction, IsArray, + ParseJson, + Jsonify, } #[derive(Debug, Eq, PartialEq, Copy, Clone, Serialize, Deserialize)] @@ -268,18 +277,17 @@ pub enum TreeNodeValue { Drop(Vec), VariableUnused, VariableAssign(Option), - VariableArrayAssign { name: String, index: Value }, + MultiDepthVariableAssign { root: Value, next_keys: Vec }, Ops(Operator), Primitive(Primitive), VariableRef(String), - BuiltInFunction(BuiltInFunctionType), + BuiltInFunction { fn_type: BuiltInFunctionType, params: Value }, IfExpr(Value), FString(String, Vec<(String, Value)>), WhileExpr(Value), Array(Vec), Struct(BTreeMap), - StructAccess { struc: Value, key: Primitive }, - ArrayAccess { index: Value, array: Value }, + MultiDepthAccess { root: Value, keys: Vec }, Function(Value), FunctionCall(Value), Foreach(Value), @@ -329,6 +337,8 @@ impl BuiltInFunctionType { BuiltInFunctionType::IsFunction => IS_FUNCTION, BuiltInFunctionType::IsArray => IS_ARRAY, BuiltInFunctionType::MakeError => MAKE_ERROR, + BuiltInFunctionType::Jsonify => JSONIFY, + BuiltInFunctionType::ParseJson => PARSE_JSON, } } } @@ -399,6 +409,8 @@ pub const FORBIDDEN_VARIABLE_NAME: &[&str] = &[ IS_BOOL, IS_ARRAY, MAKE_ERROR, + JSONIFY, + PARSE_JSON, EVAL, TO_BOOL, SQRT, diff --git a/adana-script-core/src/primitive.rs b/adana-script-core/src/primitive/core_primitive.rs similarity index 97% rename from adana-script-core/src/primitive.rs rename to adana-script-core/src/primitive/core_primitive.rs index f3ad1d3..bb999cd 100644 --- a/adana-script-core/src/primitive.rs +++ b/adana-script-core/src/primitive/core_primitive.rs @@ -1,3 +1,9 @@ +use anyhow::Result; +use serde::{Deserialize, Serialize}; + +#[cfg(not(target_arch = "wasm32"))] +use std::any::Any; + use std::{ cmp::Ordering, collections::BTreeMap, @@ -6,11 +12,7 @@ use std::{ sync::{Arc, RwLock}, }; -use anyhow::Result; - -use serde::{Deserialize, Serialize}; - -use super::{constants::NULL, Value}; +use crate::{constants::NULL, Value}; const MAX_U32_AS_I128: i128 = u32::MAX as i128; @@ -28,6 +30,7 @@ pub const TYPE_FUNCTION: &str = "function"; pub const TYPE_UNIT: &str = "unit"; pub const TYPE_STRUCT: &str = "struct"; pub const TYPE_NO_RETURN: &str = "!"; +pub const TYPE_LIB_DATA: &str = "libdata"; #[derive(Debug)] pub struct NativeLibrary { @@ -136,6 +139,13 @@ pub enum Primitive { NativeLibrary(Arc), #[serde(skip_serializing, skip_deserializing)] NativeFunction(String, Arc), + #[serde(skip_serializing, skip_deserializing)] + LibData(LibData), +} +#[derive(Debug, Clone)] +pub struct LibData { + #[cfg(not(target_arch = "wasm32"))] + pub data: Arc>, } pub type RefPrimitive = Arc>; @@ -146,62 +156,9 @@ impl Primitive { Arc::new(RwLock::new(self)) } pub fn to_value(self) -> anyhow::Result { - let v = match self { - Primitive::U8(u) => Value::U8(u), - Primitive::I8(i) => Value::I8(i), - Primitive::Int(i) => Value::Integer(i), - Primitive::Bool(b) => Value::Bool(b), - Primitive::Ref(r) => { - let r = r - .read() - .map_err(|e| { - anyhow::format_err!("could not acquire lock {e}") - })? - .clone(); - r.to_value()? - } - Primitive::Null => Value::Null, - Primitive::Double(d) => Value::Decimal(d), - Primitive::String(s) => Value::String(s), - Primitive::Array(a) => { - let mut vek = vec![]; - for p in a { - let p = p.to_value()?; - vek.push(p); - } - Value::Array(vek) - } - Primitive::Struct(s) => { - let mut map = BTreeMap::new(); - for (k, v) in s { - let p = v.to_value()?; - map.insert(k, p); - } - Value::Struct(map) - } - Primitive::Error(e) => return Err(anyhow::format_err!("{e}")), - Primitive::Function { parameters, exprs } => Value::Function { - parameters: Box::new(Value::BlockParen(parameters)), - exprs, - }, - Primitive::Unit => Value::NoOp, - Primitive::NoReturn => Value::NoOp, - Primitive::EarlyReturn(e) => { - let v = e.to_value()?; - Value::EarlyReturn(Box::new(Some(v))) - } - Primitive::NativeLibrary(_) => { - return Err(anyhow::anyhow!( - "cannot convert native lib to value" - )); - } - Primitive::NativeFunction(_method, _lib) => { - return Err(anyhow::anyhow!( - "cannot convert native function to value" - )); - } - }; - Ok(v) + // code was more complex than that before libhttp. I forgot why, + // and probably this simplification shouldn't cause any problem + Ok(Value::Primitive(self)) } } pub trait StringManipulation { @@ -446,6 +403,7 @@ impl Display for Primitive { Primitive::NativeFunction(key, _) => { write!(f, "__native_fn__{key}") } + Primitive::LibData(_) => write!(f, "__lib_data"), } } } @@ -1736,6 +1694,7 @@ impl PartialOrd for Primitive { (Primitive::Error(_), _) => None, (Primitive::Unit, _) => None, (Primitive::Function { parameters: _, exprs: _ }, _) => None, + (Primitive::LibData(_), _) => None, } } } @@ -2028,6 +1987,7 @@ impl TypeOf for Primitive { Primitive::Unit => TYPE_UNIT, Primitive::NoReturn => TYPE_NO_RETURN, Primitive::EarlyReturn(v) => v.type_of_str(), + Primitive::LibData(_) => TYPE_LIB_DATA, } } diff --git a/adana-script-core/src/primitive/json.rs b/adana-script-core/src/primitive/json.rs new file mode 100644 index 0000000..a8dcf04 --- /dev/null +++ b/adana-script-core/src/primitive/json.rs @@ -0,0 +1,84 @@ +use std::collections::BTreeMap; + +use anyhow::anyhow; +use serde_json::{json, Value}; + +use super::Primitive; + +pub trait Json { + fn from_json(s: &str) -> anyhow::Result + where + Self: Sized; + fn to_json(&self) -> anyhow::Result; +} + +fn json_to_primitive(value: Value) -> anyhow::Result { + match value { + Value::Null => Ok(Primitive::Null), + Value::Bool(b) => Ok(Primitive::Bool(b)), + Value::Number(number) => number + .as_u64() + .map(|n| Primitive::Int(n as i128)) + .or(number.as_i64().map(|n| Primitive::Int(n as i128))) + .or(number.as_f64().map(Primitive::Double)) + .ok_or(anyhow!("could not parse json number")), + Value::String(s) => Ok(Primitive::String(s)), + Value::Array(json_array) => { + let mut prim_array = Vec::with_capacity(json_array.len()); + for v in json_array { + prim_array.push(json_to_primitive(v)?); + } + Ok(Primitive::Array(prim_array)) + } + Value::Object(o) => { + let mut struct_array = BTreeMap::new(); + for (k, v) in o { + struct_array.insert(k, json_to_primitive(v)?); + } + Ok(Primitive::Struct(struct_array)) + } + } +} +fn primitive_to_value(p: &Primitive) -> anyhow::Result { + match p { + Primitive::Ref(r) => { + let r = + r.read().map_err(|e| anyhow!("could not acquire lock! {e}"))?; + primitive_to_value(&r) + } + Primitive::U8(u) => Ok(json!(u)), + Primitive::I8(u) => Ok(json!(u)), + Primitive::Int(u) => Ok(json!(u)), + Primitive::Double(u) => Ok(json!(u)), + Primitive::Bool(b) => Ok(json!(b)), + Primitive::Null => Ok(Value::Null), + Primitive::String(s) => Ok(Value::String(s.to_owned())), + Primitive::Array(prim_arr) => { + let mut json_arr = Vec::with_capacity(prim_arr.len()); + for p in prim_arr { + json_arr.push(primitive_to_value(p)?); + } + Ok(Value::Array(json_arr)) + } + Primitive::Struct(s) => { + let mut o = serde_json::Map::with_capacity(s.len()); + for (k, v) in s { + o.insert(k.to_string(), primitive_to_value(v)?); + } + Ok(Value::Object(o)) + } + v => Ok(json!(v.to_string())), + } +} + +impl Json for Primitive { + fn from_json(s: &str) -> anyhow::Result { + let value = serde_json::from_str(s)?; + json_to_primitive(value) + } + + fn to_json(&self) -> anyhow::Result { + let value = primitive_to_value(self)?; + serde_json::to_string_pretty(&value).map_err(|e| anyhow!("{e}")) + } +} diff --git a/adana-script-core/src/primitive/mod.rs b/adana-script-core/src/primitive/mod.rs new file mode 100644 index 0000000..4a7bd39 --- /dev/null +++ b/adana-script-core/src/primitive/mod.rs @@ -0,0 +1,5 @@ +mod core_primitive; +mod json; +pub use core_primitive::*; +pub use json::*; +// pub use json::*; diff --git a/adana-script/dynamic_lib/libplugin_example.so b/adana-script/dynamic_lib/libplugin_example.so index 103d17f..b4a16b0 100755 Binary files a/adana-script/dynamic_lib/libplugin_example.so and b/adana-script/dynamic_lib/libplugin_example.so differ diff --git a/adana-script/src/ast.rs b/adana-script/src/ast.rs index d743ada..9a4ea7f 100644 --- a/adana-script/src/ast.rs +++ b/adana-script/src/ast.rs @@ -266,6 +266,7 @@ pub fn to_ast( tree, curr_node_id, ), + Value::Primitive(p) => append_to_current_and_return(TreeNodeValue::Primitive(p), tree, curr_node_id), Value::ImplicitMultiply(value) => Err(anyhow::Error::msg(format!( "AST BUG: invalid implicit multiplier, unreachable branch: {value:?}", ))), @@ -386,6 +387,8 @@ pub fn to_ast( curr_node_id, ) } + Value::MultiDepthAccess { root, next_keys } => append_to_current_and_return(TreeNodeValue::MultiDepthAccess { root:*root, keys:next_keys}, tree, curr_node_id), + Value::VariableExpr { name, expr } => { anyhow::ensure!( tree.root().is_none(), @@ -395,40 +398,10 @@ pub fn to_ast( Ok(TreeNodeValue::VariableAssign(Some(n))) } else if let Value::VariableUnused = *name { Ok(TreeNodeValue::VariableAssign(None)) - } else if let Value::ArrayAccess { arr, index } = *name { - // let index = match *index { - // Value::Integer(n) => Ok(Primitive::Int(n)), - // Value::Variable(v) => variable_from_ctx(&v, false, ctx), - // v => { - // Err(anyhow::Error::msg(format!("invalid index {v:?}"))) - // } - // }?; + } else if let Value::MultiDepthAccess { root, next_keys} = *name { + Ok(TreeNodeValue::MultiDepthVariableAssign{root: *root, next_keys}) - if let Value::Variable(n) = *arr { - Ok(TreeNodeValue::VariableArrayAssign { - name: n, - index: *index, - }) - } else { - Err(anyhow::Error::msg(format!( - "invalid variable expression {arr:?} => {expr:?}" - ))) - } - } else if let Value::StructAccess { struc, key } = *name { - if let Value::Variable(n) = *struc { - Ok(TreeNodeValue::VariableArrayAssign { - name: n, - index: Value::String(key), - }) - } else { - Err(anyhow::Error::msg(format!( - "invalid variable expression {struc:?} => {expr:?}" - ))) - } - } else { - // FIXME for my future self. x.y.z or x[0][1] is not yet supported - // for assignment - // We need Primitive::Ref to make it happen + } else { Err(anyhow::Error::msg(format!( "AST ERROR: invalid variable expression {name:?} => {expr:?}", ))) @@ -464,22 +437,9 @@ pub fn to_ast( _ => unreachable!("should never happen or it's a bug"), }, Value::BuiltInFunction { fn_type, expr } => { - let fn_node = TreeNodeValue::BuiltInFunction(fn_type); - let node_id = if let Some(node_id) = curr_node_id { - let mut node = tree - .get_mut(*node_id) - .context("node id does not exist!")?; + let fn_node = TreeNodeValue::BuiltInFunction{fn_type, params: *expr}; + append_to_current_and_return(fn_node, tree, curr_node_id) - let node = node.append(fn_node); - Some(node.node_id()) - } else if let Some(mut root_node) = tree.root_mut() { - let node = root_node.append(fn_node); - Some(node.node_id()) - } else { - Some(tree.set_root(fn_node)) - }; - to_ast(ctx, *expr, tree, &node_id)?; - Ok(node_id) } v @ Value::IfExpr { cond: _, exprs: _, else_expr: _ } => { let if_node = TreeNodeValue::IfExpr(v); @@ -508,52 +468,6 @@ pub fn to_ast( tree, curr_node_id, ), - Value::ArrayAccess { arr, index } => match (*arr, *index) { - (v, index @ Value::Integer(_)) => append_to_current_and_return( - TreeNodeValue::ArrayAccess { index, array: v }, - tree, - curr_node_id, - ), - (v, index @ Value::U8(_)) => append_to_current_and_return( - TreeNodeValue::ArrayAccess { index, array: v }, - tree, - curr_node_id, - ), - (v, index @ Value::I8(_)) => append_to_current_and_return( - TreeNodeValue::ArrayAccess { index, array: v }, - tree, - curr_node_id, - ), - (v, variable @ Value::Variable(_)) => append_to_current_and_return( - TreeNodeValue::ArrayAccess { index: variable, array: v }, - tree, - curr_node_id, - ), - (v, variable @ Value::String(_)) => append_to_current_and_return( - TreeNodeValue::ArrayAccess { index: variable, array: v }, - tree, - curr_node_id, - ), - (v, variable @ Value::BlockParen(_)) => { - append_to_current_and_return( - TreeNodeValue::ArrayAccess { index: variable, array: v }, - tree, - curr_node_id, - ) - } - - (arr, index) => Err(anyhow::Error::msg(format!( - "illegal array access! array => {arr:?}, index=> {index:?}" - ))), - }, - Value::StructAccess { struc, key } => append_to_current_and_return( - TreeNodeValue::StructAccess { - struc: *struc, - key: Primitive::String(key), - }, - tree, - curr_node_id, - ), f @ Value::Function { parameters: _, exprs: _ } => { append_to_current_and_return( TreeNodeValue::Function(f), @@ -584,8 +498,7 @@ pub fn to_ast( for variable in variables { match variable { v @ Value::Variable(_) - | v @ Value::ArrayAccess { arr: _, index: _ } - | v @ Value::StructAccess { struc: _, key: _ } => { + | v @ Value::MultiDepthAccess { .. } => { vars.push(v) } _ => { diff --git a/adana-script/src/compute.rs b/adana-script/src/compute.rs index 89825ad..021bdf0 100644 --- a/adana-script/src/compute.rs +++ b/adana-script/src/compute.rs @@ -1,4 +1,4 @@ -use anyhow::{Context, Error}; +use anyhow::{anyhow, Context, Error}; use slab_tree::{NodeRef, Tree}; use std::{ borrow::Borrow, @@ -14,12 +14,12 @@ use super::{ast::to_ast, require_dynamic_lib::require_dynamic_lib}; use adana_script_core::{ primitive::{ Abs, Add, And, Array, BitShift, Cos, DisplayBinary, DisplayHex, Div, - Logarithm, Mul, Neg, Not, Or, Pow, Primitive, RefPrimitive, Rem, Round, - Sin, Sqrt, StringManipulation, Sub, Tan, ToBool, ToNumber, TypeOf, - TYPE_ARRAY, TYPE_BOOL, TYPE_DOUBLE, TYPE_ERROR, TYPE_FUNCTION, TYPE_I8, - TYPE_INT, TYPE_STRUCT, TYPE_U8, + Json, Logarithm, Mul, Neg, Not, Or, Pow, Primitive, RefPrimitive, Rem, + Round, Sin, Sqrt, StringManipulation, Sub, Tan, ToBool, ToNumber, + TypeOf, TYPE_ARRAY, TYPE_BOOL, TYPE_DOUBLE, TYPE_ERROR, TYPE_FUNCTION, + TYPE_I8, TYPE_INT, TYPE_STRUCT, TYPE_U8, }, - BuiltInFunctionType, Operator, TreeNodeValue, Value, + BuiltInFunctionType, KeyAccess, Operator, TreeNodeValue, Value, }; /// copy existing functions in a new ctx @@ -44,6 +44,392 @@ fn scoped_ctx( Ok(scope_ctx) } + +fn compute_key_access( + key: &KeyAccess, + ctx: &mut BTreeMap, + shared_lib: impl AsRef + Copy, +) -> anyhow::Result { + fn compute_key_access_ref(key: &Primitive) -> anyhow::Result { + match key { + Primitive::U8(u) => Ok(KeyAccess::Index(Primitive::U8(*u))), + Primitive::I8(u) => Ok(KeyAccess::Index(Primitive::I8(*u))), + Primitive::Int(u) => Ok(KeyAccess::Index(Primitive::Int(*u))), + Primitive::Ref(r) => { + let r = r + .read() + .map_err(|e| anyhow!("could not acquire lock {e}"))?; + compute_key_access_ref(&r) + } + Primitive::String(s) => { + Ok(KeyAccess::Key(Primitive::String(s.to_string()))) + } + _ => Err(anyhow!("illegal key access {key:?}")), + } + } + match key { + KeyAccess::Index(_) + | KeyAccess::Key(_) + | KeyAccess::FunctionCall { .. } => Ok(key.clone()), + KeyAccess::Variable(v) => { + compute_key_access_ref(&compute_lazy(v.clone(), ctx, shared_lib)?) + } + } +} +fn handle_function_call( + mut function: Primitive, + parameters: &Value, + ctx: &mut BTreeMap, + shared_lib: impl AsRef + Copy, +) -> anyhow::Result { + if let Value::BlockParen(param_values) = parameters { + // FIXME clone again + if let Primitive::Ref(r) = function { + function = r + .read() + .map_err(|e| { + anyhow::format_err!("could not acquire lock in fn call{e}") + })? + .clone(); + } + if let Primitive::Function { parameters: function_parameters, exprs } = + function + { + let mut scope_ctx = scoped_ctx(ctx)?; + for (i, param) in function_parameters.iter().enumerate() { + if let Some(value) = param_values.get(i) { + if let Value::Variable(variable_from_fn_def) = param { + let variable_from_fn_call = + compute_lazy(value.clone(), ctx, shared_lib)?; + scope_ctx.insert( + variable_from_fn_def.clone(), + variable_from_fn_call.ref_prim(), + ); + } + } else { + return Ok(Primitive::Error(format!( + "missing parameter {param:?}" + ))); + } + } + // TODO remove this and replace Arc> by Arc + // call function in a specific os thread with its own stack + // This was relative to a small stack allocated by musl + // But now it doesn't seem needed anymore + // let res = spawn(move || {}).join().map_err(|e| { + // anyhow::Error::msg(format!( + // "something wrong: {e:?}" + // )) + // })??; + let res = compute_instructions(exprs, &mut scope_ctx, shared_lib)?; + + if let Primitive::EarlyReturn(v) = res { + return Ok(*v); + } + Ok(res) + } else if let Primitive::NativeLibrary(lib) = function { + if cfg!(test) { + dbg!(&lib); + } + let mut parameters = vec![]; + for param in param_values.iter() { + if let Value::Variable(_) = param { + let variable_from_fn_call = + compute_lazy(param.clone(), ctx, shared_lib)?; + parameters.push(variable_from_fn_call); + } + } + if cfg!(test) { + dbg!(¶meters); + } + Ok(Primitive::Error("debug".into())) + //Ok(function(vec![Primitive::String("s".into())])) + } else if let Primitive::NativeFunction(key, lib) = function { + #[cfg(not(target_arch = "wasm32"))] + { + if cfg!(test) { + dbg!(&key, &lib); + } + let mut parameters = vec![]; + + for param in param_values.iter() { + let variable_from_fn_call = + compute_lazy(param.clone(), ctx, shared_lib)?; + parameters.push(variable_from_fn_call); + } + if cfg!(test) { + dbg!(¶meters); + } + + let mut scope_ctx = scoped_ctx(ctx)?; + + let slb = shared_lib.as_ref().to_path_buf(); + let fun = move |v, extra_ctx| { + scope_ctx.extend(extra_ctx); + compute_lazy(v, &mut scope_ctx, &slb) + }; + unsafe { + lib.call_function(key.as_str(), parameters, Box::new(fun)) + } + } + #[cfg(target_arch = "wasm32")] + { + return Ok(Primitive::Error(format!("Loading native function {key} doesn't work in wasm context! {lib:?}"))); + } + } else { + Ok(Primitive::Error(format!(" not a function: {function}"))) + } + } else { + Ok(Primitive::Error(format!( + "invalid function call: {parameters:?} => {function:?}" + ))) + } +} +fn fold_multidepth( + root: &Value, + next_keys: &Vec, + mut new_value: Primitive, + ctx: &mut BTreeMap, + shared_lib: impl AsRef + Copy, +) -> anyhow::Result { + fn fold( + acc: &mut Primitive, + new_value: &mut Primitive, + mut next_keys: Vec<&KeyAccess>, + ctx: &mut BTreeMap, + shared_lib: impl AsRef + Copy, + ) -> anyhow::Result { + if matches!(new_value, Primitive::Error(_)) { + return Ok(new_value.clone()); + } + let k = next_keys.remove(0); + let k = compute_key_access(k, ctx, shared_lib)?; + match k { + KeyAccess::Index(key) | KeyAccess::Key(key) => { + if next_keys.is_empty() { + if matches!(new_value, Primitive::Unit) { + // handle drop as user cannot + // construct a Primitive::Unit + acc.remove(&key)?; + return Ok(Primitive::Unit); + } + let res = acc.swap_mem(new_value, &key); + if matches!(res, Primitive::Error(_)) { + return Ok(res); + } + } else { + let mut new_value = fold( + &mut acc.index_at(&key), + new_value, + next_keys, + ctx, + shared_lib, + )?; + if matches!(new_value, Primitive::Error(_)) { + return Ok(new_value); + } + acc.swap_mem(&mut new_value, &key); + } + } + KeyAccess::FunctionCall { .. } | KeyAccess::Variable(_) => { + return Err(anyhow!("illegal assignement {next_keys:?} ")) + } + } + Ok(acc.clone()) + } + + if next_keys.is_empty() { + return Err(anyhow!("not enough keys {next_keys:?}")); + } + match root { + Value::Variable(name) | Value::VariableRef(name) => { + let mut cloned_ctx = ctx.clone(); + let mut acc = ctx + .get(name) + .context("array not found in context")? + .write() + .map_err(|e| { + anyhow::format_err!("could not acquire lock {e}") + })?; + let res = fold( + &mut acc, + &mut new_value, + next_keys.iter().collect(), + &mut cloned_ctx, + shared_lib, + )?; + + Ok(res) + } + _ => Ok(new_value), + } +} +fn compute_multidepth_access( + root: &Value, + keys: &[KeyAccess], + ctx: &mut BTreeMap, + shared_lib: impl AsRef + Copy, +) -> anyhow::Result { + fn compute_multidepth_access_primitive( + root: Primitive, + mut keys: Vec<&KeyAccess>, + ctx: &mut BTreeMap, + shared_lib: impl AsRef + Copy, + ) -> anyhow::Result { + if keys.is_empty() { + return Err(anyhow::anyhow!( + "access error. not enough argument {keys:?}" + )); + } + match root { + Primitive::Ref(r) => { + let p = r.read().map_err(|e| { + anyhow::anyhow!("could not acquire lock{e}") + })?; + compute_multidepth_access_primitive( + p.clone(), + keys, + ctx, + shared_lib, + ) + } + v @ Primitive::String(_) => { + if keys.len() != 1 { + return Err(anyhow::anyhow!( + "string access error. too many argument {keys:?}" + )); + } + let key = compute_key_access(keys.remove(0), ctx, shared_lib)?; + match key { + KeyAccess::Index(i) => Ok(v.index_at(&i)), + _ => Err(anyhow!( + "cannot use that key in this context {key:?} {v:?}" + )), + } + } + Primitive::NativeLibrary(lib) => { + match compute_key_access(keys.remove(0), ctx, shared_lib)? { + KeyAccess::Key(idx) => { + Ok(Primitive::NativeFunction(idx.to_string(), lib)) + } + KeyAccess::FunctionCall { key, parameters } => { + let key = compute_key_access(&key, ctx, shared_lib)?; + let KeyAccess::Key(idx) = key else { + return Err(anyhow!( "native lib can only be accessed with a key str {keys:?}")); + }; + let root_p= handle_function_call(Primitive::NativeFunction(idx.to_string(), lib), &Box::new(parameters), ctx, shared_lib)?; + if !keys.is_empty() { + compute_multidepth_access_primitive( + root_p, keys, ctx, shared_lib, + ) + } else { + Ok(root_p) + } + }, + _ => Err(anyhow!( + "native lib can only be accessed with a key str {keys:?}" + )) + } + } + v @ Primitive::Array(_) => { + let root_p = match compute_key_access( + keys.remove(0), + ctx, + shared_lib, + )? { + KeyAccess::Index(idx) => v.index_at(&idx), + KeyAccess::FunctionCall { key, parameters } => { + let key = compute_key_access(&key, ctx, shared_lib)?; + let KeyAccess::Index(idx) = key else { + return Err(anyhow!( "array can only be accessed with an idx {keys:?}")); + }; + + handle_function_call( + v.index_at(&idx), + &Box::new(parameters), + ctx, + shared_lib, + )? + } + _ => { + return Err(anyhow!( + "array can only be accessed with an idx {keys:?}" + )) + } + }; + if !keys.is_empty() { + compute_multidepth_access_primitive( + root_p, keys, ctx, shared_lib, + ) + } else { + Ok(root_p) + } + } + + v @ Primitive::Struct(_) => { + let root_p = match compute_key_access( + keys.remove(0), + ctx, + shared_lib, + )? { + KeyAccess::Key(idx) => v.index_at(&idx), + KeyAccess::FunctionCall { key, parameters } => { + let key = compute_key_access(&key, ctx, shared_lib)?; + let KeyAccess::Key(idx) = key else { + return Err(anyhow!( "struct can only be accessed with a key {keys:?}")); + }; + + handle_function_call( + v.index_at(&idx), + &Box::new(parameters), + ctx, + shared_lib, + )? + } + _ => { + return Err(anyhow!( + "struct can only be accessed with a key {keys:?}" + )) + } + }; + if !keys.is_empty() { + compute_multidepth_access_primitive( + root_p, keys, ctx, shared_lib, + ) + } else { + Ok(root_p) + } + } + _ => Err(anyhow!( + "illegal usage of multidepth access {root:?} => {keys:?}" + )), + } + } + let root_primitive = match root { + Value::String(s) => Primitive::String(s.to_string()), + v @ Value::FString(_, _) + | v @ Value::Variable(_) + | v @ Value::Array(_) + | v @ Value::Struct(_) + | v @ Value::BuiltInFunction { + fn_type: BuiltInFunctionType::Require, + .. + } + | v @ Value::FunctionCall { .. } + | v @ Value::VariableRef(_) => { + compute_lazy(v.clone(), ctx, shared_lib)? + } + v => return Err(anyhow::anyhow!("illegal multidepth access {v:?}")), + }; + + compute_multidepth_access_primitive( + root_primitive, + keys.iter().collect(), + ctx, + shared_lib, + ) +} + fn compute_recur( node: Option>, ctx: &mut BTreeMap, @@ -310,8 +696,105 @@ fn compute_recur( } Ok(v) } - TreeNodeValue::BuiltInFunction(fn_type) => { - let v = compute_recur(node.first_child(), ctx, shared_lib)?; + + TreeNodeValue::IfExpr(v) => { + let mut scoped_ctx = ctx.clone(); + compute_instructions( + vec![v.clone()], + &mut scoped_ctx, + shared_lib, + ) + } + TreeNodeValue::WhileExpr(v) => { + let mut scoped_ctx = ctx.clone(); + compute_instructions( + vec![v.clone()], + &mut scoped_ctx, + shared_lib, + ) + } + TreeNodeValue::Foreach(v) => { + let mut scoped_ctx = ctx.clone(); + compute_instructions( + vec![v.clone()], + &mut scoped_ctx, + shared_lib, + ) + } + TreeNodeValue::Array(arr) => { + let mut primitives = vec![]; + for v in arr { + let primitive = + compute_instructions(vec![v.clone()], ctx, shared_lib)?; + match primitive { + v @ Primitive::Error(_) => return Ok(v), + Primitive::Unit => { + return Ok(Primitive::Error( + "cannot push unit () to array".to_string(), + )) + } + _ => primitives.push(primitive), + } + } + Ok(Primitive::Array(primitives)) + } + + TreeNodeValue::Struct(struc) => { + let mut primitives = BTreeMap::new(); + for (k, v) in struc { + if !k.starts_with('_') { + let primitive = compute_instructions( + vec![v.clone()], + ctx, + shared_lib, + )?; + match primitive { + v @ Primitive::Error(_) => return Ok(v), + Primitive::Unit => { + return Ok(Primitive::Error( + "cannot push unit () to struct".to_string(), + )) + } + _ => { + primitives.insert(k.to_string(), primitive); + } + } + } + } + Ok(Primitive::Struct(primitives)) + } + TreeNodeValue::MultiDepthAccess { root, keys } => { + compute_multidepth_access(root, keys, ctx, shared_lib) + } + TreeNodeValue::MultiDepthVariableAssign { root, next_keys } => { + let new_value = + compute_recur(node.first_child(), ctx, shared_lib)?; + fold_multidepth(root, next_keys, new_value, ctx, shared_lib) + } + + TreeNodeValue::Function(Value::Function { parameters, exprs }) => { + if let Value::BlockParen(parameters) = parameters.borrow() { + if !parameters.iter().all(|v| { + matches!(v, Value::Variable(_)) + // || matches!(v, Value::String(_)) + || matches!(v, Value::VariableUnused) + }) { + return Ok(Primitive::Error(format!( + "not a valid parameter: {parameters:?}" + ))); + } + Ok(Primitive::Function { + parameters: parameters.clone(), + exprs: exprs.to_owned(), + }) + } else { + Ok(Primitive::Error(format!( + "not a valid function: {parameters:?}, {exprs:?}" + ))) + } + } + TreeNodeValue::BuiltInFunction { fn_type, params } => { + let v = compute_lazy(params.clone(), ctx, shared_lib)?; match fn_type { adana_script_core::BuiltInFunctionType::Sqrt => { Ok(v.sqrt()) @@ -436,29 +919,7 @@ fn compute_recur( )), } } - // adana_script_core::BuiltInFunctionType::ReadLines => { - // match v { - // Primitive::String(file_path) => { - // if !PathBuf::from(file_path.as_str()).exists() { - // Ok(Primitive::Error(format!( - // "file {file_path} not found" - // ))) - // } else { - // let file = File::open(file_path)?; - // let reader = BufReader::new(file); - // Ok(Primitive::Array( - // reader - // .lines() - // .map(|s| s.map(Primitive::String)) - // .collect::, _>>()?, - // )) - // } - // } - // _ => Ok(Primitive::Error( - // "wrong read lines call".to_string(), - // )), - // } - // } + adana_script_core::BuiltInFunctionType::TypeOf => { Ok(v.type_of()) } @@ -593,378 +1054,26 @@ fn compute_recur( adana_script_core::BuiltInFunctionType::MakeError => { Ok(Primitive::Error(v.to_string())) } - } - } - TreeNodeValue::IfExpr(v) => { - let mut scoped_ctx = ctx.clone(); - compute_instructions( - vec![v.clone()], - &mut scoped_ctx, - shared_lib, - ) - } - TreeNodeValue::WhileExpr(v) => { - let mut scoped_ctx = ctx.clone(); - compute_instructions( - vec![v.clone()], - &mut scoped_ctx, - shared_lib, - ) - } - TreeNodeValue::Foreach(v) => { - let mut scoped_ctx = ctx.clone(); - compute_instructions( - vec![v.clone()], - &mut scoped_ctx, - shared_lib, - ) - } - TreeNodeValue::Array(arr) => { - let mut primitives = vec![]; - for v in arr { - let primitive = - compute_instructions(vec![v.clone()], ctx, shared_lib)?; - match primitive { - v @ Primitive::Error(_) => return Ok(v), - Primitive::Unit => { - return Ok(Primitive::Error( - "cannot push unit () to array".to_string(), - )) - } - _ => primitives.push(primitive), - } - } - Ok(Primitive::Array(primitives)) - } - TreeNodeValue::ArrayAccess { index, array } => { - let error_message = || { - format!( - "illegal index {index:?} for array access {array:?}" - ) - }; - match (array, index) { - (Value::Variable(v), index) => { - let index = - compute_lazy(index.clone(), ctx, shared_lib)?; - let array = ctx - .get(v) - .context("array not found in context")? - .read() - .map_err(|e| { - anyhow::Error::msg(format!( - "could not acquire lock {e}" - )) - })?; - if let Primitive::NativeLibrary(lib) = - array.as_ref_ok()? - { - Ok(Primitive::NativeFunction( - index.to_string(), - lib.clone(), - )) - } else { - Ok(array.index_at(&index)) - } - } - - (Value::String(v), index) => { - let v = Primitive::String(v.clone()); - let index = - compute_lazy(index.clone(), ctx, shared_lib)?; - Ok(v.index_at(&index)) - } - (Value::Array(array), index) => { - let index = - match compute_lazy(index.clone(), ctx, shared_lib)? - { - Primitive::Int(index) => index as usize, - Primitive::U8(index) => index as usize, - Primitive::I8(index) => index as usize, - - _ => { - return Err(anyhow::format_err!( - "COMPUTE: illegal array access! {index:?}" - )) - } - }; - - let index = index as usize; - let value = array.get(index).context(error_message())?; - if index < array.len() { - let primitive = compute_instructions( - vec![value.clone()], - ctx, - shared_lib, - )?; - return Ok(primitive); - } - Err(anyhow::Error::msg(error_message())) + adana_script_core::BuiltInFunctionType::Jsonify => { + Ok(Primitive::String(v.to_json()?)) } - ( - v @ Value::ArrayAccess { arr: _, index: _ } - | v @ Value::StructAccess { struc: _, key: _ }, - index, - ) => { - let v = compute_lazy(v.clone(), ctx, shared_lib)?; - let index = - compute_lazy(index.clone(), ctx, shared_lib)?; - match v { - p @ Primitive::Array(_) => Ok(p.index_at(&index)), - - _ => Err(anyhow::Error::msg(error_message())), - } + adana_script_core::BuiltInFunctionType::ParseJson => { + Primitive::from_json(&v.to_string()) } - _ => Err(anyhow::Error::msg(error_message())), - } - } - TreeNodeValue::Struct(struc) => { - let mut primitives = BTreeMap::new(); - for (k, v) in struc { - if !k.starts_with('_') { - let primitive = compute_instructions( - vec![v.clone()], - ctx, - shared_lib, - )?; - match primitive { - v @ Primitive::Error(_) => return Ok(v), - Primitive::Unit => { - return Ok(Primitive::Error( - "cannot push unit () to struct".to_string(), - )) - } - _ => { - primitives.insert(k.to_string(), primitive); - } - } - } - } - Ok(Primitive::Struct(primitives)) - } - TreeNodeValue::StructAccess { struc, key } => match (struc, key) { - (Value::Variable(v), key @ Primitive::String(k)) => { - if cfg!(test) { - dbg!(&ctx); - } - let struc = ctx - .get(v) - .context("struct not found in context")? - .read() - .map_err(|e| { - anyhow::format_err!("could not acquire lock {e}") - })?; - if let Primitive::NativeLibrary(lib) = struc.as_ref_ok()? { - Ok(Primitive::NativeFunction(k.clone(), lib.clone())) - } else { - Ok(struc.index_at(key)) - } - } - ( - v @ Value::BuiltInFunction { - fn_type: BuiltInFunctionType::Require, - .. - }, - key @ Primitive::String(k), - ) => { - let native_lib = compute_lazy(v.clone(), ctx, shared_lib)?; - if let Primitive::NativeLibrary(lib) = - native_lib.as_ref_ok()? - { - Ok(Primitive::NativeFunction(k.clone(), lib.clone())) - } else { - Err(anyhow::format_err!( - "could not parse built in fn {key} => {native_lib:?}" - )) - } - } - (s @ Value::Struct(_), key @ Primitive::String(_)) - | ( - s @ Value::StructAccess { struc: _, key: _ }, - key @ Primitive::String(_), - ) - | ( - s @ Value::ArrayAccess { arr: _, index: _ }, - key @ Primitive::String(_), - ) => { - let prim_s = compute_lazy(s.clone(), ctx, shared_lib)?; - Ok(prim_s.index_at(key)) - } - _ => Ok(Primitive::Error(format!( - "Error struct access: struct {struc:?}, key {key} " - ))), - }, - TreeNodeValue::VariableArrayAssign { name, index } => { - let index = compute_lazy(index.clone(), ctx, shared_lib)?; - let mut v = compute_recur(node.first_child(), ctx, shared_lib)?; - let mut array = ctx - .get_mut(name) - .context("array not found in context")? - .write() - .map_err(|e| { - anyhow::format_err!("could not acquire lock {e}") - })?; - Ok(array.swap_mem(&mut v, &index)) - } - TreeNodeValue::Function(Value::Function { parameters, exprs }) => { - if let Value::BlockParen(parameters) = parameters.borrow() { - if !parameters.iter().all(|v| { - matches!(v, Value::Variable(_)) - // || matches!(v, Value::String(_)) - || matches!(v, Value::VariableUnused) - }) { - return Ok(Primitive::Error(format!( - "not a valid parameter: {parameters:?}" - ))); - } - Ok(Primitive::Function { - parameters: parameters.clone(), - exprs: exprs.to_owned(), - }) - } else { - Ok(Primitive::Error(format!( - "not a valid function: {parameters:?}, {exprs:?}" - ))) } } + TreeNodeValue::FunctionCall(Value::FunctionCall { parameters, - ref function, + function, }) => { - if let Value::BlockParen(param_values) = parameters.borrow() { - let mut function = compute_instructions( - vec![*function.clone()], - ctx, - shared_lib, - )?; - - // FIXME clone again - if let Primitive::Ref(r) = function { - function = r - .read() - .map_err(|e| { - anyhow::format_err!( - "could not acquire lock in fn call{e}" - ) - })? - .clone(); - } - if let Primitive::Function { - parameters: function_parameters, - exprs, - } = function - { - let mut scope_ctx = scoped_ctx(ctx)?; - for (i, param) in function_parameters.iter().enumerate() - { - if let Some(value) = param_values.get(i) { - if let Value::Variable(variable_from_fn_def) = - param - { - let variable_from_fn_call = compute_lazy( - value.clone(), - ctx, - shared_lib, - )?; - scope_ctx.insert( - variable_from_fn_def.clone(), - variable_from_fn_call.ref_prim(), - ); - } - } else { - return Ok(Primitive::Error(format!( - "missing parameter {param:?}" - ))); - } - } - // TODO remove this and replace Arc> by Arc - // call function in a specific os thread with its own stack - // This was relative to a small stack allocated by musl - // But now it doesn't seem needed anymore - // let res = spawn(move || {}).join().map_err(|e| { - // anyhow::Error::msg(format!( - // "something wrong: {e:?}" - // )) - // })??; - let res = compute_instructions( - exprs, - &mut scope_ctx, - shared_lib, - )?; - - if let Primitive::EarlyReturn(v) = res { - return Ok(*v); - } - Ok(res) - } else if let Primitive::NativeLibrary(lib) = function { - if cfg!(test) { - dbg!(&lib); - } - let mut parameters = vec![]; - for param in param_values.iter() { - if let Value::Variable(_) = param { - let variable_from_fn_call = compute_lazy( - param.clone(), - ctx, - shared_lib, - )?; - parameters.push(variable_from_fn_call); - } - } - if cfg!(test) { - dbg!(¶meters); - } - Ok(Primitive::Error("debug".into())) - //Ok(function(vec![Primitive::String("s".into())])) - } else if let Primitive::NativeFunction(key, lib) = function - { - #[cfg(not(target_arch = "wasm32"))] - { - if cfg!(test) { - dbg!(&key, &lib); - } - let mut parameters = vec![]; - - for param in param_values.iter() { - let variable_from_fn_call = compute_lazy( - param.clone(), - ctx, - shared_lib, - )?; - parameters.push(variable_from_fn_call); - } - if cfg!(test) { - dbg!(¶meters); - } - - let mut scope_ctx = scoped_ctx(ctx)?; + let function = compute_instructions( + vec![*function.clone()], + ctx, + shared_lib, + )?; - let slb = shared_lib.as_ref().to_path_buf(); - let fun = move |v, extra_ctx| { - scope_ctx.extend(extra_ctx); - compute_lazy(v, &mut scope_ctx, &slb) - }; - unsafe { - lib.call_function( - key.as_str(), - parameters, - Box::new(fun), - ) - } - } - #[cfg(target_arch = "wasm32")] - { - return Ok(Primitive::Error(format!("Loading native function {key} doesn't work in wasm context! {lib:?}"))); - } - } else { - Ok(Primitive::Error(format!( - " not a function: {function}" - ))) - } - } else { - Ok(Primitive::Error(format!( - "invalid function call: {parameters:?} => {function:?}" - ))) - } + handle_function_call(function, parameters, ctx, shared_lib) } TreeNodeValue::FunctionCall(v) => Ok(Primitive::Error(format!( "unexpected function call declaration: {v:?}" @@ -975,42 +1084,20 @@ fn compute_recur( TreeNodeValue::Break => Ok(Primitive::NoReturn), TreeNodeValue::Null => Ok(Primitive::Null), TreeNodeValue::Drop(variables) => { - pub use Primitive::{Error as PrimErr, Int}; pub use Value::Variable; for var in variables { match var { Variable(v) => { ctx.remove(v); } - Value::StructAccess { struc, key } => { - match struc.borrow(){ - Variable(s) => { - let struc = ctx.get_mut(s) - .ok_or_else(||anyhow::format_err!("ctx doesn't contains array {s}"))?; - let mut struc = struc.write() - .map_err(|e| anyhow::format_err!("DROP STRUC : could not acquire lock {e}"))?; - struc.remove(&Primitive::String(key.into()))?; - } - _ => return Ok(PrimErr(format!("only primitive within the ctx can be dropped {struc:?}"))) - } - } - Value::ArrayAccess { arr, index } => { - match arr.borrow(){ - Variable(s) => { - let array = ctx.get_mut(s) - .ok_or_else(||anyhow::format_err!("ctx doesn't contains array {s}"))?; - let mut array = array.write() - .map_err(|e| anyhow::format_err!("DROP ARRAY : could not acquire lock {e}"))?; - match index.borrow() { - Value::Integer(i) => { array.remove(&Int(*i))}, - Value::U8(i) => { array.remove(&Primitive::U8(*i))}, - Value::I8(i) => { array.remove(&Primitive::I8(*i))}, - e => return Ok(PrimErr(format!("index not an int! {e:?}"))) - - }?; - } - _ => return Ok(PrimErr(format!("only primitive within the ctx can be dropped {arr:?}"))) - } + Value::MultiDepthAccess { root, next_keys } => { + fold_multidepth( + root, + next_keys, + Primitive::Unit, + ctx, + shared_lib, + )?; } _ => { return Err(Error::msg(format!( diff --git a/adana-script/src/parser.rs b/adana-script/src/parser.rs index 3132d1e..f9e09b1 100644 --- a/adana-script/src/parser.rs +++ b/adana-script/src/parser.rs @@ -15,7 +15,8 @@ use adana_script_core::{ constants::{ BREAK, DROP, ELSE, FOR, IF, IN, MULTILINE, NULL, RETURN, STRUCT, WHILE, }, - FORBIDDEN_VARIABLE_NAME, + primitive::Primitive, + KeyAccess, FORBIDDEN_VARIABLE_NAME, }; use adana_script_core::{BuiltInFunctionType, MathConstants, Operator, Value}; @@ -163,7 +164,7 @@ fn parse_fstring(s: &str) -> Res { )(param_rest) { param_rest = pr; - match parse_expression(param_str) { + match parse_complex_expression(param_str) { Ok((_, param_value)) => { parameters.push((format!("${{{param_str}}}"), param_value)); } @@ -186,6 +187,9 @@ fn parse_string(s: &str) -> Res { })(s) } +fn parse_key_struct(s: &str) -> Res<&str> { + take_while1(|c: char| c.is_alphanumeric() || c == '_' || c == '-')(s) +} fn parse_variable_str(s: &str) -> Res<&str> { let allowed_values = |s| { take_while1(|s: char| { @@ -319,12 +323,7 @@ fn parse_fn_args(s: &str) -> Res> { fn parse_fn_call(s: &str) -> Res { map( pair( - alt(( - parse_fn, - parse_struct_access, - parse_array_access, - parse_variable, - )), + alt((parse_fn, parse_variable)), map(parse_fn_args, Value::BlockParen), ), |(function, parameters)| Value::FunctionCall { @@ -344,9 +343,8 @@ fn parse_foreach(s: &str) -> Res { tag_no_space(IN), alt(( parse_range, + parse_multidepth_access, parse_fn_call, - parse_struct_access, - parse_array_access, parse_array, parse_fstring, parse_string, @@ -381,7 +379,7 @@ fn parse_drop(s: &str) -> Res { let parser = |p| { separated_list1( tag_no_space(","), - alt((parse_struct_access, parse_array_access, parse_variable)), + alt((parse_multidepth_access, parse_variable)), )(p) }; @@ -456,6 +454,10 @@ fn parse_builtin_fn(s: &str) -> Res { parse_builtin(BuiltInFunctionType::Ceil), parse_builtin(BuiltInFunctionType::Floor), )), + alt(( + parse_builtin(BuiltInFunctionType::Jsonify), + parse_builtin(BuiltInFunctionType::ParseJson), + )), alt(( parse_builtin_many_args(BuiltInFunctionType::IsMatch), parse_builtin_many_args(BuiltInFunctionType::Match), @@ -482,9 +484,25 @@ fn parse_struct_expr(s: &str) -> Res { ))(s) } pub(super) fn parse_struct(s: &str) -> Res { - let pair_key_value = |p| { + use nom::character::complete::char as charParser; + let parse_key = |p| { + preceded( + multispace0, + terminated( + map( + preceded( + opt(charParser('"')), + terminated(parse_key_struct, opt(charParser('"'))), + ), + String::from, + ), + multispace0, + ), + )(p) + }; + let pair_key_value = move|p| { separated_pair( - preceded(multispace0, terminated(map(parse_variable_str, String::from), multispace0)), + parse_key, tag_no_space(":"), preceded(opt(comments),parse_struct_expr)) }(p); @@ -518,10 +536,70 @@ fn parse_array(s: &str) -> Res { Value::Array, )(s) } -fn parse_array_access(s: &str) -> Res { - let (rest, mut array_access) = map( + +fn parse_key_brackets(s: &str) -> Res { + map( + pair( + preceded( + tag("["), + terminated( + map( + delimited(tag("\""), parse_key_struct, tag("\"")), + |k| KeyAccess::Key(Primitive::String(k.to_string())), + ), + tag("]"), + ), + ), + opt(parse_fn_args), + ), + |(k, params)| { + if let Some(params) = params { + KeyAccess::FunctionCall { + parameters: Value::BlockParen(params), + key: Box::new(k), + } + } else { + k + } + }, + )(s) +} + +fn parse_variable_brackets(s: &str) -> Res { + map( + pair( + preceded( + tag("["), + terminated( + map( + verify(parse_variable, |v| { + matches!( + v, + Value::VariableRef(_) | Value::Variable(_) + ) + }), + KeyAccess::Variable, + ), + tag("]"), + ), + ), + opt(parse_fn_args), + ), + |(k, params)| { + if let Some(params) = params { + KeyAccess::FunctionCall { + parameters: Value::BlockParen(params), + key: Box::new(k), + } + } else { + k + } + }, + )(s) +} +fn parse_index_brackets(s: &str) -> Res { + map( pair( - alt((parse_array, parse_variable, parse_fstring, parse_string)), preceded( tag_no_space("["), terminated( @@ -542,132 +620,102 @@ fn parse_array_access(s: &str) -> Res { tag_no_space("]"), ), ), + opt(parse_fn_args), ), - |(arr, idx)| Value::ArrayAccess { - arr: Box::new(arr), - index: Box::new(idx), - }, - )(s)?; - - let mut new_rest = rest; - - // FIXME this is highly HACKY, fix me along with :463 - 'while_loop: while let Ok((rest, array)) = parse_array(new_rest) { - if let Value::Array(mut array) = array { - if array.len() == 1 && array.len() == 1 { - match array.remove(0) { - v @ Value::Integer(_) => { - array_access = Value::ArrayAccess { - arr: Box::new(array_access), - index: Box::new(v), - }; - } - v @ Value::U8(_) => { - array_access = Value::ArrayAccess { - arr: Box::new(array_access), - index: Box::new(v), - }; - } - v @ Value::I8(_) => { - array_access = Value::ArrayAccess { - arr: Box::new(array_access), - index: Box::new(v), - }; - } - Value::String(s) => { - array_access = Value::StructAccess { - struc: Box::new(array_access), - key: s.to_string(), - }; - } - _ => break 'while_loop, + |(k, params)| { + if let Some(params) = params { + KeyAccess::FunctionCall { + parameters: Value::BlockParen(params), + key: Box::new(KeyAccess::Variable(k)), } - new_rest = rest; + } else { + KeyAccess::Variable(k) } - } - } - - Ok((new_rest, array_access)) + }, + )(s) } - -fn parse_key_brackets(s: &str) -> Res<&str> { - preceded( - tag("["), - terminated( - delimited(tag("\""), parse_variable_str, tag("\"")), - tag("]"), +fn parse_key_dots(s: &str) -> Res { + map( + pair( + map(preceded(tag("."), parse_variable_str), |k| { + KeyAccess::Key(Primitive::String(k.to_string())) + }), + opt(parse_fn_args), ), + |(k, params)| { + if let Some(params) = params { + KeyAccess::FunctionCall { + parameters: Value::BlockParen(params), + key: Box::new(k), + } + } else { + k + } + }, )(s) } -fn parse_key_dots(s: &str) -> Res<&str> { - preceded(tag("."), parse_variable_str)(s) -} -fn parse_struct_access(s: &str) -> Res { - let (res, mut struc_access) = map( +fn parse_multidepth_access(s: &str) -> Res { + let (res, (root, mut next_keys)) = map( alt(( - pair(alt((parse_struct, parse_variable)), parse_key_brackets), - pair(alt((parse_struct, parse_variable)), parse_key_dots), - pair(parse_array_access, parse_key_brackets), - pair(parse_array_access, parse_key_dots), - pair(parse_builtin_fn, parse_key_dots), + pair( + parse_builtin_fn, + alt(( + parse_key_brackets, + parse_index_brackets, + parse_variable_brackets, + parse_key_dots, + )), + ), + pair( + parse_fn_call, + alt(( + parse_key_brackets, + parse_index_brackets, + parse_variable_brackets, + parse_key_dots, + )), + ), + pair( + parse_variable, + alt(( + parse_key_brackets, + parse_index_brackets, + parse_variable_brackets, + parse_key_dots, + )), + ), + pair( + parse_struct, + alt(( + parse_key_brackets, + parse_index_brackets, + parse_variable_brackets, + parse_key_dots, + )), + ), + pair( + alt((parse_array, parse_fstring, parse_string)), + alt((parse_variable_brackets, parse_index_brackets)), + ), )), - |(s, key)| Value::StructAccess { - struc: Box::new(s), - key: String::from(key), - }, + |(s, key)| (Box::new(s), vec![key]), )(s)?; let mut new_rest = res; - while let Ok((rest, key)) = - alt((parse_key_brackets, parse_key_dots))(new_rest) + while let Ok((rest, key)) = alt(( + parse_key_brackets, + parse_index_brackets, + parse_variable_brackets, + parse_key_dots, + ))(new_rest) { - struc_access = Value::StructAccess { - struc: Box::new(struc_access), - key: String::from(key), - }; + next_keys.push(key); new_rest = rest; } - // FIXME this is highly HACKY, fix me along with :393 - 'while_loop: while let Ok((rest, array)) = parse_array(new_rest) { - if let Value::Array(mut array) = array { - if array.len() == 1 { - match array.remove(0) { - v @ Value::Integer(_) => { - struc_access = Value::ArrayAccess { - arr: Box::new(struc_access), - index: Box::new(v), - }; - } - v @ Value::U8(_) => { - struc_access = Value::ArrayAccess { - arr: Box::new(struc_access), - index: Box::new(v), - }; - } - - v @ Value::I8(_) => { - struc_access = Value::ArrayAccess { - arr: Box::new(struc_access), - index: Box::new(v), - }; - } - Value::String(s) => { - struc_access = Value::StructAccess { - struc: Box::new(struc_access), - key: s.to_string(), - }; - } - _ => break 'while_loop, - } - new_rest = rest; - } - } - } - - Ok((new_rest, struc_access)) + Ok((new_rest, Value::MultiDepthAccess { root, next_keys })) } fn parse_implicit_multiply(s: &str) -> Res { @@ -684,15 +732,14 @@ fn parse_value(s: &str) -> Res { parse_block_paren, parse_operation, parse_implicit_multiply, + parse_multidepth_access, parse_struct, + parse_builtin_fn, parse_fn_call, - parse_struct_access, - parse_array_access, parse_array, parse_range, parse_number, parse_fn, - parse_builtin_fn, parse_bool, parse_fstring, parse_string, @@ -761,26 +808,11 @@ fn parse_simple_instruction(s: &str) -> Res { map( separated_pair( pair( - alt(( - parse_struct_access, - parse_array_access, - parse_variable, - )), + alt((parse_multidepth_access, parse_variable)), opt(parse_operation), ), tag_no_space("="), - alt(( - parse_fn_call, - parse_fn, - parse_array_access, - //parse_struct_access, /* FIXME seems not necessary or - // and also buggy - // missing test */ - parse_struct, - parse_fstring, - parse_array, - parse_expression, - )), + parse_complex_expression, ), |((variable, mut operator), expr)| { if let Some(operator) = operator.take() { @@ -798,16 +830,7 @@ fn parse_simple_instruction(s: &str) -> Res { } }, ), - alt(( - all_consuming(parse_fn_call), - all_consuming(parse_fn), - all_consuming(parse_struct_access), - all_consuming(parse_array_access), - all_consuming(parse_struct), - all_consuming(parse_fstring), - all_consuming(parse_array), - parse_expression, - )), + parse_complex_expression, ))(s) } @@ -873,29 +896,56 @@ where fn parse_break(s: &str) -> Res { map(tag_no_space(BREAK), |_| Value::Break)(s) } +// CONTEXT: this should maybe replace parse_expression +fn parse_complex_expression(s: &str) -> Res { + alt(( + all_consuming(parse_multidepth_access), + all_consuming(parse_builtin_fn), + all_consuming(parse_fn_call), + // TODO maybe with tuple() this giant mess can be simplified e.g + // tuple(alt(parser1, parser2,...)) + map( + tuple(( + alt((parse_multidepth_access, parse_fn_call)), + parse_operation, + parse_expression, + )), + |(k, o, v)| Value::Expression(vec![k, o, v]), + ), + parse_fn, + parse_struct, + parse_fstring, + parse_array, + parse_expression, + ))(s) +} + fn parse_early_return(s: &str) -> Res { - map(preceded(tag_no_space(RETURN), opt(parse_expression)), |v| { + map(preceded(tag_no_space(RETURN), opt(parse_complex_expression)), |v| { Value::EarlyReturn(Box::new(v)) })(s) } pub fn parse_instructions(instructions: &str) -> Res> { let (instructions, _) = opt(comments)(instructions)?; + let instructions = instructions.trim(); if instructions.is_empty() { return Ok((instructions, vec![Value::NoOp])); } + terminated( many1(preceded( opt(comments), alt(( + all_consuming(parse_value), parse_foreach, parse_while_statement, parse_if_statement, - parse_break, - parse_early_return, parse_simple_instruction, parse_drop, + parse_early_return, + parse_break, )), )), opt(comments), diff --git a/adana-script/src/tests/test_array.rs b/adana-script/src/tests/array.rs similarity index 81% rename from adana-script/src/tests/test_array.rs rename to adana-script/src/tests/array.rs index 39191df..4c3938b 100644 --- a/adana-script/src/tests/test_array.rs +++ b/adana-script/src/tests/array.rs @@ -222,3 +222,51 @@ fn test_array_access_expr() { ]) ); } +#[test] +#[serial] +fn test_array_access_from_fn_return() { + let mut ctx = BTreeMap::new(); + let expr = r#"x = ()=> {[1,2,7,8,9]}() + 4"#; + let _ = compute(expr, &mut ctx, "N/A").unwrap(); + assert_eq!( + ctx["x"].read().unwrap().clone(), + Primitive::Array(vec![ + Primitive::U8(1), + Primitive::U8(2), + Primitive::U8(7), + Primitive::U8(8), + Primitive::U8(9), + Primitive::U8(4), + ]) + ); + let expr = r#"x = ()=> {[1,2,7,8,9]}() + 4 + z="whatever" + "#; + let _ = compute(expr, &mut ctx, "N/A").unwrap(); + assert_eq!( + ctx["z"].read().unwrap().clone(), + Primitive::String("whatever".to_string()) + ); +} +#[test] +#[serial] +fn test_array_access_key_index() { + let mut ctx = BTreeMap::new(); + let expr = r#"x = ()=> {[1,2,7,8,9]}()[3] + 4"#; + let _ = compute(expr, &mut ctx, "N/A").unwrap(); + assert_eq!(ctx["x"].read().unwrap().clone(), Primitive::Int(12)); + let expr = r#"x = ()=> {[1,2,7,8,9]}()[3] + 4 + z="whatever" + "!" + ":)" + if z == "whatever!:)" { + z = "my test " + z + }else { + z = "no test" + } + "#; + let _ = compute(expr, &mut ctx, "N/A").unwrap(); + + assert_eq!( + ctx["z"].read().unwrap().clone(), + Primitive::String("my test whatever!:)".to_string()) + ); +} diff --git a/adana-script/src/tests/builtin.rs b/adana-script/src/tests/builtin.rs index f71a1d5..e8146d7 100644 --- a/adana-script/src/tests/builtin.rs +++ b/adana-script/src/tests/builtin.rs @@ -85,6 +85,105 @@ fn test_ceil() { let r = compute(r#"ceil(4.7)"#, &mut ctx, "N/A").unwrap(); assert_eq!(Primitive::Double(5.), r); } +#[test] +fn test_parse_json() { + let mut ctx = BTreeMap::new(); + let expr = r#"parse_json("""{"a": 9}""")"#; + let r = compute(expr, &mut ctx, "N/A").unwrap(); + assert_eq!( + r, + Primitive::Struct(BTreeMap::from([( + "a".to_string(), + Primitive::Int(9) + )])) + ); + let expr = r#" + struct { + firstName: "Nordine", + lastName: "Bittich", + age: 400, + notes: [1034,1032,3.18,"hello",3334], + skills: struct { + programming: "🔥", + sport: "âš½", + } + } + "#; + let expect = compute(expr, &mut ctx, "N/A").unwrap(); + let expr = r#" + s= parse_json("""{ + "age": 400, + "firstName": "Nordine", + "lastName": "Bittich", + "notes": [ + 1034, + 1032, + 3.18, + "hello", + 3334 + ], + "skills": { + "programming": "🔥", + "sport": "âš½" + } + }""") + "#; + let curr = compute(expr, &mut ctx, "N/A").unwrap(); + assert_eq!(curr, expect); +} +#[test] +fn test_jsonify() { + let mut ctx = BTreeMap::new(); + let expr = r#"jsonify(struct {a: 9})"#; + let r = compute(expr, &mut ctx, "N/A").unwrap(); + assert_eq!( + r, + Primitive::String( + r#"{ + "a": 9 +}"# + .to_string() + ) + ); + + let expr = r#" + s = struct { + firstName: "Nordine", + lastName: "Bittich", + age: 36, + notes: [1,2,3.18,"hello",4], + skills: struct { + programming: "🔥", + sport: "âš½", + } + } + jsonify(s) + "#; + let r = compute(expr, &mut ctx, "N/A").unwrap(); + assert_eq!( + Primitive::String( + r#"{ + "age": 36, + "firstName": "Nordine", + "lastName": "Bittich", + "notes": [ + 1, + 2, + 3.18, + "hello", + 4 + ], + "skills": { + "programming": "🔥", + "sport": "âš½" + } +}"# + .to_string() + ), + r + ); +} + #[test] fn test_round() { let mut ctx = BTreeMap::new(); diff --git a/adana-script/src/tests/test_chaining.rs b/adana-script/src/tests/chaining.rs similarity index 92% rename from adana-script/src/tests/test_chaining.rs rename to adana-script/src/tests/chaining.rs index cff924b..6542bdb 100644 --- a/adana-script/src/tests/test_chaining.rs +++ b/adana-script/src/tests/chaining.rs @@ -28,7 +28,7 @@ fn complex_struct_array_struct() { m: 12, y: ["hello", 3, struct {n: "world"}] } - x = x.y[0] + " " + x.y[2]["n"] # FIXME HACKY + x = x.y[0] + " " + x.y[2]["n"] "#; let mut ctx = BTreeMap::new(); @@ -81,7 +81,6 @@ fn complex_struct_struct_struct_fn() { } } x = x.a + x.y.sp + x.y.z.m() - "#; let mut ctx = BTreeMap::new(); let res = compute(expr, &mut ctx, "N/A").unwrap(); @@ -94,7 +93,7 @@ fn complex_struct_struct_struct_fn() { } #[test] -fn complex_struct_struct_struct_fn2() { +fn complex_struct_struct_struct_other() { let expr = r#" x = struct { m: 12, @@ -106,7 +105,7 @@ fn complex_struct_struct_struct_fn2() { } } } - x = multiline {x.a() + x.y.sp + x.y.z.m()} # FIXME it requires parenthesises or multiline + x = x.a() + x.y.sp + x.y.z.m() "#; let mut ctx = BTreeMap::new(); @@ -123,7 +122,7 @@ fn complex_struct_struct_struct_fn2() { fn simple_array_two_depth() { let expr = r#" z = [0, 2, "hello", [3," ", "world"]] - x = (z[2] + z[3][1] + z[3][2]) # FIXME requires parenthesises + x = z[2] + z[3][1] + z[3][2] "#; let mut ctx = BTreeMap::new(); let res = compute(expr, &mut ctx, "N/A").unwrap(); diff --git a/adana-script/src/tests/test_drop.rs b/adana-script/src/tests/drop.rs similarity index 100% rename from adana-script/src/tests/test_drop.rs rename to adana-script/src/tests/drop.rs diff --git a/adana-script/src/tests/tests_file.rs b/adana-script/src/tests/file.rs similarity index 100% rename from adana-script/src/tests/tests_file.rs rename to adana-script/src/tests/file.rs diff --git a/adana-script/src/tests/funct.rs b/adana-script/src/tests/funct.rs index 675b453..cb540f1 100644 --- a/adana-script/src/tests/funct.rs +++ b/adana-script/src/tests/funct.rs @@ -318,3 +318,28 @@ fn test_array_access_fn_call() { Primitive::String("hello nordine".into()) ); } + +#[test] +fn test_regression_struct_with_builtin() { + let mut ctx = BTreeMap::new(); + let expr = r#" + h= (req) => { + println(req) + return "hello bro!" + } + h("hello") + + "#; + let r = compute(expr, &mut ctx, "N/A").unwrap(); + assert_eq!(r, Primitive::String("hello bro!".into())); + let expr = r#" + h= (req) => { + println(req) + return "hello bro!" + } + h(struct {}) + + "#; + let r = compute(expr, &mut ctx, "N/A").unwrap(); + assert_eq!(r, Primitive::String("hello bro!".into())); +} diff --git a/adana-script/src/tests/mod.rs b/adana-script/src/tests/mod.rs index d4e4b58..1708f7b 100644 --- a/adana-script/src/tests/mod.rs +++ b/adana-script/src/tests/mod.rs @@ -1,20 +1,20 @@ +mod array; mod bitwise; mod builtin; +mod chaining; +mod drop; mod dynload; mod examples; +mod file; mod foreach; mod funct; mod is_type; mod misc; mod opassign; +mod parser; mod range; +mod reference; +mod scope_ctx; mod strings; mod struc; -mod test_array; -mod test_chaining; -mod test_drop; -mod test_parser; -mod test_reference; -mod test_scope_ctx; -mod tests_file; mod unused; diff --git a/adana-script/src/tests/test_parser.rs b/adana-script/src/tests/parser.rs similarity index 91% rename from adana-script/src/tests/test_parser.rs rename to adana-script/src/tests/parser.rs index cf15390..392cca4 100644 --- a/adana-script/src/tests/test_parser.rs +++ b/adana-script/src/tests/parser.rs @@ -2,10 +2,11 @@ use std::collections::BTreeMap; use crate::parser::parse_instructions; use adana_script_core::{ - BuiltInFunctionType, Operator, + primitive::Primitive, + BuiltInFunctionType, KeyAccess, Operator, Value::{ self, BlockParen, Expression, Function, Operation, Variable, - VariableExpr, WhileExpr, U8, + VariableExpr, WhileExpr, }, }; @@ -161,20 +162,22 @@ fn test_parse_break() { exprs: vec![ VariableExpr { name: Box::new(Variable("k".to_string(),)), - expr: Box::new(Value::ArrayAccess { - arr: Box::new(Variable("m".to_string(),)), - index: Box::new(Variable( - "count".to_string(), - )), + expr: Box::new(Value::MultiDepthAccess { + root: Box::new(Variable("m".to_string(),)), + next_keys: vec![KeyAccess::Variable( + Value::Variable("count".to_string()) + )], },) }, Value::IfExpr { cond: Box::new(BlockParen(vec![ - Value::ArrayAccess { - arr: Box::new(Variable( + Value::MultiDepthAccess { + root: Box::new(Variable( "k".to_string(), )), - index: Box::new(U8(0,)), + next_keys: vec![KeyAccess::Variable( + Value::U8(0) + )], }, Operation(Operator::Equal,), Variable("key".to_string(),), @@ -462,12 +465,12 @@ fn test_array_fn_access() { Value::Variable("x".into()) ])) }, - Value::FunctionCall { - parameters: Box::new(Value::BlockParen(vec![Value::U8(5)])), - function: Box::new(Value::ArrayAccess { - arr: Box::new(Value::Variable("n".into())), - index: Box::new(Value::U8(2)) - }) + Value::MultiDepthAccess { + root: Box::new(Value::Variable("n".into())), + next_keys: vec![KeyAccess::FunctionCall { + key: Box::new(KeyAccess::Variable(Value::U8(2))), + parameters: Value::BlockParen(vec![Value::U8(5)]) + }] } ] ); @@ -545,12 +548,12 @@ fn test_comments_end_arr() { assert_eq!("", r); assert_eq!( ins, - vec![Value::FunctionCall { - parameters: Box::new(Value::BlockParen(vec![],)), - function: Box::new(Value::ArrayAccess { - arr: Box::new(Value::Variable("x".to_string(),)), - index: Box::new(Value::U8(1,)), - }), + vec![Value::MultiDepthAccess { + root: Box::new(Value::Variable("x".to_string(),)), + next_keys: vec![KeyAccess::FunctionCall { + key: Box::new(KeyAccess::Variable(Value::U8(1))), + parameters: Value::BlockParen(vec![],) + }] }] ); let expr = r#"x =[1, ()=> {print("hello")}] # doesn't work"#; @@ -602,18 +605,24 @@ fn test_struct_access_1() { }, Value::BuiltInFunction { fn_type: BuiltInFunctionType::Println, - expr: Box::new(Value::BlockParen(vec![Value::StructAccess { - struc: Box::new(Value::Variable("person".to_string(),)), - key: "age".to_string(), - },],)), + expr: Box::new(Value::BlockParen(vec![ + Value::MultiDepthAccess { + root: Box::new(Value::Variable("person".to_string(),)), + next_keys: vec![KeyAccess::Key(Primitive::String( + "age".to_string() + ))], + }, + ],)), }, Value::VariableExpr { name: Box::new(Value::Variable("x".to_string())), expr: Box::new(Value::Array(vec![ Value::U8(9), - Value::StructAccess { - struc: Box::new(Value::Variable("person".to_string())), - key: "name".to_string() + Value::MultiDepthAccess { + root: Box::new(Value::Variable("person".to_string())), + next_keys: vec![KeyAccess::Key(Primitive::String( + "name".to_string() + ))], } ])) } @@ -631,9 +640,9 @@ fn test_parser_array_directly_access() { v, vec![VariableExpr { name: Box::new(Variable("x".into()),), - expr: Box::new(ArrayAccess { - arr: Box::new(Array(vec![U8(1,), U8(2,), U8(3,),],)), - index: Box::new(U8(0,)), + expr: Box::new(MultiDepthAccess { + root: Box::new(Array(vec![U8(1,), U8(2,), U8(3,),],)), + next_keys: vec![KeyAccess::Variable(Value::U8(0))] }), }] ) diff --git a/adana-script/src/tests/test_reference.rs b/adana-script/src/tests/reference.rs similarity index 100% rename from adana-script/src/tests/test_reference.rs rename to adana-script/src/tests/reference.rs diff --git a/adana-script/src/tests/test_scope_ctx.rs b/adana-script/src/tests/scope_ctx.rs similarity index 100% rename from adana-script/src/tests/test_scope_ctx.rs rename to adana-script/src/tests/scope_ctx.rs diff --git a/adana-script/src/tests/struc.rs b/adana-script/src/tests/struc.rs index 38506dd..1a86144 100644 --- a/adana-script/src/tests/struc.rs +++ b/adana-script/src/tests/struc.rs @@ -310,3 +310,279 @@ fn test_struct_empty() { let r = compute(expr, &mut ctx, "N/A").unwrap(); assert_eq!(r, Primitive::String("nordine".into())); } + +#[test] +fn test_struct_modify_content_type() { + let mut ctx = BTreeMap::new(); + let expr = r#" + s = struct { headers: struct{}} + s.headers["Content-Type"] = "application/json" + s.headers["Content-Type"] + "#; + let r = compute(expr, &mut ctx, "N/A").unwrap(); + assert_eq!(r, Primitive::String("application/json".into())); +} + +#[test] +fn test_struct_key_between_quotes() { + let mut ctx = BTreeMap::new(); + let expr = r#" + s = struct { headers: struct{ + "Content-Type": "text/csv", + "other": "2" + }} + s.headers["Content-Type"] + s.headers.other + "#; + let r = compute(expr, &mut ctx, "N/A").unwrap(); + assert_eq!(r, Primitive::String("text/csv2".into())); +} + +#[test] +fn test_struct_from_readme_example() { + let mut ctx = BTreeMap::new(); + let expr = r#" + person = struct { + name: "hello", + age: 20, + headers: struct { + "Content-Type": "application/json" + } + } + person + "#; + let r = compute(expr, &mut ctx, "N/A").unwrap(); + assert_eq!( + r, + Primitive::Struct(BTreeMap::from([ + ("name".to_string(), Primitive::String("hello".to_string())), + ("age".to_string(), Primitive::U8(20)), + ( + "headers".to_string(), + Primitive::Struct(BTreeMap::from([( + "Content-Type".to_string(), + Primitive::String("application/json".to_string()) + )])) + ) + ])) + ); +} +#[test] +fn test_struc_access_key9() { + let mut ctx = BTreeMap::new(); + let expr = r#"x= struct{x:"hello"}.x + " world""#; + let _ = compute(expr, &mut ctx, "N/A").unwrap(); + assert_eq!( + ctx["x"].read().unwrap().clone(), + Primitive::String("hello world".to_string()) + ); + let expr = r#"x= struct{ + x:"hello", + y: 9 + }.x + " world" + "#; + let _ = compute(expr, &mut ctx, "N/A").unwrap(); + assert_eq!( + ctx["x"].read().unwrap().clone(), + Primitive::String("hello world".to_string()) + ); +} +#[test] +fn test_struc_access_key10() { + let mut ctx = BTreeMap::new(); + let expr = r#"x= struct{x:"hello"}.x + " world" + "!" + z = "whatever" + 9 + "#; + let _ = compute(expr, &mut ctx, "N/A").unwrap(); + assert_eq!( + ctx["x"].read().unwrap().clone(), + Primitive::String("hello world!".to_string()) + ); + assert_eq!( + ctx["z"].read().unwrap().clone(), + Primitive::String("whatever9".to_string()) + ); +} +#[test] +fn test_struc_access_key11() { + let mut ctx = BTreeMap::new(); + let expr = r#" + settings = struct { + static: struct {}, + middlewares: [ + struct { + path: "/hello/:name", + handler: (req) => { + println(req) + return struct { + status: 200, + body: struct { response: """hello ${req.params.name}!""" }, + headers: struct { "Content-Type": "application/json"} + } + }, + method: "GET" + }, + struct { + path: "/", + handler: (req) => { + println(req) + return "hello bro!" + }, + method: "GET" + } + ] + } + settings.middlewares[0].handler(struct {params: struct {name: "nordine"}}) + "#; + let r = compute(expr, &mut ctx, "N/A").unwrap(); + assert_eq!( + r, + Primitive::Struct(BTreeMap::from([ + ( + "body".to_string(), + Primitive::Struct(BTreeMap::from([( + "response".to_string(), + Primitive::String("hello nordine!".to_string()) + ),])) + ), + ("status".to_string(), Primitive::U8(200)), + ( + "headers".to_string(), + Primitive::Struct(BTreeMap::from([( + "Content-Type".to_string(), + Primitive::String("application/json".to_string()) + )])) + ) + ])) + ); +} + +#[test] +fn test_struc_access_key12() { + let mut ctx = BTreeMap::new(); + let expr = r#" + handler = (req) => { + println(req) + return struct { + status: 200, + body: struct { response: """hello ${req.params.name}!""" }, + headers: struct { "Content-Type": "application/json"} + } + + } + handler(struct {params: struct {name: "nordine"}}) + "#; + let r = compute(expr, &mut ctx, "N/A").unwrap(); + assert_eq!( + r, + Primitive::Struct(BTreeMap::from([ + ( + "body".to_string(), + Primitive::Struct(BTreeMap::from([( + "response".to_string(), + Primitive::String("hello nordine!".to_string()) + ),])) + ), + ("status".to_string(), Primitive::U8(200)), + ( + "headers".to_string(), + Primitive::Struct(BTreeMap::from([( + "Content-Type".to_string(), + Primitive::String("application/json".to_string()) + )])) + ) + ])) + ); +} + +#[test] +fn test_struc_access_key13() { + let mut ctx = BTreeMap::new(); + let expr = r#" + settings = struct { + static: struct {}, + middlewares: [ + struct { + path: "/hello/:name", + handler: (req) => { + println(req) + res= struct { + status: 200, + body: struct { response: """hello ${req.params.name}!""" }, + headers: struct { "Content-Type": "application/json"} + } + return res + }, + method: "GET" + }, + struct { + path: "/", + handler: (req) => { + println(req) + return "hello bro!" + }, + method: "GET" + } + ] + } + settings.middlewares[0].handler(struct {params: struct {name: "nordine"}}) + "#; + let r = compute(expr, &mut ctx, "N/A").unwrap(); + assert_eq!( + r, + Primitive::Struct(BTreeMap::from([ + ( + "body".to_string(), + Primitive::Struct(BTreeMap::from([( + "response".to_string(), + Primitive::String("hello nordine!".to_string()) + ),])) + ), + ("status".to_string(), Primitive::U8(200)), + ( + "headers".to_string(), + Primitive::Struct(BTreeMap::from([( + "Content-Type".to_string(), + Primitive::String("application/json".to_string()) + )])) + ) + ])) + ); +} +#[test] +fn test_struc_access_key14() { + let mut ctx = BTreeMap::new(); + let expr = r#" + handler = (req) => { + println(req) + res= struct { + status: 200, + body: struct { response: """hello ${req.params.name}!""" }, + headers: struct { "Content-Type": "application/json"} + } + return res + + } + handler(struct {params: struct {name: "nordine"}}) + "#; + let r = compute(expr, &mut ctx, "N/A").unwrap(); + assert_eq!( + r, + Primitive::Struct(BTreeMap::from([ + ( + "body".to_string(), + Primitive::Struct(BTreeMap::from([( + "response".to_string(), + Primitive::String("hello nordine!".to_string()) + ),])) + ), + ("status".to_string(), Primitive::U8(200)), + ( + "headers".to_string(), + Primitive::Struct(BTreeMap::from([( + "Content-Type".to_string(), + Primitive::String("application/json".to_string()) + )])) + ) + ])) + ); +}