Merge pull request #108 from schungx/master
Constants and aggressive optimizations.
This commit is contained in:
commit
bffa3ed636
@ -18,14 +18,16 @@ include = [
|
||||
num-traits = "*"
|
||||
|
||||
[features]
|
||||
#default = ["no_function", "no_index", "no_float", "only_i32", "no_stdlib", "unchecked"]
|
||||
default = []
|
||||
#default = ["no_function", "no_index", "no_float", "only_i32", "no_stdlib", "unchecked", "no_optimize"]
|
||||
default = [ "optimize_full" ]
|
||||
debug_msgs = [] # print debug messages on function registrations and calls
|
||||
unchecked = [] # unchecked arithmetic
|
||||
no_stdlib = [] # no standard library of utility functions
|
||||
no_index = [] # no arrays and indexing
|
||||
no_float = [] # no floating-point
|
||||
no_function = [] # no script-defined functions
|
||||
no_optimize = [] # no script optimizer
|
||||
optimize_full = [] # set optimization level to Full (default is Simple)
|
||||
only_i32 = [] # set INT=i32 (useful for 32-bit systems)
|
||||
only_i64 = [] # set INT=i64 (default) and disable support for all other integer types
|
||||
|
||||
|
@ -1,4 +1,8 @@
|
||||
use rhai::{Engine, EvalAltResult, Scope, AST};
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
use rhai::OptimizationLevel;
|
||||
|
||||
use std::{
|
||||
io::{stdin, stdout, Write},
|
||||
iter,
|
||||
@ -43,6 +47,10 @@ fn print_error(input: &str, err: EvalAltResult) {
|
||||
|
||||
fn main() {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
engine.set_optimization_level(OptimizationLevel::Full);
|
||||
|
||||
let mut scope = Scope::new();
|
||||
|
||||
let mut input = String::new();
|
||||
@ -62,10 +70,11 @@ fn main() {
|
||||
match input.as_str().trim() {
|
||||
"exit" | "quit" => break, // quit
|
||||
"ast" => {
|
||||
// print the last AST
|
||||
match &ast {
|
||||
Some(ast) => println!("{:#?}", ast),
|
||||
None => println!("()"),
|
||||
if matches!(&ast, Some(_)) {
|
||||
// print the last AST
|
||||
println!("{:#?}", ast.as_ref().unwrap());
|
||||
} else {
|
||||
println!("()");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@ -73,7 +82,7 @@ fn main() {
|
||||
}
|
||||
|
||||
if let Err(err) = engine
|
||||
.compile(&input)
|
||||
.compile_with_scope(&scope, &input)
|
||||
.map_err(EvalAltResult::ErrorParsing)
|
||||
.and_then(|r| {
|
||||
ast = Some(r);
|
||||
|
@ -1,4 +1,8 @@
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
use rhai::OptimizationLevel;
|
||||
|
||||
use std::{env, fs::File, io::Read, iter, process::exit};
|
||||
|
||||
fn padding(pad: &str, len: usize) -> String {
|
||||
@ -49,6 +53,9 @@ fn main() {
|
||||
for filename in env::args().skip(1) {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
engine.set_optimization_level(OptimizationLevel::Full);
|
||||
|
||||
let mut f = match File::open(&filename) {
|
||||
Err(err) => {
|
||||
eprintln!("Error reading script file: {}\n{}", filename, err);
|
||||
@ -67,7 +74,7 @@ fn main() {
|
||||
_ => (),
|
||||
}
|
||||
|
||||
if let Err(err) = engine.consume(&contents, false) {
|
||||
if let Err(err) = engine.consume(false, &contents) {
|
||||
eprintln!("{}", padding("=", filename.len()));
|
||||
eprintln!("{}", filename);
|
||||
eprintln!("{}", padding("=", filename.len()));
|
||||
|
@ -1,12 +1,11 @@
|
||||
// This is a script to calculate prime numbers.
|
||||
|
||||
let MAX_NUMBER_TO_CHECK = 10000; // 1229 primes
|
||||
const MAX_NUMBER_TO_CHECK = 10000; // 1229 primes
|
||||
|
||||
let prime_mask = [];
|
||||
prime_mask.pad(MAX_NUMBER_TO_CHECK, true);
|
||||
|
||||
prime_mask[0] = false;
|
||||
prime_mask[1] = false;
|
||||
prime_mask[0] = prime_mask[1] = false;
|
||||
|
||||
let total_primes_found = 0;
|
||||
|
||||
|
116
src/api.rs
116
src/api.rs
@ -8,6 +8,10 @@ use crate::fn_register::RegisterFn;
|
||||
use crate::parser::{lex, parse, FnDef, Position, AST};
|
||||
use crate::result::EvalAltResult;
|
||||
use crate::scope::Scope;
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
use crate::optimize::optimize_ast;
|
||||
|
||||
use std::{
|
||||
any::{type_name, TypeId},
|
||||
fs::File,
|
||||
@ -101,8 +105,14 @@ impl<'e> Engine<'e> {
|
||||
|
||||
/// Compile a string into an AST.
|
||||
pub fn compile(&self, input: &str) -> Result<AST, ParseError> {
|
||||
self.compile_with_scope(&Scope::new(), input)
|
||||
}
|
||||
|
||||
/// Compile a string into an AST using own scope.
|
||||
/// 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> {
|
||||
let tokens_stream = lex(input);
|
||||
parse(&mut tokens_stream.peekable(), self.optimize)
|
||||
parse(&mut tokens_stream.peekable(), self, scope)
|
||||
}
|
||||
|
||||
fn read_file(path: PathBuf) -> Result<String, EvalAltResult> {
|
||||
@ -118,8 +128,20 @@ impl<'e> Engine<'e> {
|
||||
|
||||
/// Compile a file into an AST.
|
||||
pub fn compile_file(&self, path: PathBuf) -> Result<AST, EvalAltResult> {
|
||||
Self::read_file(path)
|
||||
.and_then(|contents| self.compile(&contents).map_err(|err| err.into()))
|
||||
self.compile_file_with_scope(&Scope::new(), path)
|
||||
}
|
||||
|
||||
/// Compile a file into an AST using own scope.
|
||||
/// The scope is useful for passing constants into the script for optimization.
|
||||
pub fn compile_file_with_scope(
|
||||
&self,
|
||||
scope: &Scope,
|
||||
path: PathBuf,
|
||||
) -> Result<AST, EvalAltResult> {
|
||||
Self::read_file(path).and_then(|contents| {
|
||||
self.compile_with_scope(scope, &contents)
|
||||
.map_err(|err| err.into())
|
||||
})
|
||||
}
|
||||
|
||||
/// Evaluate a file.
|
||||
@ -127,6 +149,15 @@ impl<'e> Engine<'e> {
|
||||
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.
|
||||
pub fn eval<T: Any + Clone>(&mut self, input: &str) -> Result<T, EvalAltResult> {
|
||||
let mut scope = Scope::new();
|
||||
@ -162,23 +193,21 @@ impl<'e> Engine<'e> {
|
||||
) -> Result<Dynamic, EvalAltResult> {
|
||||
engine.clear_functions();
|
||||
|
||||
#[cfg(feature = "no_function")]
|
||||
let AST(statements) = ast;
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
let statements = {
|
||||
let AST(statements, functions) = ast;
|
||||
engine.load_script_functions(functions);
|
||||
statements
|
||||
};
|
||||
|
||||
let result = statements
|
||||
.iter()
|
||||
.try_fold(().into_dynamic(), |_, stmt| engine.eval_stmt(scope, stmt));
|
||||
let mut result = ().into_dynamic();
|
||||
|
||||
for stmt in statements {
|
||||
result = engine.eval_stmt(scope, stmt)?;
|
||||
}
|
||||
|
||||
engine.clear_functions();
|
||||
|
||||
result
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
match eval_ast_internal(self, scope, ast) {
|
||||
@ -207,10 +236,25 @@ impl<'e> Engine<'e> {
|
||||
/// and not cleared from run to run.
|
||||
pub fn consume_file(
|
||||
&mut self,
|
||||
path: PathBuf,
|
||||
retain_functions: bool,
|
||||
path: PathBuf,
|
||||
) -> 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).
|
||||
@ -218,11 +262,11 @@ impl<'e> Engine<'e> {
|
||||
///
|
||||
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
|
||||
/// and not cleared from run to run.
|
||||
pub fn consume(&mut self, input: &str, retain_functions: bool) -> Result<(), EvalAltResult> {
|
||||
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, 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.
|
||||
///
|
||||
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
|
||||
@ -235,7 +279,7 @@ impl<'e> Engine<'e> {
|
||||
) -> Result<(), EvalAltResult> {
|
||||
let tokens_stream = lex(input);
|
||||
|
||||
let ast = parse(&mut tokens_stream.peekable(), self.optimize)
|
||||
let ast = parse(&mut tokens_stream.peekable(), self, scope)
|
||||
.map_err(EvalAltResult::ErrorParsing)?;
|
||||
|
||||
self.consume_ast_with_scope(scope, retain_functions, &ast)
|
||||
@ -246,6 +290,15 @@ impl<'e> Engine<'e> {
|
||||
///
|
||||
/// 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(&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(
|
||||
&mut self,
|
||||
scope: &mut Scope,
|
||||
@ -256,10 +309,6 @@ impl<'e> Engine<'e> {
|
||||
self.clear_functions();
|
||||
}
|
||||
|
||||
#[cfg(feature = "no_function")]
|
||||
let AST(statements) = ast;
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
let statements = {
|
||||
let AST(ref statements, ref functions) = ast;
|
||||
self.load_script_functions(functions);
|
||||
@ -307,7 +356,7 @@ impl<'e> Engine<'e> {
|
||||
///
|
||||
/// 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))?;
|
||||
///
|
||||
@ -345,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!`)
|
||||
///
|
||||
/// # Example
|
||||
@ -359,7 +429,7 @@ impl<'e> Engine<'e> {
|
||||
///
|
||||
/// // Override action of 'print' function
|
||||
/// engine.on_print(|s| result.push_str(s));
|
||||
/// engine.consume("print(40 + 2);", false)?;
|
||||
/// engine.consume(false, "print(40 + 2);")?;
|
||||
/// }
|
||||
/// assert_eq!(result, "42");
|
||||
/// # Ok(())
|
||||
@ -383,7 +453,7 @@ impl<'e> Engine<'e> {
|
||||
///
|
||||
/// // Override action of 'debug' function
|
||||
/// engine.on_debug(|s| result.push_str(s));
|
||||
/// engine.consume(r#"debug("hello");"#, false)?;
|
||||
/// engine.consume(false, r#"debug("hello");"#)?;
|
||||
/// }
|
||||
/// assert_eq!(result, "\"hello\"");
|
||||
/// # Ok(())
|
||||
|
@ -8,7 +8,9 @@ use crate::engine::Engine;
|
||||
use crate::fn_register::{RegisterFn, RegisterResultFn};
|
||||
use crate::parser::{Position, INT};
|
||||
use crate::result::EvalAltResult;
|
||||
use crate::FLOAT;
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
use crate::parser::FLOAT;
|
||||
|
||||
use num_traits::{
|
||||
identities::Zero, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl,
|
||||
@ -578,7 +580,7 @@ impl Engine<'_> {
|
||||
}
|
||||
|
||||
fn range<T>(from: T, to: T) -> Range<T> {
|
||||
(from..to)
|
||||
from..to
|
||||
}
|
||||
|
||||
reg_iterator::<INT>(self);
|
||||
@ -771,9 +773,12 @@ impl Engine<'_> {
|
||||
self.register_dynamic_fn("pop", |list: &mut Array| {
|
||||
list.pop().unwrap_or_else(|| ().into_dynamic())
|
||||
});
|
||||
self.register_dynamic_fn("shift", |list: &mut Array| match list.len() {
|
||||
0 => ().into_dynamic(),
|
||||
_ => list.remove(0),
|
||||
self.register_dynamic_fn("shift", |list: &mut Array| {
|
||||
if !list.is_empty() {
|
||||
().into_dynamic()
|
||||
} else {
|
||||
list.remove(0)
|
||||
}
|
||||
});
|
||||
self.register_fn("len", |list: &mut Array| list.len() as INT);
|
||||
self.register_fn("clear", |list: &mut Array| list.clear());
|
||||
|
@ -1,6 +1,8 @@
|
||||
//! Helper module which defines `FnArgs` to make function calling easier.
|
||||
|
||||
use crate::any::{Any, Dynamic};
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
use crate::engine::Array;
|
||||
|
||||
/// Trait that represent arguments to a function call.
|
||||
|
283
src/engine.rs
283
src/engine.rs
@ -3,7 +3,10 @@
|
||||
use crate::any::{Any, AnyExt, Dynamic, Variant};
|
||||
use crate::parser::{Expr, FnDef, Position, ReturnType, Stmt};
|
||||
use crate::result::EvalAltResult;
|
||||
use crate::scope::Scope;
|
||||
use crate::scope::{Scope, VariableType};
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
use crate::optimize::OptimizationLevel;
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
use crate::INT;
|
||||
@ -63,17 +66,20 @@ pub struct FnSpec<'a> {
|
||||
/// ```
|
||||
pub struct Engine<'e> {
|
||||
/// 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
|
||||
pub(crate) ext_functions: HashMap<FnSpec<'e>, Box<FnAny>>,
|
||||
/// A hashmap containing all script-defined functions
|
||||
pub(crate) script_functions: Vec<Arc<FnDef>>,
|
||||
/// A hashmap containing all iterators known to the engine
|
||||
pub(crate) type_iterators: HashMap<TypeId, Box<IteratorFn>>,
|
||||
/// A hashmap mapping type names to pretty-print names
|
||||
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>,
|
||||
/// Closure for implementing the debug commands
|
||||
pub(crate) on_debug: Box<dyn FnMut(&str) + 'e>,
|
||||
}
|
||||
|
||||
@ -93,13 +99,20 @@ impl Engine<'_> {
|
||||
|
||||
// Create the new scripting Engine
|
||||
let mut engine = Engine {
|
||||
optimize: true,
|
||||
ext_functions: HashMap::new(),
|
||||
script_functions: Vec::new(),
|
||||
type_iterators: HashMap::new(),
|
||||
type_names,
|
||||
on_print: Box::new(default_print), // default print/debug implementations
|
||||
on_debug: Box::new(default_print),
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
#[cfg(not(feature = "optimize_full"))]
|
||||
optimization_level: OptimizationLevel::Simple,
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
#[cfg(feature = "optimize_full")]
|
||||
optimization_level: OptimizationLevel::Full,
|
||||
};
|
||||
|
||||
engine.register_core_lib();
|
||||
@ -110,9 +123,32 @@ impl Engine<'_> {
|
||||
engine
|
||||
}
|
||||
|
||||
/// Control whether the `Engine` will optimize an AST after compilation
|
||||
pub fn set_optimization(&mut self, optimize: bool) {
|
||||
self.optimize = optimize
|
||||
/// Control whether and how the `Engine` will optimize an AST after compilation
|
||||
#[cfg(not(feature = "no_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
|
||||
@ -148,12 +184,13 @@ impl Engine<'_> {
|
||||
fn_def
|
||||
.params
|
||||
.iter()
|
||||
.zip(args.iter().map(|x| (*x).into_dynamic())),
|
||||
.zip(args.iter().map(|x| (*x).into_dynamic()))
|
||||
.map(|(name, value)| (name, VariableType::Normal, value)),
|
||||
);
|
||||
|
||||
// Evaluate
|
||||
// Convert return statement to return value
|
||||
return match self.eval_stmt(&mut scope, &fn_def.body) {
|
||||
// Convert return statement to return value
|
||||
Err(EvalAltResult::Return(x, _)) => Ok(x),
|
||||
other => other,
|
||||
};
|
||||
@ -164,13 +201,13 @@ impl Engine<'_> {
|
||||
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) {
|
||||
// Run external function
|
||||
let result = func(args, pos)?;
|
||||
|
||||
// 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_DEBUG => self.on_debug.as_mut(),
|
||||
_ => return Ok(result),
|
||||
@ -184,7 +221,7 @@ impl Engine<'_> {
|
||||
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
|
||||
return Ok(self
|
||||
.map_type_name(args[0].type_name())
|
||||
@ -192,23 +229,23 @@ impl Engine<'_> {
|
||||
.into_dynamic());
|
||||
}
|
||||
|
||||
if spec.name.starts_with(FUNC_GETTER) {
|
||||
if fn_name.starts_with(FUNC_GETTER) {
|
||||
// Getter function not found
|
||||
return Err(EvalAltResult::ErrorDotExpr(
|
||||
format!(
|
||||
"- property '{}' unknown or write-only",
|
||||
&spec.name[FUNC_GETTER.len()..]
|
||||
&fn_name[FUNC_GETTER.len()..]
|
||||
),
|
||||
pos,
|
||||
));
|
||||
}
|
||||
|
||||
if spec.name.starts_with(FUNC_SETTER) {
|
||||
if fn_name.starts_with(FUNC_SETTER) {
|
||||
// Setter function not found
|
||||
return Err(EvalAltResult::ErrorDotExpr(
|
||||
format!(
|
||||
"- property '{}' unknown or read-only",
|
||||
&spec.name[FUNC_SETTER.len()..]
|
||||
&fn_name[FUNC_SETTER.len()..]
|
||||
),
|
||||
pos,
|
||||
));
|
||||
@ -227,7 +264,7 @@ impl Engine<'_> {
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Err(EvalAltResult::ErrorFunctionNotFound(
|
||||
format!("{} ({})", spec.name, types_list.join(", ")),
|
||||
format!("{} ({})", fn_name, types_list.join(", ")),
|
||||
pos,
|
||||
))
|
||||
}
|
||||
@ -248,14 +285,14 @@ impl Engine<'_> {
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let args = once(this_ptr)
|
||||
.chain(values.iter_mut().map(|b| b.as_mut()))
|
||||
.chain(values.iter_mut().map(Dynamic::as_mut))
|
||||
.collect();
|
||||
|
||||
self.call_fn_raw(fn_name, args, def_val.as_ref(), *pos)
|
||||
}
|
||||
|
||||
// xxx.id
|
||||
Expr::Identifier(id, pos) => {
|
||||
Expr::Property(id, pos) => {
|
||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||
|
||||
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
||||
@ -266,7 +303,7 @@ impl Engine<'_> {
|
||||
Expr::Index(idx_lhs, idx_expr, idx_pos) => {
|
||||
let (val, _) = match idx_lhs.as_ref() {
|
||||
// xxx.id[idx_expr]
|
||||
Expr::Identifier(id, pos) => {
|
||||
Expr::Property(id, pos) => {
|
||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||
(
|
||||
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?,
|
||||
@ -294,7 +331,7 @@ impl Engine<'_> {
|
||||
// xxx.dot_lhs.rhs
|
||||
Expr::Dot(dot_lhs, rhs, _) => match dot_lhs.as_ref() {
|
||||
// xxx.id.rhs
|
||||
Expr::Identifier(id, pos) => {
|
||||
Expr::Property(id, pos) => {
|
||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||
|
||||
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
||||
@ -305,7 +342,7 @@ impl Engine<'_> {
|
||||
Expr::Index(idx_lhs, idx_expr, idx_pos) => {
|
||||
let (val, _) = match idx_lhs.as_ref() {
|
||||
// xxx.id[idx_expr].rhs
|
||||
Expr::Identifier(id, pos) => {
|
||||
Expr::Property(id, pos) => {
|
||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||
(
|
||||
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?,
|
||||
@ -353,8 +390,8 @@ impl Engine<'_> {
|
||||
) -> Result<Dynamic, EvalAltResult> {
|
||||
match dot_lhs {
|
||||
// id.???
|
||||
Expr::Identifier(id, pos) => {
|
||||
let (src_idx, mut target) = Self::search_scope(scope, id, Ok, *pos)?;
|
||||
Expr::Variable(id, pos) => {
|
||||
let (src_idx, _, mut target) = Self::search_scope(scope, id, Ok, *pos)?;
|
||||
let val = self.get_dot_val_helper(scope, target.as_mut(), dot_rhs);
|
||||
|
||||
// In case the expression mutated `target`, we need to update it back into the scope because it is cloned.
|
||||
@ -371,16 +408,26 @@ impl Engine<'_> {
|
||||
let val = self.get_dot_val_helper(scope, target.as_mut(), dot_rhs);
|
||||
|
||||
// In case the expression mutated `target`, we need to update it back into the scope because it is cloned.
|
||||
if let Some((id, src_idx)) = src {
|
||||
Self::update_indexed_var_in_scope(
|
||||
src_type,
|
||||
scope,
|
||||
id,
|
||||
src_idx,
|
||||
idx,
|
||||
target,
|
||||
idx_lhs.position(),
|
||||
)?;
|
||||
if let Some((id, var_type, src_idx)) = src {
|
||||
match var_type {
|
||||
VariableType::Constant => {
|
||||
return Err(EvalAltResult::ErrorAssignmentToConstant(
|
||||
id.to_string(),
|
||||
idx_lhs.position(),
|
||||
));
|
||||
}
|
||||
VariableType::Normal => {
|
||||
Self::update_indexed_var_in_scope(
|
||||
src_type,
|
||||
scope,
|
||||
id,
|
||||
src_idx,
|
||||
idx,
|
||||
target,
|
||||
dot_rhs.position(),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val
|
||||
@ -400,11 +447,11 @@ impl Engine<'_> {
|
||||
id: &str,
|
||||
map: impl FnOnce(Dynamic) -> Result<T, EvalAltResult>,
|
||||
begin: Position,
|
||||
) -> Result<(usize, T), EvalAltResult> {
|
||||
) -> Result<(usize, VariableType, T), EvalAltResult> {
|
||||
scope
|
||||
.get(id)
|
||||
.ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.into(), begin))
|
||||
.and_then(move |(idx, _, val)| map(val).map(|v| (idx, v)))
|
||||
.and_then(move |(idx, _, var_type, val)| map(val).map(|v| (idx, var_type, v)))
|
||||
}
|
||||
|
||||
/// Evaluate the value of an index (must evaluate to INT)
|
||||
@ -476,19 +523,32 @@ impl Engine<'_> {
|
||||
lhs: &'a Expr,
|
||||
idx_expr: &Expr,
|
||||
idx_pos: Position,
|
||||
) -> Result<(IndexSourceType, Option<(&'a str, usize)>, usize, Dynamic), EvalAltResult> {
|
||||
) -> Result<
|
||||
(
|
||||
IndexSourceType,
|
||||
Option<(&'a str, VariableType, usize)>,
|
||||
usize,
|
||||
Dynamic,
|
||||
),
|
||||
EvalAltResult,
|
||||
> {
|
||||
let idx = self.eval_index_value(scope, idx_expr)?;
|
||||
|
||||
match lhs {
|
||||
// id[idx_expr]
|
||||
Expr::Identifier(id, _) => Self::search_scope(
|
||||
Expr::Variable(id, _) => Self::search_scope(
|
||||
scope,
|
||||
&id,
|
||||
|val| self.get_indexed_value(&val, idx, idx_expr.position(), idx_pos),
|
||||
lhs.position(),
|
||||
)
|
||||
.map(|(src_idx, (val, src_type))| {
|
||||
(src_type, Some((id.as_str(), src_idx)), idx as usize, val)
|
||||
.map(|(src_idx, var_type, (val, src_type))| {
|
||||
(
|
||||
src_type,
|
||||
Some((id.as_str(), var_type, src_idx)),
|
||||
idx as usize,
|
||||
val,
|
||||
)
|
||||
}),
|
||||
|
||||
// (expr)[idx_expr]
|
||||
@ -543,8 +603,7 @@ impl Engine<'_> {
|
||||
Ok(Self::str_replace_char(s, idx as usize, ch).into_dynamic())
|
||||
}
|
||||
|
||||
// All other variable types should be an error
|
||||
_ => panic!("array or string source type expected for indexing"),
|
||||
IndexSourceType::Expression => panic!("expression cannot be indexed for update"),
|
||||
}
|
||||
}
|
||||
|
||||
@ -585,7 +644,7 @@ impl Engine<'_> {
|
||||
) -> Result<Dynamic, EvalAltResult> {
|
||||
match dot_rhs {
|
||||
// xxx.id
|
||||
Expr::Identifier(id, pos) => {
|
||||
Expr::Property(id, pos) => {
|
||||
let set_fn_name = format!("{}{}", FUNC_SETTER, id);
|
||||
|
||||
self.call_fn_raw(&set_fn_name, vec![this_ptr, new_val.as_mut()], None, *pos)
|
||||
@ -596,7 +655,7 @@ impl Engine<'_> {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(lhs, idx_expr, idx_pos) => match lhs.as_ref() {
|
||||
// xxx.id[idx_expr]
|
||||
Expr::Identifier(id, pos) => {
|
||||
Expr::Property(id, pos) => {
|
||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||
|
||||
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
||||
@ -620,7 +679,7 @@ impl Engine<'_> {
|
||||
// xxx.lhs.{...}
|
||||
Expr::Dot(lhs, rhs, _) => match lhs.as_ref() {
|
||||
// xxx.id.rhs
|
||||
Expr::Identifier(id, pos) => {
|
||||
Expr::Property(id, pos) => {
|
||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||
|
||||
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
||||
@ -640,7 +699,7 @@ impl Engine<'_> {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(lhs, idx_expr, idx_pos) => match lhs.as_ref() {
|
||||
// xxx.id[idx_expr].rhs
|
||||
Expr::Identifier(id, pos) => {
|
||||
Expr::Property(id, pos) => {
|
||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||
|
||||
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
||||
@ -702,11 +761,23 @@ impl Engine<'_> {
|
||||
dot_rhs: &Expr,
|
||||
new_val: Dynamic,
|
||||
val_pos: Position,
|
||||
op_pos: Position,
|
||||
) -> Result<Dynamic, EvalAltResult> {
|
||||
match dot_lhs {
|
||||
// id.???
|
||||
Expr::Identifier(id, pos) => {
|
||||
let (src_idx, mut target) = Self::search_scope(scope, id, Ok, *pos)?;
|
||||
Expr::Variable(id, pos) => {
|
||||
let (src_idx, var_type, mut target) = Self::search_scope(scope, id, Ok, *pos)?;
|
||||
|
||||
match var_type {
|
||||
VariableType::Constant => {
|
||||
return Err(EvalAltResult::ErrorAssignmentToConstant(
|
||||
id.to_string(),
|
||||
op_pos,
|
||||
))
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let val =
|
||||
self.set_dot_val_helper(scope, target.as_mut(), dot_rhs, new_val, val_pos);
|
||||
|
||||
@ -726,16 +797,20 @@ impl Engine<'_> {
|
||||
self.set_dot_val_helper(scope, target.as_mut(), dot_rhs, new_val, val_pos);
|
||||
|
||||
// In case the expression mutated `target`, we need to update it back into the scope because it is cloned.
|
||||
if let Some((id, src_idx)) = src {
|
||||
Self::update_indexed_var_in_scope(
|
||||
src_type,
|
||||
scope,
|
||||
id,
|
||||
src_idx,
|
||||
idx,
|
||||
target,
|
||||
lhs.position(),
|
||||
)?;
|
||||
if let Some((id, var_type, src_idx)) = src {
|
||||
match var_type {
|
||||
VariableType::Constant => {
|
||||
return Err(EvalAltResult::ErrorAssignmentToConstant(
|
||||
id.to_string(),
|
||||
lhs.position(),
|
||||
));
|
||||
}
|
||||
VariableType::Normal => {
|
||||
Self::update_indexed_var_in_scope(
|
||||
src_type, scope, id, src_idx, idx, target, val_pos,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val
|
||||
@ -758,9 +833,10 @@ impl Engine<'_> {
|
||||
Expr::IntegerConstant(i, _) => Ok(i.into_dynamic()),
|
||||
Expr::StringConstant(s, _) => Ok(s.into_dynamic()),
|
||||
Expr::CharConstant(c, _) => Ok(c.into_dynamic()),
|
||||
Expr::Identifier(id, pos) => {
|
||||
Self::search_scope(scope, id, Ok, *pos).map(|(_, val)| val)
|
||||
Expr::Variable(id, pos) => {
|
||||
Self::search_scope(scope, id, Ok, *pos).map(|(_, _, val)| val)
|
||||
}
|
||||
Expr::Property(_, _) => panic!("unexpected property."),
|
||||
|
||||
// lhs[idx_expr]
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
@ -768,26 +844,25 @@ impl Engine<'_> {
|
||||
.eval_index_expr(scope, lhs, idx_expr, *idx_pos)
|
||||
.map(|(_, _, _, x)| x),
|
||||
|
||||
#[cfg(feature = "no_index")]
|
||||
Expr::Index(_, _, _) => panic!("encountered an index expression during no_index!"),
|
||||
|
||||
// Statement block
|
||||
Expr::Stmt(stmt, _) => self.eval_stmt(scope, stmt),
|
||||
|
||||
// lhs = rhs
|
||||
Expr::Assignment(lhs, rhs, _) => {
|
||||
Expr::Assignment(lhs, rhs, op_pos) => {
|
||||
let rhs_val = self.eval_expr(scope, rhs)?;
|
||||
|
||||
match lhs.as_ref() {
|
||||
// name = rhs
|
||||
Expr::Identifier(name, pos) => {
|
||||
if let Some((idx, _, _)) = scope.get(name) {
|
||||
*scope.get_mut(name, idx) = rhs_val;
|
||||
Ok(().into_dynamic())
|
||||
} else {
|
||||
Err(EvalAltResult::ErrorVariableNotFound(name.clone(), *pos))
|
||||
Expr::Variable(name, pos) => match scope.get(name) {
|
||||
Some((idx, _, VariableType::Normal, _)) => {
|
||||
*scope.get_mut(name, idx) = rhs_val.clone();
|
||||
Ok(rhs_val)
|
||||
}
|
||||
}
|
||||
Some((_, _, VariableType::Constant, _)) => Err(
|
||||
EvalAltResult::ErrorAssignmentToConstant(name.to_string(), *op_pos),
|
||||
),
|
||||
_ => Err(EvalAltResult::ErrorVariableNotFound(name.clone(), *pos)),
|
||||
},
|
||||
|
||||
// idx_lhs[idx_expr] = rhs
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
@ -795,16 +870,24 @@ impl Engine<'_> {
|
||||
let (src_type, src, idx, _) =
|
||||
self.eval_index_expr(scope, idx_lhs, idx_expr, *idx_pos)?;
|
||||
|
||||
if let Some((id, src_idx)) = src {
|
||||
Ok(Self::update_indexed_var_in_scope(
|
||||
src_type,
|
||||
scope,
|
||||
&id,
|
||||
src_idx,
|
||||
idx,
|
||||
rhs_val,
|
||||
rhs.position(),
|
||||
)?)
|
||||
if let Some((id, var_type, src_idx)) = src {
|
||||
match var_type {
|
||||
VariableType::Constant => {
|
||||
return Err(EvalAltResult::ErrorAssignmentToConstant(
|
||||
id.to_string(),
|
||||
idx_lhs.position(),
|
||||
));
|
||||
}
|
||||
VariableType::Normal => Ok(Self::update_indexed_var_in_scope(
|
||||
src_type,
|
||||
scope,
|
||||
&id,
|
||||
src_idx,
|
||||
idx,
|
||||
rhs_val,
|
||||
rhs.position(),
|
||||
)?),
|
||||
}
|
||||
} else {
|
||||
Err(EvalAltResult::ErrorAssignmentToUnknownLHS(
|
||||
idx_lhs.position(),
|
||||
@ -814,9 +897,15 @@ impl Engine<'_> {
|
||||
|
||||
// dot_lhs.dot_rhs = rhs
|
||||
Expr::Dot(dot_lhs, dot_rhs, _) => {
|
||||
self.set_dot_val(scope, dot_lhs, dot_rhs, rhs_val, rhs.position())
|
||||
self.set_dot_val(scope, dot_lhs, dot_rhs, rhs_val, rhs.position(), *op_pos)
|
||||
}
|
||||
|
||||
// Error assignment to constant
|
||||
expr if expr.is_constant() => Err(EvalAltResult::ErrorAssignmentToConstant(
|
||||
expr.get_constant_str(),
|
||||
lhs.position(),
|
||||
)),
|
||||
|
||||
// Syntax error
|
||||
_ => Err(EvalAltResult::ErrorAssignmentToUnknownLHS(lhs.position())),
|
||||
}
|
||||
@ -834,8 +923,6 @@ impl Engine<'_> {
|
||||
|
||||
Ok(Box::new(arr))
|
||||
}
|
||||
#[cfg(feature = "no_index")]
|
||||
Expr::Array(_, _) => panic!("encountered an array during no_index!"),
|
||||
|
||||
// Dump AST
|
||||
Expr::FunctionCall(fn_name, args_expr_list, _, pos) if fn_name == KEYWORD_DUMP_AST => {
|
||||
@ -925,7 +1012,16 @@ impl Engine<'_> {
|
||||
Stmt::Noop(_) => Ok(().into_dynamic()),
|
||||
|
||||
// Expression as statement
|
||||
Stmt::Expr(expr) => self.eval_expr(scope, expr),
|
||||
Stmt::Expr(expr) => {
|
||||
let result = self.eval_expr(scope, expr)?;
|
||||
|
||||
Ok(if !matches!(expr.as_ref(), Expr::Assignment(_, _, _)) {
|
||||
result
|
||||
} else {
|
||||
// If it is an assignment, erase the result at the root
|
||||
().into_dynamic()
|
||||
})
|
||||
}
|
||||
|
||||
// Block scope
|
||||
Stmt::Block(block, _) => {
|
||||
@ -967,9 +1063,9 @@ impl Engine<'_> {
|
||||
Ok(guard_val) => {
|
||||
if *guard_val {
|
||||
match self.eval_stmt(scope, body) {
|
||||
Ok(_) => (),
|
||||
Err(EvalAltResult::LoopBreak) => return Ok(().into_dynamic()),
|
||||
Err(x) => return Err(x),
|
||||
_ => (),
|
||||
}
|
||||
} else {
|
||||
return Ok(().into_dynamic());
|
||||
@ -982,9 +1078,9 @@ impl Engine<'_> {
|
||||
// Loop statement
|
||||
Stmt::Loop(body) => loop {
|
||||
match self.eval_stmt(scope, body) {
|
||||
Ok(_) => (),
|
||||
Err(EvalAltResult::LoopBreak) => return Ok(().into_dynamic()),
|
||||
Err(x) => return Err(x),
|
||||
_ => (),
|
||||
}
|
||||
},
|
||||
|
||||
@ -1001,9 +1097,9 @@ impl Engine<'_> {
|
||||
*scope.get_mut(name, idx) = a;
|
||||
|
||||
match self.eval_stmt(scope, body) {
|
||||
Ok(_) => (),
|
||||
Err(EvalAltResult::LoopBreak) => break,
|
||||
Err(x) => return Err(x),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
scope.pop();
|
||||
@ -1045,7 +1141,7 @@ impl Engine<'_> {
|
||||
// Let statement
|
||||
Stmt::Let(name, Some(expr), _) => {
|
||||
let val = self.eval_expr(scope, expr)?;
|
||||
scope.push_dynamic(name.clone(), val);
|
||||
scope.push_dynamic(name.clone(), VariableType::Normal, val);
|
||||
Ok(().into_dynamic())
|
||||
}
|
||||
|
||||
@ -1053,6 +1149,15 @@ impl Engine<'_> {
|
||||
scope.push(name.clone(), ());
|
||||
Ok(().into_dynamic())
|
||||
}
|
||||
|
||||
// Const statement
|
||||
Stmt::Const(name, expr, _) if expr.is_constant() => {
|
||||
let val = self.eval_expr(scope, expr)?;
|
||||
scope.push_dynamic(name.clone(), VariableType::Constant, val);
|
||||
Ok(().into_dynamic())
|
||||
}
|
||||
|
||||
Stmt::Const(_, _, _) => panic!("constant expression not constant!"),
|
||||
}
|
||||
}
|
||||
|
||||
|
97
src/error.rs
97
src/error.rs
@ -18,20 +18,11 @@ pub enum LexError {
|
||||
MalformedChar(String),
|
||||
/// Error in the script text.
|
||||
InputError(String),
|
||||
/// An identifier is in an invalid format.
|
||||
MalformedIdentifier(String),
|
||||
}
|
||||
|
||||
impl Error for LexError {
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
Self::UnexpectedChar(_) => "Unexpected character",
|
||||
Self::UnterminatedString => "Open string is not terminated",
|
||||
Self::MalformedEscapeSequence(_) => "Unexpected values in escape sequence",
|
||||
Self::MalformedNumber(_) => "Unexpected characters in number",
|
||||
Self::MalformedChar(_) => "Char constant not a single character",
|
||||
Self::InputError(_) => "Input error",
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Error for LexError {}
|
||||
|
||||
impl fmt::Display for LexError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
@ -40,8 +31,11 @@ impl fmt::Display for LexError {
|
||||
Self::MalformedEscapeSequence(s) => write!(f, "Invalid escape sequence: '{}'", s),
|
||||
Self::MalformedNumber(s) => write!(f, "Invalid number: '{}'", s),
|
||||
Self::MalformedChar(s) => write!(f, "Invalid character: '{}'", s),
|
||||
Self::MalformedIdentifier(s) => {
|
||||
write!(f, "Variable name is not in a legal format: '{}'", s)
|
||||
}
|
||||
Self::InputError(s) => write!(f, "{}", s),
|
||||
_ => write!(f, "{}", self.description()),
|
||||
Self::UnterminatedString => write!(f, "Open string is not terminated"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -62,21 +56,34 @@ pub enum ParseErrorType {
|
||||
/// An open `{` is missing the corresponding closing `}`.
|
||||
MissingRightBrace(String),
|
||||
/// An open `[` is missing the corresponding closing `]`.
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
MissingRightBracket(String),
|
||||
/// An expression in function call arguments `()` has syntax error.
|
||||
MalformedCallExpr(String),
|
||||
/// An expression in indexing brackets `[]` has syntax error.
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
MalformedIndexExpr(String),
|
||||
/// Missing a variable name after the `let` keyword.
|
||||
VarExpectsIdentifier,
|
||||
/// Invalid expression assigned to constant.
|
||||
ForbiddenConstantExpr(String),
|
||||
/// Missing a variable name after the `let`, `const` or `for` keywords.
|
||||
VariableExpected,
|
||||
/// A `for` statement is missing the `in` keyword.
|
||||
MissingIn,
|
||||
/// Defining a function `fn` in an appropriate place (e.g. inside another function).
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
WrongFnDefinition,
|
||||
/// Missing a function name after the `fn` keyword.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
FnMissingName,
|
||||
/// A function definition is missing the parameters list. Wrapped value is the function name.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
FnMissingParams(String),
|
||||
/// Assignment to an inappropriate LHS (left-hand-side) expression.
|
||||
AssignmentToInvalidLHS,
|
||||
/// Assignment to a copy of a value.
|
||||
AssignmentToCopy,
|
||||
/// Assignment to an a constant variable.
|
||||
AssignmentToConstant(String),
|
||||
}
|
||||
|
||||
/// Error when parsing a script.
|
||||
@ -98,10 +105,8 @@ impl ParseError {
|
||||
pub fn position(&self) -> Position {
|
||||
self.1
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParseError {
|
||||
fn description(&self) -> &str {
|
||||
pub(crate) fn desc(&self) -> &str {
|
||||
match self.0 {
|
||||
ParseErrorType::BadInput(ref p) => p,
|
||||
ParseErrorType::InputPastEndOfFile => "Script is incomplete",
|
||||
@ -109,40 +114,64 @@ impl Error for ParseError {
|
||||
ParseErrorType::MissingRightParen(_) => "Expecting ')'",
|
||||
ParseErrorType::MissingLeftBrace => "Expecting '{'",
|
||||
ParseErrorType::MissingRightBrace(_) => "Expecting '}'",
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
ParseErrorType::MissingRightBracket(_) => "Expecting ']'",
|
||||
ParseErrorType::MalformedCallExpr(_) => "Invalid expression in function call arguments",
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
ParseErrorType::MalformedIndexExpr(_) => "Invalid index in indexing expression",
|
||||
ParseErrorType::VarExpectsIdentifier => "Expecting name of a variable",
|
||||
ParseErrorType::ForbiddenConstantExpr(_) => "Expecting a constant",
|
||||
ParseErrorType::MissingIn => "Expecting 'in'",
|
||||
ParseErrorType::VariableExpected => "Expecting name of a variable",
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
ParseErrorType::FnMissingName => "Expecting name in function declaration",
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
ParseErrorType::FnMissingParams(_) => "Expecting parameters in function declaration",
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
ParseErrorType::WrongFnDefinition => "Function definitions must be at top level and cannot be inside a block or another function",
|
||||
ParseErrorType::AssignmentToInvalidLHS => "Cannot assign to this expression because it will only be changing a copy of the value"
|
||||
ParseErrorType::AssignmentToInvalidLHS => "Cannot assign to this expression",
|
||||
ParseErrorType::AssignmentToCopy => "Cannot assign to this expression because it will only be changing a copy of the value",
|
||||
ParseErrorType::AssignmentToConstant(_) => "Cannot assign to a constant variable."
|
||||
}
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&dyn Error> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParseError {}
|
||||
|
||||
impl fmt::Display for ParseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.0 {
|
||||
ParseErrorType::BadInput(ref s)
|
||||
| ParseErrorType::MalformedIndexExpr(ref s)
|
||||
| ParseErrorType::MalformedCallExpr(ref s) => {
|
||||
write!(f, "{}", if s.is_empty() { self.description() } else { s })?
|
||||
ParseErrorType::BadInput(ref s) | ParseErrorType::MalformedCallExpr(ref s) => {
|
||||
write!(f, "{}", if s.is_empty() { self.desc() } else { s })?
|
||||
}
|
||||
ParseErrorType::UnknownOperator(ref s) => write!(f, "{}: '{}'", self.description(), s)?,
|
||||
ParseErrorType::ForbiddenConstantExpr(ref s) => {
|
||||
write!(f, "Expecting a constant to assign to '{}'", s)?
|
||||
}
|
||||
ParseErrorType::UnknownOperator(ref s) => write!(f, "{}: '{}'", self.desc(), s)?,
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
ParseErrorType::MalformedIndexExpr(ref s) => {
|
||||
write!(f, "{}", if s.is_empty() { self.desc() } else { s })?
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
ParseErrorType::FnMissingParams(ref s) => {
|
||||
write!(f, "Expecting parameters for function '{}'", s)?
|
||||
}
|
||||
ParseErrorType::MissingRightParen(ref s)
|
||||
| ParseErrorType::MissingRightBrace(ref s)
|
||||
| ParseErrorType::MissingRightBracket(ref s) => {
|
||||
write!(f, "{} for {}", self.description(), s)?
|
||||
|
||||
ParseErrorType::MissingRightParen(ref s) | ParseErrorType::MissingRightBrace(ref s) => {
|
||||
write!(f, "{} for {}", self.desc(), s)?
|
||||
}
|
||||
_ => write!(f, "{}", self.description())?,
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
ParseErrorType::MissingRightBracket(ref s) => write!(f, "{} for {}", self.desc(), s)?,
|
||||
|
||||
ParseErrorType::AssignmentToConstant(ref s) if s.is_empty() => {
|
||||
write!(f, "{}", self.desc())?
|
||||
}
|
||||
ParseErrorType::AssignmentToConstant(ref s) => {
|
||||
write!(f, "Cannot assign to constant '{}'", s)?
|
||||
}
|
||||
_ => write!(f, "{}", self.desc())?,
|
||||
}
|
||||
|
||||
if !self.1.is_eof() {
|
||||
|
@ -196,13 +196,10 @@ macro_rules! def_register {
|
||||
|
||||
// Call the user-supplied function using ($clone) to
|
||||
// potentially clone the value, otherwise pass the reference.
|
||||
match f($(($clone)($par)),*) {
|
||||
Ok(r) => Ok(Box::new(r) as Dynamic),
|
||||
Err(mut err) => {
|
||||
err.set_position(pos);
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
f($(($clone)($par)),*).map(|r| Box::new(r) as Dynamic).map_err(|mut err| {
|
||||
err.set_position(pos);
|
||||
err
|
||||
})
|
||||
};
|
||||
self.register_fn_raw(name, Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun));
|
||||
}
|
||||
|
10
src/lib.rs
10
src/lib.rs
@ -78,9 +78,15 @@ pub use call::FuncArgs;
|
||||
pub use engine::Engine;
|
||||
pub use error::{ParseError, ParseErrorType};
|
||||
pub use fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn};
|
||||
pub use parser::{Position, AST, FLOAT, INT};
|
||||
pub use parser::{Position, AST, INT};
|
||||
pub use result::EvalAltResult;
|
||||
pub use scope::Scope;
|
||||
pub use scope::{Scope, ScopeEntry, VariableType};
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
pub use engine::Array;
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
pub use parser::FLOAT;
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
pub use optimize::OptimizationLevel;
|
||||
|
385
src/optimize.rs
385
src/optimize.rs
@ -1,48 +1,108 @@
|
||||
use crate::engine::KEYWORD_DUMP_AST;
|
||||
use crate::parser::{Expr, Stmt};
|
||||
#![cfg(not(feature = "no_optimize"))]
|
||||
|
||||
fn optimize_stmt(stmt: Stmt, changed: &mut bool, preserve_result: bool) -> Stmt {
|
||||
use crate::any::Dynamic;
|
||||
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 std::sync::Arc;
|
||||
|
||||
/// 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,
|
||||
}
|
||||
|
||||
struct State<'a> {
|
||||
changed: bool,
|
||||
constants: Vec<(String, Expr)>,
|
||||
engine: Option<&'a Engine<'a>>,
|
||||
}
|
||||
|
||||
impl State<'_> {
|
||||
pub fn new() -> Self {
|
||||
State {
|
||||
changed: false,
|
||||
constants: vec![],
|
||||
engine: None,
|
||||
}
|
||||
}
|
||||
pub fn reset(&mut self) {
|
||||
self.changed = false;
|
||||
}
|
||||
pub fn set_dirty(&mut self) {
|
||||
self.changed = true;
|
||||
}
|
||||
pub fn is_dirty(&self) -> bool {
|
||||
self.changed
|
||||
}
|
||||
pub fn contains_constant(&self, name: &str) -> bool {
|
||||
self.constants.iter().any(|(n, _)| n == name)
|
||||
}
|
||||
pub fn restore_constants(&mut self, len: usize) {
|
||||
self.constants.truncate(len)
|
||||
}
|
||||
pub fn push_constant(&mut self, name: &str, value: Expr) {
|
||||
self.constants.push((name.to_string(), value))
|
||||
}
|
||||
pub fn find_constant(&self, name: &str) -> Option<&Expr> {
|
||||
for (n, expr) in self.constants.iter().rev() {
|
||||
if n == name {
|
||||
return Some(expr);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -> Stmt {
|
||||
match stmt {
|
||||
Stmt::IfElse(expr, stmt1, None) if stmt1.is_noop() => {
|
||||
*changed = true;
|
||||
state.set_dirty();
|
||||
|
||||
let pos = expr.position();
|
||||
let expr = optimize_expr(*expr, changed);
|
||||
let expr = optimize_expr(*expr, state);
|
||||
|
||||
match expr {
|
||||
Expr::False(_) | Expr::True(_) => Stmt::Noop(stmt1.position()),
|
||||
expr => {
|
||||
let stmt = Stmt::Expr(Box::new(expr));
|
||||
if matches!(expr, Expr::False(_) | Expr::True(_)) {
|
||||
Stmt::Noop(stmt1.position())
|
||||
} else {
|
||||
let stmt = Stmt::Expr(Box::new(expr));
|
||||
|
||||
if preserve_result {
|
||||
Stmt::Block(vec![stmt, *stmt1], pos)
|
||||
} else {
|
||||
stmt
|
||||
}
|
||||
if preserve_result {
|
||||
Stmt::Block(vec![stmt, *stmt1], pos)
|
||||
} else {
|
||||
stmt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Stmt::IfElse(expr, stmt1, None) => match *expr {
|
||||
Expr::False(pos) => {
|
||||
*changed = true;
|
||||
state.set_dirty();
|
||||
Stmt::Noop(pos)
|
||||
}
|
||||
Expr::True(_) => optimize_stmt(*stmt1, changed, true),
|
||||
Expr::True(_) => optimize_stmt(*stmt1, state, true),
|
||||
expr => Stmt::IfElse(
|
||||
Box::new(optimize_expr(expr, changed)),
|
||||
Box::new(optimize_stmt(*stmt1, changed, true)),
|
||||
Box::new(optimize_expr(expr, state)),
|
||||
Box::new(optimize_stmt(*stmt1, state, true)),
|
||||
None,
|
||||
),
|
||||
},
|
||||
|
||||
Stmt::IfElse(expr, stmt1, Some(stmt2)) => match *expr {
|
||||
Expr::False(_) => optimize_stmt(*stmt2, changed, true),
|
||||
Expr::True(_) => optimize_stmt(*stmt1, changed, true),
|
||||
Expr::False(_) => optimize_stmt(*stmt2, state, true),
|
||||
Expr::True(_) => optimize_stmt(*stmt1, state, true),
|
||||
expr => Stmt::IfElse(
|
||||
Box::new(optimize_expr(expr, changed)),
|
||||
Box::new(optimize_stmt(*stmt1, changed, true)),
|
||||
match optimize_stmt(*stmt2, changed, true) {
|
||||
Box::new(optimize_expr(expr, state)),
|
||||
Box::new(optimize_stmt(*stmt1, state, true)),
|
||||
match optimize_stmt(*stmt2, state, true) {
|
||||
stmt if stmt.is_noop() => None,
|
||||
stmt => Some(Box::new(stmt)),
|
||||
},
|
||||
@ -51,47 +111,51 @@ fn optimize_stmt(stmt: Stmt, changed: &mut bool, preserve_result: bool) -> Stmt
|
||||
|
||||
Stmt::While(expr, stmt) => match *expr {
|
||||
Expr::False(pos) => {
|
||||
*changed = true;
|
||||
state.set_dirty();
|
||||
Stmt::Noop(pos)
|
||||
}
|
||||
Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(*stmt, changed, false))),
|
||||
Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(*stmt, state, false))),
|
||||
expr => Stmt::While(
|
||||
Box::new(optimize_expr(expr, changed)),
|
||||
Box::new(optimize_stmt(*stmt, changed, false)),
|
||||
Box::new(optimize_expr(expr, state)),
|
||||
Box::new(optimize_stmt(*stmt, state, false)),
|
||||
),
|
||||
},
|
||||
|
||||
Stmt::Loop(stmt) => Stmt::Loop(Box::new(optimize_stmt(*stmt, changed, false))),
|
||||
Stmt::Loop(stmt) => Stmt::Loop(Box::new(optimize_stmt(*stmt, state, false))),
|
||||
Stmt::For(id, expr, stmt) => Stmt::For(
|
||||
id,
|
||||
Box::new(optimize_expr(*expr, changed)),
|
||||
Box::new(optimize_stmt(*stmt, changed, false)),
|
||||
Box::new(optimize_expr(*expr, state)),
|
||||
Box::new(optimize_stmt(*stmt, state, false)),
|
||||
),
|
||||
Stmt::Let(id, Some(expr), pos) => {
|
||||
Stmt::Let(id, Some(Box::new(optimize_expr(*expr, changed))), pos)
|
||||
Stmt::Let(id, Some(Box::new(optimize_expr(*expr, state))), pos)
|
||||
}
|
||||
Stmt::Let(_, None, _) => stmt,
|
||||
|
||||
Stmt::Block(statements, pos) => {
|
||||
let orig_len = statements.len();
|
||||
let orig_constants_len = state.constants.len();
|
||||
|
||||
let mut result: Vec<_> = statements
|
||||
.into_iter() // For each statement
|
||||
.rev() // Scan in reverse
|
||||
.map(|s| optimize_stmt(s, changed, preserve_result)) // Optimize the statement
|
||||
.map(|stmt| {
|
||||
if let Stmt::Const(name, value, pos) = stmt {
|
||||
state.push_constant(&name, *value);
|
||||
state.set_dirty();
|
||||
Stmt::Noop(pos) // No need to keep constants
|
||||
} else {
|
||||
optimize_stmt(stmt, state, preserve_result) // Optimize the statement
|
||||
}
|
||||
})
|
||||
.enumerate()
|
||||
.filter(|(i, s)| s.is_op() || (preserve_result && *i == 0)) // Remove no-op's but leave the last one if we need the result
|
||||
.map(|(_, s)| s)
|
||||
.rev()
|
||||
.filter(|(i, stmt)| stmt.is_op() || (preserve_result && *i == orig_len - 1)) // Remove no-op's but leave the last one if we need the result
|
||||
.map(|(_, stmt)| stmt)
|
||||
.collect();
|
||||
|
||||
// Remove all raw expression statements that are pure except for the very last statement
|
||||
let last_stmt = if preserve_result { result.pop() } else { None };
|
||||
|
||||
result.retain(|stmt| match stmt {
|
||||
Stmt::Expr(expr) if expr.is_pure() => false,
|
||||
_ => true,
|
||||
});
|
||||
result.retain(|stmt| !matches!(stmt, Stmt::Expr(expr) if expr.is_pure()));
|
||||
|
||||
if let Some(stmt) = last_stmt {
|
||||
result.push(stmt);
|
||||
@ -106,7 +170,6 @@ fn optimize_stmt(stmt: Stmt, changed: &mut bool, preserve_result: bool) -> Stmt
|
||||
match expr {
|
||||
Stmt::Let(_, None, _) => removed = true,
|
||||
Stmt::Let(_, Some(val_expr), _) if val_expr.is_pure() => removed = true,
|
||||
|
||||
_ => {
|
||||
result.push(expr);
|
||||
break;
|
||||
@ -123,59 +186,82 @@ fn optimize_stmt(stmt: Stmt, changed: &mut bool, preserve_result: bool) -> Stmt
|
||||
.into_iter()
|
||||
.rev()
|
||||
.enumerate()
|
||||
.map(|(i, s)| optimize_stmt(s, changed, i == 0)) // Optimize all other statements again
|
||||
.map(|(i, s)| optimize_stmt(s, state, i == 0)) // Optimize all other statements again
|
||||
.rev()
|
||||
.collect();
|
||||
}
|
||||
|
||||
*changed = *changed || orig_len != result.len();
|
||||
if orig_len != result.len() {
|
||||
state.set_dirty();
|
||||
}
|
||||
|
||||
state.restore_constants(orig_constants_len);
|
||||
|
||||
match result[..] {
|
||||
// No statements in block - change to No-op
|
||||
[] => {
|
||||
*changed = true;
|
||||
state.set_dirty();
|
||||
Stmt::Noop(pos)
|
||||
}
|
||||
// Only one statement - promote
|
||||
[_] => {
|
||||
*changed = true;
|
||||
state.set_dirty();
|
||||
result.remove(0)
|
||||
}
|
||||
_ => Stmt::Block(result, pos),
|
||||
}
|
||||
}
|
||||
|
||||
Stmt::Expr(expr) => Stmt::Expr(Box::new(optimize_expr(*expr, changed))),
|
||||
Stmt::Expr(expr) => Stmt::Expr(Box::new(optimize_expr(*expr, state))),
|
||||
|
||||
Stmt::ReturnWithVal(Some(expr), is_return, pos) => Stmt::ReturnWithVal(
|
||||
Some(Box::new(optimize_expr(*expr, changed))),
|
||||
is_return,
|
||||
pos,
|
||||
),
|
||||
Stmt::ReturnWithVal(Some(expr), is_return, pos) => {
|
||||
Stmt::ReturnWithVal(Some(Box::new(optimize_expr(*expr, state))), is_return, pos)
|
||||
}
|
||||
|
||||
stmt => stmt,
|
||||
}
|
||||
}
|
||||
|
||||
fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
|
||||
fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
||||
match expr {
|
||||
Expr::Stmt(stmt, pos) => match optimize_stmt(*stmt, changed, true) {
|
||||
Expr::Stmt(stmt, pos) => match optimize_stmt(*stmt, state, true) {
|
||||
Stmt::Noop(_) => {
|
||||
*changed = true;
|
||||
state.set_dirty();
|
||||
Expr::Unit(pos)
|
||||
}
|
||||
Stmt::Expr(expr) => {
|
||||
*changed = true;
|
||||
state.set_dirty();
|
||||
*expr
|
||||
}
|
||||
stmt => Expr::Stmt(Box::new(stmt), pos),
|
||||
},
|
||||
Expr::Assignment(id, expr, pos) => {
|
||||
Expr::Assignment(id, Box::new(optimize_expr(*expr, changed)), pos)
|
||||
}
|
||||
Expr::Assignment(id1, expr1, pos1) => match *expr1 {
|
||||
Expr::Assignment(id2, expr2, pos2) => match (*id1, *id2) {
|
||||
(Expr::Variable(var1, _), Expr::Variable(var2, _)) if var1 == var2 => {
|
||||
// Assignment to the same variable - fold
|
||||
state.set_dirty();
|
||||
|
||||
Expr::Assignment(
|
||||
Box::new(Expr::Variable(var1, pos1)),
|
||||
Box::new(optimize_expr(*expr2, state)),
|
||||
pos1,
|
||||
)
|
||||
}
|
||||
(id1, id2) => Expr::Assignment(
|
||||
Box::new(id1),
|
||||
Box::new(Expr::Assignment(
|
||||
Box::new(id2),
|
||||
Box::new(optimize_expr(*expr2, state)),
|
||||
pos2,
|
||||
)),
|
||||
pos1,
|
||||
),
|
||||
},
|
||||
expr => Expr::Assignment(id1, Box::new(optimize_expr(expr, state)), pos1),
|
||||
},
|
||||
Expr::Dot(lhs, rhs, pos) => Expr::Dot(
|
||||
Box::new(optimize_expr(*lhs, changed)),
|
||||
Box::new(optimize_expr(*rhs, changed)),
|
||||
Box::new(optimize_expr(*lhs, state)),
|
||||
Box::new(optimize_expr(*rhs, state)),
|
||||
pos,
|
||||
),
|
||||
|
||||
@ -184,19 +270,25 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
|
||||
(Expr::Array(mut items, _), Expr::IntegerConstant(i, _))
|
||||
if i >= 0 && (i as usize) < items.len() && items.iter().all(|x| x.is_pure()) =>
|
||||
{
|
||||
// Array where everything is a pure - promote the indexed item.
|
||||
// Array literal where everything is pure - promote the indexed item.
|
||||
// All other items can be thrown away.
|
||||
*changed = true;
|
||||
state.set_dirty();
|
||||
items.remove(i as usize)
|
||||
}
|
||||
(Expr::StringConstant(s, pos), Expr::IntegerConstant(i, _))
|
||||
if i >= 0 && (i as usize) < s.chars().count() =>
|
||||
{
|
||||
// String literal indexing - get the character
|
||||
state.set_dirty();
|
||||
Expr::CharConstant(s.chars().nth(i as usize).expect("should get char"), pos)
|
||||
}
|
||||
|
||||
(lhs, rhs) => Expr::Index(
|
||||
Box::new(optimize_expr(lhs, changed)),
|
||||
Box::new(optimize_expr(rhs, changed)),
|
||||
Box::new(optimize_expr(lhs, state)),
|
||||
Box::new(optimize_expr(rhs, state)),
|
||||
pos,
|
||||
),
|
||||
},
|
||||
#[cfg(feature = "no_index")]
|
||||
Expr::Index(_, _, _) => panic!("encountered an index expression during no_index!"),
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Array(items, pos) => {
|
||||
@ -204,95 +296,164 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
|
||||
|
||||
let items: Vec<_> = items
|
||||
.into_iter()
|
||||
.map(|expr| optimize_expr(expr, changed))
|
||||
.map(|expr| optimize_expr(expr, state))
|
||||
.collect();
|
||||
|
||||
*changed = *changed || orig_len != items.len();
|
||||
if orig_len != items.len() {
|
||||
state.set_dirty();
|
||||
}
|
||||
|
||||
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::True(_), rhs) => {
|
||||
*changed = true;
|
||||
state.set_dirty();
|
||||
rhs
|
||||
}
|
||||
(Expr::False(pos), _) => {
|
||||
*changed = true;
|
||||
state.set_dirty();
|
||||
Expr::False(pos)
|
||||
}
|
||||
(lhs, Expr::True(_)) => {
|
||||
*changed = true;
|
||||
state.set_dirty();
|
||||
lhs
|
||||
}
|
||||
(lhs, rhs) => Expr::And(
|
||||
Box::new(optimize_expr(lhs, changed)),
|
||||
Box::new(optimize_expr(rhs, changed)),
|
||||
Box::new(optimize_expr(lhs, state)),
|
||||
Box::new(optimize_expr(rhs, state)),
|
||||
),
|
||||
},
|
||||
Expr::Or(lhs, rhs) => match (*lhs, *rhs) {
|
||||
(Expr::False(_), rhs) => {
|
||||
*changed = true;
|
||||
state.set_dirty();
|
||||
rhs
|
||||
}
|
||||
(Expr::True(pos), _) => {
|
||||
*changed = true;
|
||||
state.set_dirty();
|
||||
Expr::True(pos)
|
||||
}
|
||||
(lhs, Expr::False(_)) => {
|
||||
*changed = true;
|
||||
state.set_dirty();
|
||||
lhs
|
||||
}
|
||||
(lhs, rhs) => Expr::Or(
|
||||
Box::new(optimize_expr(lhs, changed)),
|
||||
Box::new(optimize_expr(rhs, changed)),
|
||||
Box::new(optimize_expr(lhs, state)),
|
||||
Box::new(optimize_expr(rhs, state)),
|
||||
),
|
||||
},
|
||||
|
||||
// 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)
|
||||
}
|
||||
// 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();
|
||||
engine.call_ext_fn_raw(&id, call_args, pos).ok().map(|r|
|
||||
r.or(def_value.clone()).and_then(|result| map_dynamic_to_expr(result, pos).0)
|
||||
.map(|expr| {
|
||||
state.set_dirty();
|
||||
expr
|
||||
})).flatten()
|
||||
.unwrap_or_else(|| Expr::FunctionCall(id, args, def_value, pos))
|
||||
}
|
||||
// Optimize the function call arguments
|
||||
Expr::FunctionCall(id, args, def_value, pos) => {
|
||||
let orig_len = args.len();
|
||||
|
||||
let args: Vec<_> = args
|
||||
.into_iter()
|
||||
.map(|a| optimize_expr(a, changed))
|
||||
.collect();
|
||||
let args: Vec<_> = args.into_iter().map(|a| optimize_expr(a, state)).collect();
|
||||
|
||||
*changed = *changed || orig_len != args.len();
|
||||
if orig_len != args.len() {
|
||||
state.set_dirty();
|
||||
}
|
||||
|
||||
Expr::FunctionCall(id, args, def_value, pos)
|
||||
}
|
||||
|
||||
Expr::Variable(ref name, _) if state.contains_constant(name) => {
|
||||
state.set_dirty();
|
||||
|
||||
// Replace constant with value
|
||||
state
|
||||
.find_constant(name)
|
||||
.expect("should find constant in scope!")
|
||||
.clone()
|
||||
}
|
||||
|
||||
expr => expr,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn optimize(statements: Vec<Stmt>) -> 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;
|
||||
|
||||
loop {
|
||||
let mut changed = false;
|
||||
state.reset();
|
||||
state.restore_constants(orig_constants_len);
|
||||
|
||||
let num_statements = result.len();
|
||||
|
||||
result = result
|
||||
.into_iter()
|
||||
.rev() // Scan in reverse
|
||||
.enumerate()
|
||||
.map(|(i, stmt)| {
|
||||
// Keep all variable declarations at this level
|
||||
let keep = stmt.is_var();
|
||||
if let Stmt::Const(name, value, _) = &stmt {
|
||||
// Load constants
|
||||
state.push_constant(name, value.as_ref().clone());
|
||||
stmt // Keep it in the top scope
|
||||
} else {
|
||||
// Keep all variable declarations at this level
|
||||
// and always keep the last return value
|
||||
let keep = stmt.is_var() || i == num_statements - 1;
|
||||
|
||||
// Always keep the last return value
|
||||
optimize_stmt(stmt, &mut changed, keep || i == 0)
|
||||
optimize_stmt(stmt, &mut state, keep)
|
||||
}
|
||||
})
|
||||
.rev()
|
||||
.collect();
|
||||
|
||||
if !changed {
|
||||
if !state.is_dirty() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -301,10 +462,7 @@ pub(crate) fn optimize(statements: Vec<Stmt>) -> Vec<Stmt> {
|
||||
let last_stmt = result.pop();
|
||||
|
||||
// Remove all pure statements at top level
|
||||
result.retain(|stmt| match stmt {
|
||||
Stmt::Expr(expr) if expr.is_pure() => false,
|
||||
_ => true,
|
||||
});
|
||||
result.retain(|stmt| !matches!(stmt, Stmt::Expr(expr) if expr.is_pure()));
|
||||
|
||||
if let Some(stmt) = last_stmt {
|
||||
result.push(stmt); // Add back the last statement
|
||||
@ -312,3 +470,32 @@ pub(crate) fn optimize(statements: Vec<Stmt>) -> Vec<Stmt> {
|
||||
|
||||
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(),
|
||||
)
|
||||
}
|
||||
|
515
src/parser.rs
515
src/parser.rs
@ -1,8 +1,12 @@
|
||||
//! 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::optimize::optimize;
|
||||
use crate::scope::{Scope, VariableType};
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
use crate::optimize::optimize_ast;
|
||||
|
||||
use std::{
|
||||
borrow::Cow, char, cmp::Ordering, fmt, iter::Peekable, str::Chars, str::FromStr, sync::Arc,
|
||||
@ -22,6 +26,7 @@ pub type INT = i64;
|
||||
pub type INT = i32;
|
||||
|
||||
/// The system floating-point type
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
pub type FLOAT = f64;
|
||||
|
||||
type LERR = LexError;
|
||||
@ -143,12 +148,9 @@ impl fmt::Debug for Position {
|
||||
|
||||
/// Compiled AST (abstract syntax tree) of a Rhai script.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AST(
|
||||
pub(crate) Vec<Stmt>,
|
||||
#[cfg(not(feature = "no_function"))] pub(crate) Vec<Arc<FnDef>>,
|
||||
);
|
||||
pub struct AST(pub(crate) Vec<Stmt>, pub(crate) Vec<Arc<FnDef>>);
|
||||
|
||||
#[derive(Debug)] // Do not derive Clone because it is expensive
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FnDef {
|
||||
pub name: String,
|
||||
pub params: Vec<String>,
|
||||
@ -179,6 +181,7 @@ pub enum Stmt {
|
||||
Loop(Box<Stmt>),
|
||||
For(String, Box<Expr>, Box<Stmt>),
|
||||
Let(String, Option<Box<Expr>>, Position),
|
||||
Const(String, Box<Expr>, Position),
|
||||
Block(Vec<Stmt>, Position),
|
||||
Expr(Box<Expr>),
|
||||
Break(Position),
|
||||
@ -187,30 +190,22 @@ pub enum Stmt {
|
||||
|
||||
impl Stmt {
|
||||
pub fn is_noop(&self) -> bool {
|
||||
match self {
|
||||
Stmt::Noop(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
matches!(self, Stmt::Noop(_))
|
||||
}
|
||||
|
||||
pub fn is_op(&self) -> bool {
|
||||
match self {
|
||||
Stmt::Noop(_) => false,
|
||||
_ => true,
|
||||
}
|
||||
!matches!(self, Stmt::Noop(_))
|
||||
}
|
||||
|
||||
pub fn is_var(&self) -> bool {
|
||||
match self {
|
||||
Stmt::Let(_, _, _) => true,
|
||||
_ => false,
|
||||
}
|
||||
matches!(self, Stmt::Let(_, _, _))
|
||||
}
|
||||
|
||||
pub fn position(&self) -> Position {
|
||||
match self {
|
||||
Stmt::Noop(pos)
|
||||
| Stmt::Let(_, _, pos)
|
||||
| Stmt::Const(_, _, pos)
|
||||
| Stmt::Block(_, pos)
|
||||
| Stmt::Break(pos)
|
||||
| Stmt::ReturnWithVal(_, _, pos) => *pos,
|
||||
@ -225,14 +220,17 @@ pub enum Expr {
|
||||
IntegerConstant(INT, Position),
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
FloatConstant(FLOAT, Position),
|
||||
Identifier(String, Position),
|
||||
Variable(String, Position),
|
||||
Property(String, Position),
|
||||
CharConstant(char, Position),
|
||||
StringConstant(String, Position),
|
||||
Stmt(Box<Stmt>, Position),
|
||||
FunctionCall(String, Vec<Expr>, Option<Dynamic>, Position),
|
||||
Assignment(Box<Expr>, Box<Expr>, Position),
|
||||
Dot(Box<Expr>, Box<Expr>, Position),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Index(Box<Expr>, Box<Expr>, Position),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Array(Vec<Expr>, Position),
|
||||
And(Box<Expr>, Box<Expr>),
|
||||
Or(Box<Expr>, Box<Expr>),
|
||||
@ -242,27 +240,73 @@ pub enum Expr {
|
||||
}
|
||||
|
||||
impl Expr {
|
||||
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 {
|
||||
Expr::IntegerConstant(i, _) => i.to_string(),
|
||||
Expr::CharConstant(c, _) => c.to_string(),
|
||||
Expr::StringConstant(_, _) => "string".to_string(),
|
||||
Expr::True(_) => "true".to_string(),
|
||||
Expr::False(_) => "false".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"))]
|
||||
Expr::FloatConstant(f, _) => f.to_string(),
|
||||
|
||||
_ => panic!("cannot get value of non-constant expression"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn position(&self) -> Position {
|
||||
match self {
|
||||
Expr::IntegerConstant(_, pos)
|
||||
| Expr::Identifier(_, pos)
|
||||
| Expr::CharConstant(_, pos)
|
||||
| Expr::StringConstant(_, pos)
|
||||
| Expr::Variable(_, pos)
|
||||
| Expr::Property(_, pos)
|
||||
| Expr::Stmt(_, pos)
|
||||
| Expr::FunctionCall(_, _, _, pos)
|
||||
| Expr::Array(_, pos)
|
||||
| Expr::True(pos)
|
||||
| Expr::False(pos)
|
||||
| Expr::Unit(pos) => *pos,
|
||||
|
||||
Expr::Assignment(e, _, _)
|
||||
| Expr::Dot(e, _, _)
|
||||
| Expr::Index(e, _, _)
|
||||
| Expr::And(e, _)
|
||||
| Expr::Or(e, _) => e.position(),
|
||||
Expr::Assignment(e, _, _) | Expr::Dot(e, _, _) | Expr::And(e, _) | Expr::Or(e, _) => {
|
||||
e.position()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Expr::FloatConstant(_, pos) => *pos,
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Array(_, pos) => *pos,
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(e, _, _) => e.position(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -271,9 +315,15 @@ impl Expr {
|
||||
/// A pure expression has no side effects.
|
||||
pub fn is_pure(&self) -> bool {
|
||||
match self {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
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(),
|
||||
expr => expr.is_constant() || expr.is_identifier(),
|
||||
|
||||
#[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(_, _)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -289,12 +339,9 @@ impl Expr {
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Expr::FloatConstant(_, _) => true,
|
||||
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
pub fn is_identifier(&self) -> bool {
|
||||
match self {
|
||||
Expr::Identifier(_, _) => true,
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Array(expressions, _) => expressions.iter().all(Expr::is_constant),
|
||||
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@ -312,7 +359,9 @@ pub enum Token {
|
||||
RightBrace,
|
||||
LeftParen,
|
||||
RightParen,
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
LeftBracket,
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
RightBracket,
|
||||
Plus,
|
||||
UnaryPlus,
|
||||
@ -328,6 +377,7 @@ pub enum Token {
|
||||
True,
|
||||
False,
|
||||
Let,
|
||||
Const,
|
||||
If,
|
||||
Else,
|
||||
While,
|
||||
@ -343,6 +393,7 @@ pub enum Token {
|
||||
Or,
|
||||
Ampersand,
|
||||
And,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Fn,
|
||||
Break,
|
||||
Return,
|
||||
@ -386,7 +437,9 @@ impl Token {
|
||||
RightBrace => "}",
|
||||
LeftParen => "(",
|
||||
RightParen => ")",
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
LeftBracket => "[",
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
RightBracket => "]",
|
||||
Plus => "+",
|
||||
UnaryPlus => "+",
|
||||
@ -402,6 +455,7 @@ impl Token {
|
||||
True => "true",
|
||||
False => "false",
|
||||
Let => "let",
|
||||
Const => "const",
|
||||
If => "if",
|
||||
Else => "else",
|
||||
While => "while",
|
||||
@ -417,6 +471,7 @@ impl Token {
|
||||
Or => "||",
|
||||
Ampersand => "&",
|
||||
And => "&&",
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Fn => "fn",
|
||||
Break => "break",
|
||||
Return => "return",
|
||||
@ -456,8 +511,6 @@ impl Token {
|
||||
// RightBrace | {expr} - expr not unary & is closing
|
||||
LeftParen | // {-expr} - is unary
|
||||
// RightParen | (expr) - expr not unary & is closing
|
||||
LeftBracket | // [-expr] - is unary
|
||||
// RightBracket | [expr] - expr not unary & is closing
|
||||
Plus |
|
||||
UnaryPlus |
|
||||
Minus |
|
||||
@ -501,6 +554,10 @@ impl Token {
|
||||
In |
|
||||
PowerOfAssign => true,
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
LeftBracket => true, // [-expr] - is unary
|
||||
// RightBracket | [expr] - expr not unary & is closing
|
||||
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@ -510,9 +567,12 @@ impl Token {
|
||||
use self::Token::*;
|
||||
|
||||
match *self {
|
||||
RightBrace | RightParen | RightBracket | Plus | Minus | Multiply | Divide | Comma
|
||||
| Equals | LessThan | GreaterThan | LessThanEqualsTo | GreaterThanEqualsTo
|
||||
| EqualsTo | NotEqualsTo | Pipe | Or | Ampersand | And | PowerOf => true,
|
||||
RightParen | Plus | Minus | Multiply | Divide | Comma | Equals | LessThan
|
||||
| GreaterThan | LessThanEqualsTo | GreaterThanEqualsTo | EqualsTo | NotEqualsTo
|
||||
| Pipe | Or | Ampersand | And | PowerOf => true,
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
RightBrace | RightBracket => true,
|
||||
|
||||
_ => false,
|
||||
}
|
||||
@ -822,13 +882,15 @@ impl<'a> TokenIterator<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
let out: String = result.iter().collect();
|
||||
let has_letter = result.iter().any(char::is_ascii_alphabetic);
|
||||
let identifier: String = result.iter().collect();
|
||||
|
||||
return Some((
|
||||
match out.as_str() {
|
||||
match identifier.as_str() {
|
||||
"true" => Token::True,
|
||||
"false" => Token::False,
|
||||
"let" => Token::Let,
|
||||
"const" => Token::Const,
|
||||
"if" => Token::If,
|
||||
"else" => Token::Else,
|
||||
"while" => Token::While,
|
||||
@ -836,10 +898,15 @@ impl<'a> TokenIterator<'a> {
|
||||
"break" => Token::Break,
|
||||
"return" => Token::Return,
|
||||
"throw" => Token::Throw,
|
||||
"fn" => Token::Fn,
|
||||
"for" => Token::For,
|
||||
"in" => Token::In,
|
||||
_ => Token::Identifier(out),
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
"fn" => Token::Fn,
|
||||
|
||||
_ if has_letter => Token::Identifier(identifier),
|
||||
|
||||
_ => Token::LexError(LERR::MalformedIdentifier(identifier)),
|
||||
},
|
||||
pos,
|
||||
));
|
||||
@ -873,8 +940,12 @@ impl<'a> TokenIterator<'a> {
|
||||
'}' => return Some((Token::RightBrace, pos)),
|
||||
'(' => return Some((Token::LeftParen, pos)),
|
||||
')' => return Some((Token::RightParen, pos)),
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
'[' => return Some((Token::LeftBracket, pos)),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
']' => return Some((Token::RightBracket, pos)),
|
||||
|
||||
'+' => {
|
||||
return Some((
|
||||
match self.char_stream.peek() {
|
||||
@ -1146,7 +1217,7 @@ pub fn lex(input: &str) -> TokenIterator<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_precedence(token: &Token) -> i8 {
|
||||
fn get_precedence(token: &Token) -> u8 {
|
||||
match *token {
|
||||
Token::Equals
|
||||
| Token::PlusAssign
|
||||
@ -1161,28 +1232,49 @@ fn get_precedence(token: &Token) -> i8 {
|
||||
| Token::ModuloAssign
|
||||
| Token::PowerOfAssign => 10,
|
||||
|
||||
Token::Or | Token::XOr | Token::Pipe => 11,
|
||||
Token::Or | Token::XOr | Token::Pipe => 50,
|
||||
|
||||
Token::And | Token::Ampersand => 12,
|
||||
Token::And | Token::Ampersand => 60,
|
||||
|
||||
Token::LessThan
|
||||
| Token::LessThanEqualsTo
|
||||
| Token::GreaterThan
|
||||
| Token::GreaterThanEqualsTo
|
||||
| Token::EqualsTo
|
||||
| Token::NotEqualsTo => 15,
|
||||
| Token::NotEqualsTo => 70,
|
||||
|
||||
Token::Plus | Token::Minus => 20,
|
||||
Token::Plus | Token::Minus => 80,
|
||||
|
||||
Token::Divide | Token::Multiply | Token::PowerOf => 40,
|
||||
Token::Divide | Token::Multiply | Token::PowerOf => 90,
|
||||
|
||||
Token::LeftShift | Token::RightShift => 50,
|
||||
Token::LeftShift | Token::RightShift => 100,
|
||||
|
||||
Token::Modulo => 60,
|
||||
Token::Modulo => 110,
|
||||
|
||||
Token::Period => 100,
|
||||
Token::Period => 120,
|
||||
|
||||
_ => -1,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_bind_right(token: &Token) -> bool {
|
||||
match *token {
|
||||
Token::Equals
|
||||
| Token::PlusAssign
|
||||
| Token::MinusAssign
|
||||
| Token::MultiplyAssign
|
||||
| Token::DivideAssign
|
||||
| Token::LeftShiftAssign
|
||||
| Token::RightShiftAssign
|
||||
| Token::AndAssign
|
||||
| Token::OrAssign
|
||||
| Token::XOrAssign
|
||||
| Token::ModuloAssign
|
||||
| Token::PowerOfAssign => true,
|
||||
|
||||
Token::Period => true,
|
||||
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1360,10 +1452,10 @@ fn parse_ident_expr<'a>(
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Some(&(Token::LeftBracket, pos)) => {
|
||||
input.next();
|
||||
parse_index_expr(Box::new(Expr::Identifier(id, begin)), input, pos)
|
||||
parse_index_expr(Box::new(Expr::Variable(id, begin)), input, pos)
|
||||
}
|
||||
Some(_) => Ok(Expr::Identifier(id, begin)),
|
||||
None => Ok(Expr::Identifier(id, Position::eof())),
|
||||
Some(_) => Ok(Expr::Variable(id, begin)),
|
||||
None => Ok(Expr::Variable(id, Position::eof())),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1522,37 +1614,75 @@ fn parse_unary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pars
|
||||
}
|
||||
|
||||
fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result<Expr, ParseError> {
|
||||
fn valid_assignment_chain(expr: &Expr) -> (bool, Position) {
|
||||
fn valid_assignment_chain(expr: &Expr, is_top: bool) -> Option<ParseError> {
|
||||
match expr {
|
||||
Expr::Identifier(_, pos) => (true, *pos),
|
||||
Expr::Variable(_, _) => {
|
||||
assert!(is_top, "property expected but gets variable");
|
||||
None
|
||||
}
|
||||
Expr::Property(_, _) => {
|
||||
assert!(!is_top, "variable expected but gets property");
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(idx_lhs, _, _) if idx_lhs.is_identifier() => (true, idx_lhs.position()),
|
||||
Expr::Index(idx_lhs, _, _) if matches!(idx_lhs.as_ref(), &Expr::Variable(_, _)) => {
|
||||
assert!(is_top, "property expected but gets variable");
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(idx_lhs, _, _) => (false, idx_lhs.position()),
|
||||
Expr::Index(idx_lhs, _, _) if matches!(idx_lhs.as_ref(), &Expr::Property(_, _)) => {
|
||||
assert!(!is_top, "variable expected but gets property");
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(idx_lhs, _, pos) => Some(ParseError::new(
|
||||
match idx_lhs.as_ref() {
|
||||
Expr::Index(_, _, _) => ParseErrorType::AssignmentToCopy,
|
||||
_ => ParseErrorType::AssignmentToInvalidLHS,
|
||||
},
|
||||
*pos,
|
||||
)),
|
||||
|
||||
Expr::Dot(dot_lhs, dot_rhs, _) => match dot_lhs.as_ref() {
|
||||
Expr::Identifier(_, _) => valid_assignment_chain(dot_rhs),
|
||||
Expr::Variable(_, _) if is_top => valid_assignment_chain(dot_rhs, false),
|
||||
Expr::Property(_, _) if !is_top => valid_assignment_chain(dot_rhs, false),
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(idx_lhs, _, _) if idx_lhs.is_identifier() => {
|
||||
valid_assignment_chain(dot_rhs)
|
||||
Expr::Index(idx_lhs, _, _)
|
||||
if matches!(idx_lhs.as_ref(), &Expr::Variable(_, _)) && is_top =>
|
||||
{
|
||||
valid_assignment_chain(dot_rhs, false)
|
||||
}
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(idx_lhs, _, _) => (false, idx_lhs.position()),
|
||||
Expr::Index(idx_lhs, _, _)
|
||||
if matches!(idx_lhs.as_ref(), &Expr::Property(_, _)) && !is_top =>
|
||||
{
|
||||
valid_assignment_chain(dot_rhs, false)
|
||||
}
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(idx_lhs, _, _) => Some(ParseError::new(
|
||||
ParseErrorType::AssignmentToCopy,
|
||||
idx_lhs.position(),
|
||||
)),
|
||||
|
||||
_ => (false, dot_lhs.position()),
|
||||
expr => panic!("unexpected dot expression {:#?}", expr),
|
||||
},
|
||||
|
||||
_ => (false, expr.position()),
|
||||
_ => Some(ParseError::new(
|
||||
ParseErrorType::AssignmentToInvalidLHS,
|
||||
expr.position(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
//println!("{:#?} = {:#?}", lhs, rhs);
|
||||
|
||||
match valid_assignment_chain(&lhs) {
|
||||
(true, _) => Ok(Expr::Assignment(Box::new(lhs), Box::new(rhs), pos)),
|
||||
(false, pos) => Err(ParseError::new(PERR::AssignmentToInvalidLHS, pos)),
|
||||
match valid_assignment_chain(&lhs, true) {
|
||||
None => Ok(Expr::Assignment(Box::new(lhs), Box::new(rhs), pos)),
|
||||
Some(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1573,39 +1703,47 @@ fn parse_op_assignment(
|
||||
|
||||
fn parse_binary_op<'a>(
|
||||
input: &mut Peekable<TokenIterator<'a>>,
|
||||
precedence: i8,
|
||||
parent_precedence: u8,
|
||||
lhs: Expr,
|
||||
) -> Result<Expr, ParseError> {
|
||||
let mut current_lhs = lhs;
|
||||
|
||||
loop {
|
||||
let mut current_precedence = -1;
|
||||
let (current_precedence, bind_right) = if let Some(&(ref current_op, _)) = input.peek() {
|
||||
(get_precedence(current_op), is_bind_right(current_op))
|
||||
} else {
|
||||
(0, false)
|
||||
};
|
||||
|
||||
if let Some(&(ref current_op, _)) = input.peek() {
|
||||
current_precedence = get_precedence(current_op);
|
||||
}
|
||||
|
||||
if current_precedence < precedence {
|
||||
// Bind left to the parent lhs expression if precedence is higher
|
||||
// If same precedence, then check if the operator binds right
|
||||
if current_precedence < parent_precedence
|
||||
|| (current_precedence == parent_precedence && !bind_right)
|
||||
{
|
||||
return Ok(current_lhs);
|
||||
}
|
||||
|
||||
if let Some((op_token, pos)) = input.next() {
|
||||
input.peek();
|
||||
|
||||
let mut rhs = parse_unary(input)?;
|
||||
let rhs = parse_unary(input)?;
|
||||
|
||||
let mut next_precedence = -1;
|
||||
let next_precedence = if let Some(&(ref next_op, _)) = input.peek() {
|
||||
get_precedence(next_op)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
if let Some(&(ref next_op, _)) = input.peek() {
|
||||
next_precedence = get_precedence(next_op);
|
||||
}
|
||||
|
||||
if current_precedence < next_precedence {
|
||||
rhs = parse_binary_op(input, current_precedence + 1, rhs)?;
|
||||
} else if current_precedence >= 100 {
|
||||
// Always bind right to left for precedence over 100
|
||||
rhs = parse_binary_op(input, current_precedence, rhs)?;
|
||||
}
|
||||
// Bind to right if the next operator has higher precedence
|
||||
// If same precedence, then check if the operator binds right
|
||||
let rhs = if (current_precedence == next_precedence && bind_right)
|
||||
|| current_precedence < next_precedence
|
||||
{
|
||||
parse_binary_op(input, current_precedence, rhs)?
|
||||
} else {
|
||||
// Otherwise bind to left (even if next operator has the same precedence)
|
||||
rhs
|
||||
};
|
||||
|
||||
current_lhs = match op_token {
|
||||
Token::Plus => Expr::FunctionCall("+".into(), vec![current_lhs, rhs], None, pos),
|
||||
@ -1619,7 +1757,29 @@ fn parse_binary_op<'a>(
|
||||
Token::PlusAssign => parse_op_assignment("+", current_lhs, rhs, pos)?,
|
||||
Token::MinusAssign => parse_op_assignment("-", current_lhs, rhs, pos)?,
|
||||
|
||||
Token::Period => Expr::Dot(Box::new(current_lhs), Box::new(rhs), pos),
|
||||
Token::Period => {
|
||||
fn change_var_to_property(expr: Expr) -> Expr {
|
||||
match expr {
|
||||
Expr::Dot(lhs, rhs, pos) => Expr::Dot(
|
||||
Box::new(change_var_to_property(*lhs)),
|
||||
Box::new(change_var_to_property(*rhs)),
|
||||
pos,
|
||||
),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(lhs, idx, pos) => {
|
||||
Expr::Index(Box::new(change_var_to_property(*lhs)), idx, pos)
|
||||
}
|
||||
Expr::Variable(s, pos) => Expr::Property(s, pos),
|
||||
expr => expr,
|
||||
}
|
||||
}
|
||||
|
||||
Expr::Dot(
|
||||
Box::new(current_lhs),
|
||||
Box::new(change_var_to_property(rhs)),
|
||||
pos,
|
||||
)
|
||||
}
|
||||
|
||||
// Comparison operators default to false when passed invalid operands
|
||||
Token::EqualsTo => Expr::FunctionCall(
|
||||
@ -1696,7 +1856,7 @@ fn parse_binary_op<'a>(
|
||||
|
||||
fn parse_expr<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, ParseError> {
|
||||
let lhs = parse_unary(input)?;
|
||||
parse_binary_op(input, 0, lhs)
|
||||
parse_binary_op(input, 1, lhs)
|
||||
}
|
||||
|
||||
fn parse_if<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
|
||||
@ -1709,9 +1869,10 @@ fn parse_if<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseEr
|
||||
Some(&(Token::Else, _)) => {
|
||||
input.next();
|
||||
|
||||
let else_body = match input.peek() {
|
||||
Some(&(Token::If, _)) => parse_if(input)?,
|
||||
_ => parse_block(input)?,
|
||||
let else_body = if matches!(input.peek(), Some(&(Token::If, _))) {
|
||||
parse_if(input)?
|
||||
} else {
|
||||
parse_block(input)?
|
||||
};
|
||||
|
||||
Ok(Stmt::IfElse(
|
||||
@ -1746,14 +1907,17 @@ fn parse_for<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseE
|
||||
|
||||
let name = match input.next() {
|
||||
Some((Token::Identifier(s), _)) => s,
|
||||
Some((_, pos)) => return Err(ParseError::new(PERR::VarExpectsIdentifier, pos)),
|
||||
None => return Err(ParseError::new(PERR::VarExpectsIdentifier, Position::eof())),
|
||||
Some((Token::LexError(s), pos)) => {
|
||||
return Err(ParseError::new(PERR::BadInput(s.to_string()), pos))
|
||||
}
|
||||
Some((_, pos)) => return Err(ParseError::new(PERR::VariableExpected, pos)),
|
||||
None => return Err(ParseError::new(PERR::VariableExpected, Position::eof())),
|
||||
};
|
||||
|
||||
match input.next() {
|
||||
Some((Token::In, _)) => {}
|
||||
Some((_, pos)) => return Err(ParseError::new(PERR::VarExpectsIdentifier, pos)),
|
||||
None => return Err(ParseError::new(PERR::VarExpectsIdentifier, Position::eof())),
|
||||
Some((Token::In, _)) => (),
|
||||
Some((_, pos)) => return Err(ParseError::new(PERR::MissingIn, pos)),
|
||||
None => return Err(ParseError::new(PERR::MissingIn, Position::eof())),
|
||||
}
|
||||
|
||||
let expr = parse_expr(input)?;
|
||||
@ -1763,7 +1927,10 @@ fn parse_for<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseE
|
||||
Ok(Stmt::For(name, Box::new(expr), Box::new(body)))
|
||||
}
|
||||
|
||||
fn parse_var<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
|
||||
fn parse_var<'a>(
|
||||
input: &mut Peekable<TokenIterator<'a>>,
|
||||
var_type: VariableType,
|
||||
) -> Result<Stmt, ParseError> {
|
||||
let pos = match input.next() {
|
||||
Some((_, tok_pos)) => tok_pos,
|
||||
_ => return Err(ParseError::new(PERR::InputPastEndOfFile, Position::eof())),
|
||||
@ -1771,17 +1938,31 @@ fn parse_var<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseE
|
||||
|
||||
let name = match input.next() {
|
||||
Some((Token::Identifier(s), _)) => s,
|
||||
Some((_, pos)) => return Err(ParseError::new(PERR::VarExpectsIdentifier, pos)),
|
||||
None => return Err(ParseError::new(PERR::VarExpectsIdentifier, Position::eof())),
|
||||
Some((Token::LexError(s), pos)) => {
|
||||
return Err(ParseError::new(PERR::BadInput(s.to_string()), pos))
|
||||
}
|
||||
Some((_, pos)) => return Err(ParseError::new(PERR::VariableExpected, pos)),
|
||||
None => return Err(ParseError::new(PERR::VariableExpected, Position::eof())),
|
||||
};
|
||||
|
||||
match input.peek() {
|
||||
Some(&(Token::Equals, _)) => {
|
||||
input.next();
|
||||
let init_value = parse_expr(input)?;
|
||||
Ok(Stmt::Let(name, Some(Box::new(init_value)), pos))
|
||||
if matches!(input.peek(), Some(&(Token::Equals, _))) {
|
||||
input.next();
|
||||
let init_value = parse_expr(input)?;
|
||||
|
||||
match var_type {
|
||||
VariableType::Normal => Ok(Stmt::Let(name, Some(Box::new(init_value)), pos)),
|
||||
|
||||
VariableType::Constant if init_value.is_constant() => {
|
||||
Ok(Stmt::Const(name, Box::new(init_value), pos))
|
||||
}
|
||||
// Constants require a constant expression
|
||||
VariableType::Constant => Err(ParseError(
|
||||
PERR::ForbiddenConstantExpr(name.to_string()),
|
||||
init_value.position(),
|
||||
)),
|
||||
}
|
||||
_ => Ok(Stmt::Let(name, None, pos)),
|
||||
} else {
|
||||
Ok(Stmt::Let(name, None, pos))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1796,6 +1977,8 @@ fn parse_block<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Pars
|
||||
|
||||
match input.peek() {
|
||||
Some(&(Token::RightBrace, _)) => (), // empty block
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Some(&(Token::Fn, pos)) => return Err(ParseError::new(PERR::WrongFnDefinition, pos)),
|
||||
|
||||
_ => {
|
||||
@ -1849,7 +2032,7 @@ fn parse_stmt<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Parse
|
||||
let return_type = match token {
|
||||
Token::Return => ReturnType::Return,
|
||||
Token::Throw => ReturnType::Exception,
|
||||
_ => panic!("unexpected token!"),
|
||||
_ => panic!("token should be return or throw"),
|
||||
};
|
||||
|
||||
input.next();
|
||||
@ -1867,7 +2050,8 @@ fn parse_stmt<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Parse
|
||||
}
|
||||
}
|
||||
Some(&(Token::LeftBrace, _)) => parse_block(input),
|
||||
Some(&(Token::Let, _)) => parse_var(input),
|
||||
Some(&(Token::Let, _)) => parse_var(input, VariableType::Normal),
|
||||
Some(&(Token::Const, _)) => parse_var(input, VariableType::Constant),
|
||||
_ => parse_expr_stmt(input),
|
||||
}
|
||||
}
|
||||
@ -1900,11 +2084,10 @@ fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseE
|
||||
|
||||
let mut params = Vec::new();
|
||||
|
||||
match input.peek() {
|
||||
Some(&(Token::RightParen, _)) => {
|
||||
input.next();
|
||||
}
|
||||
_ => loop {
|
||||
if matches!(input.peek(), Some(&(Token::RightParen, _))) {
|
||||
input.next();
|
||||
} else {
|
||||
loop {
|
||||
match input.next() {
|
||||
Some((Token::RightParen, _)) => break,
|
||||
Some((Token::Comma, _)) => (),
|
||||
@ -1928,7 +2111,7 @@ fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseE
|
||||
))
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let body = parse_block(input)?;
|
||||
@ -1941,13 +2124,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>>,
|
||||
optimize_ast: bool,
|
||||
) -> Result<AST, ParseError> {
|
||||
) -> Result<(Vec<Stmt>, Vec<FnDef>), ParseError> {
|
||||
let mut statements = Vec::<Stmt>::new();
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
let mut functions = Vec::<FnDef>::new();
|
||||
|
||||
while input.peek().is_some() {
|
||||
@ -1971,30 +2151,79 @@ fn parse_top_level<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(AST(
|
||||
if optimize_ast {
|
||||
optimize(statements)
|
||||
} 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]);
|
||||
fn_def.body = body.pop().unwrap_or_else(|| Stmt::Noop(pos));
|
||||
}
|
||||
Arc::new(fn_def)
|
||||
})
|
||||
.collect(),
|
||||
));
|
||||
Ok((statements, functions))
|
||||
}
|
||||
|
||||
pub fn parse<'a>(
|
||||
pub fn parse<'a, 'e>(
|
||||
input: &mut Peekable<TokenIterator<'a>>,
|
||||
optimize_ast: bool,
|
||||
engine: &Engine<'e>,
|
||||
scope: &Scope,
|
||||
) -> Result<AST, ParseError> {
|
||||
parse_top_level(input, optimize_ast)
|
||||
let (statements, functions) = parse_top_level(input)?;
|
||||
|
||||
Ok(
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
optimize_ast(engine, scope, statements, functions),
|
||||
#[cfg(feature = "no_optimize")]
|
||||
AST(statements, functions.into_iter().map(Arc::new).collect()),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> (Option<Expr>, Dynamic) {
|
||||
if value.is::<INT>() {
|
||||
let value2 = value.clone();
|
||||
(
|
||||
Some(Expr::IntegerConstant(
|
||||
*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)
|
||||
}
|
||||
}
|
||||
|
@ -41,6 +41,8 @@ pub enum EvalAltResult {
|
||||
ErrorVariableNotFound(String, Position),
|
||||
/// Assignment to an inappropriate LHS (left-hand-side) expression.
|
||||
ErrorAssignmentToUnknownLHS(Position),
|
||||
/// Assignment to a constant variable.
|
||||
ErrorAssignmentToConstant(String, Position),
|
||||
/// Returned type is not the same as the required output type.
|
||||
/// Wrapped value is the type of the actual result.
|
||||
ErrorMismatchOutputType(String, Position),
|
||||
@ -59,10 +61,10 @@ pub enum EvalAltResult {
|
||||
Return(Dynamic, Position),
|
||||
}
|
||||
|
||||
impl Error for EvalAltResult {
|
||||
fn description(&self) -> &str {
|
||||
impl EvalAltResult {
|
||||
pub(crate) fn desc(&self) -> &str {
|
||||
match self {
|
||||
Self::ErrorParsing(p) => p.description(),
|
||||
Self::ErrorParsing(p) => p.desc(),
|
||||
Self::ErrorFunctionNotFound(_, _) => "Function not found",
|
||||
Self::ErrorFunctionArgsMismatch(_, _, _, _) => {
|
||||
"Function call with wrong number of arguments"
|
||||
@ -89,6 +91,7 @@ impl Error for EvalAltResult {
|
||||
Self::ErrorAssignmentToUnknownLHS(_) => {
|
||||
"Assignment to an unsupported left-hand side expression"
|
||||
}
|
||||
Self::ErrorAssignmentToConstant(_, _) => "Assignment to a constant variable",
|
||||
Self::ErrorMismatchOutputType(_, _) => "Output type is incorrect",
|
||||
Self::ErrorReadingScriptFile(_, _) => "Cannot read from script file",
|
||||
Self::ErrorDotExpr(_, _) => "Malformed dot expression",
|
||||
@ -98,15 +101,13 @@ impl Error for EvalAltResult {
|
||||
Self::Return(_, _) => "[Not Error] Function returns value",
|
||||
}
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&dyn Error> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for EvalAltResult {}
|
||||
|
||||
impl fmt::Display for EvalAltResult {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let desc = self.description();
|
||||
let desc = self.desc();
|
||||
|
||||
match self {
|
||||
Self::ErrorFunctionNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos),
|
||||
@ -116,6 +117,7 @@ impl fmt::Display for EvalAltResult {
|
||||
Self::ErrorIfGuard(pos) => write!(f, "{} ({})", desc, pos),
|
||||
Self::ErrorFor(pos) => write!(f, "{} ({})", desc, pos),
|
||||
Self::ErrorAssignmentToUnknownLHS(pos) => write!(f, "{} ({})", desc, pos),
|
||||
Self::ErrorAssignmentToConstant(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos),
|
||||
Self::ErrorMismatchOutputType(s, pos) => write!(f, "{}: {} ({})", desc, s, pos),
|
||||
Self::ErrorDotExpr(s, pos) if !s.is_empty() => write!(f, "{} {} ({})", desc, s, pos),
|
||||
Self::ErrorDotExpr(_, pos) => write!(f, "{} ({})", desc, pos),
|
||||
@ -213,6 +215,7 @@ impl EvalAltResult {
|
||||
| Self::ErrorFor(pos)
|
||||
| Self::ErrorVariableNotFound(_, pos)
|
||||
| Self::ErrorAssignmentToUnknownLHS(pos)
|
||||
| Self::ErrorAssignmentToConstant(_, pos)
|
||||
| Self::ErrorMismatchOutputType(_, pos)
|
||||
| Self::ErrorDotExpr(_, pos)
|
||||
| Self::ErrorArithmetic(_, pos)
|
||||
@ -238,6 +241,7 @@ impl EvalAltResult {
|
||||
| Self::ErrorFor(ref mut pos)
|
||||
| Self::ErrorVariableNotFound(_, ref mut pos)
|
||||
| Self::ErrorAssignmentToUnknownLHS(ref mut pos)
|
||||
| Self::ErrorAssignmentToConstant(_, ref mut pos)
|
||||
| Self::ErrorMismatchOutputType(_, ref mut pos)
|
||||
| Self::ErrorDotExpr(_, ref mut pos)
|
||||
| Self::ErrorArithmetic(_, ref mut pos)
|
||||
|
156
src/scope.rs
156
src/scope.rs
@ -1,11 +1,33 @@
|
||||
//! Module that defines the `Scope` type representing a function call-stack scope.
|
||||
|
||||
use crate::any::{Any, Dynamic};
|
||||
use crate::parser::{map_dynamic_to_expr, Expr, Position};
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// A type containing information about current scope.
|
||||
/// Useful for keeping state between `Engine` runs.
|
||||
/// Type of a variable in the Scope.
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)]
|
||||
pub enum VariableType {
|
||||
/// Normal variable.
|
||||
Normal,
|
||||
/// Immutable constant value.
|
||||
Constant,
|
||||
}
|
||||
|
||||
/// An entry in the Scope.
|
||||
pub struct ScopeEntry<'a> {
|
||||
/// Name of the variable.
|
||||
pub name: Cow<'a, str>,
|
||||
/// Type of the variable.
|
||||
pub var_type: VariableType,
|
||||
/// Current value of the variable.
|
||||
pub value: Dynamic,
|
||||
/// A constant expression if the initial value matches one of the recognized types.
|
||||
pub expr: Option<Expr>,
|
||||
}
|
||||
|
||||
/// A type containing information about the current scope.
|
||||
/// Useful for keeping state between `Engine` evaluation runs.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@ -25,7 +47,7 @@ use std::borrow::Cow;
|
||||
///
|
||||
/// When searching for variables, newly-added variables are found before similarly-named but older variables,
|
||||
/// allowing for automatic _shadowing_ of variables.
|
||||
pub struct Scope<'a>(Vec<(Cow<'a, str>, Dynamic)>);
|
||||
pub struct Scope<'a>(Vec<ScopeEntry<'a>>);
|
||||
|
||||
impl<'a> Scope<'a> {
|
||||
/// Create a new Scope.
|
||||
@ -44,18 +66,67 @@ impl<'a> Scope<'a> {
|
||||
}
|
||||
|
||||
/// Add (push) a new variable to the Scope.
|
||||
pub fn push<K: Into<Cow<'a, str>>, T: Any>(&mut self, key: K, value: T) {
|
||||
self.0.push((key.into(), Box::new(value)));
|
||||
pub fn push<K: Into<Cow<'a, str>>, T: Any>(&mut self, name: K, value: T) {
|
||||
let value = value.into_dynamic();
|
||||
|
||||
// Map into constant expressions
|
||||
//let (expr, value) = map_dynamic_to_expr(value, Position::none());
|
||||
|
||||
self.0.push(ScopeEntry {
|
||||
name: name.into(),
|
||||
var_type: VariableType::Normal,
|
||||
value,
|
||||
expr: None,
|
||||
});
|
||||
}
|
||||
|
||||
/// Add (push) a new variable to the Scope.
|
||||
pub(crate) fn push_dynamic<K: Into<Cow<'a, str>>>(&mut self, key: K, value: Dynamic) {
|
||||
self.0.push((key.into(), value));
|
||||
/// Add (push) a new constant to the Scope.
|
||||
///
|
||||
/// Constants are immutable and cannot be assigned to. Their values never change.
|
||||
/// Constants propagation is a technique used to optimize an AST.
|
||||
/// However, in order to be used for optimization, constants must be in one of the recognized types:
|
||||
/// `INT` (default to `i64`, `i32` if `only_i32`), `f64`, `String`, `char` and `bool`.
|
||||
pub fn push_constant<K: Into<Cow<'a, str>>, T: Any>(&mut self, name: K, value: T) {
|
||||
let value = value.into_dynamic();
|
||||
|
||||
// Map into constant expressions
|
||||
let (expr, value) = map_dynamic_to_expr(value, Position::none());
|
||||
|
||||
self.0.push(ScopeEntry {
|
||||
name: name.into(),
|
||||
var_type: VariableType::Constant,
|
||||
value,
|
||||
expr,
|
||||
});
|
||||
}
|
||||
|
||||
/// Add (push) a new variable with a `Dynamic` value to the Scope.
|
||||
pub(crate) fn push_dynamic<K: Into<Cow<'a, str>>>(
|
||||
&mut self,
|
||||
name: K,
|
||||
var_type: VariableType,
|
||||
value: Dynamic,
|
||||
) {
|
||||
let (expr, value) = map_dynamic_to_expr(value, Position::none());
|
||||
|
||||
self.0.push(ScopeEntry {
|
||||
name: name.into(),
|
||||
var_type,
|
||||
value,
|
||||
expr,
|
||||
});
|
||||
}
|
||||
|
||||
/// Remove (pop) the last variable from the Scope.
|
||||
pub fn pop(&mut self) -> Option<(String, Dynamic)> {
|
||||
self.0.pop().map(|(key, value)| (key.to_string(), value))
|
||||
pub fn pop(&mut self) -> Option<(String, VariableType, Dynamic)> {
|
||||
self.0.pop().map(
|
||||
|ScopeEntry {
|
||||
name,
|
||||
var_type,
|
||||
value,
|
||||
..
|
||||
}| (name.to_string(), var_type, value),
|
||||
)
|
||||
}
|
||||
|
||||
/// Truncate (rewind) the Scope to a previous size.
|
||||
@ -64,13 +135,23 @@ impl<'a> Scope<'a> {
|
||||
}
|
||||
|
||||
/// Find a variable in the Scope, starting from the last.
|
||||
pub fn get(&self, key: &str) -> Option<(usize, &str, Dynamic)> {
|
||||
pub fn get(&self, key: &str) -> Option<(usize, &str, VariableType, Dynamic)> {
|
||||
self.0
|
||||
.iter()
|
||||
.enumerate()
|
||||
.rev() // Always search a Scope in reverse order
|
||||
.find(|(_, (name, _))| name == key)
|
||||
.map(|(i, (name, value))| (i, name.as_ref(), value.clone()))
|
||||
.find(|(_, ScopeEntry { name, .. })| name == key)
|
||||
.map(
|
||||
|(
|
||||
i,
|
||||
ScopeEntry {
|
||||
name,
|
||||
var_type,
|
||||
value,
|
||||
..
|
||||
},
|
||||
)| (i, name.as_ref(), *var_type, value.clone()),
|
||||
)
|
||||
}
|
||||
|
||||
/// Get the value of a variable in the Scope, starting from the last.
|
||||
@ -79,53 +160,50 @@ impl<'a> Scope<'a> {
|
||||
.iter()
|
||||
.enumerate()
|
||||
.rev() // Always search a Scope in reverse order
|
||||
.find(|(_, (name, _))| name == key)
|
||||
.and_then(|(_, (_, value))| value.downcast_ref::<T>())
|
||||
.map(|value| value.clone())
|
||||
.find(|(_, ScopeEntry { name, .. })| name == key)
|
||||
.and_then(|(_, ScopeEntry { value, .. })| value.downcast_ref::<T>())
|
||||
.map(T::clone)
|
||||
}
|
||||
|
||||
/// Get a mutable reference to a variable in the Scope.
|
||||
pub(crate) fn get_mut(&mut self, key: &str, index: usize) -> &mut Dynamic {
|
||||
pub(crate) fn get_mut(&mut self, name: &str, index: usize) -> &mut Dynamic {
|
||||
let entry = self.0.get_mut(index).expect("invalid index in Scope");
|
||||
|
||||
assert_eq!(entry.0, key, "incorrect key at Scope entry");
|
||||
assert_ne!(
|
||||
entry.var_type,
|
||||
VariableType::Constant,
|
||||
"get mut of constant variable"
|
||||
);
|
||||
assert_eq!(entry.name, name, "incorrect key at Scope entry");
|
||||
|
||||
&mut entry.1
|
||||
&mut entry.value
|
||||
}
|
||||
|
||||
/// Get a mutable reference to a variable in the Scope and downcast it to a specific type
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
pub(crate) fn get_mut_by_type<T: Any + Clone>(&mut self, key: &str, index: usize) -> &mut T {
|
||||
self.get_mut(key, index)
|
||||
pub(crate) fn get_mut_by_type<T: Any + Clone>(&mut self, name: &str, index: usize) -> &mut T {
|
||||
self.get_mut(name, index)
|
||||
.downcast_mut::<T>()
|
||||
.expect("wrong type cast")
|
||||
}
|
||||
|
||||
/// Get an iterator to variables in the Scope.
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&str, &Dynamic)> {
|
||||
self.0
|
||||
.iter()
|
||||
.rev() // Always search a Scope in reverse order
|
||||
.map(|(key, value)| (key.as_ref(), value))
|
||||
pub fn iter(&self) -> impl Iterator<Item = &ScopeEntry> {
|
||||
self.0.iter().rev() // Always search a Scope in reverse order
|
||||
}
|
||||
|
||||
/*
|
||||
/// Get a mutable iterator to variables in the Scope.
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &mut Dynamic)> {
|
||||
self.0
|
||||
.iter_mut()
|
||||
.rev() // Always search a Scope in reverse order
|
||||
.map(|(key, value)| (key.as_ref(), value))
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
impl<'a, K> std::iter::Extend<(K, Dynamic)> for Scope<'a>
|
||||
impl<'a, K> std::iter::Extend<(K, VariableType, Dynamic)> for Scope<'a>
|
||||
where
|
||||
K: Into<Cow<'a, str>>,
|
||||
{
|
||||
fn extend<T: IntoIterator<Item = (K, Dynamic)>>(&mut self, iter: T) {
|
||||
fn extend<T: IntoIterator<Item = (K, VariableType, Dynamic)>>(&mut self, iter: T) {
|
||||
self.0
|
||||
.extend(iter.into_iter().map(|(key, value)| (key.into(), value)));
|
||||
.extend(iter.into_iter().map(|(name, var_type, value)| ScopeEntry {
|
||||
name: name.into(),
|
||||
var_type,
|
||||
value,
|
||||
expr: None,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,10 @@ fn test_arrays() -> Result<(), EvalAltResult> {
|
||||
|
||||
assert_eq!(engine.eval::<INT>("let x = [1, 2, 3]; x[1]")?, 2);
|
||||
assert_eq!(engine.eval::<INT>("let y = [1, 2, 3]; y[1] = 5; y[1]")?, 5);
|
||||
assert_eq!(
|
||||
engine.eval::<char>(r#"let y = [1, [ 42, 88, "93" ], 3]; y[1][2][1]"#)?,
|
||||
'3'
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -48,10 +52,12 @@ fn test_array_with_structs() -> Result<(), EvalAltResult> {
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
"let a = [new_ts()]; \
|
||||
a[0].x = 100; \
|
||||
a[0].update(); \
|
||||
a[0].x",
|
||||
r"
|
||||
let a = [new_ts()];
|
||||
a[0].x = 100;
|
||||
a[0].update();
|
||||
a[0].x
|
||||
"
|
||||
)?,
|
||||
1100
|
||||
);
|
||||
|
@ -1,25 +1,24 @@
|
||||
#![cfg(not(feature = "no_stdlib"))]
|
||||
#![cfg(not(feature = "no_function"))]
|
||||
use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
#[test]
|
||||
fn test_engine_call_fn() -> Result<(), EvalAltResult> {
|
||||
fn test_call_fn() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine.consume(
|
||||
true,
|
||||
r"
|
||||
fn hello(x, y) {
|
||||
x.len() + y
|
||||
x + y
|
||||
}
|
||||
fn hello(x) {
|
||||
x * 2
|
||||
}
|
||||
",
|
||||
true,
|
||||
)?;
|
||||
|
||||
let r: i64 = engine.call_fn("hello", (String::from("abc"), 123 as INT))?;
|
||||
assert_eq!(r, 126);
|
||||
let r: i64 = engine.call_fn("hello", (42 as INT, 123 as INT))?;
|
||||
assert_eq!(r, 165);
|
||||
|
||||
let r: i64 = engine.call_fn("hello", 123 as INT)?;
|
||||
assert_eq!(r, 246);
|
@ -11,12 +11,12 @@ fn test_chars() -> Result<(), EvalAltResult> {
|
||||
{
|
||||
assert_eq!(engine.eval::<char>(r#"let x="hello"; x[2]"#)?, 'l');
|
||||
assert_eq!(
|
||||
engine.eval::<String>(r#"let x="hello"; x[2]='$'; x"#)?,
|
||||
engine.eval::<String>(r#"let y="hello"; y[2]='$'; y"#)?,
|
||||
"he$lo".to_string()
|
||||
);
|
||||
}
|
||||
|
||||
assert!(engine.eval::<char>("'\\uhello'").is_err());
|
||||
assert!(engine.eval::<char>(r"'\uhello'").is_err());
|
||||
assert!(engine.eval::<char>("''").is_err());
|
||||
|
||||
Ok(())
|
||||
|
21
tests/constants.rs
Normal file
21
tests/constants.rs
Normal file
@ -0,0 +1,21 @@
|
||||
use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
#[test]
|
||||
fn test_constant() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
assert_eq!(engine.eval::<INT>("const x = 123; x")?, 123);
|
||||
|
||||
assert!(
|
||||
matches!(engine.eval::<INT>("const x = 123; x = 42;").expect_err("expects error"),
|
||||
EvalAltResult::ErrorAssignmentToConstant(var, _) if var == "x")
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
assert!(
|
||||
matches!(engine.eval::<INT>("const x = [1, 2, 3, 4, 5]; x[2] = 42;").expect_err("expects error"),
|
||||
EvalAltResult::ErrorAssignmentToConstant(var, _) if var == "x")
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
@ -6,12 +6,9 @@ fn test_decrement() -> Result<(), EvalAltResult> {
|
||||
|
||||
assert_eq!(engine.eval::<INT>("let x = 10; x -= 7; x")?, 3);
|
||||
|
||||
let r = engine.eval::<String>("let s = \"test\"; s -= \"ing\"; s");
|
||||
|
||||
match r {
|
||||
Err(EvalAltResult::ErrorFunctionNotFound(err, _)) if err == "- (string, string)" => (),
|
||||
_ => panic!(),
|
||||
}
|
||||
assert!(matches!(engine
|
||||
.eval::<String>(r#"let s = "test"; s -= "ing"; s"#)
|
||||
.expect_err("expects error"), EvalAltResult::ErrorFunctionNotFound(err, _) if err == "- (string, string)"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
110
tests/math.rs
110
tests/math.rs
@ -24,54 +24,76 @@ fn test_math() -> Result<(), EvalAltResult> {
|
||||
{
|
||||
#[cfg(not(feature = "only_i32"))]
|
||||
{
|
||||
match engine.eval::<INT>("(-9223372036854775808).abs()") {
|
||||
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
||||
r => panic!("should return overflow error: {:?}", r),
|
||||
}
|
||||
match engine.eval::<INT>("9223372036854775807 + 1") {
|
||||
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
||||
r => panic!("should return overflow error: {:?}", r),
|
||||
}
|
||||
match engine.eval::<INT>("-9223372036854775808 - 1") {
|
||||
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
||||
r => panic!("should return underflow error: {:?}", r),
|
||||
}
|
||||
match engine.eval::<INT>("9223372036854775807 * 9223372036854775807") {
|
||||
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
||||
r => panic!("should return overflow error: {:?}", r),
|
||||
}
|
||||
match engine.eval::<INT>("9223372036854775807 / 0") {
|
||||
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
||||
r => panic!("should return division by zero error: {:?}", r),
|
||||
}
|
||||
match engine.eval::<INT>("9223372036854775807 % 0") {
|
||||
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
||||
r => panic!("should return division by zero error: {:?}", r),
|
||||
}
|
||||
assert!(matches!(
|
||||
engine
|
||||
.eval::<INT>("(-9223372036854775808).abs()")
|
||||
.expect_err("expects negation overflow"),
|
||||
EvalAltResult::ErrorArithmetic(_, _)
|
||||
));
|
||||
assert!(matches!(
|
||||
engine
|
||||
.eval::<INT>("9223372036854775807 + 1")
|
||||
.expect_err("expects overflow"),
|
||||
EvalAltResult::ErrorArithmetic(_, _)
|
||||
));
|
||||
assert!(matches!(
|
||||
engine
|
||||
.eval::<INT>("-9223372036854775808 - 1")
|
||||
.expect_err("expects underflow"),
|
||||
EvalAltResult::ErrorArithmetic(_, _)
|
||||
));
|
||||
assert!(matches!(
|
||||
engine
|
||||
.eval::<INT>("9223372036854775807 * 9223372036854775807")
|
||||
.expect_err("expects overflow"),
|
||||
EvalAltResult::ErrorArithmetic(_, _)
|
||||
));
|
||||
assert!(matches!(
|
||||
engine
|
||||
.eval::<INT>("9223372036854775807 / 0")
|
||||
.expect_err("expects division by zero"),
|
||||
EvalAltResult::ErrorArithmetic(_, _)
|
||||
));
|
||||
assert!(matches!(
|
||||
engine
|
||||
.eval::<INT>("9223372036854775807 % 0")
|
||||
.expect_err("expects division by zero"),
|
||||
EvalAltResult::ErrorArithmetic(_, _)
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(feature = "only_i32")]
|
||||
{
|
||||
match engine.eval::<INT>("2147483647 + 1") {
|
||||
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
||||
r => panic!("should return overflow error: {:?}", r),
|
||||
}
|
||||
match engine.eval::<INT>("-2147483648 - 1") {
|
||||
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
||||
r => panic!("should return underflow error: {:?}", r),
|
||||
}
|
||||
match engine.eval::<INT>("2147483647 * 2147483647") {
|
||||
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
||||
r => panic!("should return overflow error: {:?}", r),
|
||||
}
|
||||
match engine.eval::<INT>("2147483647 / 0") {
|
||||
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
||||
r => panic!("should return division by zero error: {:?}", r),
|
||||
}
|
||||
match engine.eval::<INT>("2147483647 % 0") {
|
||||
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
||||
r => panic!("should return division by zero error: {:?}", r),
|
||||
}
|
||||
assert!(matches!(
|
||||
engine
|
||||
.eval::<INT>("2147483647 + 1")
|
||||
.expect_err("expects overflow"),
|
||||
EvalAltResult::ErrorArithmetic(_, _)
|
||||
));
|
||||
assert!(matches!(
|
||||
engine
|
||||
.eval::<INT>("-2147483648 - 1")
|
||||
.expect_err("expects underflow"),
|
||||
EvalAltResult::ErrorArithmetic(_, _)
|
||||
));
|
||||
assert!(matches!(
|
||||
engine
|
||||
.eval::<INT>("2147483647 * 2147483647")
|
||||
.expect_err("expects overflow"),
|
||||
EvalAltResult::ErrorArithmetic(_, _)
|
||||
));
|
||||
assert!(matches!(
|
||||
engine
|
||||
.eval::<INT>("2147483647 / 0")
|
||||
.expect_err("expects division by zero"),
|
||||
EvalAltResult::ErrorArithmetic(_, _)
|
||||
));
|
||||
assert!(matches!(
|
||||
engine
|
||||
.eval::<INT>("2147483647 % 0")
|
||||
.expect_err("expects division by zero"),
|
||||
EvalAltResult::ErrorArithmetic(_, _)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,12 +5,10 @@ use rhai::{Engine, EvalAltResult, RegisterFn, INT};
|
||||
fn test_mismatched_op() {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
let r = engine.eval::<INT>("60 + \"hello\"");
|
||||
|
||||
match r {
|
||||
Err(EvalAltResult::ErrorMismatchOutputType(err, _)) if err == "string" => (),
|
||||
_ => panic!(),
|
||||
}
|
||||
assert!(
|
||||
matches!(engine.eval::<INT>(r#"60 + "hello""#).expect_err("expects error"),
|
||||
EvalAltResult::ErrorMismatchOutputType(err, _) if err == "string")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -30,15 +28,17 @@ fn test_mismatched_op_custom_type() {
|
||||
engine.register_type_with_name::<TestStruct>("TestStruct");
|
||||
engine.register_fn("new_ts", TestStruct::new);
|
||||
|
||||
let r = engine.eval::<INT>("60 + new_ts()");
|
||||
let r = engine
|
||||
.eval::<INT>("60 + new_ts()")
|
||||
.expect_err("expects error");
|
||||
|
||||
match r {
|
||||
#[cfg(feature = "only_i32")]
|
||||
Err(EvalAltResult::ErrorFunctionNotFound(err, _)) if err == "+ (i32, TestStruct)" => (),
|
||||
#[cfg(feature = "only_i32")]
|
||||
assert!(
|
||||
matches!(r, EvalAltResult::ErrorFunctionNotFound(err, _) if err == "+ (i32, TestStruct)")
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "only_i32"))]
|
||||
Err(EvalAltResult::ErrorFunctionNotFound(err, _)) if err == "+ (i64, TestStruct)" => (),
|
||||
|
||||
_ => panic!(),
|
||||
}
|
||||
#[cfg(not(feature = "only_i32"))]
|
||||
assert!(
|
||||
matches!(r, EvalAltResult::ErrorFunctionNotFound(err, _) if err == "+ (i64, TestStruct)")
|
||||
);
|
||||
}
|
||||
|
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(())
|
||||
}
|
@ -1,4 +1,10 @@
|
||||
use rhai::{Engine, EvalAltResult, FLOAT, INT};
|
||||
use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
use rhai::FLOAT;
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
const EPSILON: FLOAT = 0.0000000001;
|
||||
|
||||
#[test]
|
||||
fn test_power_of() -> Result<(), EvalAltResult> {
|
||||
@ -9,9 +15,8 @@ fn test_power_of() -> Result<(), EvalAltResult> {
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
{
|
||||
assert_eq!(
|
||||
engine.eval::<FLOAT>("2.2 ~ 3.3")?,
|
||||
13.489468760533386 as FLOAT
|
||||
assert!(
|
||||
(engine.eval::<FLOAT>("2.2 ~ 3.3")? - 13.489468760533386 as FLOAT).abs() <= EPSILON
|
||||
);
|
||||
assert_eq!(engine.eval::<FLOAT>("2.0~-2.0")?, 0.25 as FLOAT);
|
||||
assert_eq!(engine.eval::<FLOAT>("(-2.0~-2.0)")?, 0.25 as FLOAT);
|
||||
@ -31,9 +36,9 @@ fn test_power_of_equals() -> Result<(), EvalAltResult> {
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
{
|
||||
assert_eq!(
|
||||
engine.eval::<FLOAT>("let x = 2.2; x ~= 3.3; x")?,
|
||||
13.489468760533386 as FLOAT
|
||||
assert!(
|
||||
(engine.eval::<FLOAT>("let x = 2.2; x ~= 3.3; x")? - 13.489468760533386 as FLOAT).abs()
|
||||
<= EPSILON
|
||||
);
|
||||
assert_eq!(
|
||||
engine.eval::<FLOAT>("let x = 2.0; x ~= -2.0; x")?,
|
||||
|
@ -1,18 +1,14 @@
|
||||
use rhai::{Engine, EvalAltResult, INT};
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
|
||||
#[test]
|
||||
fn test_throw() {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
match engine.eval::<INT>(r#"if true { throw "hello" }"#) {
|
||||
Ok(_) => panic!("not an error"),
|
||||
Err(EvalAltResult::ErrorRuntime(s, _)) if s == "hello" => (),
|
||||
Err(err) => panic!("wrong error: {}", err),
|
||||
}
|
||||
assert!(matches!(
|
||||
engine.eval::<()>(r#"if true { throw "hello" }"#).expect_err("expects error"),
|
||||
EvalAltResult::ErrorRuntime(s, _) if s == "hello"));
|
||||
|
||||
match engine.eval::<INT>(r#"throw;"#) {
|
||||
Ok(_) => panic!("not an error"),
|
||||
Err(EvalAltResult::ErrorRuntime(s, _)) if s == "" => (),
|
||||
Err(err) => panic!("wrong error: {}", err),
|
||||
}
|
||||
assert!(matches!(
|
||||
engine.eval::<()>(r#"throw;"#).expect_err("expects error"),
|
||||
EvalAltResult::ErrorRuntime(s, _) if s == ""));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user