Allow call_fn with only one parameter; consume can retain functions, eval cannot.

This commit is contained in:
Stephen Chung 2020-03-12 14:54:14 +08:00
parent e24d3a7ade
commit 560da5fdc6
11 changed files with 176 additions and 156 deletions

View File

@ -167,28 +167,35 @@ let ast = engine.compile_file("hello_world.rhai")?;
```
Rhai also allows you to work _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust.
You do this via `call_fn`, which takes a compiled AST (output from `compile`) and the
function call arguments:
You do this via `call_fn`:
```rust
use rhai::Engine;
let mut engine = Engine::new();
// Define a function in a script and compile to AST
let ast = engine.compile(
// Define a function in a script and load it into the Engine.
engine.consume(
r"
fn hello(x, y) { // a function with two arguments: String and i64
x.len() + y // returning i64
}
")?;
fn hello(x, y) { // a function with two parameters: String and i64
x.len() + y // returning i64
}
// Evaluate the function in the AST, passing arguments into the script as a tuple.
fn hello(x) { // script-functions can be overloaded: this one takes only one parameter
x * 2 // returning i64
}
", true)?; // pass true to 'retain_functions' otherwise these functions will be cleared
// at the end of consume()
// Evaluate the function in the AST, passing arguments into the script as a tuple if there are more than one.
// Beware, arguments must be of the correct types because Rhai does not have built-in type conversions.
// If you pass in arguments of the wrong type, the Engine will not find the function.
let result: i64 = engine.call_fn("hello", &ast, ( String::from("abc"), 123_i64 ) )?;
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ put arguments in a tuple
let result: i64 = engine.call_fn("hello", 123_i64)?
// ^^^^^^^ this one calls the 'hello' function with one parameter (so need for tuple)
```
Values and types
@ -318,7 +325,7 @@ fn main()
}
```
You can also see in this example how you can register multiple functions (or in this case multiple instances of the same function) to the same name in script. This gives you a way to overload functions the correct one, based on the types of the arguments, from your script.
You can also see in this example how you can register multiple functions (or in this case multiple instances of the same function) to the same name in script. This gives you a way to overload functions the correct one, based on the types of the parameters, from your script.
Fallible functions
------------------
@ -975,8 +982,8 @@ fn add(x, y) {
print(add(2, 3));
```
Functions defined in script always take `Dynamic` arguments (i.e. the arguments can be of any type).
It is important to remember that all arguments are passed by _value_, so all functions are _pure_ (i.e. they never modify their arguments).
Functions defined in script always take `Dynamic` parameters (i.e. the parameter can be of any type).
It is important to remember that all parameters are passed by _value_, so all functions are _pure_ (i.e. they never modify their parameters).
Any update to an argument will **not** be reflected back to the caller. This can introduce subtle bugs, if you are not careful.
```rust

View File

@ -4,9 +4,9 @@ fn main() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
let mut scope = Scope::new();
engine.eval_with_scope::<()>(&mut scope, false, "let x = 4 + 5")?;
engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5")?;
let result = engine.eval_with_scope::<i64>(&mut scope, false, "x")?;
let result = engine.eval_with_scope::<i64>(&mut scope, "x")?;
println!("result: {}", result);

View File

@ -67,7 +67,7 @@ fn main() {
_ => (),
}
if let Err(err) = engine.consume(&contents) {
if let Err(err) = engine.consume(&contents, false) {
eprintln!("{}", padding("=", filename.len()));
eprintln!("{}", filename);
eprintln!("{}", padding("=", filename.len()));

View File

@ -2,16 +2,17 @@
use crate::any::{Any, AnyExt, Dynamic};
use crate::call::FuncArgs;
use crate::engine::{Engine, FnAny, FnCallArgs, FnSpec};
use crate::engine::{Engine, FnAny, FnSpec};
use crate::error::ParseError;
use crate::fn_register::RegisterFn;
use crate::parser::{lex, parse, Position, AST};
use crate::parser::{lex, parse, FnDef, Position, AST};
use crate::result::EvalAltResult;
use crate::scope::Scope;
use std::{
any::{type_name, TypeId},
fs::File,
io::prelude::*,
sync::Arc,
};
impl<'e> Engine<'e> {
@ -99,8 +100,8 @@ impl<'e> Engine<'e> {
/// Compile a string into an AST.
pub fn compile(&self, input: &str) -> Result<AST, ParseError> {
let tokens = lex(input);
parse(&mut tokens.peekable(), self.optimize)
let tokens_stream = lex(input);
parse(&mut tokens_stream.peekable(), self.optimize)
}
fn read_file(filename: &str) -> Result<String, EvalAltResult> {
@ -128,62 +129,45 @@ impl<'e> Engine<'e> {
/// Evaluate a string.
pub fn eval<T: Any + Clone>(&mut self, input: &str) -> Result<T, EvalAltResult> {
let mut scope = Scope::new();
self.eval_with_scope(&mut scope, false, input)
self.eval_with_scope(&mut scope, input)
}
/// Evaluate a string with own scope.
///
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
/// and not cleared from run to run.
pub fn eval_with_scope<T: Any + Clone>(
&mut self,
scope: &mut Scope,
retain_functions: bool,
input: &str,
) -> Result<T, EvalAltResult> {
let ast = self.compile(input).map_err(EvalAltResult::ErrorParsing)?;
self.eval_ast_with_scope(scope, retain_functions, &ast)
self.eval_ast_with_scope(scope, &ast)
}
/// Evaluate an AST.
pub fn eval_ast<T: Any + Clone>(&mut self, ast: &AST) -> Result<T, EvalAltResult> {
let mut scope = Scope::new();
self.eval_ast_with_scope(&mut scope, false, ast)
self.eval_ast_with_scope(&mut scope, ast)
}
/// Evaluate an AST with own scope.
///
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
/// and not cleared from run to run.
pub fn eval_ast_with_scope<T: Any + Clone>(
&mut self,
scope: &mut Scope,
retain_functions: bool,
ast: &AST,
) -> Result<T, EvalAltResult> {
fn eval_ast_internal(
engine: &mut Engine,
scope: &mut Scope,
retain_functions: bool,
ast: &AST,
) -> Result<Dynamic, EvalAltResult> {
engine.clear_functions();
#[cfg(feature = "no_function")]
let AST(statements) = ast;
#[cfg(not(feature = "no_function"))]
let statements = {
let AST(statements, functions) = ast;
for f in functions {
match engine
.script_functions
.binary_search_by(|fn_def| fn_def.compare(&f.name, f.params.len()))
{
Ok(n) => engine.script_functions[n] = f.clone(),
Err(n) => engine.script_functions.insert(n, f.clone()),
}
}
engine.load_script_functions(functions);
statements
};
@ -191,14 +175,12 @@ impl<'e> Engine<'e> {
.iter()
.try_fold(().into_dynamic(), |_, stmt| engine.eval_stmt(scope, stmt));
if !retain_functions {
engine.clear_functions();
}
engine.clear_functions();
result
}
match eval_ast_internal(self, scope, retain_functions, ast) {
match eval_ast_internal(self, scope, ast) {
Err(EvalAltResult::Return(out, pos)) => out.downcast::<T>().map(|v| *v).map_err(|a| {
EvalAltResult::ErrorMismatchOutputType(
self.map_type_name((*a).type_name()).to_string(),
@ -219,7 +201,14 @@ impl<'e> Engine<'e> {
/// Evaluate a file, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors.
pub fn consume_file(&mut self, filename: &str) -> Result<(), EvalAltResult> {
///
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
/// and not cleared from run to run.
pub fn consume_file(
&mut self,
filename: &str,
retain_functions: bool,
) -> Result<(), EvalAltResult> {
let mut f = File::open(filename)
.map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err))?;
@ -227,13 +216,16 @@ impl<'e> Engine<'e> {
f.read_to_string(&mut contents)
.map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err))
.and_then(|_| self.consume(&contents))
.and_then(|_| self.consume(&contents, retain_functions))
}
/// Evaluate a string, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors.
pub fn consume(&mut self, input: &str) -> Result<(), EvalAltResult> {
self.consume_with_scope(&mut Scope::new(), false, input)
///
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
/// and not cleared from run to run.
pub fn consume(&mut self, input: &str, retain_functions: bool) -> Result<(), EvalAltResult> {
self.consume_with_scope(&mut Scope::new(), retain_functions, input)
}
/// Evaluate a string, but throw away the result and only return error (if any).
@ -247,45 +239,54 @@ impl<'e> Engine<'e> {
retain_functions: bool,
input: &str,
) -> Result<(), EvalAltResult> {
let tokens = lex(input);
let tokens_stream = lex(input);
parse(&mut tokens.peekable(), self.optimize)
.map_err(|err| EvalAltResult::ErrorParsing(err))
.and_then(|ast| {
#[cfg(feature = "no_function")]
let AST(statements) = ast;
let ast = parse(&mut tokens_stream.peekable(), self.optimize)
.map_err(EvalAltResult::ErrorParsing)?;
#[cfg(not(feature = "no_function"))]
let statements = {
let AST(ref statements, ref functions) = ast;
if !retain_functions {
self.clear_functions();
}
for f in functions {
match self
.script_functions
.binary_search_by(|fn_def| fn_def.compare(&f.name, f.params.len()))
{
Ok(n) => self.script_functions[n] = f.clone(),
Err(n) => self.script_functions.insert(n, f.clone()),
}
}
#[cfg(feature = "no_function")]
let AST(statements) = ast;
statements
};
#[cfg(not(feature = "no_function"))]
let statements = {
let AST(ref statements, ref functions) = ast;
self.load_script_functions(functions);
statements
};
let val = statements
.iter()
.try_fold(().into_dynamic(), |_, o| self.eval_stmt(scope, o))
.map(|_| ());
let result = statements
.iter()
.try_fold(().into_dynamic(), |_, o| self.eval_stmt(scope, o))
.map(|_| ());
if !retain_functions {
self.clear_functions();
}
if !retain_functions {
self.clear_functions();
}
val
})
result
}
/// Call a script function defined in a compiled AST.
/// Load a list of functions into the Engine.
pub(crate) fn load_script_functions<'a>(
&mut self,
functions: impl IntoIterator<Item = &'a Arc<FnDef>>,
) {
for f in functions.into_iter() {
match self
.script_functions
.binary_search_by(|fn_def| fn_def.compare(&f.name, f.params.len()))
{
Ok(n) => self.script_functions[n] = f.clone(),
Err(n) => self.script_functions.insert(n, f.clone()),
}
}
}
/// Call a script function retained inside the Engine.
///
/// # Example
///
@ -298,9 +299,9 @@ impl<'e> Engine<'e> {
///
/// let mut engine = Engine::new();
///
/// let ast = engine.compile("fn add(x, y) { x.len() + y }")?;
/// engine.consume("fn add(x, y) { x.len() + y }", true)?;
///
/// let result: i64 = engine.call_fn("add", &ast, (String::from("abc"), 123_i64))?;
/// let result: i64 = engine.call_fn("add", (String::from("abc"), 123_i64))?;
///
/// assert_eq!(result, 126);
/// # }
@ -311,41 +312,22 @@ impl<'e> Engine<'e> {
pub fn call_fn<A: FuncArgs, T: Any + Clone>(
&mut self,
name: &str,
ast: &AST,
args: A,
) -> Result<T, EvalAltResult> {
// Split out non-generic portion to avoid exploding code size
fn call_fn_internal(
engine: &mut Engine,
name: &str,
ast: &AST,
args: FnCallArgs,
mut values: Vec<Dynamic>,
) -> Result<Dynamic, EvalAltResult> {
for f in &ast.1 {
match engine
.script_functions
.binary_search_by(|fn_def| fn_def.compare(&f.name, f.params.len()))
{
Ok(n) => engine.script_functions[n] = f.clone(),
Err(n) => engine.script_functions.insert(n, f.clone()),
}
}
let values: Vec<_> = values.iter_mut().map(Dynamic::as_mut).collect();
let result = engine.call_fn_raw(name, args, None, Position::none());
engine.clear_functions();
let result = engine.call_fn_raw(name, values, None, Position::none());
result
}
let mut arg_values = args.into_vec();
call_fn_internal(
self,
name,
ast,
arg_values.iter_mut().map(|v| v.as_mut()).collect(),
)
.and_then(|b| {
call_fn_internal(self, name, args.into_vec()).and_then(|b| {
b.downcast().map(|b| *b).map_err(|a| {
EvalAltResult::ErrorMismatchOutputType(
self.map_type_name((*a).type_name()).into(),
@ -369,7 +351,7 @@ impl<'e> Engine<'e> {
///
/// // Override action of 'print' function
/// engine.on_print(|s| result.push_str(s));
/// engine.consume("print(40 + 2);")?;
/// engine.consume("print(40 + 2);", false)?;
/// }
/// assert_eq!(result, "42");
/// # Ok(())
@ -393,7 +375,7 @@ impl<'e> Engine<'e> {
///
/// // Override action of 'debug' function
/// engine.on_debug(|s| result.push_str(s));
/// engine.consume(r#"debug("hello");"#)?;
/// engine.consume(r#"debug("hello");"#, false)?;
/// }
/// assert_eq!(result, "\"hello\"");
/// # Ok(())

View File

@ -1,6 +1,7 @@
//! Helper module which defines `FnArgs` to make function calling easier.
use crate::any::{Any, Dynamic};
use crate::engine::Array;
/// Trait that represent arguments to a function call.
pub trait FuncArgs {
@ -8,12 +9,36 @@ pub trait FuncArgs {
fn into_vec(self) -> Vec<Dynamic>;
}
impl<T: Any> FuncArgs for &mut Vec<T> {
fn into_vec(self) -> Vec<Dynamic> {
self.iter_mut().map(|x| x.into_dynamic()).collect()
}
macro_rules! impl_std_args {
($($p:ty),*) => {
$(
impl FuncArgs for $p {
fn into_vec(self) -> Vec<Dynamic> {
vec![self.into_dynamic()]
}
}
)*
};
}
impl_std_args!(String, char, bool);
#[cfg(not(feature = "no_index"))]
impl_std_args!(Array);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
impl_std_args!(u8, i8, u16, i16, u32, i32, u64, i64);
#[cfg(feature = "only_i32")]
impl_std_args!(i32);
#[cfg(feature = "only_i64")]
impl_std_args!(i64);
#[cfg(not(feature = "no_float"))]
impl_std_args!(f32, f64);
macro_rules! impl_args {
($($p:ident),*) => {
impl<$($p: Any + Clone),*> FuncArgs for ($($p,)*)
@ -33,7 +58,9 @@ macro_rules! impl_args {
};
(@pop) => {
};
(@pop $head:ident $(, $tail:ident)*) => {
(@pop $head:ident) => {
};
(@pop $head:ident $(, $tail:ident)+) => {
impl_args!($($tail),*);
};
}

View File

@ -139,19 +139,20 @@ impl Engine<'_> {
.script_functions
.binary_search_by(|f| f.compare(fn_name, args.len()))
{
let func = self.script_functions[n].clone();
let mut scope = Scope::new();
let fn_def = self.script_functions[n].clone();
scope.extend(
// Put arguments into scope as variables
func.params
fn_def
.params
.iter()
.zip(args.iter().map(|x| (*x).into_dynamic())),
);
// Evaluate
return match self.eval_stmt(&mut scope, &func.body) {
return match self.eval_stmt(&mut scope, &fn_def.body) {
// Convert return statement to return value
Err(EvalAltResult::Return(x, _)) => Ok(x),
other => other,
@ -240,14 +241,14 @@ impl Engine<'_> {
) -> Result<Dynamic, EvalAltResult> {
match dot_rhs {
// xxx.fn_name(args)
Expr::FunctionCall(fn_name, args, def_val, pos) => {
let mut args = args
Expr::FunctionCall(fn_name, arg_expr_list, def_val, pos) => {
let mut values = arg_expr_list
.iter()
.map(|arg| self.eval_expr(scope, arg))
.map(|arg_expr| self.eval_expr(scope, arg_expr))
.collect::<Result<Vec<_>, _>>()?;
let args = once(this_ptr)
.chain(args.iter_mut().map(|b| b.as_mut()))
.chain(values.iter_mut().map(|b| b.as_mut()))
.collect();
self.call_fn_raw(fn_name, args, def_val.as_ref(), *pos)
@ -837,15 +838,15 @@ impl Engine<'_> {
Expr::Array(_, _) => panic!("encountered an array during no_index!"),
// Dump AST
Expr::FunctionCall(fn_name, args, _, pos) if fn_name == KEYWORD_DUMP_AST => {
let pos = if args.len() == 0 {
Expr::FunctionCall(fn_name, args_expr_list, _, pos) if fn_name == KEYWORD_DUMP_AST => {
let pos = if args_expr_list.len() == 0 {
*pos
} else {
args[0].position()
args_expr_list[0].position()
};
// Change the argument to a debug dump of the expressions
let result = args
let result = args_expr_list
.into_iter()
.map(|expr| format!("{:#?}", expr))
.collect::<Vec<_>>()
@ -861,15 +862,15 @@ impl Engine<'_> {
}
// Normal function call
Expr::FunctionCall(fn_name, args, def_val, pos) => {
let mut args = args
Expr::FunctionCall(fn_name, args_expr_list, def_val, pos) => {
let mut values = args_expr_list
.iter()
.map(|expr| self.eval_expr(scope, expr))
.collect::<Result<Vec<Dynamic>, _>>()?;
self.call_fn_raw(
fn_name,
args.iter_mut().map(|b| b.as_mut()).collect(),
values.iter_mut().map(|b| b.as_mut()).collect(),
def_val.as_ref(),
*pos,
)

View File

@ -50,7 +50,7 @@ fn optimize_stmt(stmt: Stmt, changed: &mut bool, preserve_result: bool) -> Stmt
Stmt::Let(_, None, _) => stmt,
Stmt::Block(statements, pos) => {
let original_len = statements.len();
let orig_len = statements.len();
let mut result: Vec<_> = statements
.into_iter() // For each statement
@ -105,7 +105,7 @@ fn optimize_stmt(stmt: Stmt, changed: &mut bool, preserve_result: bool) -> Stmt
.collect();
}
*changed = *changed || original_len != result.len();
*changed = *changed || orig_len != result.len();
match result[..] {
// No statements in block - change to No-op
@ -177,14 +177,14 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
#[cfg(not(feature = "no_index"))]
Expr::Array(items, pos) => {
let original_len = items.len();
let orig_len = items.len();
let items: Vec<_> = items
.into_iter()
.map(|expr| optimize_expr(expr, changed))
.collect();
*changed = *changed || original_len != items.len();
*changed = *changed || orig_len != items.len();
Expr::Array(items, pos)
}
@ -233,14 +233,14 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
Expr::FunctionCall(id, args, def_value, pos)
}
Expr::FunctionCall(id, args, def_value, pos) => {
let original_len = args.len();
let orig_len = args.len();
let args: Vec<_> = args
.into_iter()
.map(|a| optimize_expr(a, changed))
.collect();
*changed = *changed || original_len != args.len();
*changed = *changed || orig_len != args.len();
Expr::FunctionCall(id, args, def_value, pos)
}

View File

@ -1212,26 +1212,26 @@ fn parse_call_expr<'a>(
input: &mut Peekable<TokenIterator<'a>>,
begin: Position,
) -> Result<Expr, ParseError> {
let mut args = Vec::new();
let mut args_expr_list = Vec::new();
if let Some(&(Token::RightParen, _)) = input.peek() {
input.next();
return Ok(Expr::FunctionCall(id, args, None, begin));
return Ok(Expr::FunctionCall(id, args_expr_list, None, begin));
}
loop {
args.push(parse_expr(input)?);
args_expr_list.push(parse_expr(input)?);
match input.peek() {
Some(&(Token::RightParen, _)) => {
input.next();
return Ok(Expr::FunctionCall(id, args, None, begin));
return Ok(Expr::FunctionCall(id, args_expr_list, None, begin));
}
Some(&(Token::Comma, _)) => (),
Some(&(_, pos)) => {
return Err(ParseError::new(
PERR::MissingRightParen(format!(
"closing the arguments list to function call of '{}'",
"closing the parameters list to function call of '{}'",
id
)),
pos,
@ -1240,7 +1240,7 @@ fn parse_call_expr<'a>(
None => {
return Err(ParseError::new(
PERR::MissingRightParen(format!(
"closing the arguments list to function call of '{}'",
"closing the parameters list to function call of '{}'",
id
)),
Position::eof(),

View File

@ -16,9 +16,9 @@ use std::borrow::Cow;
/// let mut engine = Engine::new();
/// let mut my_scope = Scope::new();
///
/// engine.eval_with_scope::<()>(&mut my_scope, false, "let x = 5;")?;
/// engine.eval_with_scope::<()>(&mut my_scope, "let x = 5;")?;
///
/// assert_eq!(engine.eval_with_scope::<i64>(&mut my_scope, false, "x + 1")?, 6);
/// assert_eq!(engine.eval_with_scope::<i64>(&mut my_scope, "x + 1")?, 6);
/// # Ok(())
/// # }
/// ```

View File

@ -6,17 +6,23 @@ use rhai::{Engine, EvalAltResult, INT};
fn test_engine_call_fn() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
let ast = engine.compile(
engine.consume(
r"
fn hello(x, y) {
x.len() + y
}
fn hello(x) {
x * 2
}
",
true,
)?;
let result: INT = engine.call_fn("hello", &ast, (String::from("abc"), 123 as INT))?;
let r: i64 = engine.call_fn("hello", (String::from("abc"), 123 as INT))?;
assert_eq!(r, 126);
assert_eq!(result, 126);
let r: i64 = engine.call_fn("hello", 123 as INT)?;
assert_eq!(r, 246);
Ok(())
}

View File

@ -5,15 +5,12 @@ fn test_var_scope() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
let mut scope = Scope::new();
engine.eval_with_scope::<()>(&mut scope, false, "let x = 4 + 5")?;
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, false, "x")?, 9);
engine.eval_with_scope::<()>(&mut scope, false, "x = x + 1; x = x + 2;")?;
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, false, "x")?, 12);
assert_eq!(
engine.eval_with_scope::<()>(&mut scope, false, "{let x = 3}")?,
()
);
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, false, "x")?, 12);
engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5")?;
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "x")?, 9);
engine.eval_with_scope::<()>(&mut scope, "x = x + 1; x = x + 2;")?;
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "x")?, 12);
assert_eq!(engine.eval_with_scope::<()>(&mut scope, "{let x = 3}")?, ());
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "x")?, 12);
Ok(())
}
@ -33,11 +30,11 @@ fn test_scope_eval() -> Result<(), EvalAltResult> {
// First invocation
engine
.eval_with_scope::<()>(&mut scope, false, " let x = 4 + 5 - y + z; y = 1;")
.eval_with_scope::<()>(&mut scope, " let x = 4 + 5 - y + z; y = 1;")
.expect("y and z not found?");
// Second invocation using the same state
let result = engine.eval_with_scope::<INT>(&mut scope, false, "x")?;
let result = engine.eval_with_scope::<INT>(&mut scope, "x")?;
println!("result: {}", result); // should print 966