Improve AST evaluation efficiency by sharing functions.
This commit is contained in:
parent
d1cffac420
commit
29150faef2
@ -20,7 +20,7 @@ categories = [ "no-std", "embedded", "parser-implementations" ]
|
|||||||
num-traits = "*"
|
num-traits = "*"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
#default = ["no_function", "no_index", "no_object", "no_float", "only_i32", "no_stdlib", "unchecked", "no_optimize"]
|
#default = ["no_function", "no_index", "no_object", "no_float", "only_i32", "no_stdlib", "unchecked", "no_optimize", "sync"]
|
||||||
default = []
|
default = []
|
||||||
unchecked = [] # unchecked arithmetic
|
unchecked = [] # unchecked arithmetic
|
||||||
no_stdlib = [] # no standard library of utility functions
|
no_stdlib = [] # no standard library of utility functions
|
||||||
|
24
README.md
24
README.md
@ -209,12 +209,12 @@ Compiling a script file is also supported:
|
|||||||
let ast = engine.compile_file("hello_world.rhai".into())?;
|
let ast = engine.compile_file("hello_world.rhai".into())?;
|
||||||
```
|
```
|
||||||
|
|
||||||
Rhai also allows working _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust - via `call_fn`:
|
Rhai also allows working _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust - via `call_fn`
|
||||||
|
or its cousins `call_fn1` (one argument) and `call_fn0` (no argument).
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Define a function in a script and load it into the Engine.
|
// Define functions in a script.
|
||||||
// Pass true to 'retain_functions' otherwise these functions will be cleared at the end of consume()
|
let ast = engine.compile(true,
|
||||||
engine.consume(true,
|
|
||||||
r"
|
r"
|
||||||
// a function with two parameters: String and i64
|
// a function with two parameters: String and i64
|
||||||
fn hello(x, y) {
|
fn hello(x, y) {
|
||||||
@ -225,18 +225,26 @@ engine.consume(true,
|
|||||||
fn hello(x) {
|
fn hello(x) {
|
||||||
x * 2
|
x * 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this one takes no parameters
|
||||||
|
fn hello() {
|
||||||
|
42
|
||||||
|
}
|
||||||
")?;
|
")?;
|
||||||
|
|
||||||
// Evaluate the function in the AST, passing arguments into the script as a tuple
|
// Evaluate a function defined in the script, passing arguments into the script as a tuple
|
||||||
// if there are more than one. Beware, arguments must be of the correct types because
|
// if there are more than one. Beware, arguments must be of the correct types because
|
||||||
// Rhai does not have built-in type conversions. If arguments of the wrong types are passed,
|
// Rhai does not have built-in type conversions. If arguments of the wrong types are passed,
|
||||||
// the Engine will not find the function.
|
// 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(&ast, "hello", ( String::from("abc"), 123_i64 ) )?;
|
||||||
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ put arguments in a tuple
|
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ put arguments in a tuple
|
||||||
|
|
||||||
let result: i64 = engine.call_fn("hello", 123_i64)?
|
let result: i64 = engine.call_fn1(&ast, "hello", 123_i64)?
|
||||||
// ^^^^^^^ calls 'hello' with one parameter (no need for tuple)
|
// ^^^^^^^^ use 'call_fn1' for one argument
|
||||||
|
|
||||||
|
let result: i64 = engine.call_fn0(&ast, "hello")?
|
||||||
|
// ^^^^^^^^ use 'call_fn0' for no arguments
|
||||||
```
|
```
|
||||||
|
|
||||||
Evaluate expressions only
|
Evaluate expressions only
|
||||||
|
@ -77,8 +77,9 @@ fn main() {
|
|||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
|
|
||||||
let mut input = String::new();
|
let mut input = String::new();
|
||||||
let mut ast_u: Option<AST> = None;
|
let mut main_ast = AST::new();
|
||||||
let mut ast: Option<AST> = None;
|
let mut ast_u = AST::new();
|
||||||
|
let mut ast = AST::new();
|
||||||
|
|
||||||
println!("Rhai REPL tool");
|
println!("Rhai REPL tool");
|
||||||
println!("==============");
|
println!("==============");
|
||||||
@ -112,6 +113,10 @@ fn main() {
|
|||||||
|
|
||||||
let script = input.trim();
|
let script = input.trim();
|
||||||
|
|
||||||
|
if script.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Implement standard commands
|
// Implement standard commands
|
||||||
match script {
|
match script {
|
||||||
"help" => {
|
"help" => {
|
||||||
@ -120,21 +125,13 @@ fn main() {
|
|||||||
}
|
}
|
||||||
"exit" | "quit" => break, // quit
|
"exit" | "quit" => break, // quit
|
||||||
"astu" => {
|
"astu" => {
|
||||||
if matches!(&ast_u, Some(_)) {
|
// print the last un-optimized AST
|
||||||
// print the last un-optimized AST
|
println!("{:#?}", &ast_u);
|
||||||
println!("{:#?}", ast_u.as_ref().unwrap());
|
|
||||||
} else {
|
|
||||||
println!("()");
|
|
||||||
}
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
"ast" => {
|
"ast" => {
|
||||||
if matches!(&ast, Some(_)) {
|
// print the last AST
|
||||||
// print the last AST
|
println!("{:#?}", &ast);
|
||||||
println!("{:#?}", ast.as_ref().unwrap());
|
|
||||||
} else {
|
|
||||||
println!("()");
|
|
||||||
}
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
@ -144,12 +141,12 @@ fn main() {
|
|||||||
.compile_with_scope(&scope, &script)
|
.compile_with_scope(&scope, &script)
|
||||||
.map_err(EvalAltResult::ErrorParsing)
|
.map_err(EvalAltResult::ErrorParsing)
|
||||||
.and_then(|r| {
|
.and_then(|r| {
|
||||||
ast_u = Some(r);
|
ast_u = r;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_optimize"))]
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
{
|
{
|
||||||
engine.set_optimization_level(OptimizationLevel::Full);
|
engine.set_optimization_level(OptimizationLevel::Full);
|
||||||
ast = Some(engine.optimize_ast(&scope, ast_u.as_ref().unwrap()));
|
ast = engine.optimize_ast(&scope, &ast_u);
|
||||||
engine.set_optimization_level(OptimizationLevel::None);
|
engine.set_optimization_level(OptimizationLevel::None);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,12 +155,21 @@ fn main() {
|
|||||||
ast = ast_u.clone();
|
ast = ast_u.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
engine
|
// Merge the AST into the main
|
||||||
.consume_ast_with_scope(&mut scope, true, ast.as_ref().unwrap())
|
main_ast = main_ast.merge(&ast);
|
||||||
|
|
||||||
|
// Evaluate
|
||||||
|
let result = engine
|
||||||
|
.consume_ast_with_scope(&mut scope, &main_ast)
|
||||||
.or_else(|err| match err {
|
.or_else(|err| match err {
|
||||||
EvalAltResult::Return(_, _) => Ok(()),
|
EvalAltResult::Return(_, _) => Ok(()),
|
||||||
err => Err(err),
|
err => Err(err),
|
||||||
})
|
});
|
||||||
|
|
||||||
|
// Throw away all the statements, leaving only the functions
|
||||||
|
main_ast.retain_functions();
|
||||||
|
|
||||||
|
result
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
println!();
|
println!();
|
||||||
|
@ -72,7 +72,7 @@ fn main() {
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(err) = engine.consume(false, &contents) {
|
if let Err(err) = engine.consume(&contents) {
|
||||||
eprintln!("{:=<1$}", "", filename.len());
|
eprintln!("{:=<1$}", "", filename.len());
|
||||||
eprintln!("{}", filename);
|
eprintln!("{}", filename);
|
||||||
eprintln!("{:=<1$}", "", filename.len());
|
eprintln!("{:=<1$}", "", filename.len());
|
||||||
|
193
src/api.rs
193
src/api.rs
@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
use crate::any::{Any, AnyExt, Dynamic};
|
use crate::any::{Any, AnyExt, Dynamic};
|
||||||
use crate::call::FuncArgs;
|
use crate::call::FuncArgs;
|
||||||
use crate::engine::{make_getter, make_setter, Engine, FnAny, FnSpec, FunctionsLib};
|
use crate::engine::{make_getter, make_setter, 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, parse_global_expr, FnDef, Position, AST};
|
use crate::parser::{lex, parse, parse_global_expr, Position, AST};
|
||||||
use crate::result::EvalAltResult;
|
use crate::result::EvalAltResult;
|
||||||
use crate::scope::Scope;
|
use crate::scope::Scope;
|
||||||
|
|
||||||
@ -17,7 +17,6 @@ use crate::stdlib::{
|
|||||||
boxed::Box,
|
boxed::Box,
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
sync::Arc,
|
|
||||||
vec::Vec,
|
vec::Vec,
|
||||||
};
|
};
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
@ -746,7 +745,7 @@ impl<'e> Engine<'e> {
|
|||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
ast: &AST,
|
ast: &AST,
|
||||||
) -> Result<T, EvalAltResult> {
|
) -> Result<T, EvalAltResult> {
|
||||||
self.eval_ast_with_scope_raw(scope, false, ast)?
|
self.eval_ast_with_scope_raw(scope, ast)?
|
||||||
.try_cast::<T>()
|
.try_cast::<T>()
|
||||||
.map_err(|a| {
|
.map_err(|a| {
|
||||||
EvalAltResult::ErrorMismatchOutputType(
|
EvalAltResult::ErrorMismatchOutputType(
|
||||||
@ -759,16 +758,11 @@ impl<'e> Engine<'e> {
|
|||||||
pub(crate) fn eval_ast_with_scope_raw(
|
pub(crate) fn eval_ast_with_scope_raw(
|
||||||
&mut self,
|
&mut self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
retain_functions: bool,
|
|
||||||
ast: &AST,
|
ast: &AST,
|
||||||
) -> Result<Dynamic, EvalAltResult> {
|
) -> Result<Dynamic, EvalAltResult> {
|
||||||
if !retain_functions {
|
|
||||||
self.clear_functions();
|
|
||||||
}
|
|
||||||
|
|
||||||
let statements = {
|
let statements = {
|
||||||
let AST(statements, functions) = ast;
|
let AST(statements, functions) = ast;
|
||||||
self.load_script_functions(functions);
|
self.fn_lib = Some(functions.clone());
|
||||||
statements
|
statements
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -776,9 +770,7 @@ impl<'e> Engine<'e> {
|
|||||||
.iter()
|
.iter()
|
||||||
.try_fold(().into_dynamic(), |_, stmt| self.eval_stmt(scope, stmt, 0));
|
.try_fold(().into_dynamic(), |_, stmt| self.eval_stmt(scope, stmt, 0));
|
||||||
|
|
||||||
if !retain_functions {
|
self.fn_lib = None;
|
||||||
self.clear_functions();
|
|
||||||
}
|
|
||||||
|
|
||||||
result.or_else(|err| match err {
|
result.or_else(|err| match err {
|
||||||
EvalAltResult::Return(out, _) => Ok(out),
|
EvalAltResult::Return(out, _) => Ok(out),
|
||||||
@ -788,52 +780,33 @@ 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.
|
||||||
///
|
|
||||||
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
|
|
||||||
/// and not cleared from run to run.
|
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
pub fn consume_file(
|
pub fn consume_file(&mut self, path: PathBuf) -> Result<(), EvalAltResult> {
|
||||||
&mut self,
|
Self::read_file(path).and_then(|contents| self.consume(&contents))
|
||||||
retain_functions: bool,
|
|
||||||
path: PathBuf,
|
|
||||||
) -> Result<(), EvalAltResult> {
|
|
||||||
Self::read_file(path).and_then(|contents| self.consume(retain_functions, &contents))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate a file with own scope, but throw away the result and only return error (if any).
|
/// Evaluate a file with own scope, 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.
|
||||||
///
|
|
||||||
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
|
|
||||||
/// and not cleared from run to run.
|
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
pub fn consume_file_with_scope(
|
pub fn consume_file_with_scope(
|
||||||
&mut self,
|
&mut self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
retain_functions: bool,
|
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
) -> Result<(), EvalAltResult> {
|
) -> Result<(), EvalAltResult> {
|
||||||
Self::read_file(path)
|
Self::read_file(path).and_then(|contents| self.consume_with_scope(scope, &contents))
|
||||||
.and_then(|contents| self.consume_with_scope(scope, retain_functions, &contents))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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> {
|
||||||
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
|
self.consume_with_scope(&mut Scope::new(), input)
|
||||||
/// and not cleared from run to run.
|
|
||||||
pub fn consume(&mut self, retain_functions: bool, input: &str) -> Result<(), EvalAltResult> {
|
|
||||||
self.consume_with_scope(&mut Scope::new(), retain_functions, input)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate a string with own scope, but throw away the result and only return error (if any).
|
/// Evaluate a string with own scope, 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.
|
||||||
///
|
|
||||||
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
|
|
||||||
/// and not cleared from run to run.
|
|
||||||
pub fn consume_with_scope(
|
pub fn consume_with_scope(
|
||||||
&mut self,
|
&mut self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
retain_functions: bool,
|
|
||||||
input: &str,
|
input: &str,
|
||||||
) -> Result<(), EvalAltResult> {
|
) -> Result<(), EvalAltResult> {
|
||||||
let tokens_stream = lex(input);
|
let tokens_stream = lex(input);
|
||||||
@ -841,36 +814,25 @@ impl<'e> Engine<'e> {
|
|||||||
let ast = parse(&mut tokens_stream.peekable(), self, scope)
|
let ast = parse(&mut tokens_stream.peekable(), self, scope)
|
||||||
.map_err(EvalAltResult::ErrorParsing)?;
|
.map_err(EvalAltResult::ErrorParsing)?;
|
||||||
|
|
||||||
self.consume_ast_with_scope(scope, retain_functions, &ast)
|
self.consume_ast_with_scope(scope, &ast)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate an AST, but throw away the result and only return error (if any).
|
/// Evaluate an AST, 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_ast(&mut self, ast: &AST) -> Result<(), EvalAltResult> {
|
||||||
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
|
self.consume_ast_with_scope(&mut Scope::new(), ast)
|
||||||
/// and not cleared from run to run.
|
|
||||||
pub fn consume_ast(&mut self, retain_functions: bool, ast: &AST) -> Result<(), EvalAltResult> {
|
|
||||||
self.consume_ast_with_scope(&mut Scope::new(), retain_functions, ast)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate an `AST` with own scope, but throw away the result and only return error (if any).
|
/// Evaluate an `AST` with own scope, 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.
|
||||||
///
|
|
||||||
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
|
|
||||||
/// and not cleared from run to run.
|
|
||||||
pub fn consume_ast_with_scope(
|
pub fn consume_ast_with_scope(
|
||||||
&mut self,
|
&mut self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
retain_functions: bool,
|
|
||||||
ast: &AST,
|
ast: &AST,
|
||||||
) -> Result<(), EvalAltResult> {
|
) -> Result<(), EvalAltResult> {
|
||||||
if !retain_functions {
|
|
||||||
self.clear_functions();
|
|
||||||
}
|
|
||||||
|
|
||||||
let statements = {
|
let statements = {
|
||||||
let AST(ref statements, ref functions) = ast;
|
let AST(ref statements, ref functions) = ast;
|
||||||
self.load_script_functions(functions);
|
self.fn_lib = Some(functions.clone());
|
||||||
statements
|
statements
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -878,9 +840,7 @@ impl<'e> Engine<'e> {
|
|||||||
.iter()
|
.iter()
|
||||||
.try_fold(().into_dynamic(), |_, stmt| self.eval_stmt(scope, stmt, 0));
|
.try_fold(().into_dynamic(), |_, stmt| self.eval_stmt(scope, stmt, 0));
|
||||||
|
|
||||||
if !retain_functions {
|
self.fn_lib = None;
|
||||||
self.clear_functions();
|
|
||||||
}
|
|
||||||
|
|
||||||
result.map(|_| ()).or_else(|err| match err {
|
result.map(|_| ()).or_else(|err| match err {
|
||||||
EvalAltResult::Return(_, _) => Ok(()),
|
EvalAltResult::Return(_, _) => Ok(()),
|
||||||
@ -888,21 +848,7 @@ impl<'e> Engine<'e> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load a list of functions into the Engine.
|
/// Call a script function defined in an `AST` with no argument.
|
||||||
pub(crate) fn load_script_functions<'a>(
|
|
||||||
&mut self,
|
|
||||||
functions: impl IntoIterator<Item = &'a Arc<FnDef>>,
|
|
||||||
) {
|
|
||||||
if self.fn_lib.is_none() {
|
|
||||||
self.fn_lib = Some(FunctionsLib::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
functions.into_iter().cloned().for_each(|f| {
|
|
||||||
self.fn_lib.as_mut().unwrap().add_or_replace_function(f);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Call a script function retained inside the Engine.
|
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
@ -915,11 +861,71 @@ impl<'e> Engine<'e> {
|
|||||||
///
|
///
|
||||||
/// let mut engine = Engine::new();
|
/// let mut engine = Engine::new();
|
||||||
///
|
///
|
||||||
/// // Set 'retain_functions' in 'consume' to keep the function definitions
|
/// let ast = engine.compile("fn num() { 42 }")?;
|
||||||
/// engine.consume(true, "fn add(x, y) { len(x) + y }")?;
|
|
||||||
///
|
///
|
||||||
/// // Call the script-defined function
|
/// // Call the script-defined function
|
||||||
/// let result: i64 = engine.call_fn("add", (String::from("abc"), 123_i64))?;
|
/// let result: i64 = engine.call_fn0(&ast, "num")?;
|
||||||
|
///
|
||||||
|
/// assert_eq!(result, 42);
|
||||||
|
/// # }
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
pub fn call_fn0<T: Any + Clone>(&mut self, ast: &AST, name: &str) -> Result<T, EvalAltResult> {
|
||||||
|
self.call_fn_internal(ast, name, vec![])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call a script function defined in an `AST` with one argument.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||||
|
/// # #[cfg(not(feature = "no_stdlib"))]
|
||||||
|
/// # #[cfg(not(feature = "no_function"))]
|
||||||
|
/// # {
|
||||||
|
/// use rhai::Engine;
|
||||||
|
///
|
||||||
|
/// let mut engine = Engine::new();
|
||||||
|
///
|
||||||
|
/// let ast = engine.compile("fn inc(x) { x + 1 }")?;
|
||||||
|
///
|
||||||
|
/// // Call the script-defined function
|
||||||
|
/// let result: i64 = engine.call_fn1(&ast, "inc", 123_i64)?;
|
||||||
|
///
|
||||||
|
/// assert_eq!(result, 124);
|
||||||
|
/// # }
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
pub fn call_fn1<A: Any + Clone, T: Any + Clone>(
|
||||||
|
&mut self,
|
||||||
|
ast: &AST,
|
||||||
|
name: &str,
|
||||||
|
arg: A,
|
||||||
|
) -> Result<T, EvalAltResult> {
|
||||||
|
self.call_fn_internal(ast, name, vec![arg.into_dynamic()])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call a script function defined in an `AST` with multiple arguments.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||||
|
/// # #[cfg(not(feature = "no_stdlib"))]
|
||||||
|
/// # #[cfg(not(feature = "no_function"))]
|
||||||
|
/// # {
|
||||||
|
/// use rhai::Engine;
|
||||||
|
///
|
||||||
|
/// let mut engine = Engine::new();
|
||||||
|
///
|
||||||
|
/// let ast = engine.compile("fn add(x, y) { len(x) + y }")?;
|
||||||
|
///
|
||||||
|
/// // Call the script-defined function
|
||||||
|
/// let result: i64 = engine.call_fn(&ast, "add", (String::from("abc"), 123_i64))?;
|
||||||
///
|
///
|
||||||
/// assert_eq!(result, 126);
|
/// assert_eq!(result, 126);
|
||||||
/// # }
|
/// # }
|
||||||
@ -929,20 +935,37 @@ impl<'e> Engine<'e> {
|
|||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
pub fn call_fn<A: FuncArgs, T: Any + Clone>(
|
pub fn call_fn<A: FuncArgs, T: Any + Clone>(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
ast: &AST,
|
||||||
name: &str,
|
name: &str,
|
||||||
args: A,
|
args: A,
|
||||||
) -> Result<T, EvalAltResult> {
|
) -> Result<T, EvalAltResult> {
|
||||||
let mut values = args.into_vec();
|
self.call_fn_internal(ast, name, args.into_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
fn call_fn_internal<T: Any + Clone>(
|
||||||
|
&mut self,
|
||||||
|
ast: &AST,
|
||||||
|
name: &str,
|
||||||
|
mut values: Vec<Dynamic>,
|
||||||
|
) -> Result<T, EvalAltResult> {
|
||||||
let mut arg_values: Vec<_> = values.iter_mut().map(Dynamic::as_mut).collect();
|
let mut arg_values: Vec<_> = values.iter_mut().map(Dynamic::as_mut).collect();
|
||||||
|
|
||||||
self.call_fn_raw(name, &mut arg_values, None, Position::none(), 0)?
|
self.fn_lib = Some(ast.1.clone());
|
||||||
|
|
||||||
|
let result = self
|
||||||
|
.call_fn_raw(name, &mut arg_values, None, Position::none(), 0)?
|
||||||
.try_cast()
|
.try_cast()
|
||||||
.map_err(|a| {
|
.map_err(|a| {
|
||||||
EvalAltResult::ErrorMismatchOutputType(
|
EvalAltResult::ErrorMismatchOutputType(
|
||||||
self.map_type_name((*a).type_name()).into(),
|
self.map_type_name((*a).type_name()).into(),
|
||||||
Position::none(),
|
Position::none(),
|
||||||
)
|
)
|
||||||
})
|
});
|
||||||
|
|
||||||
|
self.fn_lib = None;
|
||||||
|
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Optimize the `AST` with constants defined in an external Scope.
|
/// Optimize the `AST` with constants defined in an external Scope.
|
||||||
@ -958,10 +981,12 @@ impl<'e> Engine<'e> {
|
|||||||
/// (i.e. with `scope.push_constant(...)`). Then, the `AST is cloned and the copy re-optimized before running.
|
/// (i.e. with `scope.push_constant(...)`). Then, the `AST is cloned and the copy re-optimized before running.
|
||||||
#[cfg(not(feature = "no_optimize"))]
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
pub fn optimize_ast(&self, scope: &Scope, ast: &AST) -> AST {
|
pub fn optimize_ast(&self, scope: &Scope, ast: &AST) -> AST {
|
||||||
let statements = ast.0.clone();
|
optimize_into_ast(
|
||||||
let functions = ast.1.iter().map(|f| (**f).clone()).collect();
|
self,
|
||||||
|
scope,
|
||||||
optimize_into_ast(self, scope, statements, functions)
|
ast.0.clone(),
|
||||||
|
ast.1.iter().map(|fn_def| fn_def.as_ref().clone()).collect(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Override default action of `print` (print to stdout using `println!`)
|
/// Override default action of `print` (print to stdout using `println!`)
|
||||||
@ -978,7 +1003,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(false, "print(40 + 2);")?;
|
/// engine.consume("print(40 + 2);")?;
|
||||||
/// }
|
/// }
|
||||||
/// assert_eq!(result, "42");
|
/// assert_eq!(result, "42");
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
@ -1002,7 +1027,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(false, "print(40 + 2);")?;
|
/// engine.consume("print(40 + 2);")?;
|
||||||
/// }
|
/// }
|
||||||
/// assert_eq!(result, "42");
|
/// assert_eq!(result, "42");
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
@ -1027,7 +1052,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(false, r#"debug("hello");"#)?;
|
/// engine.consume(r#"debug("hello");"#)?;
|
||||||
/// }
|
/// }
|
||||||
/// assert_eq!(result, "\"hello\"");
|
/// assert_eq!(result, "\"hello\"");
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
@ -1051,7 +1076,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(false, r#"debug("hello");"#)?;
|
/// engine.consume(r#"debug("hello");"#)?;
|
||||||
/// }
|
/// }
|
||||||
/// assert_eq!(result, "\"hello\"");
|
/// assert_eq!(result, "\"hello\"");
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
|
35
src/call.rs
35
src/call.rs
@ -3,10 +3,6 @@
|
|||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
use crate::any::{Any, Dynamic};
|
use crate::any::{Any, Dynamic};
|
||||||
use crate::parser::INT;
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
|
||||||
use crate::engine::Array;
|
|
||||||
|
|
||||||
use crate::stdlib::{string::String, vec, vec::Vec};
|
use crate::stdlib::{string::String, vec, vec::Vec};
|
||||||
|
|
||||||
@ -18,36 +14,7 @@ pub trait FuncArgs {
|
|||||||
fn into_vec(self) -> Vec<Dynamic>;
|
fn into_vec(self) -> Vec<Dynamic>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Macro to implement `FuncArgs` for a single standard type that can be converted
|
// Macro to implement `FuncArgs` for tuples of standard types (each can be
|
||||||
/// into `Dynamic`.
|
|
||||||
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(any(feature = "only_i32", feature = "only_i64"))]
|
|
||||||
impl_std_args!(INT);
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
|
||||||
impl_std_args!(f32, f64);
|
|
||||||
|
|
||||||
/// Macro to implement `FuncArgs` for tuples of standard types (each can be
|
|
||||||
/// converted into `Dynamic`).
|
/// converted into `Dynamic`).
|
||||||
macro_rules! impl_args {
|
macro_rules! impl_args {
|
||||||
($($p:ident),*) => {
|
($($p:ident),*) => {
|
||||||
|
135
src/engine.rs
135
src/engine.rs
@ -1,7 +1,7 @@
|
|||||||
//! Main module defining the script evaluation `Engine`.
|
//! Main module defining the script evaluation `Engine`.
|
||||||
|
|
||||||
use crate::any::{Any, AnyExt, Dynamic, Variant};
|
use crate::any::{Any, AnyExt, Dynamic, Variant};
|
||||||
use crate::parser::{Expr, FnDef, Position, ReturnType, Stmt, INT};
|
use crate::parser::{Expr, FnDef, Position, ReturnType, Stmt, AST, INT};
|
||||||
use crate::result::EvalAltResult;
|
use crate::result::EvalAltResult;
|
||||||
use crate::scope::{EntryRef as ScopeSource, EntryType as ScopeEntryType, Scope};
|
use crate::scope::{EntryRef as ScopeSource, EntryType as ScopeEntryType, Scope};
|
||||||
|
|
||||||
@ -16,6 +16,8 @@ use crate::stdlib::{
|
|||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
format,
|
format,
|
||||||
iter::once,
|
iter::once,
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
|
rc::Rc,
|
||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
vec,
|
vec,
|
||||||
@ -130,8 +132,11 @@ pub struct FnSpec<'a> {
|
|||||||
/// to search for it.
|
/// to search for it.
|
||||||
///
|
///
|
||||||
/// So instead this is implemented as a sorted list and binary searched.
|
/// So instead this is implemented as a sorted list and binary searched.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct FunctionsLib(Vec<Arc<FnDef>>);
|
pub struct FunctionsLib(
|
||||||
|
#[cfg(feature = "sync")] Vec<Arc<FnDef>>,
|
||||||
|
#[cfg(not(feature = "sync"))] Vec<Rc<FnDef>>,
|
||||||
|
);
|
||||||
|
|
||||||
impl FnDef {
|
impl FnDef {
|
||||||
/// Function to order two FnDef records, for binary search.
|
/// Function to order two FnDef records, for binary search.
|
||||||
@ -150,28 +155,75 @@ impl FunctionsLib {
|
|||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
FunctionsLib(Vec::new())
|
FunctionsLib(Vec::new())
|
||||||
}
|
}
|
||||||
|
/// Create a new `FunctionsLib` from a collection of `FnDef`.
|
||||||
|
pub fn from_vec(vec: Vec<FnDef>) -> Self {
|
||||||
|
#[cfg(feature = "sync")]
|
||||||
|
{
|
||||||
|
FunctionsLib(vec.into_iter().map(Arc::new).collect())
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "sync"))]
|
||||||
|
{
|
||||||
|
FunctionsLib(vec.into_iter().map(Rc::new).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
/// Does a certain function exist in the `FunctionsLib`?
|
/// Does a certain function exist in the `FunctionsLib`?
|
||||||
pub fn has_function(&self, name: &str, params: usize) -> bool {
|
pub fn has_function(&self, name: &str, params: usize) -> bool {
|
||||||
self.0.binary_search_by(|f| f.compare(name, params)).is_ok()
|
self.0.binary_search_by(|f| f.compare(name, params)).is_ok()
|
||||||
}
|
}
|
||||||
/// Add a function (or replace an existing one) in the `FunctionsLib`.
|
|
||||||
pub fn add_or_replace_function(&mut self, fn_def: Arc<FnDef>) {
|
|
||||||
match self
|
|
||||||
.0
|
|
||||||
.binary_search_by(|f| f.compare(&fn_def.name, fn_def.params.len()))
|
|
||||||
{
|
|
||||||
Ok(n) => self.0[n] = fn_def,
|
|
||||||
Err(n) => self.0.insert(n, fn_def),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Get a function definition from the `FunctionsLib`.
|
/// Get a function definition from the `FunctionsLib`.
|
||||||
pub fn get_function(&self, name: &str, params: usize) -> Option<Arc<FnDef>> {
|
pub fn get_function(&self, name: &str, params: usize) -> Option<&FnDef> {
|
||||||
if let Ok(n) = self.0.binary_search_by(|f| f.compare(name, params)) {
|
if let Ok(n) = self.0.binary_search_by(|f| f.compare(name, params)) {
|
||||||
Some(self.0[n].clone())
|
Some(&self.0[n])
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Merge another `FunctionsLib` into this `FunctionsLib`.
|
||||||
|
pub fn merge(&self, other: &Self) -> Self {
|
||||||
|
if self.is_empty() {
|
||||||
|
other.clone()
|
||||||
|
} else if other.is_empty() {
|
||||||
|
self.clone()
|
||||||
|
} else {
|
||||||
|
let mut functions = self.clone();
|
||||||
|
|
||||||
|
other.iter().cloned().for_each(|fn_def| {
|
||||||
|
if let Some((n, _)) = functions
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(_, f)| f.name == fn_def.name && f.params.len() == fn_def.params.len())
|
||||||
|
{
|
||||||
|
functions[n] = fn_def;
|
||||||
|
} else {
|
||||||
|
functions.push(fn_def);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
functions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for FunctionsLib {
|
||||||
|
#[cfg(feature = "sync")]
|
||||||
|
type Target = Vec<Arc<FnDef>>;
|
||||||
|
#[cfg(not(feature = "sync"))]
|
||||||
|
type Target = Vec<Rc<FnDef>>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for FunctionsLib {
|
||||||
|
#[cfg(feature = "sync")]
|
||||||
|
fn deref_mut(&mut self) -> &mut Vec<Arc<FnDef>> {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "sync"))]
|
||||||
|
fn deref_mut(&mut self) -> &mut Vec<Rc<FnDef>> {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rhai main scripting engine.
|
/// Rhai main scripting engine.
|
||||||
@ -193,8 +245,14 @@ impl FunctionsLib {
|
|||||||
pub struct Engine<'e> {
|
pub struct Engine<'e> {
|
||||||
/// A hashmap containing all compiled functions known to the engine.
|
/// A hashmap containing all compiled functions known to the engine.
|
||||||
pub(crate) functions: Option<HashMap<FnSpec<'e>, Box<FnAny>>>,
|
pub(crate) functions: Option<HashMap<FnSpec<'e>, Box<FnAny>>>,
|
||||||
|
|
||||||
/// A hashmap containing all script-defined functions.
|
/// A hashmap containing all script-defined functions.
|
||||||
pub(crate) fn_lib: Option<FunctionsLib>,
|
#[cfg(feature = "sync")]
|
||||||
|
pub(crate) fn_lib: Option<Arc<FunctionsLib>>,
|
||||||
|
/// A hashmap containing all script-defined functions.
|
||||||
|
#[cfg(not(feature = "sync"))]
|
||||||
|
pub(crate) fn_lib: Option<Rc<FunctionsLib>>,
|
||||||
|
|
||||||
/// A hashmap containing all iterators known to the engine.
|
/// A hashmap containing all iterators known to the engine.
|
||||||
pub(crate) type_iterators: Option<HashMap<TypeId, Box<IteratorFn>>>,
|
pub(crate) type_iterators: Option<HashMap<TypeId, Box<IteratorFn>>>,
|
||||||
/// A hashmap mapping type names to pretty-print names.
|
/// A hashmap mapping type names to pretty-print names.
|
||||||
@ -203,12 +261,14 @@ pub struct Engine<'e> {
|
|||||||
/// Closure for implementing the `print` command.
|
/// Closure for implementing the `print` command.
|
||||||
#[cfg(feature = "sync")]
|
#[cfg(feature = "sync")]
|
||||||
pub(crate) on_print: Option<Box<dyn FnMut(&str) + Send + Sync + 'e>>,
|
pub(crate) on_print: Option<Box<dyn FnMut(&str) + Send + Sync + 'e>>,
|
||||||
|
/// Closure for implementing the `print` command.
|
||||||
#[cfg(not(feature = "sync"))]
|
#[cfg(not(feature = "sync"))]
|
||||||
pub(crate) on_print: Option<Box<dyn FnMut(&str) + 'e>>,
|
pub(crate) on_print: Option<Box<dyn FnMut(&str) + 'e>>,
|
||||||
|
|
||||||
/// Closure for implementing the `debug` command.
|
/// Closure for implementing the `debug` command.
|
||||||
#[cfg(feature = "sync")]
|
#[cfg(feature = "sync")]
|
||||||
pub(crate) on_debug: Option<Box<dyn FnMut(&str) + Send + Sync + 'e>>,
|
pub(crate) on_debug: Option<Box<dyn FnMut(&str) + Send + Sync + 'e>>,
|
||||||
|
/// Closure for implementing the `debug` command.
|
||||||
#[cfg(not(feature = "sync"))]
|
#[cfg(not(feature = "sync"))]
|
||||||
pub(crate) on_debug: Option<Box<dyn FnMut(&str) + 'e>>,
|
pub(crate) on_debug: Option<Box<dyn FnMut(&str) + 'e>>,
|
||||||
|
|
||||||
@ -382,8 +442,8 @@ impl Engine<'_> {
|
|||||||
level: usize,
|
level: usize,
|
||||||
) -> Result<Dynamic, EvalAltResult> {
|
) -> Result<Dynamic, EvalAltResult> {
|
||||||
// First search in script-defined functions (can override built-in)
|
// First search in script-defined functions (can override built-in)
|
||||||
if let Some(ref fn_lib) = self.fn_lib {
|
if let Some(ref fn_lib_arc) = self.fn_lib {
|
||||||
if let Some(fn_def) = fn_lib.get_function(fn_name, args.len()) {
|
if let Some(fn_def) = fn_lib_arc.clone().get_function(fn_name, args.len()) {
|
||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
|
|
||||||
scope.extend(
|
scope.extend(
|
||||||
@ -1276,6 +1336,7 @@ impl Engine<'_> {
|
|||||||
let pos = args_expr_list[0].position();
|
let pos = args_expr_list[0].position();
|
||||||
let r = self.eval_expr(scope, &args_expr_list[0], level)?;
|
let r = self.eval_expr(scope, &args_expr_list[0], level)?;
|
||||||
|
|
||||||
|
// Get the script text by evaluating the expression
|
||||||
let script =
|
let script =
|
||||||
r.downcast_ref::<String>()
|
r.downcast_ref::<String>()
|
||||||
.map(String::as_str)
|
.map(String::as_str)
|
||||||
@ -1286,8 +1347,9 @@ impl Engine<'_> {
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
// Compile the script text
|
||||||
#[cfg(not(feature = "no_optimize"))]
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
let ast = {
|
let mut ast = {
|
||||||
let orig_optimization_level = self.optimization_level;
|
let orig_optimization_level = self.optimization_level;
|
||||||
|
|
||||||
self.set_optimization_level(OptimizationLevel::None);
|
self.set_optimization_level(OptimizationLevel::None);
|
||||||
@ -1298,11 +1360,38 @@ impl Engine<'_> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "no_optimize")]
|
#[cfg(feature = "no_optimize")]
|
||||||
let ast = self.compile(script).map_err(EvalAltResult::ErrorParsing)?;
|
let mut ast = self.compile(script).map_err(EvalAltResult::ErrorParsing)?;
|
||||||
|
|
||||||
Ok(self
|
// If new functions are defined, merge it into the current functions library
|
||||||
.eval_ast_with_scope_raw(scope, true, &ast)
|
let merged = AST(
|
||||||
.map_err(|err| err.set_position(pos))?)
|
ast.0,
|
||||||
|
if let Some(ref fn_lib) = self.fn_lib {
|
||||||
|
#[cfg(feature = "sync")]
|
||||||
|
{
|
||||||
|
Arc::new(fn_lib.as_ref().merge(&ast.1))
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "sync"))]
|
||||||
|
{
|
||||||
|
Rc::new(fn_lib.as_ref().merge(&ast.1))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ast.1
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Evaluate the AST
|
||||||
|
let result = self
|
||||||
|
.eval_ast_with_scope_raw(scope, &merged)
|
||||||
|
.map_err(|err| err.set_position(pos));
|
||||||
|
|
||||||
|
// Update the new functions library if there are new functions
|
||||||
|
self.fn_lib = if !merged.1.is_empty() {
|
||||||
|
Some(merged.1)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(result?)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normal function call
|
// Normal function call
|
||||||
|
@ -2,13 +2,15 @@
|
|||||||
|
|
||||||
use crate::any::{Any, Dynamic};
|
use crate::any::{Any, Dynamic};
|
||||||
use crate::engine::{
|
use crate::engine::{
|
||||||
Engine, KEYWORD_DEBUG, KEYWORD_DUMP_AST, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF,
|
Engine, FunctionsLib, KEYWORD_DEBUG, KEYWORD_DUMP_AST, KEYWORD_EVAL, KEYWORD_PRINT,
|
||||||
|
KEYWORD_TYPE_OF,
|
||||||
};
|
};
|
||||||
use crate::parser::{map_dynamic_to_expr, Expr, FnDef, ReturnType, Stmt, AST};
|
use crate::parser::{map_dynamic_to_expr, Expr, FnDef, ReturnType, Stmt, AST};
|
||||||
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
|
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
|
||||||
|
|
||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
boxed::Box,
|
boxed::Box,
|
||||||
|
rc::Rc,
|
||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
vec,
|
vec,
|
||||||
@ -451,8 +453,8 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
|||||||
&& args.iter().all(|expr| expr.is_constant()) // all arguments are constants
|
&& args.iter().all(|expr| expr.is_constant()) // all arguments are constants
|
||||||
=> {
|
=> {
|
||||||
// First search in script-defined functions (can override built-in)
|
// First search in script-defined functions (can override built-in)
|
||||||
if let Some(ref fn_lib) = state.engine.fn_lib {
|
if let Some(ref fn_lib_arc) = state.engine.fn_lib {
|
||||||
if fn_lib.has_function(&id, args.len()) {
|
if fn_lib_arc.has_function(&id, args.len()) {
|
||||||
// A script-defined function overrides the built-in function - do not make the call
|
// A script-defined function overrides the built-in function - do not make the call
|
||||||
return Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos);
|
return Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos);
|
||||||
}
|
}
|
||||||
@ -584,15 +586,10 @@ pub fn optimize_into_ast(
|
|||||||
statements: Vec<Stmt>,
|
statements: Vec<Stmt>,
|
||||||
functions: Vec<FnDef>,
|
functions: Vec<FnDef>,
|
||||||
) -> AST {
|
) -> AST {
|
||||||
AST(
|
let fn_lib = FunctionsLib::from_vec(
|
||||||
match engine.optimization_level {
|
|
||||||
OptimizationLevel::None => statements,
|
|
||||||
OptimizationLevel::Simple | OptimizationLevel::Full => {
|
|
||||||
optimize(statements, engine, &scope)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
functions
|
functions
|
||||||
.into_iter()
|
.iter()
|
||||||
|
.cloned()
|
||||||
.map(|mut fn_def| {
|
.map(|mut fn_def| {
|
||||||
if engine.optimization_level != OptimizationLevel::None {
|
if engine.optimization_level != OptimizationLevel::None {
|
||||||
let pos = fn_def.body.position();
|
let pos = fn_def.body.position();
|
||||||
@ -612,9 +609,21 @@ pub fn optimize_into_ast(
|
|||||||
stmt => stmt,
|
stmt => stmt,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
fn_def
|
||||||
Arc::new(fn_def)
|
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
|
);
|
||||||
|
|
||||||
|
AST(
|
||||||
|
match engine.optimization_level {
|
||||||
|
OptimizationLevel::None => statements,
|
||||||
|
OptimizationLevel::Simple | OptimizationLevel::Full => {
|
||||||
|
optimize(statements, engine, &scope)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
#[cfg(feature = "sync")]
|
||||||
|
Arc::new(fn_lib),
|
||||||
|
#[cfg(not(feature = "sync"))]
|
||||||
|
Rc::new(fn_lib),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
101
src/parser.rs
101
src/parser.rs
@ -1,7 +1,7 @@
|
|||||||
//! Main module defining the lexer and parser.
|
//! Main module defining the lexer and parser.
|
||||||
|
|
||||||
use crate::any::{Any, AnyExt, Dynamic};
|
use crate::any::{Any, AnyExt, Dynamic};
|
||||||
use crate::engine::Engine;
|
use crate::engine::{Engine, FunctionsLib};
|
||||||
use crate::error::{LexError, ParseError, ParseErrorType};
|
use crate::error::{LexError, ParseError, ParseErrorType};
|
||||||
use crate::scope::{EntryType as ScopeEntryType, Scope};
|
use crate::scope::{EntryType as ScopeEntryType, Scope};
|
||||||
|
|
||||||
@ -16,6 +16,7 @@ use crate::stdlib::{
|
|||||||
fmt, format,
|
fmt, format,
|
||||||
iter::Peekable,
|
iter::Peekable,
|
||||||
ops::Add,
|
ops::Add,
|
||||||
|
rc::Rc,
|
||||||
str::Chars,
|
str::Chars,
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
@ -165,10 +166,19 @@ impl fmt::Debug for Position {
|
|||||||
///
|
///
|
||||||
/// Currently, `AST` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`.
|
/// Currently, `AST` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct AST(pub(crate) Vec<Stmt>, pub(crate) Vec<Arc<FnDef>>);
|
pub struct AST(
|
||||||
|
pub(crate) Vec<Stmt>,
|
||||||
|
#[cfg(feature = "sync")] pub(crate) Arc<FunctionsLib>,
|
||||||
|
#[cfg(not(feature = "sync"))] pub(crate) Rc<FunctionsLib>,
|
||||||
|
);
|
||||||
|
|
||||||
impl AST {
|
impl AST {
|
||||||
/// Merge two `AST` into one. Both `AST`'s are consumed and a new, merged, version
|
/// Create a new `AST`.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Merge two `AST` into one. Both `AST`'s are untouched and a new, merged, version
|
||||||
/// is returned.
|
/// is returned.
|
||||||
///
|
///
|
||||||
/// The second `AST` is simply appended to the end of the first _without any processing_.
|
/// The second `AST` is simply appended to the end of the first _without any processing_.
|
||||||
@ -192,10 +202,10 @@ impl AST {
|
|||||||
/// let ast1 = engine.compile(r#"fn foo(x) { 42 + x } foo(1)"#)?;
|
/// let ast1 = engine.compile(r#"fn foo(x) { 42 + x } foo(1)"#)?;
|
||||||
/// let ast2 = engine.compile(r#"fn foo(n) { "hello" + n } foo("!")"#)?;
|
/// let ast2 = engine.compile(r#"fn foo(n) { "hello" + n } foo("!")"#)?;
|
||||||
///
|
///
|
||||||
/// let ast = ast1.merge(ast2); // Merge 'ast2' into 'ast1'
|
/// let ast = ast1.merge(&ast2); // Merge 'ast2' into 'ast1'
|
||||||
///
|
///
|
||||||
/// // Notice that using the '+' operator also works:
|
/// // Notice that using the '+' operator also works:
|
||||||
/// // let ast = ast1 + ast2;
|
/// // let ast = &ast1 + &ast2;
|
||||||
///
|
///
|
||||||
/// // 'ast' is essentially:
|
/// // 'ast' is essentially:
|
||||||
/// //
|
/// //
|
||||||
@ -210,29 +220,62 @@ impl AST {
|
|||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn merge(self, mut other: Self) -> Self {
|
pub fn merge(&self, other: &Self) -> Self {
|
||||||
let Self(mut ast, mut functions) = self;
|
let Self(ast, functions) = self;
|
||||||
|
|
||||||
ast.append(&mut other.0);
|
let ast = match (ast.is_empty(), other.0.is_empty()) {
|
||||||
|
(false, false) => {
|
||||||
for fn_def in other.1 {
|
let mut ast = ast.clone();
|
||||||
if let Some((n, _)) = functions
|
ast.extend(other.0.iter().cloned());
|
||||||
.iter()
|
ast
|
||||||
.enumerate()
|
|
||||||
.find(|(_, f)| f.name == fn_def.name && f.params.len() == fn_def.params.len())
|
|
||||||
{
|
|
||||||
functions[n] = fn_def;
|
|
||||||
} else {
|
|
||||||
functions.push(fn_def);
|
|
||||||
}
|
}
|
||||||
}
|
(false, true) => ast.clone(),
|
||||||
|
(true, false) => other.0.clone(),
|
||||||
|
(true, true) => vec![],
|
||||||
|
};
|
||||||
|
|
||||||
Self(ast, functions)
|
#[cfg(feature = "sync")]
|
||||||
|
{
|
||||||
|
Self(ast, Arc::new(functions.merge(other.1.as_ref())))
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "sync"))]
|
||||||
|
{
|
||||||
|
Self(ast, Rc::new(functions.merge(other.1.as_ref())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clear all function definitions in the `AST`.
|
||||||
|
pub fn clear_functions(&mut self) {
|
||||||
|
#[cfg(feature = "sync")]
|
||||||
|
{
|
||||||
|
self.1 = Arc::new(FunctionsLib::new());
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "sync"))]
|
||||||
|
{
|
||||||
|
self.1 = Rc::new(FunctionsLib::new());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clear all statements in the `AST`, leaving only function definitions.
|
||||||
|
pub fn retain_functions(&mut self) {
|
||||||
|
self.0 = vec![];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Add<Self> for AST {
|
impl Default for AST {
|
||||||
type Output = Self;
|
fn default() -> Self {
|
||||||
|
#[cfg(feature = "sync")]
|
||||||
|
{
|
||||||
|
Self(vec![], Arc::new(FunctionsLib::new()))
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "sync"))]
|
||||||
|
{
|
||||||
|
Self(vec![], Rc::new(FunctionsLib::new()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Add<Self> for &AST {
|
||||||
|
type Output = AST;
|
||||||
|
|
||||||
fn add(self, rhs: Self) -> Self::Output {
|
fn add(self, rhs: Self) -> Self::Output {
|
||||||
self.merge(rhs)
|
self.merge(rhs)
|
||||||
@ -2592,7 +2635,17 @@ pub fn parse_global_expr<'a, 'e>(
|
|||||||
//
|
//
|
||||||
// Do not optimize AST if `no_optimize`
|
// Do not optimize AST if `no_optimize`
|
||||||
#[cfg(feature = "no_optimize")]
|
#[cfg(feature = "no_optimize")]
|
||||||
AST(vec![Stmt::Expr(Box::new(expr))], vec![]),
|
AST(
|
||||||
|
vec![Stmt::Expr(Box::new(expr))],
|
||||||
|
#[cfg(feature = "sync")]
|
||||||
|
{
|
||||||
|
Arc::new(FunctionsLib::new())
|
||||||
|
},
|
||||||
|
#[cfg(not(feature = "sync"))]
|
||||||
|
{
|
||||||
|
Rc::new(FunctionsLib::new())
|
||||||
|
},
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2667,7 +2720,7 @@ pub fn parse<'a, 'e>(
|
|||||||
//
|
//
|
||||||
// Do not optimize AST if `no_optimize`
|
// Do not optimize AST if `no_optimize`
|
||||||
#[cfg(feature = "no_optimize")]
|
#[cfg(feature = "no_optimize")]
|
||||||
AST(statements, functions.into_iter().map(Arc::new).collect()),
|
AST(statements, Arc::new(FunctionsLib::from_vec(functions))),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,8 +22,7 @@ fn test_fn() -> Result<(), EvalAltResult> {
|
|||||||
fn test_call_fn() -> Result<(), EvalAltResult> {
|
fn test_call_fn() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
engine.consume(
|
let ast = engine.compile(
|
||||||
true,
|
|
||||||
r"
|
r"
|
||||||
fn hello(x, y) {
|
fn hello(x, y) {
|
||||||
x + y
|
x + y
|
||||||
@ -31,14 +30,20 @@ fn test_call_fn() -> Result<(), EvalAltResult> {
|
|||||||
fn hello(x) {
|
fn hello(x) {
|
||||||
x * 2
|
x * 2
|
||||||
}
|
}
|
||||||
",
|
fn hello() {
|
||||||
|
42
|
||||||
|
}
|
||||||
|
",
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let r: i64 = engine.call_fn("hello", (42 as INT, 123 as INT))?;
|
let r: i64 = engine.call_fn(&ast, "hello", (42 as INT, 123 as INT))?;
|
||||||
assert_eq!(r, 165);
|
assert_eq!(r, 165);
|
||||||
|
|
||||||
let r: i64 = engine.call_fn("hello", 123 as INT)?;
|
let r: i64 = engine.call_fn1(&ast, "hello", 123 as INT)?;
|
||||||
assert_eq!(r, 246);
|
assert_eq!(r, 246);
|
||||||
|
|
||||||
|
let r: i64 = engine.call_fn0(&ast, "hello")?;
|
||||||
|
assert_eq!(r, 42);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user