Add full optimization level for aggressive optimizing.
This commit is contained in:
parent
f80e499e84
commit
372321dfe3
@ -18,7 +18,7 @@ include = [
|
|||||||
num-traits = "*"
|
num-traits = "*"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
#default = ["no_function", "no_index", "no_float", "only_i32", "no_stdlib", "unchecked"]
|
#default = ["no_function", "no_index", "no_float", "only_i32", "no_stdlib", "unchecked", "no_optimize"]
|
||||||
default = []
|
default = []
|
||||||
debug_msgs = [] # print debug messages on function registrations and calls
|
debug_msgs = [] # print debug messages on function registrations and calls
|
||||||
unchecked = [] # unchecked arithmetic
|
unchecked = [] # unchecked arithmetic
|
||||||
|
66
README.md
66
README.md
@ -869,13 +869,13 @@ Compound assignment operators
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
let number = 5;
|
let number = 5;
|
||||||
number += 4; // number = number + 4
|
number += 4; // number = number + 4
|
||||||
number -= 3; // number = number - 3
|
number -= 3; // number = number - 3
|
||||||
number *= 2; // number = number * 2
|
number *= 2; // number = number * 2
|
||||||
number /= 1; // number = number / 1
|
number /= 1; // number = number / 1
|
||||||
number %= 3; // number = number % 3
|
number %= 3; // number = number % 3
|
||||||
number <<= 2; // number = number << 2
|
number <<= 2; // number = number << 2
|
||||||
number >>= 1; // number = number >> 1
|
number >>= 1; // number = number >> 1
|
||||||
```
|
```
|
||||||
|
|
||||||
The `+=` operator can also be used to build strings:
|
The `+=` operator can also be used to build strings:
|
||||||
@ -1096,8 +1096,8 @@ for entry in log {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Optimizations
|
Script optimization
|
||||||
=============
|
===================
|
||||||
|
|
||||||
Rhai includes an _optimizer_ that tries to optimize a script after parsing. This can reduce resource utilization and increase execution speed.
|
Rhai includes an _optimizer_ that tries to optimize a script after parsing. This can reduce resource utilization and increase execution speed.
|
||||||
Script optimization can be turned off via the [`no_optimize`] feature.
|
Script optimization can be turned off via the [`no_optimize`] feature.
|
||||||
@ -1167,21 +1167,55 @@ const DECISION_2 = false;
|
|||||||
const DECISION_3 = false;
|
const DECISION_3 = false;
|
||||||
|
|
||||||
if DECISION_1 {
|
if DECISION_1 {
|
||||||
: // this branch is kept
|
: // this branch is kept and promoted to the parent level
|
||||||
} else if DECISION_2 {
|
} else if DECISION_2 {
|
||||||
: // this branch is eliminated
|
: // this branch is eliminated
|
||||||
} else if DECISION_3 {
|
} else if DECISION_3 {
|
||||||
: // this branch is eliminated
|
: // this branch is eliminated
|
||||||
} else {
|
} else {
|
||||||
: // this branch is eliminated
|
: // this branch is eliminated
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
In general, boolean constants are most effective if you want the optimizer to automatically prune large `if`-`else` branches because they do not depend on operators.
|
In general, boolean constants are most effective if you want the optimizer to automatically prune large `if`-`else` branches because they do not depend on operators.
|
||||||
|
|
||||||
|
Alternatively, turn the optimizer to [`OptimizationLevel::Full`]
|
||||||
|
|
||||||
Here be dragons!
|
Here be dragons!
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
### Optimization levels
|
||||||
|
|
||||||
|
There are actually three levels of optimizations: `None`, `Simple` and `Full`.
|
||||||
|
|
||||||
|
`None` is obvious - no optimization on the AST is performed.
|
||||||
|
|
||||||
|
`Simple` performs relatively _safe_ optimizations without causing side effects (i.e. it only relies on static analysis and will not actually perform any function calls). `Simple` is the default.
|
||||||
|
|
||||||
|
`Full` is _much_ more aggressive, _including_ running functions on constant arguments to determine their result. One benefit to this is that many more optimization opportunities arise, especially with regards to comparison operators.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// The following run with OptimizationLevel::Full
|
||||||
|
|
||||||
|
const DECISION = 1;
|
||||||
|
|
||||||
|
if DECISION == 1 { // this condition is now eliminated because 'DECISION == 1' is a function call to the '==' function, and it returns 'true'
|
||||||
|
print("hello!"); // the 'true' block is promoted to the parent level
|
||||||
|
} else {
|
||||||
|
print("boo!"); // the 'else' block is eliminated
|
||||||
|
}
|
||||||
|
|
||||||
|
print("hello!"); // <- the above is equivalent to this
|
||||||
|
```
|
||||||
|
|
||||||
|
### Side effect considerations
|
||||||
|
|
||||||
|
All built-in operators have _pure_ functions (i.e. they do not cause side effects) so using [`OptimizationLevel::Full`] is usually quite safe.
|
||||||
|
Beware, however, that if custom functions are registered, they'll also be called. If custom functions are registered to replace built-in operator functions,
|
||||||
|
the custom functions will be called and _may_ cause side-effects.
|
||||||
|
|
||||||
|
### Subtle semantic changes
|
||||||
|
|
||||||
Some optimizations can be quite aggressive and can alter subtle semantics of the script. For example:
|
Some optimizations can be quite aggressive and can alter subtle semantics of the script. For example:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@ -1214,12 +1248,14 @@ print("end!");
|
|||||||
In the script above, if `my_decision` holds anything other than a boolean value, the script should have been terminated due to a type error.
|
In the script above, if `my_decision` holds anything other than a boolean value, the script should have been terminated due to a type error.
|
||||||
However, after optimization, the entire `if` statement is removed, thus the script silently runs to completion without errors.
|
However, after optimization, the entire `if` statement is removed, thus the script silently runs to completion without errors.
|
||||||
|
|
||||||
|
### Turning off optimizations
|
||||||
|
|
||||||
It is usually a bad idea to depend on a script failing or such kind of subtleties, but if it turns out to be necessary (why? I would never guess),
|
It is usually a bad idea to depend on a script failing or such kind of subtleties, but if it turns out to be necessary (why? I would never guess),
|
||||||
there is a setting in `Engine` to turn off optimizations.
|
there is a setting in `Engine` to turn off optimizations.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let engine = rhai::Engine::new();
|
let engine = rhai::Engine::new();
|
||||||
engine.set_optimization(false); // turn off the optimizer
|
engine.set_optimization_level(rhai::OptimizationLevel::None); // turn off the optimizer
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@ -1237,3 +1273,5 @@ engine.set_optimization(false); // turn off the optimizer
|
|||||||
[`Engine`]: #hello-world
|
[`Engine`]: #hello-world
|
||||||
[`Scope`]: #initializing-and-maintaining-state
|
[`Scope`]: #initializing-and-maintaining-state
|
||||||
[`Dynamic`]: #values-and-types
|
[`Dynamic`]: #values-and-types
|
||||||
|
|
||||||
|
[`OptimizationLevel::Full`]: #optimization-levels
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
|
use rhai::OptimizationLevel;
|
||||||
use rhai::{Engine, EvalAltResult, Scope, AST};
|
use rhai::{Engine, EvalAltResult, Scope, AST};
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
io::{stdin, stdout, Write},
|
io::{stdin, stdout, Write},
|
||||||
iter,
|
iter,
|
||||||
@ -43,6 +46,10 @@ fn print_error(input: &str, err: EvalAltResult) {
|
|||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
|
engine.set_optimization_level(OptimizationLevel::Full);
|
||||||
|
|
||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
|
|
||||||
let mut input = String::new();
|
let mut input = String::new();
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
|
use rhai::OptimizationLevel;
|
||||||
use rhai::{Engine, EvalAltResult};
|
use rhai::{Engine, EvalAltResult};
|
||||||
use std::{env, fs::File, io::Read, iter, process::exit};
|
use std::{env, fs::File, io::Read, iter, process::exit};
|
||||||
|
|
||||||
@ -49,6 +51,9 @@ fn main() {
|
|||||||
for filename in env::args().skip(1) {
|
for filename in env::args().skip(1) {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
|
engine.set_optimization_level(OptimizationLevel::Full);
|
||||||
|
|
||||||
let mut f = match File::open(&filename) {
|
let mut f = match File::open(&filename) {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("Error reading script file: {}\n{}", filename, err);
|
eprintln!("Error reading script file: {}\n{}", filename, err);
|
||||||
@ -67,7 +72,7 @@ fn main() {
|
|||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(err) = engine.consume(&contents, false) {
|
if let Err(err) = engine.consume(false, &contents) {
|
||||||
eprintln!("{}", padding("=", filename.len()));
|
eprintln!("{}", padding("=", filename.len()));
|
||||||
eprintln!("{}", filename);
|
eprintln!("{}", filename);
|
||||||
eprintln!("{}", padding("=", filename.len()));
|
eprintln!("{}", padding("=", filename.len()));
|
||||||
|
84
src/api.rs
84
src/api.rs
@ -8,6 +8,10 @@ use crate::fn_register::RegisterFn;
|
|||||||
use crate::parser::{lex, parse, FnDef, 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;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
|
use crate::optimize::optimize_ast;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
any::{type_name, TypeId},
|
any::{type_name, TypeId},
|
||||||
fs::File,
|
fs::File,
|
||||||
@ -108,7 +112,7 @@ impl<'e> Engine<'e> {
|
|||||||
/// The scope is useful for passing constants into the script for optimization.
|
/// The scope is useful for passing constants into the script for optimization.
|
||||||
pub fn compile_with_scope(&self, scope: &Scope, input: &str) -> Result<AST, ParseError> {
|
pub fn compile_with_scope(&self, scope: &Scope, input: &str) -> Result<AST, ParseError> {
|
||||||
let tokens_stream = lex(input);
|
let tokens_stream = lex(input);
|
||||||
parse(&mut tokens_stream.peekable(), scope, self.optimize)
|
parse(&mut tokens_stream.peekable(), self, scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_file(path: PathBuf) -> Result<String, EvalAltResult> {
|
fn read_file(path: PathBuf) -> Result<String, EvalAltResult> {
|
||||||
@ -145,6 +149,15 @@ impl<'e> Engine<'e> {
|
|||||||
Self::read_file(path).and_then(|contents| self.eval::<T>(&contents))
|
Self::read_file(path).and_then(|contents| self.eval::<T>(&contents))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Evaluate a file with own scope.
|
||||||
|
pub fn eval_file_with_scope<T: Any + Clone>(
|
||||||
|
&mut self,
|
||||||
|
scope: &mut Scope,
|
||||||
|
path: PathBuf,
|
||||||
|
) -> Result<T, EvalAltResult> {
|
||||||
|
Self::read_file(path).and_then(|contents| self.eval_with_scope::<T>(scope, &contents))
|
||||||
|
}
|
||||||
|
|
||||||
/// 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();
|
||||||
@ -180,10 +193,6 @@ impl<'e> Engine<'e> {
|
|||||||
) -> Result<Dynamic, EvalAltResult> {
|
) -> Result<Dynamic, EvalAltResult> {
|
||||||
engine.clear_functions();
|
engine.clear_functions();
|
||||||
|
|
||||||
#[cfg(feature = "no_function")]
|
|
||||||
let AST(statements) = ast;
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
let statements = {
|
let statements = {
|
||||||
let AST(statements, functions) = ast;
|
let AST(statements, functions) = ast;
|
||||||
engine.load_script_functions(functions);
|
engine.load_script_functions(functions);
|
||||||
@ -227,10 +236,25 @@ impl<'e> Engine<'e> {
|
|||||||
/// and not cleared from run to run.
|
/// and not cleared from run to run.
|
||||||
pub fn consume_file(
|
pub fn consume_file(
|
||||||
&mut self,
|
&mut self,
|
||||||
path: PathBuf,
|
|
||||||
retain_functions: bool,
|
retain_functions: bool,
|
||||||
|
path: PathBuf,
|
||||||
) -> Result<(), EvalAltResult> {
|
) -> Result<(), EvalAltResult> {
|
||||||
Self::read_file(path).and_then(|contents| self.consume(&contents, retain_functions))
|
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).
|
||||||
|
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||||
|
///
|
||||||
|
/// 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_with_scope(
|
||||||
|
&mut self,
|
||||||
|
scope: &mut Scope,
|
||||||
|
retain_functions: bool,
|
||||||
|
path: PathBuf,
|
||||||
|
) -> Result<(), EvalAltResult> {
|
||||||
|
Self::read_file(path)
|
||||||
|
.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).
|
||||||
@ -238,11 +262,11 @@ impl<'e> Engine<'e> {
|
|||||||
///
|
///
|
||||||
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
|
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
|
||||||
/// and not cleared from run to run.
|
/// and not cleared from run to run.
|
||||||
pub fn consume(&mut self, input: &str, retain_functions: bool) -> Result<(), EvalAltResult> {
|
pub fn consume(&mut self, retain_functions: bool, input: &str) -> Result<(), EvalAltResult> {
|
||||||
self.consume_with_scope(&mut Scope::new(), retain_functions, input)
|
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 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.
|
||||||
///
|
///
|
||||||
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
|
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
|
||||||
@ -255,7 +279,7 @@ impl<'e> Engine<'e> {
|
|||||||
) -> Result<(), EvalAltResult> {
|
) -> Result<(), EvalAltResult> {
|
||||||
let tokens_stream = lex(input);
|
let tokens_stream = lex(input);
|
||||||
|
|
||||||
let ast = parse(&mut tokens_stream.peekable(), scope, self.optimize)
|
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, retain_functions, &ast)
|
||||||
@ -266,6 +290,15 @@ impl<'e> Engine<'e> {
|
|||||||
///
|
///
|
||||||
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
|
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
|
||||||
/// and not cleared from run to run.
|
/// 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).
|
||||||
|
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||||
|
///
|
||||||
|
/// 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_ast_with_scope(
|
pub fn consume_ast_with_scope(
|
||||||
&mut self,
|
&mut self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
@ -276,10 +309,6 @@ impl<'e> Engine<'e> {
|
|||||||
self.clear_functions();
|
self.clear_functions();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "no_function")]
|
|
||||||
let AST(statements) = ast;
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
let statements = {
|
let statements = {
|
||||||
let AST(ref statements, ref functions) = ast;
|
let AST(ref statements, ref functions) = ast;
|
||||||
self.load_script_functions(functions);
|
self.load_script_functions(functions);
|
||||||
@ -327,7 +356,7 @@ impl<'e> Engine<'e> {
|
|||||||
///
|
///
|
||||||
/// let mut engine = Engine::new();
|
/// let mut engine = Engine::new();
|
||||||
///
|
///
|
||||||
/// engine.consume("fn add(x, y) { x.len() + y }", true)?;
|
/// engine.consume(true, "fn add(x, y) { x.len() + y }")?;
|
||||||
///
|
///
|
||||||
/// let result: i64 = engine.call_fn("add", (String::from("abc"), 123_i64))?;
|
/// let result: i64 = engine.call_fn("add", (String::from("abc"), 123_i64))?;
|
||||||
///
|
///
|
||||||
@ -365,6 +394,27 @@ impl<'e> Engine<'e> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Optimize the AST with constants defined in an external Scope.
|
||||||
|
/// An optimized copy of the AST is returned while the original AST is untouched.
|
||||||
|
///
|
||||||
|
/// Although optimization is performed by default during compilation, sometimes it is necessary to
|
||||||
|
/// _re_-optimize an AST. For example, when working with constants that are passed in via an
|
||||||
|
/// external scope, it will be more efficient to optimize the AST once again to take advantage
|
||||||
|
/// of the new constants.
|
||||||
|
///
|
||||||
|
/// With this method, it is no longer necessary to recompile a large script. The script AST can be
|
||||||
|
/// compiled just once. Before evaluation, constants are passed into the `Engine` via an external scope
|
||||||
|
/// (i.e. with `scope.push_constant(...)`). Then, the AST is cloned and the copy re-optimized before running.
|
||||||
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
|
pub fn optimize_ast(&self, scope: &Scope, ast: &AST) -> AST {
|
||||||
|
optimize_ast(
|
||||||
|
self,
|
||||||
|
scope,
|
||||||
|
ast.0.clone(),
|
||||||
|
ast.1.iter().map(|f| (**f).clone()).collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Override default action of `print` (print to stdout using `println!`)
|
/// Override default action of `print` (print to stdout using `println!`)
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
@ -379,7 +429,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);", false)?;
|
/// engine.consume(false, "print(40 + 2);")?;
|
||||||
/// }
|
/// }
|
||||||
/// assert_eq!(result, "42");
|
/// assert_eq!(result, "42");
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
@ -403,7 +453,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");"#, false)?;
|
/// engine.consume(false, r#"debug("hello");"#)?;
|
||||||
/// }
|
/// }
|
||||||
/// assert_eq!(result, "\"hello\"");
|
/// assert_eq!(result, "\"hello\"");
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
|
@ -5,6 +5,9 @@ use crate::parser::{Expr, FnDef, Position, ReturnType, Stmt};
|
|||||||
use crate::result::EvalAltResult;
|
use crate::result::EvalAltResult;
|
||||||
use crate::scope::{Scope, VariableType};
|
use crate::scope::{Scope, VariableType};
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
|
use crate::optimize::OptimizationLevel;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
use crate::INT;
|
use crate::INT;
|
||||||
|
|
||||||
@ -63,17 +66,20 @@ pub struct FnSpec<'a> {
|
|||||||
/// ```
|
/// ```
|
||||||
pub struct Engine<'e> {
|
pub struct Engine<'e> {
|
||||||
/// Optimize the AST after compilation
|
/// Optimize the AST after compilation
|
||||||
pub(crate) optimize: bool,
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
|
pub(crate) optimization_level: OptimizationLevel,
|
||||||
/// A hashmap containing all compiled functions known to the engine
|
/// A hashmap containing all compiled functions known to the engine
|
||||||
pub(crate) ext_functions: HashMap<FnSpec<'e>, Box<FnAny>>,
|
pub(crate) ext_functions: HashMap<FnSpec<'e>, Box<FnAny>>,
|
||||||
/// A hashmap containing all script-defined functions
|
/// A hashmap containing all script-defined functions
|
||||||
pub(crate) script_functions: Vec<Arc<FnDef>>,
|
pub(crate) script_functions: Vec<Arc<FnDef>>,
|
||||||
/// A hashmap containing all iterators known to the engine
|
/// A hashmap containing all iterators known to the engine
|
||||||
pub(crate) type_iterators: HashMap<TypeId, Box<IteratorFn>>,
|
pub(crate) type_iterators: HashMap<TypeId, Box<IteratorFn>>,
|
||||||
|
/// A hashmap mapping type names to pretty-print names
|
||||||
pub(crate) type_names: HashMap<String, String>,
|
pub(crate) type_names: HashMap<String, String>,
|
||||||
|
|
||||||
// Closures for implementing the print/debug commands
|
/// Closure for implementing the print commands
|
||||||
pub(crate) on_print: Box<dyn FnMut(&str) + 'e>,
|
pub(crate) on_print: Box<dyn FnMut(&str) + 'e>,
|
||||||
|
/// Closure for implementing the debug commands
|
||||||
pub(crate) on_debug: Box<dyn FnMut(&str) + 'e>,
|
pub(crate) on_debug: Box<dyn FnMut(&str) + 'e>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,7 +99,8 @@ impl Engine<'_> {
|
|||||||
|
|
||||||
// Create the new scripting Engine
|
// Create the new scripting Engine
|
||||||
let mut engine = Engine {
|
let mut engine = Engine {
|
||||||
optimize: true,
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
|
optimization_level: OptimizationLevel::Full,
|
||||||
ext_functions: HashMap::new(),
|
ext_functions: HashMap::new(),
|
||||||
script_functions: Vec::new(),
|
script_functions: Vec::new(),
|
||||||
type_iterators: HashMap::new(),
|
type_iterators: HashMap::new(),
|
||||||
@ -110,9 +117,32 @@ impl Engine<'_> {
|
|||||||
engine
|
engine
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Control whether the `Engine` will optimize an AST after compilation
|
/// Control whether and how the `Engine` will optimize an AST after compilation
|
||||||
pub fn set_optimization(&mut self, optimize: bool) {
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
self.optimize = optimize
|
pub fn set_optimization_level(&mut self, optimization_level: OptimizationLevel) {
|
||||||
|
self.optimization_level = optimization_level
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call a registered function
|
||||||
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
|
pub(crate) fn call_ext_fn_raw(
|
||||||
|
&self,
|
||||||
|
fn_name: &str,
|
||||||
|
args: FnCallArgs,
|
||||||
|
pos: Position,
|
||||||
|
) -> Result<Option<Dynamic>, EvalAltResult> {
|
||||||
|
let spec = FnSpec {
|
||||||
|
name: fn_name.into(),
|
||||||
|
args: Some(args.iter().map(|a| Any::type_id(&**a)).collect()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Search built-in's and external functions
|
||||||
|
if let Some(func) = self.ext_functions.get(&spec) {
|
||||||
|
// Run external function
|
||||||
|
Ok(Some(func(args, pos)?))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Universal method for calling functions, that are either
|
/// Universal method for calling functions, that are either
|
||||||
@ -165,13 +195,13 @@ impl Engine<'_> {
|
|||||||
args: Some(args.iter().map(|a| Any::type_id(&**a)).collect()),
|
args: Some(args.iter().map(|a| Any::type_id(&**a)).collect()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Then search built-in's and external functions
|
// Search built-in's and external functions
|
||||||
if let Some(func) = self.ext_functions.get(&spec) {
|
if let Some(func) = self.ext_functions.get(&spec) {
|
||||||
// Run external function
|
// Run external function
|
||||||
let result = func(args, pos)?;
|
let result = func(args, pos)?;
|
||||||
|
|
||||||
// See if the function match print/debug (which requires special processing)
|
// See if the function match print/debug (which requires special processing)
|
||||||
let callback = match spec.name.as_ref() {
|
let callback = match fn_name {
|
||||||
KEYWORD_PRINT => self.on_print.as_mut(),
|
KEYWORD_PRINT => self.on_print.as_mut(),
|
||||||
KEYWORD_DEBUG => self.on_debug.as_mut(),
|
KEYWORD_DEBUG => self.on_debug.as_mut(),
|
||||||
_ => return Ok(result),
|
_ => return Ok(result),
|
||||||
@ -185,7 +215,7 @@ impl Engine<'_> {
|
|||||||
return Ok(callback(val).into_dynamic());
|
return Ok(callback(val).into_dynamic());
|
||||||
}
|
}
|
||||||
|
|
||||||
if spec.name == KEYWORD_TYPE_OF && args.len() == 1 {
|
if fn_name == KEYWORD_TYPE_OF && args.len() == 1 {
|
||||||
// Handle `type_of` function
|
// Handle `type_of` function
|
||||||
return Ok(self
|
return Ok(self
|
||||||
.map_type_name(args[0].type_name())
|
.map_type_name(args[0].type_name())
|
||||||
@ -193,23 +223,23 @@ impl Engine<'_> {
|
|||||||
.into_dynamic());
|
.into_dynamic());
|
||||||
}
|
}
|
||||||
|
|
||||||
if spec.name.starts_with(FUNC_GETTER) {
|
if fn_name.starts_with(FUNC_GETTER) {
|
||||||
// Getter function not found
|
// Getter function not found
|
||||||
return Err(EvalAltResult::ErrorDotExpr(
|
return Err(EvalAltResult::ErrorDotExpr(
|
||||||
format!(
|
format!(
|
||||||
"- property '{}' unknown or write-only",
|
"- property '{}' unknown or write-only",
|
||||||
&spec.name[FUNC_GETTER.len()..]
|
&fn_name[FUNC_GETTER.len()..]
|
||||||
),
|
),
|
||||||
pos,
|
pos,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if spec.name.starts_with(FUNC_SETTER) {
|
if fn_name.starts_with(FUNC_SETTER) {
|
||||||
// Setter function not found
|
// Setter function not found
|
||||||
return Err(EvalAltResult::ErrorDotExpr(
|
return Err(EvalAltResult::ErrorDotExpr(
|
||||||
format!(
|
format!(
|
||||||
"- property '{}' unknown or read-only",
|
"- property '{}' unknown or read-only",
|
||||||
&spec.name[FUNC_SETTER.len()..]
|
&fn_name[FUNC_SETTER.len()..]
|
||||||
),
|
),
|
||||||
pos,
|
pos,
|
||||||
));
|
));
|
||||||
@ -228,7 +258,7 @@ impl Engine<'_> {
|
|||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
Err(EvalAltResult::ErrorFunctionNotFound(
|
Err(EvalAltResult::ErrorFunctionNotFound(
|
||||||
format!("{} ({})", spec.name, types_list.join(", ")),
|
format!("{} ({})", fn_name, types_list.join(", ")),
|
||||||
pos,
|
pos,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@ -249,7 +279,7 @@ impl Engine<'_> {
|
|||||||
.collect::<Result<Vec<_>, _>>()?;
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
let args = once(this_ptr)
|
let args = once(this_ptr)
|
||||||
.chain(values.iter_mut().map(|b| b.as_mut()))
|
.chain(values.iter_mut().map(Dynamic::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)
|
||||||
@ -567,8 +597,7 @@ impl Engine<'_> {
|
|||||||
Ok(Self::str_replace_char(s, idx as usize, ch).into_dynamic())
|
Ok(Self::str_replace_char(s, idx as usize, ch).into_dynamic())
|
||||||
}
|
}
|
||||||
|
|
||||||
// All other variable types should be an error
|
IndexSourceType::Expression => panic!("expression cannot be indexed for update"),
|
||||||
_ => panic!("array or string source type expected for indexing"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -809,9 +838,6 @@ impl Engine<'_> {
|
|||||||
.eval_index_expr(scope, lhs, idx_expr, *idx_pos)
|
.eval_index_expr(scope, lhs, idx_expr, *idx_pos)
|
||||||
.map(|(_, _, _, x)| x),
|
.map(|(_, _, _, x)| x),
|
||||||
|
|
||||||
#[cfg(feature = "no_index")]
|
|
||||||
Expr::Index(_, _, _) => panic!("encountered an index expression during no_index!"),
|
|
||||||
|
|
||||||
// Statement block
|
// Statement block
|
||||||
Expr::Stmt(stmt, _) => self.eval_stmt(scope, stmt),
|
Expr::Stmt(stmt, _) => self.eval_stmt(scope, stmt),
|
||||||
|
|
||||||
@ -870,7 +896,7 @@ impl Engine<'_> {
|
|||||||
|
|
||||||
// Error assignment to constant
|
// Error assignment to constant
|
||||||
expr if expr.is_constant() => Err(EvalAltResult::ErrorAssignmentToConstant(
|
expr if expr.is_constant() => Err(EvalAltResult::ErrorAssignmentToConstant(
|
||||||
expr.get_value_str(),
|
expr.get_constant_str(),
|
||||||
lhs.position(),
|
lhs.position(),
|
||||||
)),
|
)),
|
||||||
|
|
||||||
@ -891,8 +917,6 @@ impl Engine<'_> {
|
|||||||
|
|
||||||
Ok(Box::new(arr))
|
Ok(Box::new(arr))
|
||||||
}
|
}
|
||||||
#[cfg(feature = "no_index")]
|
|
||||||
Expr::Array(_, _) => panic!("encountered an array during no_index!"),
|
|
||||||
|
|
||||||
// Dump AST
|
// Dump AST
|
||||||
Expr::FunctionCall(fn_name, args_expr_list, _, pos) if fn_name == KEYWORD_DUMP_AST => {
|
Expr::FunctionCall(fn_name, args_expr_list, _, pos) if fn_name == KEYWORD_DUMP_AST => {
|
||||||
|
@ -87,3 +87,6 @@ pub use engine::Array;
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
pub use parser::FLOAT;
|
pub use parser::FLOAT;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
|
pub use optimize::OptimizationLevel;
|
||||||
|
153
src/optimize.rs
153
src/optimize.rs
@ -1,21 +1,41 @@
|
|||||||
#![cfg(not(feature = "no_optimize"))]
|
#![cfg(not(feature = "no_optimize"))]
|
||||||
|
|
||||||
use crate::engine::KEYWORD_DUMP_AST;
|
use crate::any::Dynamic;
|
||||||
use crate::parser::{Expr, Stmt};
|
use crate::engine::{Engine, FnCallArgs, KEYWORD_DEBUG, KEYWORD_DUMP_AST, KEYWORD_PRINT};
|
||||||
|
use crate::parser::{map_dynamic_to_expr, Expr, FnDef, Stmt, AST};
|
||||||
use crate::scope::{Scope, ScopeEntry, VariableType};
|
use crate::scope::{Scope, ScopeEntry, VariableType};
|
||||||
|
|
||||||
struct State {
|
use std::sync::Arc;
|
||||||
changed: bool,
|
|
||||||
constants: Vec<(String, Expr)>,
|
/// Level of optimization performed
|
||||||
|
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
|
||||||
|
pub enum OptimizationLevel {
|
||||||
|
/// No optimization performed
|
||||||
|
None,
|
||||||
|
/// Only perform simple optimizations without evaluating functions
|
||||||
|
Simple,
|
||||||
|
/// Full optimizations performed, including evaluating functions.
|
||||||
|
/// Take care that this may cause side effects.
|
||||||
|
Full,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
struct State<'a> {
|
||||||
|
changed: bool,
|
||||||
|
constants: Vec<(String, Expr)>,
|
||||||
|
engine: Option<&'a Engine<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State<'_> {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
State {
|
State {
|
||||||
changed: false,
|
changed: false,
|
||||||
constants: vec![],
|
constants: vec![],
|
||||||
|
engine: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.changed = false;
|
||||||
|
}
|
||||||
pub fn set_dirty(&mut self) {
|
pub fn set_dirty(&mut self) {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
}
|
}
|
||||||
@ -42,7 +62,7 @@ impl State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
|
fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -> Stmt {
|
||||||
match stmt {
|
match stmt {
|
||||||
Stmt::IfElse(expr, stmt1, None) if stmt1.is_noop() => {
|
Stmt::IfElse(expr, stmt1, None) if stmt1.is_noop() => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
@ -114,7 +134,7 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
|
|||||||
|
|
||||||
Stmt::Block(statements, pos) => {
|
Stmt::Block(statements, pos) => {
|
||||||
let orig_len = statements.len();
|
let orig_len = statements.len();
|
||||||
let orig_constants = state.constants.len();
|
let orig_constants_len = state.constants.len();
|
||||||
|
|
||||||
let mut result: Vec<_> = statements
|
let mut result: Vec<_> = statements
|
||||||
.into_iter() // For each statement
|
.into_iter() // For each statement
|
||||||
@ -175,7 +195,7 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
|
|||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
state.restore_constants(orig_constants);
|
state.restore_constants(orig_constants_len);
|
||||||
|
|
||||||
match result[..] {
|
match result[..] {
|
||||||
// No statements in block - change to No-op
|
// No statements in block - change to No-op
|
||||||
@ -202,7 +222,7 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
||||||
match expr {
|
match expr {
|
||||||
Expr::Stmt(stmt, pos) => match optimize_stmt(*stmt, state, true) {
|
Expr::Stmt(stmt, pos) => match optimize_stmt(*stmt, state, true) {
|
||||||
Stmt::Noop(_) => {
|
Stmt::Noop(_) => {
|
||||||
@ -261,8 +281,6 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
|||||||
pos,
|
pos,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
#[cfg(feature = "no_index")]
|
|
||||||
Expr::Index(_, _, _) => panic!("encountered an index expression during no_index!"),
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Array(items, pos) => {
|
Expr::Array(items, pos) => {
|
||||||
@ -280,9 +298,6 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
|||||||
Expr::Array(items, pos)
|
Expr::Array(items, pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "no_index")]
|
|
||||||
Expr::Array(_, _) => panic!("encountered an array during no_index!"),
|
|
||||||
|
|
||||||
Expr::And(lhs, rhs) => match (*lhs, *rhs) {
|
Expr::And(lhs, rhs) => match (*lhs, *rhs) {
|
||||||
(Expr::True(_), rhs) => {
|
(Expr::True(_), rhs) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
@ -320,9 +335,33 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Do not optimize anything within `dump_ast`
|
||||||
Expr::FunctionCall(id, args, def_value, pos) if id == KEYWORD_DUMP_AST => {
|
Expr::FunctionCall(id, args, def_value, pos) if id == KEYWORD_DUMP_AST => {
|
||||||
Expr::FunctionCall(id, args, def_value, pos)
|
Expr::FunctionCall(id, args, def_value, pos)
|
||||||
}
|
}
|
||||||
|
// Actually call function to optimize it
|
||||||
|
Expr::FunctionCall(id, args, def_value, pos)
|
||||||
|
if id != KEYWORD_DEBUG // not debug
|
||||||
|
&& id != KEYWORD_PRINT // not print
|
||||||
|
&& state.engine.map(|eng| eng.optimization_level == OptimizationLevel::Full).unwrap_or(false) // full optimizations
|
||||||
|
&& args.iter().all(|expr| expr.is_constant()) // all arguments are constants
|
||||||
|
=>
|
||||||
|
{
|
||||||
|
let engine = state.engine.expect("engine should be Some");
|
||||||
|
let mut arg_values: Vec<_> = args.iter().map(Expr::get_constant_value).collect();
|
||||||
|
let call_args: FnCallArgs = arg_values.iter_mut().map(Dynamic::as_mut).collect();
|
||||||
|
if let Ok(r) = engine.call_ext_fn_raw(&id, call_args, pos) {
|
||||||
|
r.and_then(|result| map_dynamic_to_expr(result, pos).0)
|
||||||
|
.map(|expr| {
|
||||||
|
state.set_dirty();
|
||||||
|
expr
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| Expr::FunctionCall(id, args, def_value, pos))
|
||||||
|
} else {
|
||||||
|
Expr::FunctionCall(id, args, def_value, pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Optimize the function call arguments
|
||||||
Expr::FunctionCall(id, args, def_value, pos) => {
|
Expr::FunctionCall(id, args, def_value, pos) => {
|
||||||
let orig_len = args.len();
|
let orig_len = args.len();
|
||||||
|
|
||||||
@ -341,7 +380,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
|||||||
// Replace constant with value
|
// Replace constant with value
|
||||||
state
|
state
|
||||||
.find_constant(name)
|
.find_constant(name)
|
||||||
.expect("can't find constant in scope!")
|
.expect("should find constant in scope!")
|
||||||
.clone()
|
.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -349,26 +388,47 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn optimize(statements: Vec<Stmt>, scope: &Scope) -> Vec<Stmt> {
|
pub(crate) fn optimize<'a>(
|
||||||
|
statements: Vec<Stmt>,
|
||||||
|
engine: Option<&Engine<'a>>,
|
||||||
|
scope: &Scope,
|
||||||
|
) -> Vec<Stmt> {
|
||||||
|
// If optimization level is None then skip optimizing
|
||||||
|
if engine
|
||||||
|
.map(|eng| eng.optimization_level == OptimizationLevel::None)
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
return statements;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up the state
|
||||||
|
let mut state = State::new();
|
||||||
|
state.engine = engine;
|
||||||
|
|
||||||
|
scope
|
||||||
|
.iter()
|
||||||
|
.filter(|ScopeEntry { var_type, expr, .. }| {
|
||||||
|
// Get all the constants with definite constant expressions
|
||||||
|
*var_type == VariableType::Constant
|
||||||
|
&& expr.as_ref().map(Expr::is_constant).unwrap_or(false)
|
||||||
|
})
|
||||||
|
.for_each(|ScopeEntry { name, expr, .. }| {
|
||||||
|
state.push_constant(
|
||||||
|
name.as_ref(),
|
||||||
|
expr.as_ref().expect("should be Some(expr)").clone(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let orig_constants_len = state.constants.len();
|
||||||
|
|
||||||
|
// Optimization loop
|
||||||
let mut result = statements;
|
let mut result = statements;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let mut state = State::new();
|
state.reset();
|
||||||
let num_statements = result.len();
|
state.restore_constants(orig_constants_len);
|
||||||
|
|
||||||
scope
|
let num_statements = result.len();
|
||||||
.iter()
|
|
||||||
.filter(|ScopeEntry { var_type, expr, .. }| {
|
|
||||||
// Get all the constants with definite constant expressions
|
|
||||||
*var_type == VariableType::Constant
|
|
||||||
&& expr.as_ref().map(|e| e.is_constant()).unwrap_or(false)
|
|
||||||
})
|
|
||||||
.for_each(|ScopeEntry { name, expr, .. }| {
|
|
||||||
state.push_constant(
|
|
||||||
name.as_ref(),
|
|
||||||
expr.as_ref().expect("should be Some(expr)").clone(),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
result = result
|
result = result
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -405,3 +465,32 @@ pub(crate) fn optimize(statements: Vec<Stmt>, scope: &Scope) -> Vec<Stmt> {
|
|||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn optimize_ast(
|
||||||
|
engine: &Engine,
|
||||||
|
scope: &Scope,
|
||||||
|
statements: Vec<Stmt>,
|
||||||
|
functions: Vec<FnDef>,
|
||||||
|
) -> AST {
|
||||||
|
AST(
|
||||||
|
match engine.optimization_level {
|
||||||
|
OptimizationLevel::None => statements,
|
||||||
|
OptimizationLevel::Simple => optimize(statements, None, &scope),
|
||||||
|
OptimizationLevel::Full => optimize(statements, Some(engine), &scope),
|
||||||
|
},
|
||||||
|
functions
|
||||||
|
.into_iter()
|
||||||
|
.map(|mut fn_def| {
|
||||||
|
match engine.optimization_level {
|
||||||
|
OptimizationLevel::None => (),
|
||||||
|
OptimizationLevel::Simple | OptimizationLevel::Full => {
|
||||||
|
let pos = fn_def.body.position();
|
||||||
|
let mut body = optimize(vec![fn_def.body], None, &Scope::new());
|
||||||
|
fn_def.body = body.pop().unwrap_or_else(|| Stmt::Noop(pos));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Arc::new(fn_def)
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
251
src/parser.rs
251
src/parser.rs
@ -1,11 +1,12 @@
|
|||||||
//! Main module defining the lexer and parser.
|
//! Main module defining the lexer and parser.
|
||||||
|
|
||||||
use crate::any::Dynamic;
|
use crate::any::{Any, AnyExt, Dynamic};
|
||||||
|
use crate::engine::Engine;
|
||||||
use crate::error::{LexError, ParseError, ParseErrorType};
|
use crate::error::{LexError, ParseError, ParseErrorType};
|
||||||
use crate::scope::{Scope, VariableType};
|
use crate::scope::{Scope, VariableType};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_optimize"))]
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
use crate::optimize::optimize;
|
use crate::optimize::optimize_ast;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow, char, cmp::Ordering, fmt, iter::Peekable, str::Chars, str::FromStr, sync::Arc,
|
borrow::Cow, char, cmp::Ordering, fmt, iter::Peekable, str::Chars, str::FromStr, sync::Arc,
|
||||||
@ -147,49 +148,9 @@ impl fmt::Debug for Position {
|
|||||||
|
|
||||||
/// Compiled AST (abstract syntax tree) of a Rhai script.
|
/// Compiled AST (abstract syntax tree) of a Rhai script.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct AST(
|
pub struct AST(pub(crate) Vec<Stmt>, pub(crate) Vec<Arc<FnDef>>);
|
||||||
pub(crate) Vec<Stmt>,
|
|
||||||
#[cfg(not(feature = "no_function"))] pub(crate) Vec<Arc<FnDef>>,
|
|
||||||
);
|
|
||||||
|
|
||||||
impl AST {
|
#[derive(Debug, Clone)]
|
||||||
/// Optimize the AST with constants defined in an external Scope.
|
|
||||||
///
|
|
||||||
/// Although optimization is performed by default during compilation, sometimes it is necessary to
|
|
||||||
/// _re_-optimize an AST. For example, when working with constants that are passed in via an
|
|
||||||
/// external scope, it will be more efficient to optimize the AST once again to take advantage
|
|
||||||
/// of the new constants.
|
|
||||||
///
|
|
||||||
/// With this method, it is no longer necessary to regenerate a large script with hard-coded
|
|
||||||
/// constant values. The script AST can be compiled just once. During actual evaluation,
|
|
||||||
/// constants are passed into the Engine via an external scope (i.e. with `scope.push_constant(...)`).
|
|
||||||
/// Then, the AST is cloned and the copy re-optimized before running.
|
|
||||||
#[cfg(not(feature = "no_optimize"))]
|
|
||||||
pub fn optimize(self, scope: &Scope) -> Self {
|
|
||||||
AST(
|
|
||||||
crate::optimize::optimize(self.0, scope),
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
self.1
|
|
||||||
.into_iter()
|
|
||||||
.map(|fn_def| {
|
|
||||||
let pos = fn_def.body.position();
|
|
||||||
let body = optimize(vec![fn_def.body.clone()], scope)
|
|
||||||
.into_iter()
|
|
||||||
.next()
|
|
||||||
.unwrap_or_else(|| Stmt::Noop(pos));
|
|
||||||
Arc::new(FnDef {
|
|
||||||
name: fn_def.name.clone(),
|
|
||||||
params: fn_def.params.clone(),
|
|
||||||
body,
|
|
||||||
pos: fn_def.pos,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)] // Do not derive Clone because it is expensive
|
|
||||||
pub struct FnDef {
|
pub struct FnDef {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub params: Vec<String>,
|
pub params: Vec<String>,
|
||||||
@ -267,7 +228,9 @@ pub enum Expr {
|
|||||||
FunctionCall(String, Vec<Expr>, Option<Dynamic>, Position),
|
FunctionCall(String, Vec<Expr>, Option<Dynamic>, Position),
|
||||||
Assignment(Box<Expr>, Box<Expr>, Position),
|
Assignment(Box<Expr>, Box<Expr>, Position),
|
||||||
Dot(Box<Expr>, Box<Expr>, Position),
|
Dot(Box<Expr>, Box<Expr>, Position),
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
Index(Box<Expr>, Box<Expr>, Position),
|
Index(Box<Expr>, Box<Expr>, Position),
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
Array(Vec<Expr>, Position),
|
Array(Vec<Expr>, Position),
|
||||||
And(Box<Expr>, Box<Expr>),
|
And(Box<Expr>, Box<Expr>),
|
||||||
Or(Box<Expr>, Box<Expr>),
|
Or(Box<Expr>, Box<Expr>),
|
||||||
@ -277,7 +240,30 @@ pub enum Expr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Expr {
|
impl Expr {
|
||||||
pub fn get_value_str(&self) -> String {
|
pub fn get_constant_value(&self) -> Dynamic {
|
||||||
|
match self {
|
||||||
|
Expr::IntegerConstant(i, _) => i.into_dynamic(),
|
||||||
|
Expr::CharConstant(c, _) => c.into_dynamic(),
|
||||||
|
Expr::StringConstant(s, _) => s.into_dynamic(),
|
||||||
|
Expr::True(_) => true.into_dynamic(),
|
||||||
|
Expr::False(_) => false.into_dynamic(),
|
||||||
|
Expr::Unit(_) => ().into_dynamic(),
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
Expr::Array(items, _) if items.iter().all(Expr::is_constant) => items
|
||||||
|
.iter()
|
||||||
|
.map(Expr::get_constant_value)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.into_dynamic(),
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
Expr::FloatConstant(f, _) => f.into_dynamic(),
|
||||||
|
|
||||||
|
_ => panic!("cannot get value of non-constant expression"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_constant_str(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
Expr::IntegerConstant(i, _) => i.to_string(),
|
Expr::IntegerConstant(i, _) => i.to_string(),
|
||||||
Expr::CharConstant(c, _) => c.to_string(),
|
Expr::CharConstant(c, _) => c.to_string(),
|
||||||
@ -286,10 +272,13 @@ impl Expr {
|
|||||||
Expr::False(_) => "false".to_string(),
|
Expr::False(_) => "false".to_string(),
|
||||||
Expr::Unit(_) => "()".to_string(),
|
Expr::Unit(_) => "()".to_string(),
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
Expr::Array(items, _) if items.iter().all(Expr::is_constant) => "array".to_string(),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Expr::FloatConstant(f, _) => f.to_string(),
|
Expr::FloatConstant(f, _) => f.to_string(),
|
||||||
|
|
||||||
_ => "".to_string(),
|
_ => panic!("cannot get value of non-constant expression"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -302,19 +291,22 @@ impl Expr {
|
|||||||
| Expr::Property(_, pos)
|
| Expr::Property(_, pos)
|
||||||
| Expr::Stmt(_, pos)
|
| Expr::Stmt(_, pos)
|
||||||
| Expr::FunctionCall(_, _, _, pos)
|
| Expr::FunctionCall(_, _, _, pos)
|
||||||
| Expr::Array(_, pos)
|
|
||||||
| Expr::True(pos)
|
| Expr::True(pos)
|
||||||
| Expr::False(pos)
|
| Expr::False(pos)
|
||||||
| Expr::Unit(pos) => *pos,
|
| Expr::Unit(pos) => *pos,
|
||||||
|
|
||||||
Expr::Assignment(e, _, _)
|
Expr::Assignment(e, _, _) | Expr::Dot(e, _, _) | Expr::And(e, _) | Expr::Or(e, _) => {
|
||||||
| Expr::Dot(e, _, _)
|
e.position()
|
||||||
| Expr::Index(e, _, _)
|
}
|
||||||
| Expr::And(e, _)
|
|
||||||
| Expr::Or(e, _) => e.position(),
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Expr::FloatConstant(_, pos) => *pos,
|
Expr::FloatConstant(_, pos) => *pos,
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
Expr::Array(_, pos) => *pos,
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
Expr::Index(e, _, _) => e.position(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -323,8 +315,14 @@ impl Expr {
|
|||||||
/// A pure expression has no side effects.
|
/// A pure expression has no side effects.
|
||||||
pub fn is_pure(&self) -> bool {
|
pub fn is_pure(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Array(expressions, _) => expressions.iter().all(Expr::is_pure),
|
Expr::Array(expressions, _) => expressions.iter().all(Expr::is_pure),
|
||||||
Expr::And(x, y) | Expr::Or(x, y) | Expr::Index(x, y, _) => x.is_pure() && y.is_pure(),
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
Expr::Index(x, y, _) => x.is_pure() && y.is_pure(),
|
||||||
|
|
||||||
|
Expr::And(x, y) | Expr::Or(x, y) => x.is_pure() && y.is_pure(),
|
||||||
|
|
||||||
expr => expr.is_constant() || matches!(expr, Expr::Variable(_, _)),
|
expr => expr.is_constant() || matches!(expr, Expr::Variable(_, _)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -338,11 +336,12 @@ impl Expr {
|
|||||||
| Expr::False(_)
|
| Expr::False(_)
|
||||||
| Expr::Unit(_) => true,
|
| Expr::Unit(_) => true,
|
||||||
|
|
||||||
Expr::Array(expressions, _) => expressions.iter().all(Expr::is_constant),
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Expr::FloatConstant(_, _) => true,
|
Expr::FloatConstant(_, _) => true,
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
Expr::Array(expressions, _) => expressions.iter().all(Expr::is_constant),
|
||||||
|
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -360,7 +359,9 @@ pub enum Token {
|
|||||||
RightBrace,
|
RightBrace,
|
||||||
LeftParen,
|
LeftParen,
|
||||||
RightParen,
|
RightParen,
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
LeftBracket,
|
LeftBracket,
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
RightBracket,
|
RightBracket,
|
||||||
Plus,
|
Plus,
|
||||||
UnaryPlus,
|
UnaryPlus,
|
||||||
@ -392,6 +393,7 @@ pub enum Token {
|
|||||||
Or,
|
Or,
|
||||||
Ampersand,
|
Ampersand,
|
||||||
And,
|
And,
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
Fn,
|
Fn,
|
||||||
Break,
|
Break,
|
||||||
Return,
|
Return,
|
||||||
@ -435,7 +437,9 @@ impl Token {
|
|||||||
RightBrace => "}",
|
RightBrace => "}",
|
||||||
LeftParen => "(",
|
LeftParen => "(",
|
||||||
RightParen => ")",
|
RightParen => ")",
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
LeftBracket => "[",
|
LeftBracket => "[",
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
RightBracket => "]",
|
RightBracket => "]",
|
||||||
Plus => "+",
|
Plus => "+",
|
||||||
UnaryPlus => "+",
|
UnaryPlus => "+",
|
||||||
@ -467,6 +471,7 @@ impl Token {
|
|||||||
Or => "||",
|
Or => "||",
|
||||||
Ampersand => "&",
|
Ampersand => "&",
|
||||||
And => "&&",
|
And => "&&",
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
Fn => "fn",
|
Fn => "fn",
|
||||||
Break => "break",
|
Break => "break",
|
||||||
Return => "return",
|
Return => "return",
|
||||||
@ -506,8 +511,6 @@ impl Token {
|
|||||||
// RightBrace | {expr} - expr not unary & is closing
|
// RightBrace | {expr} - expr not unary & is closing
|
||||||
LeftParen | // {-expr} - is unary
|
LeftParen | // {-expr} - is unary
|
||||||
// RightParen | (expr) - expr not unary & is closing
|
// RightParen | (expr) - expr not unary & is closing
|
||||||
LeftBracket | // [-expr] - is unary
|
|
||||||
// RightBracket | [expr] - expr not unary & is closing
|
|
||||||
Plus |
|
Plus |
|
||||||
UnaryPlus |
|
UnaryPlus |
|
||||||
Minus |
|
Minus |
|
||||||
@ -551,6 +554,10 @@ impl Token {
|
|||||||
In |
|
In |
|
||||||
PowerOfAssign => true,
|
PowerOfAssign => true,
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
LeftBracket => true, // [-expr] - is unary
|
||||||
|
// RightBracket | [expr] - expr not unary & is closing
|
||||||
|
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -560,9 +567,12 @@ impl Token {
|
|||||||
use self::Token::*;
|
use self::Token::*;
|
||||||
|
|
||||||
match *self {
|
match *self {
|
||||||
RightBrace | RightParen | RightBracket | Plus | Minus | Multiply | Divide | Comma
|
RightParen | Plus | Minus | Multiply | Divide | Comma | Equals | LessThan
|
||||||
| Equals | LessThan | GreaterThan | LessThanEqualsTo | GreaterThanEqualsTo
|
| GreaterThan | LessThanEqualsTo | GreaterThanEqualsTo | EqualsTo | NotEqualsTo
|
||||||
| EqualsTo | NotEqualsTo | Pipe | Or | Ampersand | And | PowerOf => true,
|
| Pipe | Or | Ampersand | And | PowerOf => true,
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
RightBrace | RightBracket => true,
|
||||||
|
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
@ -887,9 +897,12 @@ impl<'a> TokenIterator<'a> {
|
|||||||
"break" => Token::Break,
|
"break" => Token::Break,
|
||||||
"return" => Token::Return,
|
"return" => Token::Return,
|
||||||
"throw" => Token::Throw,
|
"throw" => Token::Throw,
|
||||||
"fn" => Token::Fn,
|
|
||||||
"for" => Token::For,
|
"for" => Token::For,
|
||||||
"in" => Token::In,
|
"in" => Token::In,
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
"fn" => Token::Fn,
|
||||||
|
|
||||||
_ => Token::Identifier(out),
|
_ => Token::Identifier(out),
|
||||||
},
|
},
|
||||||
pos,
|
pos,
|
||||||
@ -924,8 +937,12 @@ impl<'a> TokenIterator<'a> {
|
|||||||
'}' => return Some((Token::RightBrace, pos)),
|
'}' => return Some((Token::RightBrace, pos)),
|
||||||
'(' => return Some((Token::LeftParen, pos)),
|
'(' => return Some((Token::LeftParen, pos)),
|
||||||
')' => return Some((Token::RightParen, pos)),
|
')' => return Some((Token::RightParen, pos)),
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
'[' => return Some((Token::LeftBracket, pos)),
|
'[' => return Some((Token::LeftBracket, pos)),
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
']' => return Some((Token::RightBracket, pos)),
|
']' => return Some((Token::RightBracket, pos)),
|
||||||
|
|
||||||
'+' => {
|
'+' => {
|
||||||
return Some((
|
return Some((
|
||||||
match self.char_stream.peek() {
|
match self.char_stream.peek() {
|
||||||
@ -1745,6 +1762,7 @@ fn parse_binary_op<'a>(
|
|||||||
Box::new(change_var_to_property(*rhs)),
|
Box::new(change_var_to_property(*rhs)),
|
||||||
pos,
|
pos,
|
||||||
),
|
),
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(lhs, idx, pos) => {
|
Expr::Index(lhs, idx, pos) => {
|
||||||
Expr::Index(Box::new(change_var_to_property(*lhs)), idx, pos)
|
Expr::Index(Box::new(change_var_to_property(*lhs)), idx, pos)
|
||||||
}
|
}
|
||||||
@ -1950,6 +1968,8 @@ fn parse_block<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Pars
|
|||||||
|
|
||||||
match input.peek() {
|
match input.peek() {
|
||||||
Some(&(Token::RightBrace, _)) => (), // empty block
|
Some(&(Token::RightBrace, _)) => (), // empty block
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
Some(&(Token::Fn, pos)) => return Err(ParseError::new(PERR::WrongFnDefinition, pos)),
|
Some(&(Token::Fn, pos)) => return Err(ParseError::new(PERR::WrongFnDefinition, pos)),
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
@ -2003,7 +2023,7 @@ fn parse_stmt<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Parse
|
|||||||
let return_type = match token {
|
let return_type = match token {
|
||||||
Token::Return => ReturnType::Return,
|
Token::Return => ReturnType::Return,
|
||||||
Token::Throw => ReturnType::Exception,
|
Token::Throw => ReturnType::Exception,
|
||||||
_ => panic!("unexpected token!"),
|
_ => panic!("token should be return or throw"),
|
||||||
};
|
};
|
||||||
|
|
||||||
input.next();
|
input.next();
|
||||||
@ -2095,14 +2115,10 @@ fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseE
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_top_level<'a>(
|
fn parse_top_level<'a, 'e>(
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
scope: &Scope,
|
) -> Result<(Vec<Stmt>, Vec<FnDef>), ParseError> {
|
||||||
optimize_ast: bool,
|
|
||||||
) -> Result<AST, ParseError> {
|
|
||||||
let mut statements = Vec::<Stmt>::new();
|
let mut statements = Vec::<Stmt>::new();
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
let mut functions = Vec::<FnDef>::new();
|
let mut functions = Vec::<FnDef>::new();
|
||||||
|
|
||||||
while input.peek().is_some() {
|
while input.peek().is_some() {
|
||||||
@ -2126,40 +2142,79 @@ fn parse_top_level<'a>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok((statements, functions))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse<'a, 'e>(
|
||||||
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
|
engine: &Engine<'e>,
|
||||||
|
scope: &Scope,
|
||||||
|
) -> Result<AST, ParseError> {
|
||||||
|
let (statements, functions) = parse_top_level(input)?;
|
||||||
|
|
||||||
Ok(
|
Ok(
|
||||||
#[cfg(not(feature = "no_optimize"))]
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
AST(
|
optimize_ast(engine, scope, statements, functions),
|
||||||
if optimize_ast {
|
|
||||||
optimize(statements, &scope)
|
|
||||||
} else {
|
|
||||||
statements
|
|
||||||
},
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
functions
|
|
||||||
.into_iter()
|
|
||||||
.map(|mut fn_def| {
|
|
||||||
if optimize_ast {
|
|
||||||
let pos = fn_def.body.position();
|
|
||||||
let mut body = optimize(vec![fn_def.body], &scope);
|
|
||||||
fn_def.body = body.pop().unwrap_or_else(|| Stmt::Noop(pos));
|
|
||||||
}
|
|
||||||
Arc::new(fn_def)
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
),
|
|
||||||
#[cfg(feature = "no_optimize")]
|
#[cfg(feature = "no_optimize")]
|
||||||
AST(
|
AST(statements, functions.into_iter().map(Arc::new).collect()),
|
||||||
statements,
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
functions.into_iter().map(Arc::new).collect(),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse<'a>(
|
pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> (Option<Expr>, Dynamic) {
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
if value.is::<INT>() {
|
||||||
scope: &Scope,
|
let value2 = value.clone();
|
||||||
optimize_ast: bool,
|
(
|
||||||
) -> Result<AST, ParseError> {
|
Some(Expr::IntegerConstant(
|
||||||
parse_top_level(input, scope, optimize_ast)
|
*value.downcast::<INT>().expect("value should be INT"),
|
||||||
|
pos,
|
||||||
|
)),
|
||||||
|
value2,
|
||||||
|
)
|
||||||
|
} else if value.is::<char>() {
|
||||||
|
let value2 = value.clone();
|
||||||
|
(
|
||||||
|
Some(Expr::CharConstant(
|
||||||
|
*value.downcast::<char>().expect("value should be char"),
|
||||||
|
pos,
|
||||||
|
)),
|
||||||
|
value2,
|
||||||
|
)
|
||||||
|
} else if value.is::<String>() {
|
||||||
|
let value2 = value.clone();
|
||||||
|
(
|
||||||
|
Some(Expr::StringConstant(
|
||||||
|
*value.downcast::<String>().expect("value should be String"),
|
||||||
|
pos,
|
||||||
|
)),
|
||||||
|
value2,
|
||||||
|
)
|
||||||
|
} else if value.is::<bool>() {
|
||||||
|
let value2 = value.clone();
|
||||||
|
(
|
||||||
|
Some(
|
||||||
|
if *value.downcast::<bool>().expect("value should be bool") {
|
||||||
|
Expr::True(pos)
|
||||||
|
} else {
|
||||||
|
Expr::False(pos)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
value2,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
{
|
||||||
|
if value.is::<FLOAT>() {
|
||||||
|
let value2 = value.clone();
|
||||||
|
return (
|
||||||
|
Some(Expr::FloatConstant(
|
||||||
|
*value.downcast::<FLOAT>().expect("value should be FLOAT"),
|
||||||
|
pos,
|
||||||
|
)),
|
||||||
|
value2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(None, value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
74
src/scope.rs
74
src/scope.rs
@ -1,10 +1,7 @@
|
|||||||
//! Module that defines the `Scope` type representing a function call-stack scope.
|
//! Module that defines the `Scope` type representing a function call-stack scope.
|
||||||
|
|
||||||
use crate::any::{Any, AnyExt, Dynamic};
|
use crate::any::{Any, Dynamic};
|
||||||
use crate::parser::{Expr, Position, INT};
|
use crate::parser::{map_dynamic_to_expr, Expr, Position};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
|
||||||
use crate::parser::FLOAT;
|
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
@ -73,7 +70,7 @@ impl<'a> Scope<'a> {
|
|||||||
let value = value.into_dynamic();
|
let value = value.into_dynamic();
|
||||||
|
|
||||||
// Map into constant expressions
|
// Map into constant expressions
|
||||||
//let (expr, value) = map_dynamic_to_expr(value);
|
//let (expr, value) = map_dynamic_to_expr(value, Position::none());
|
||||||
|
|
||||||
self.0.push(ScopeEntry {
|
self.0.push(ScopeEntry {
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
@ -93,7 +90,7 @@ impl<'a> Scope<'a> {
|
|||||||
let value = value.into_dynamic();
|
let value = value.into_dynamic();
|
||||||
|
|
||||||
// Map into constant expressions
|
// Map into constant expressions
|
||||||
let (expr, value) = map_dynamic_to_expr(value);
|
let (expr, value) = map_dynamic_to_expr(value, Position::none());
|
||||||
|
|
||||||
self.0.push(ScopeEntry {
|
self.0.push(ScopeEntry {
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
@ -110,13 +107,13 @@ impl<'a> Scope<'a> {
|
|||||||
var_type: VariableType,
|
var_type: VariableType,
|
||||||
value: Dynamic,
|
value: Dynamic,
|
||||||
) {
|
) {
|
||||||
//let (expr, value) = map_dynamic_to_expr(value);
|
let (expr, value) = map_dynamic_to_expr(value, Position::none());
|
||||||
|
|
||||||
self.0.push(ScopeEntry {
|
self.0.push(ScopeEntry {
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
var_type,
|
var_type,
|
||||||
value,
|
value,
|
||||||
expr: None,
|
expr,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,62 +207,3 @@ where
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_dynamic_to_expr(value: Dynamic) -> (Option<Expr>, Dynamic) {
|
|
||||||
if value.is::<INT>() {
|
|
||||||
let value2 = value.clone();
|
|
||||||
(
|
|
||||||
Some(Expr::IntegerConstant(
|
|
||||||
*value.downcast::<INT>().expect("value should be INT"),
|
|
||||||
Position::none(),
|
|
||||||
)),
|
|
||||||
value2,
|
|
||||||
)
|
|
||||||
} else if value.is::<char>() {
|
|
||||||
let value2 = value.clone();
|
|
||||||
(
|
|
||||||
Some(Expr::CharConstant(
|
|
||||||
*value.downcast::<char>().expect("value should be char"),
|
|
||||||
Position::none(),
|
|
||||||
)),
|
|
||||||
value2,
|
|
||||||
)
|
|
||||||
} else if value.is::<String>() {
|
|
||||||
let value2 = value.clone();
|
|
||||||
(
|
|
||||||
Some(Expr::StringConstant(
|
|
||||||
*value.downcast::<String>().expect("value should be String"),
|
|
||||||
Position::none(),
|
|
||||||
)),
|
|
||||||
value2,
|
|
||||||
)
|
|
||||||
} else if value.is::<bool>() {
|
|
||||||
let value2 = value.clone();
|
|
||||||
(
|
|
||||||
Some(
|
|
||||||
if *value.downcast::<bool>().expect("value should be bool") {
|
|
||||||
Expr::True(Position::none())
|
|
||||||
} else {
|
|
||||||
Expr::False(Position::none())
|
|
||||||
},
|
|
||||||
),
|
|
||||||
value2,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
|
||||||
{
|
|
||||||
if value.is::<FLOAT>() {
|
|
||||||
let value2 = value.clone();
|
|
||||||
return (
|
|
||||||
Some(Expr::FloatConstant(
|
|
||||||
*value.downcast::<FLOAT>().expect("value should be FLOAT"),
|
|
||||||
Position::none(),
|
|
||||||
)),
|
|
||||||
value2,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(None, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,25 +1,24 @@
|
|||||||
#![cfg(not(feature = "no_stdlib"))]
|
|
||||||
#![cfg(not(feature = "no_function"))]
|
#![cfg(not(feature = "no_function"))]
|
||||||
use rhai::{Engine, EvalAltResult, INT};
|
use rhai::{Engine, EvalAltResult, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_engine_call_fn() -> Result<(), EvalAltResult> {
|
fn test_call_fn() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
engine.consume(
|
engine.consume(
|
||||||
|
true,
|
||||||
r"
|
r"
|
||||||
fn hello(x, y) {
|
fn hello(x, y) {
|
||||||
x.len() + y
|
x + y
|
||||||
}
|
}
|
||||||
fn hello(x) {
|
fn hello(x) {
|
||||||
x * 2
|
x * 2
|
||||||
}
|
}
|
||||||
",
|
",
|
||||||
true,
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let r: i64 = engine.call_fn("hello", (String::from("abc"), 123 as INT))?;
|
let r: i64 = engine.call_fn("hello", (42 as INT, 123 as INT))?;
|
||||||
assert_eq!(r, 126);
|
assert_eq!(r, 165);
|
||||||
|
|
||||||
let r: i64 = engine.call_fn("hello", 123 as INT)?;
|
let r: i64 = engine.call_fn("hello", 123 as INT)?;
|
||||||
assert_eq!(r, 246);
|
assert_eq!(r, 246);
|
@ -1,19 +1,19 @@
|
|||||||
use rhai::{Engine, EvalAltResult};
|
use rhai::{Engine, EvalAltResult, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_constant() -> Result<(), EvalAltResult> {
|
fn test_constant() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
assert_eq!(engine.eval::<i64>("const x = 123; x")?, 123);
|
assert_eq!(engine.eval::<INT>("const x = 123; x")?, 123);
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
matches!(engine.eval::<i64>("const x = 123; x = 42;").expect_err("expects error"),
|
matches!(engine.eval::<INT>("const x = 123; x = 42;").expect_err("expects error"),
|
||||||
EvalAltResult::ErrorAssignmentToConstant(var, _) if var == "x")
|
EvalAltResult::ErrorAssignmentToConstant(var, _) if var == "x")
|
||||||
);
|
);
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
assert!(
|
assert!(
|
||||||
matches!(engine.eval::<i64>("const x = [1, 2, 3, 4, 5]; x[2] = 42;").expect_err("expects error"),
|
matches!(engine.eval::<INT>("const x = [1, 2, 3, 4, 5]; x[2] = 42;").expect_err("expects error"),
|
||||||
EvalAltResult::ErrorAssignmentToConstant(var, _) if var == "x")
|
EvalAltResult::ErrorAssignmentToConstant(var, _) if var == "x")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -32,13 +32,13 @@ fn test_mismatched_op_custom_type() {
|
|||||||
.eval::<INT>("60 + new_ts()")
|
.eval::<INT>("60 + new_ts()")
|
||||||
.expect_err("expects error");
|
.expect_err("expects error");
|
||||||
|
|
||||||
match r {
|
#[cfg(feature = "only_i32")]
|
||||||
#[cfg(feature = "only_i32")]
|
assert!(
|
||||||
EvalAltResult::ErrorFunctionNotFound(err, _) if err == "+ (i32, TestStruct)" => (),
|
matches!(r, EvalAltResult::ErrorFunctionNotFound(err, _) if err == "+ (i32, TestStruct)")
|
||||||
|
);
|
||||||
|
|
||||||
#[cfg(not(feature = "only_i32"))]
|
#[cfg(not(feature = "only_i32"))]
|
||||||
EvalAltResult::ErrorFunctionNotFound(err, _) if err == "+ (i64, TestStruct)" => (),
|
assert!(
|
||||||
|
matches!(r, EvalAltResult::ErrorFunctionNotFound(err, _) if err == "+ (i64, TestStruct)")
|
||||||
_ => panic!(),
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
32
tests/optimizer.rs
Normal file
32
tests/optimizer.rs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#![cfg(not(feature = "no_optimize"))]
|
||||||
|
|
||||||
|
use rhai::{Engine, EvalAltResult, OptimizationLevel, INT};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_optimizer() -> Result<(), EvalAltResult> {
|
||||||
|
fn run_test(engine: &mut Engine) -> Result<(), EvalAltResult> {
|
||||||
|
assert_eq!(engine.eval::<INT>(r"if true { 42 } else { 123 }")?, 42);
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(r"if 1 == 1 || 2 > 3 { 42 } else { 123 }")?,
|
||||||
|
42
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(r#"const abc = "hello"; if abc < "foo" { 42 } else { 123 }"#)?,
|
||||||
|
123
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
engine.set_optimization_level(OptimizationLevel::None);
|
||||||
|
run_test(&mut engine)?;
|
||||||
|
|
||||||
|
engine.set_optimization_level(OptimizationLevel::Simple);
|
||||||
|
run_test(&mut engine)?;
|
||||||
|
|
||||||
|
engine.set_optimization_level(OptimizationLevel::Full);
|
||||||
|
run_test(&mut engine)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user