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. 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 You do this via `call_fn`:
function call arguments:
```rust ```rust
use rhai::Engine; use rhai::Engine;
let mut engine = Engine::new(); let mut engine = Engine::new();
// Define a function in a script and compile to AST // Define a function in a script and load it into the Engine.
let ast = engine.compile( engine.consume(
r" r"
fn hello(x, y) { // a function with two arguments: String and i64 fn hello(x, y) { // a function with two parameters: String and i64
x.len() + y // returning 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. // 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. // 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 ) )?; let result: i64 = engine.call_fn("hello", &ast, ( String::from("abc"), 123_i64 ) )?;
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ put arguments in a tuple // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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 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 Fallible functions
------------------ ------------------
@ -975,8 +982,8 @@ fn add(x, y) {
print(add(2, 3)); print(add(2, 3));
``` ```
Functions defined in script always take `Dynamic` arguments (i.e. the arguments can be of any type). Functions defined in script always take `Dynamic` parameters (i.e. the parameter 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). 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. Any update to an argument will **not** be reflected back to the caller. This can introduce subtle bugs, if you are not careful.
```rust ```rust

View File

@ -4,9 +4,9 @@ fn main() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
let mut scope = Scope::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); 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!("{}", padding("=", filename.len()));
eprintln!("{}", filename); eprintln!("{}", filename);
eprintln!("{}", padding("=", filename.len())); eprintln!("{}", padding("=", filename.len()));

View File

@ -2,16 +2,17 @@
use crate::any::{Any, AnyExt, Dynamic}; use crate::any::{Any, AnyExt, Dynamic};
use crate::call::FuncArgs; use crate::call::FuncArgs;
use crate::engine::{Engine, FnAny, FnCallArgs, FnSpec}; use crate::engine::{Engine, FnAny, FnSpec};
use crate::error::ParseError; use crate::error::ParseError;
use crate::fn_register::RegisterFn; 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::result::EvalAltResult;
use crate::scope::Scope; use crate::scope::Scope;
use std::{ use std::{
any::{type_name, TypeId}, any::{type_name, TypeId},
fs::File, fs::File,
io::prelude::*, io::prelude::*,
sync::Arc,
}; };
impl<'e> Engine<'e> { impl<'e> Engine<'e> {
@ -99,8 +100,8 @@ impl<'e> Engine<'e> {
/// Compile a string into an AST. /// Compile a string into an AST.
pub fn compile(&self, input: &str) -> Result<AST, ParseError> { pub fn compile(&self, input: &str) -> Result<AST, ParseError> {
let tokens = lex(input); let tokens_stream = lex(input);
parse(&mut tokens.peekable(), self.optimize) parse(&mut tokens_stream.peekable(), self.optimize)
} }
fn read_file(filename: &str) -> Result<String, EvalAltResult> { fn read_file(filename: &str) -> Result<String, EvalAltResult> {
@ -128,62 +129,45 @@ impl<'e> Engine<'e> {
/// Evaluate a string. /// Evaluate a string.
pub fn eval<T: Any + Clone>(&mut self, input: &str) -> Result<T, EvalAltResult> { pub fn eval<T: Any + Clone>(&mut self, input: &str) -> Result<T, EvalAltResult> {
let mut scope = Scope::new(); 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. /// 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>( pub fn eval_with_scope<T: Any + Clone>(
&mut self, &mut self,
scope: &mut Scope, scope: &mut Scope,
retain_functions: bool,
input: &str, input: &str,
) -> Result<T, EvalAltResult> { ) -> Result<T, EvalAltResult> {
let ast = self.compile(input).map_err(EvalAltResult::ErrorParsing)?; 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. /// Evaluate an AST.
pub fn eval_ast<T: Any + Clone>(&mut self, ast: &AST) -> Result<T, EvalAltResult> { pub fn eval_ast<T: Any + Clone>(&mut self, ast: &AST) -> Result<T, EvalAltResult> {
let mut scope = Scope::new(); 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. /// 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>( pub fn eval_ast_with_scope<T: Any + Clone>(
&mut self, &mut self,
scope: &mut Scope, scope: &mut Scope,
retain_functions: bool,
ast: &AST, ast: &AST,
) -> Result<T, EvalAltResult> { ) -> Result<T, EvalAltResult> {
fn eval_ast_internal( fn eval_ast_internal(
engine: &mut Engine, engine: &mut Engine,
scope: &mut Scope, scope: &mut Scope,
retain_functions: bool,
ast: &AST, ast: &AST,
) -> Result<Dynamic, EvalAltResult> { ) -> Result<Dynamic, EvalAltResult> {
engine.clear_functions();
#[cfg(feature = "no_function")] #[cfg(feature = "no_function")]
let AST(statements) = ast; let AST(statements) = ast;
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
let statements = { let statements = {
let AST(statements, functions) = ast; let AST(statements, functions) = ast;
engine.load_script_functions(functions);
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()),
}
}
statements statements
}; };
@ -191,14 +175,12 @@ impl<'e> Engine<'e> {
.iter() .iter()
.try_fold(().into_dynamic(), |_, stmt| engine.eval_stmt(scope, stmt)); .try_fold(().into_dynamic(), |_, stmt| engine.eval_stmt(scope, stmt));
if !retain_functions { engine.clear_functions();
engine.clear_functions();
}
result 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| { Err(EvalAltResult::Return(out, pos)) => out.downcast::<T>().map(|v| *v).map_err(|a| {
EvalAltResult::ErrorMismatchOutputType( EvalAltResult::ErrorMismatchOutputType(
self.map_type_name((*a).type_name()).to_string(), 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). /// 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. /// 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) let mut f = File::open(filename)
.map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err))?; .map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err))?;
@ -227,13 +216,16 @@ impl<'e> Engine<'e> {
f.read_to_string(&mut contents) f.read_to_string(&mut contents)
.map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err)) .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). /// 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. /// 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). /// 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, retain_functions: bool,
input: &str, input: &str,
) -> Result<(), EvalAltResult> { ) -> Result<(), EvalAltResult> {
let tokens = lex(input); let tokens_stream = lex(input);
parse(&mut tokens.peekable(), self.optimize) let ast = parse(&mut tokens_stream.peekable(), self.optimize)
.map_err(|err| EvalAltResult::ErrorParsing(err)) .map_err(EvalAltResult::ErrorParsing)?;
.and_then(|ast| {
#[cfg(feature = "no_function")]
let AST(statements) = ast;
#[cfg(not(feature = "no_function"))] if !retain_functions {
let statements = { self.clear_functions();
let AST(ref statements, ref functions) = ast; }
for f in functions { #[cfg(feature = "no_function")]
match self let AST(statements) = ast;
.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()),
}
}
statements #[cfg(not(feature = "no_function"))]
}; let statements = {
let AST(ref statements, ref functions) = ast;
self.load_script_functions(functions);
statements
};
let val = statements let result = statements
.iter() .iter()
.try_fold(().into_dynamic(), |_, o| self.eval_stmt(scope, o)) .try_fold(().into_dynamic(), |_, o| self.eval_stmt(scope, o))
.map(|_| ()); .map(|_| ());
if !retain_functions { if !retain_functions {
self.clear_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 /// # Example
/// ///
@ -298,9 +299,9 @@ impl<'e> Engine<'e> {
/// ///
/// let mut engine = Engine::new(); /// 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); /// assert_eq!(result, 126);
/// # } /// # }
@ -311,41 +312,22 @@ impl<'e> Engine<'e> {
pub fn call_fn<A: FuncArgs, T: Any + Clone>( pub fn call_fn<A: FuncArgs, T: Any + Clone>(
&mut self, &mut self,
name: &str, name: &str,
ast: &AST,
args: A, args: A,
) -> Result<T, EvalAltResult> { ) -> Result<T, EvalAltResult> {
// Split out non-generic portion to avoid exploding code size
fn call_fn_internal( fn call_fn_internal(
engine: &mut Engine, engine: &mut Engine,
name: &str, name: &str,
ast: &AST, mut values: Vec<Dynamic>,
args: FnCallArgs,
) -> Result<Dynamic, EvalAltResult> { ) -> Result<Dynamic, EvalAltResult> {
for f in &ast.1 { let values: Vec<_> = values.iter_mut().map(Dynamic::as_mut).collect();
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 result = engine.call_fn_raw(name, args, None, Position::none()); let result = engine.call_fn_raw(name, values, None, Position::none());
engine.clear_functions();
result result
} }
let mut arg_values = args.into_vec(); call_fn_internal(self, name, args.into_vec()).and_then(|b| {
call_fn_internal(
self,
name,
ast,
arg_values.iter_mut().map(|v| v.as_mut()).collect(),
)
.and_then(|b| {
b.downcast().map(|b| *b).map_err(|a| { b.downcast().map(|b| *b).map_err(|a| {
EvalAltResult::ErrorMismatchOutputType( EvalAltResult::ErrorMismatchOutputType(
self.map_type_name((*a).type_name()).into(), self.map_type_name((*a).type_name()).into(),
@ -369,7 +351,7 @@ impl<'e> Engine<'e> {
/// ///
/// // Override action of 'print' function /// // Override action of 'print' function
/// engine.on_print(|s| result.push_str(s)); /// engine.on_print(|s| result.push_str(s));
/// engine.consume("print(40 + 2);")?; /// engine.consume("print(40 + 2);", false)?;
/// } /// }
/// assert_eq!(result, "42"); /// assert_eq!(result, "42");
/// # Ok(()) /// # Ok(())
@ -393,7 +375,7 @@ impl<'e> Engine<'e> {
/// ///
/// // Override action of 'debug' function /// // Override action of 'debug' function
/// engine.on_debug(|s| result.push_str(s)); /// engine.on_debug(|s| result.push_str(s));
/// engine.consume(r#"debug("hello");"#)?; /// engine.consume(r#"debug("hello");"#, false)?;
/// } /// }
/// assert_eq!(result, "\"hello\""); /// assert_eq!(result, "\"hello\"");
/// # Ok(()) /// # Ok(())

View File

@ -1,6 +1,7 @@
//! Helper module which defines `FnArgs` to make function calling easier. //! Helper module which defines `FnArgs` to make function calling easier.
use crate::any::{Any, Dynamic}; use crate::any::{Any, Dynamic};
use crate::engine::Array;
/// Trait that represent arguments to a function call. /// Trait that represent arguments to a function call.
pub trait FuncArgs { pub trait FuncArgs {
@ -8,12 +9,36 @@ pub trait FuncArgs {
fn into_vec(self) -> Vec<Dynamic>; fn into_vec(self) -> Vec<Dynamic>;
} }
impl<T: Any> FuncArgs for &mut Vec<T> { macro_rules! impl_std_args {
fn into_vec(self) -> Vec<Dynamic> { ($($p:ty),*) => {
self.iter_mut().map(|x| x.into_dynamic()).collect() $(
} 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 { macro_rules! impl_args {
($($p:ident),*) => { ($($p:ident),*) => {
impl<$($p: Any + Clone),*> FuncArgs for ($($p,)*) impl<$($p: Any + Clone),*> FuncArgs for ($($p,)*)
@ -33,7 +58,9 @@ macro_rules! impl_args {
}; };
(@pop) => { (@pop) => {
}; };
(@pop $head:ident $(, $tail:ident)*) => { (@pop $head:ident) => {
};
(@pop $head:ident $(, $tail:ident)+) => {
impl_args!($($tail),*); impl_args!($($tail),*);
}; };
} }

View File

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

View File

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

View File

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

View File

@ -16,9 +16,9 @@ use std::borrow::Cow;
/// let mut engine = Engine::new(); /// let mut engine = Engine::new();
/// let mut my_scope = Scope::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(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```

View File

@ -6,17 +6,23 @@ use rhai::{Engine, EvalAltResult, INT};
fn test_engine_call_fn() -> Result<(), EvalAltResult> { fn test_engine_call_fn() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
let ast = engine.compile( engine.consume(
r" r"
fn hello(x, y) { fn hello(x, y) {
x.len() + 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(()) Ok(())
} }

View File

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