Skip to content

Commit

Permalink
jsonify / parse_json
Browse files Browse the repository at this point in the history
  • Loading branch information
nbittich committed Aug 21, 2024
1 parent 23e02c7 commit 41f672c
Show file tree
Hide file tree
Showing 9 changed files with 225 additions and 12 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,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

Expand Down
1 change: 1 addition & 0 deletions adana-script-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@ regex = { workspace = true, default-features = false, features = [
"unicode-script",
"unicode-segment",
] }
serde_json.workspace = true
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
libloading.workspace = true
16 changes: 12 additions & 4 deletions adana-script-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -236,6 +238,8 @@ pub enum BuiltInFunctionType {
IsDouble,
IsFunction,
IsArray,
ParseJson,
Jsonify,
}

#[derive(Debug, Eq, PartialEq, Copy, Clone, Serialize, Deserialize)]
Expand Down Expand Up @@ -332,6 +336,8 @@ impl BuiltInFunctionType {
BuiltInFunctionType::IsFunction => IS_FUNCTION,
BuiltInFunctionType::IsArray => IS_ARRAY,
BuiltInFunctionType::MakeError => MAKE_ERROR,
BuiltInFunctionType::Jsonify => JSONIFY,
BuiltInFunctionType::ParseJson => PARSE_JSON,
}
}
}
Expand Down Expand Up @@ -402,6 +408,8 @@ pub const FORBIDDEN_VARIABLE_NAME: &[&str] = &[
IS_BOOL,
IS_ARRAY,
MAKE_ERROR,
JSONIFY,
PARSE_JSON,
EVAL,
TO_BOOL,
SQRT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use std::{
sync::{Arc, RwLock},
};

use super::{constants::NULL, Value};
use crate::{constants::NULL, Value};

const MAX_U32_AS_I128: i128 = u32::MAX as i128;

Expand Down
84 changes: 84 additions & 0 deletions adana-script-core/src/primitive/json.rs
Original file line number Diff line number Diff line change
@@ -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<Self>
where
Self: Sized;
fn to_json(&self) -> anyhow::Result<String>;
}

fn json_to_primitive(value: Value) -> anyhow::Result<Primitive> {
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<Value> {
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<Self> {
let value = serde_json::from_str(s)?;
json_to_primitive(value)
}

fn to_json(&self) -> anyhow::Result<String> {
let value = primitive_to_value(self)?;
serde_json::to_string_pretty(&value).map_err(|e| anyhow!("{e}"))
}
}
5 changes: 5 additions & 0 deletions adana-script-core/src/primitive/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mod core_primitive;
mod json;
pub use core_primitive::*;
pub use json::*;
// pub use json::*;
14 changes: 10 additions & 4 deletions adana-script/src/compute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ 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, KeyAccess, Operator, TreeNodeValue, Value,
};
Expand Down Expand Up @@ -1054,6 +1054,12 @@ fn compute_recur(
adana_script_core::BuiltInFunctionType::MakeError => {
Ok(Primitive::Error(v.to_string()))
}
adana_script_core::BuiltInFunctionType::Jsonify => {
Ok(Primitive::String(v.to_json()?))
}
adana_script_core::BuiltInFunctionType::ParseJson => {
Primitive::from_json(&v.to_string())
}
}
}

Expand Down
14 changes: 11 additions & 3 deletions adana-script/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,10 @@ fn parse_builtin_fn(s: &str) -> Res<Value> {
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),
Expand Down Expand Up @@ -742,6 +746,9 @@ fn parse_value(s: &str) -> Res<Value> {
parse_variable,
parse_constant,
parse_null,
parse_drop,
parse_early_return,
parse_break,
)),
opt(comments),
),
Expand Down Expand Up @@ -810,6 +817,7 @@ fn parse_simple_instruction(s: &str) -> Res<Value> {
tag_no_space("="),
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,...))
Expand Down Expand Up @@ -926,21 +934,21 @@ fn parse_early_return(s: &str) -> Res<Value> {

pub fn parse_instructions(instructions: &str) -> Res<Vec<Value>> {
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,
)),
)),
opt(comments),
Expand Down
99 changes: 99 additions & 0 deletions adana-script/src/tests/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down

0 comments on commit 41f672c

Please sign in to comment.