From 219f4a54eddd98114f21b7f9258d74cb71efdf50 Mon Sep 17 00:00:00 2001 From: Tobie Morgan Hitchcock Date: Mon, 27 Jun 2022 16:57:11 +0100 Subject: [PATCH] Enable ability to pass arguments to JavaScript functions --- lib/src/fnc/script.rs | 20 ++++-- lib/src/sql/function.rs | 123 ++++++++++++++++++++----------------- lib/src/sql/value/value.rs | 2 +- 3 files changed, 84 insertions(+), 61 deletions(-) diff --git a/lib/src/fnc/script.rs b/lib/src/fnc/script.rs index c75b32bf..5873d7b5 100644 --- a/lib/src/fnc/script.rs +++ b/lib/src/fnc/script.rs @@ -105,16 +105,20 @@ impl Class for JsRecord { } } -pub fn run(ctx: &Context, doc: Option<&Value>, src: &str) -> Result { +pub fn run(ctx: &Context, src: &str, arg: Vec, doc: Option<&Value>) -> Result { let _ = ctx.check()?; // Create an execution context let mut ctx = Boa::default(); - // Retrieve the current document - let obj = doc.map_or(JsValue::Undefined, JsValue::from); + // Convert the arguments to JavaScript + let args = JsValue::from(Value::from(arg)); + // Convert the current document to JavaScript + let this = doc.map_or(JsValue::Undefined, JsValue::from); // Create the main function structure - let src = format!("(function() {{ {} }}).call(document)", src); + let src = format!("(function() {{ {} }}).apply(self, args)", src); // Register the current document as a global object - ctx.register_global_property("document", obj, Attribute::default()); + ctx.register_global_property("self", this, Attribute::default()); + // Register the current document as a global object + ctx.register_global_property("args", args, Attribute::default()); // Register the JsDuration type as a global class ctx.register_global_class::().unwrap(); // Register the JsRecord type as a global class @@ -130,6 +134,12 @@ pub fn run(ctx: &Context, doc: Option<&Value>, src: &str) -> Result for JsValue { + fn from(v: Value) -> Self { + JsValue::from(&v) + } +} + impl From<&Datetime> for Date { fn from(v: &Datetime) -> Self { let mut obj = Self::default(); diff --git a/lib/src/sql/function.rs b/lib/src/sql/function.rs index d23dbc4e..b4ed85c4 100644 --- a/lib/src/sql/function.rs +++ b/lib/src/sql/function.rs @@ -6,12 +6,11 @@ use crate::fnc; use crate::sql::comment::mightbespace; use crate::sql::common::commas; use crate::sql::error::IResult; -use crate::sql::script::{script, Script}; +use crate::sql::script::{script as func, Script}; use crate::sql::value::{single, value, Value}; use nom::branch::alt; use nom::bytes::complete::tag; use nom::character::complete::char; -use nom::combinator::opt; use nom::multi::separated_list0; use serde::{Deserialize, Serialize}; use std::cmp::Ordering; @@ -20,9 +19,9 @@ use std::fmt; #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub enum Function { Future(Value), - Script(Script), Cast(String, Value), Normal(String, Vec), + Script(Script, Vec), } impl PartialOrd for Function { @@ -111,32 +110,36 @@ impl Function { doc: Option<&Value>, ) -> Result { match self { - Function::Cast(s, e) => { - let a = e.compute(ctx, opt, txn, doc).await?; - fnc::cast::run(ctx, s, a) + Function::Future(v) => match opt.futures { + true => { + let v = v.compute(ctx, opt, txn, doc).await?; + fnc::future::run(ctx, v) + } + false => Ok(self.to_owned().into()), + }, + Function::Cast(s, x) => { + let v = x.compute(ctx, opt, txn, doc).await?; + fnc::cast::run(ctx, s, v) } - Function::Normal(s, e) => { - let mut a: Vec = Vec::with_capacity(e.len()); - for v in e { + Function::Normal(s, x) => { + let mut a: Vec = Vec::with_capacity(x.len()); + for v in x { a.push(v.compute(ctx, opt, txn, doc).await?); } fnc::run(ctx, s, a).await } - Function::Future(e) => match opt.futures { - true => { - let a = e.compute(ctx, opt, txn, doc).await?; - fnc::future::run(ctx, a) + Function::Script(s, x) => { + if cfg!(feature = "scripting") { + let mut a: Vec = Vec::with_capacity(x.len()); + for v in x { + a.push(v.compute(ctx, opt, txn, doc).await?); + } + fnc::script::run(ctx, s, a, doc) + } else { + Err(Error::InvalidScript { + message: String::from("Embedded functions are not enabled."), + }) } - false => Ok(self.to_owned().into()), - }, - #[allow(unused_variables)] - Function::Script(s) => { - #[cfg(feature = "scripting")] - return fnc::script::run(ctx, doc, s); - #[cfg(not(feature = "scripting"))] - return Err(Error::InvalidScript { - message: String::from("Embedded functions are not enabled."), - }); } } } @@ -146,8 +149,13 @@ impl fmt::Display for Function { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Function::Future(ref e) => write!(f, "fn::future -> {{ {} }}", e), - Function::Script(ref s) => write!(f, "fn::script -> {{{}}}", s), Function::Cast(ref s, ref e) => write!(f, "<{}> {}", s, e), + Function::Script(ref s, ref e) => write!( + f, + "fn::script -> ({}) => {{{}}}", + e.iter().map(|ref v| format!("{}", v)).collect::>().join(", "), + s, + ), Function::Normal(ref s, ref e) => write!( f, "{}({})", @@ -159,21 +167,7 @@ impl fmt::Display for Function { } pub fn function(i: &str) -> IResult<&str, Function> { - alt((scripts, future, casts, normal))(i) -} - -fn scripts(i: &str) -> IResult<&str, Function> { - let (i, _) = tag("fn::script")(i)?; - let (i, _) = mightbespace(i)?; - let (i, _) = char('-')(i)?; - let (i, _) = char('>')(i)?; - let (i, _) = mightbespace(i)?; - let (i, _) = opt(tag("function()"))(i)?; - let (i, _) = mightbespace(i)?; - let (i, _) = char('{')(i)?; - let (i, v) = script(i)?; - let (i, _) = char('}')(i)?; - Ok((i, Function::Script(v))) + alt((future, normal, script, cast))(i) } fn future(i: &str) -> IResult<&str, Function> { @@ -190,7 +184,35 @@ fn future(i: &str) -> IResult<&str, Function> { Ok((i, Function::Future(v))) } -fn casts(i: &str) -> IResult<&str, Function> { +fn normal(i: &str) -> IResult<&str, Function> { + let (i, s) = function_names(i)?; + let (i, _) = char('(')(i)?; + let (i, _) = mightbespace(i)?; + let (i, a) = separated_list0(commas, value)(i)?; + let (i, _) = mightbespace(i)?; + let (i, _) = char(')')(i)?; + Ok((i, Function::Normal(s.to_string(), a))) +} + +fn script(i: &str) -> IResult<&str, Function> { + let (i, _) = tag("fn::script")(i)?; + let (i, _) = mightbespace(i)?; + let (i, _) = char('-')(i)?; + let (i, _) = char('>')(i)?; + let (i, _) = mightbespace(i)?; + let (i, _) = tag("(")(i)?; + let (i, a) = separated_list0(commas, value)(i)?; + let (i, _) = tag(")")(i)?; + let (i, _) = mightbespace(i)?; + let (i, _) = tag("=>")(i)?; + let (i, _) = mightbespace(i)?; + let (i, _) = char('{')(i)?; + let (i, v) = func(i)?; + let (i, _) = char('}')(i)?; + Ok((i, Function::Script(v, a))) +} + +fn cast(i: &str) -> IResult<&str, Function> { let (i, _) = char('<')(i)?; let (i, s) = function_casts(i)?; let (i, _) = char('>')(i)?; @@ -199,16 +221,6 @@ fn casts(i: &str) -> IResult<&str, Function> { Ok((i, Function::Cast(s.to_string(), v))) } -fn normal(i: &str) -> IResult<&str, Function> { - let (i, s) = function_names(i)?; - let (i, _) = char('(')(i)?; - let (i, _) = mightbespace(i)?; - let (i, v) = separated_list0(commas, value)(i)?; - let (i, _) = mightbespace(i)?; - let (i, _) = char(')')(i)?; - Ok((i, Function::Normal(s.to_string(), v))) -} - fn function_casts(i: &str) -> IResult<&str, &str> { alt(( tag("bool"), @@ -495,19 +507,20 @@ mod tests { #[test] fn function_script_expression() { - let sql = "fn::script -> { return this.tags.filter(t => { return t.length > 3; }); }"; + let sql = "fn::script -> () => { return this.tags.filter(t => { return t.length > 3; }); }"; let res = function(sql); assert!(res.is_ok()); let out = res.unwrap().1; assert_eq!( - "fn::script -> { return this.tags.filter(t => { return t.length > 3; }); }", + "fn::script -> () => { return this.tags.filter(t => { return t.length > 3; }); }", format!("{}", out) ); assert_eq!( out, - Function::Script(Script::parse( - " return this.tags.filter(t => { return t.length > 3; }); " - )) + Function::Script( + Script::parse(" return this.tags.filter(t => { return t.length > 3; }); "), + vec![] + ) ); } } diff --git a/lib/src/sql/value/value.rs b/lib/src/sql/value/value.rs index e6affddc..70a4e4f7 100644 --- a/lib/src/sql/value/value.rs +++ b/lib/src/sql/value/value.rs @@ -668,7 +668,7 @@ impl Value { Value::Datetime(v) => v.0.to_string().into(), Value::Function(v) => match v.as_ref() { Function::Future(_) => "fn::future".to_string().into(), - Function::Script(_) => "fn::script".to_string().into(), + Function::Script(_, _) => "fn::script".to_string().into(), Function::Normal(f, _) => f.to_string().into(), Function::Cast(_, v) => v.to_idiom(), },