Merge pull request #108 from schungx/master

Constants and aggressive optimizations.
This commit is contained in:
Stephen Chung 2020-03-16 13:12:57 +08:00 committed by GitHub
commit bffa3ed636
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1770 additions and 717 deletions

View File

@ -18,14 +18,16 @@ include = [
num-traits = "*" num-traits = "*"
[features] [features]
#default = ["no_function", "no_index", "no_float", "only_i32", "no_stdlib", "unchecked"] #default = ["no_function", "no_index", "no_float", "only_i32", "no_stdlib", "unchecked", "no_optimize"]
default = [] default = [ "optimize_full" ]
debug_msgs = [] # print debug messages on function registrations and calls debug_msgs = [] # print debug messages on function registrations and calls
unchecked = [] # unchecked arithmetic unchecked = [] # unchecked arithmetic
no_stdlib = [] # no standard library of utility functions no_stdlib = [] # no standard library of utility functions
no_index = [] # no arrays and indexing no_index = [] # no arrays and indexing
no_float = [] # no floating-point no_float = [] # no floating-point
no_function = [] # no script-defined functions 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_i32 = [] # set INT=i32 (useful for 32-bit systems)
only_i64 = [] # set INT=i64 (default) and disable support for all other integer types only_i64 = [] # set INT=i64 (default) and disable support for all other integer types

570
README.md

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,8 @@
use rhai::{Engine, EvalAltResult, Scope, AST}; use rhai::{Engine, EvalAltResult, Scope, AST};
#[cfg(not(feature = "no_optimize"))]
use rhai::OptimizationLevel;
use std::{ use std::{
io::{stdin, stdout, Write}, io::{stdin, stdout, Write},
iter, iter,
@ -43,6 +47,10 @@ fn print_error(input: &str, err: EvalAltResult) {
fn main() { fn main() {
let mut engine = Engine::new(); let mut engine = Engine::new();
#[cfg(not(feature = "no_optimize"))]
engine.set_optimization_level(OptimizationLevel::Full);
let mut scope = Scope::new(); let mut scope = Scope::new();
let mut input = String::new(); let mut input = String::new();
@ -62,10 +70,11 @@ fn main() {
match input.as_str().trim() { match input.as_str().trim() {
"exit" | "quit" => break, // quit "exit" | "quit" => break, // quit
"ast" => { "ast" => {
// print the last AST if matches!(&ast, Some(_)) {
match &ast { // print the last AST
Some(ast) => println!("{:#?}", ast), println!("{:#?}", ast.as_ref().unwrap());
None => println!("()"), } else {
println!("()");
} }
continue; continue;
} }
@ -73,7 +82,7 @@ fn main() {
} }
if let Err(err) = engine if let Err(err) = engine
.compile(&input) .compile_with_scope(&scope, &input)
.map_err(EvalAltResult::ErrorParsing) .map_err(EvalAltResult::ErrorParsing)
.and_then(|r| { .and_then(|r| {
ast = Some(r); ast = Some(r);

View File

@ -1,4 +1,8 @@
use rhai::{Engine, EvalAltResult}; use rhai::{Engine, EvalAltResult};
#[cfg(not(feature = "no_optimize"))]
use rhai::OptimizationLevel;
use std::{env, fs::File, io::Read, iter, process::exit}; use std::{env, fs::File, io::Read, iter, process::exit};
fn padding(pad: &str, len: usize) -> String { fn padding(pad: &str, len: usize) -> String {
@ -49,6 +53,9 @@ fn main() {
for filename in env::args().skip(1) { for filename in env::args().skip(1) {
let mut engine = Engine::new(); let mut engine = Engine::new();
#[cfg(not(feature = "no_optimize"))]
engine.set_optimization_level(OptimizationLevel::Full);
let mut f = match File::open(&filename) { let mut f = match File::open(&filename) {
Err(err) => { Err(err) => {
eprintln!("Error reading script file: {}\n{}", filename, err); eprintln!("Error reading script file: {}\n{}", filename, err);
@ -67,7 +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!("{}", padding("=", filename.len()));
eprintln!("{}", filename); eprintln!("{}", filename);
eprintln!("{}", padding("=", filename.len())); eprintln!("{}", padding("=", filename.len()));

View File

@ -1,12 +1,11 @@
// This is a script to calculate prime numbers. // 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 = []; let prime_mask = [];
prime_mask.pad(MAX_NUMBER_TO_CHECK, true); prime_mask.pad(MAX_NUMBER_TO_CHECK, true);
prime_mask[0] = false; prime_mask[0] = prime_mask[1] = false;
prime_mask[1] = false;
let total_primes_found = 0; let total_primes_found = 0;

View File

@ -8,6 +8,10 @@ use crate::fn_register::RegisterFn;
use crate::parser::{lex, parse, FnDef, Position, AST}; use crate::parser::{lex, parse, FnDef, Position, AST};
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::scope::Scope; use crate::scope::Scope;
#[cfg(not(feature = "no_optimize"))]
use crate::optimize::optimize_ast;
use std::{ use std::{
any::{type_name, TypeId}, any::{type_name, TypeId},
fs::File, fs::File,
@ -101,8 +105,14 @@ impl<'e> Engine<'e> {
/// Compile a string into an AST. /// Compile a string into an AST.
pub fn compile(&self, input: &str) -> Result<AST, ParseError> { pub fn compile(&self, input: &str) -> Result<AST, ParseError> {
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); 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> { fn read_file(path: PathBuf) -> Result<String, EvalAltResult> {
@ -118,8 +128,20 @@ impl<'e> Engine<'e> {
/// Compile a file into an AST. /// Compile a file into an AST.
pub fn compile_file(&self, path: PathBuf) -> Result<AST, EvalAltResult> { pub fn compile_file(&self, path: PathBuf) -> Result<AST, EvalAltResult> {
Self::read_file(path) self.compile_file_with_scope(&Scope::new(), path)
.and_then(|contents| self.compile(&contents).map_err(|err| err.into())) }
/// 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. /// Evaluate a file.
@ -127,6 +149,15 @@ impl<'e> Engine<'e> {
Self::read_file(path).and_then(|contents| self.eval::<T>(&contents)) Self::read_file(path).and_then(|contents| self.eval::<T>(&contents))
} }
/// Evaluate a file with own scope.
pub fn eval_file_with_scope<T: Any + Clone>(
&mut self,
scope: &mut Scope,
path: PathBuf,
) -> Result<T, EvalAltResult> {
Self::read_file(path).and_then(|contents| self.eval_with_scope::<T>(scope, &contents))
}
/// Evaluate a string. /// Evaluate a string.
pub fn eval<T: Any + Clone>(&mut self, input: &str) -> Result<T, EvalAltResult> { pub fn eval<T: Any + Clone>(&mut self, input: &str) -> Result<T, EvalAltResult> {
let mut scope = Scope::new(); let mut scope = Scope::new();
@ -162,23 +193,21 @@ impl<'e> Engine<'e> {
) -> Result<Dynamic, EvalAltResult> { ) -> Result<Dynamic, EvalAltResult> {
engine.clear_functions(); engine.clear_functions();
#[cfg(feature = "no_function")]
let AST(statements) = ast;
#[cfg(not(feature = "no_function"))]
let statements = { let statements = {
let AST(statements, functions) = ast; let AST(statements, functions) = ast;
engine.load_script_functions(functions); engine.load_script_functions(functions);
statements statements
}; };
let result = statements let mut result = ().into_dynamic();
.iter()
.try_fold(().into_dynamic(), |_, stmt| engine.eval_stmt(scope, stmt)); for stmt in statements {
result = engine.eval_stmt(scope, stmt)?;
}
engine.clear_functions(); engine.clear_functions();
result Ok(result)
} }
match eval_ast_internal(self, scope, ast) { match eval_ast_internal(self, scope, ast) {
@ -207,10 +236,25 @@ impl<'e> Engine<'e> {
/// and not cleared from run to run. /// and not cleared from run to run.
pub fn consume_file( pub fn consume_file(
&mut self, &mut self,
path: PathBuf,
retain_functions: bool, retain_functions: bool,
path: PathBuf,
) -> Result<(), EvalAltResult> { ) -> Result<(), EvalAltResult> {
Self::read_file(path).and_then(|contents| self.consume(&contents, retain_functions)) Self::read_file(path).and_then(|contents| self.consume(retain_functions, &contents))
}
/// Evaluate a file with own scope, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors.
///
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
/// and not cleared from run to run.
pub fn consume_file_with_scope(
&mut self,
scope: &mut Scope,
retain_functions: bool,
path: PathBuf,
) -> Result<(), EvalAltResult> {
Self::read_file(path)
.and_then(|contents| self.consume_with_scope(scope, retain_functions, &contents))
} }
/// Evaluate a string, but throw away the result and only return error (if any). /// Evaluate a string, but throw away the result and only return error (if any).
@ -218,11 +262,11 @@ impl<'e> Engine<'e> {
/// ///
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ /// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
/// and not cleared from run to run. /// and not cleared from run to run.
pub fn consume(&mut self, input: &str, retain_functions: bool) -> Result<(), EvalAltResult> { pub fn consume(&mut self, retain_functions: bool, input: &str) -> Result<(), EvalAltResult> {
self.consume_with_scope(&mut Scope::new(), retain_functions, input) self.consume_with_scope(&mut Scope::new(), retain_functions, input)
} }
/// Evaluate a string, but throw away the result and only return error (if any). /// Evaluate a string with own scope, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors. /// Useful for when you don't need the result, but still need to keep track of possible errors.
/// ///
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ /// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
@ -235,7 +279,7 @@ impl<'e> Engine<'e> {
) -> Result<(), EvalAltResult> { ) -> Result<(), EvalAltResult> {
let tokens_stream = lex(input); let tokens_stream = lex(input);
let ast = parse(&mut tokens_stream.peekable(), self.optimize) let ast = parse(&mut tokens_stream.peekable(), self, scope)
.map_err(EvalAltResult::ErrorParsing)?; .map_err(EvalAltResult::ErrorParsing)?;
self.consume_ast_with_scope(scope, retain_functions, &ast) self.consume_ast_with_scope(scope, retain_functions, &ast)
@ -246,6 +290,15 @@ impl<'e> Engine<'e> {
/// ///
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ /// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
/// and not cleared from run to run. /// and not cleared from run to run.
pub fn consume_ast(&mut self, retain_functions: bool, ast: &AST) -> Result<(), EvalAltResult> {
self.consume_ast_with_scope(&mut Scope::new(), retain_functions, ast)
}
/// Evaluate an AST with own scope, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors.
///
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
/// and not cleared from run to run.
pub fn consume_ast_with_scope( pub fn consume_ast_with_scope(
&mut self, &mut self,
scope: &mut Scope, scope: &mut Scope,
@ -256,10 +309,6 @@ impl<'e> Engine<'e> {
self.clear_functions(); self.clear_functions();
} }
#[cfg(feature = "no_function")]
let AST(statements) = ast;
#[cfg(not(feature = "no_function"))]
let statements = { let statements = {
let AST(ref statements, ref functions) = ast; let AST(ref statements, ref functions) = ast;
self.load_script_functions(functions); self.load_script_functions(functions);
@ -307,7 +356,7 @@ impl<'e> Engine<'e> {
/// ///
/// let mut engine = Engine::new(); /// let mut engine = Engine::new();
/// ///
/// engine.consume("fn add(x, y) { x.len() + y }", true)?; /// engine.consume(true, "fn add(x, y) { x.len() + y }")?;
/// ///
/// let result: i64 = engine.call_fn("add", (String::from("abc"), 123_i64))?; /// let result: i64 = engine.call_fn("add", (String::from("abc"), 123_i64))?;
/// ///
@ -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!`) /// Override default action of `print` (print to stdout using `println!`)
/// ///
/// # Example /// # Example
@ -359,7 +429,7 @@ impl<'e> Engine<'e> {
/// ///
/// // Override action of 'print' function /// // Override action of 'print' function
/// engine.on_print(|s| result.push_str(s)); /// engine.on_print(|s| result.push_str(s));
/// engine.consume("print(40 + 2);", false)?; /// engine.consume(false, "print(40 + 2);")?;
/// } /// }
/// assert_eq!(result, "42"); /// assert_eq!(result, "42");
/// # Ok(()) /// # Ok(())
@ -383,7 +453,7 @@ impl<'e> Engine<'e> {
/// ///
/// // Override action of 'debug' function /// // Override action of 'debug' function
/// engine.on_debug(|s| result.push_str(s)); /// engine.on_debug(|s| result.push_str(s));
/// engine.consume(r#"debug("hello");"#, false)?; /// engine.consume(false, r#"debug("hello");"#)?;
/// } /// }
/// assert_eq!(result, "\"hello\""); /// assert_eq!(result, "\"hello\"");
/// # Ok(()) /// # Ok(())

View File

@ -8,7 +8,9 @@ use crate::engine::Engine;
use crate::fn_register::{RegisterFn, RegisterResultFn}; use crate::fn_register::{RegisterFn, RegisterResultFn};
use crate::parser::{Position, INT}; use crate::parser::{Position, INT};
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::FLOAT;
#[cfg(not(feature = "no_float"))]
use crate::parser::FLOAT;
use num_traits::{ use num_traits::{
identities::Zero, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, identities::Zero, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl,
@ -578,7 +580,7 @@ impl Engine<'_> {
} }
fn range<T>(from: T, to: T) -> Range<T> { fn range<T>(from: T, to: T) -> Range<T> {
(from..to) from..to
} }
reg_iterator::<INT>(self); reg_iterator::<INT>(self);
@ -771,9 +773,12 @@ impl Engine<'_> {
self.register_dynamic_fn("pop", |list: &mut Array| { self.register_dynamic_fn("pop", |list: &mut Array| {
list.pop().unwrap_or_else(|| ().into_dynamic()) list.pop().unwrap_or_else(|| ().into_dynamic())
}); });
self.register_dynamic_fn("shift", |list: &mut Array| match list.len() { self.register_dynamic_fn("shift", |list: &mut Array| {
0 => ().into_dynamic(), if !list.is_empty() {
_ => list.remove(0), ().into_dynamic()
} else {
list.remove(0)
}
}); });
self.register_fn("len", |list: &mut Array| list.len() as INT); self.register_fn("len", |list: &mut Array| list.len() as INT);
self.register_fn("clear", |list: &mut Array| list.clear()); self.register_fn("clear", |list: &mut Array| list.clear());

View File

@ -1,6 +1,8 @@
//! Helper module which defines `FnArgs` to make function calling easier. //! Helper module which defines `FnArgs` to make function calling easier.
use crate::any::{Any, Dynamic}; use crate::any::{Any, Dynamic};
#[cfg(not(feature = "no_index"))]
use crate::engine::Array; use crate::engine::Array;
/// Trait that represent arguments to a function call. /// Trait that represent arguments to a function call.

View File

@ -3,7 +3,10 @@
use crate::any::{Any, AnyExt, Dynamic, Variant}; use crate::any::{Any, AnyExt, Dynamic, Variant};
use crate::parser::{Expr, FnDef, Position, ReturnType, Stmt}; use crate::parser::{Expr, FnDef, Position, ReturnType, Stmt};
use crate::result::EvalAltResult; 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"))] #[cfg(not(feature = "no_index"))]
use crate::INT; use crate::INT;
@ -63,17 +66,20 @@ pub struct FnSpec<'a> {
/// ``` /// ```
pub struct Engine<'e> { pub struct Engine<'e> {
/// Optimize the AST after compilation /// Optimize the AST after compilation
pub(crate) optimize: bool, #[cfg(not(feature = "no_optimize"))]
pub(crate) optimization_level: OptimizationLevel,
/// A hashmap containing all compiled functions known to the engine /// A hashmap containing all compiled functions known to the engine
pub(crate) ext_functions: HashMap<FnSpec<'e>, Box<FnAny>>, pub(crate) ext_functions: HashMap<FnSpec<'e>, Box<FnAny>>,
/// A hashmap containing all script-defined functions /// A hashmap containing all script-defined functions
pub(crate) script_functions: Vec<Arc<FnDef>>, pub(crate) script_functions: Vec<Arc<FnDef>>,
/// A hashmap containing all iterators known to the engine /// A hashmap containing all iterators known to the engine
pub(crate) type_iterators: HashMap<TypeId, Box<IteratorFn>>, pub(crate) type_iterators: HashMap<TypeId, Box<IteratorFn>>,
/// A hashmap mapping type names to pretty-print names
pub(crate) type_names: HashMap<String, String>, pub(crate) type_names: HashMap<String, String>,
// Closures for implementing the print/debug commands /// Closure for implementing the print commands
pub(crate) on_print: Box<dyn FnMut(&str) + 'e>, pub(crate) on_print: Box<dyn FnMut(&str) + 'e>,
/// Closure for implementing the debug commands
pub(crate) on_debug: Box<dyn FnMut(&str) + 'e>, pub(crate) on_debug: Box<dyn FnMut(&str) + 'e>,
} }
@ -93,13 +99,20 @@ impl Engine<'_> {
// Create the new scripting Engine // Create the new scripting Engine
let mut engine = Engine { let mut engine = Engine {
optimize: true,
ext_functions: HashMap::new(), ext_functions: HashMap::new(),
script_functions: Vec::new(), script_functions: Vec::new(),
type_iterators: HashMap::new(), type_iterators: HashMap::new(),
type_names, type_names,
on_print: Box::new(default_print), // default print/debug implementations on_print: Box::new(default_print), // default print/debug implementations
on_debug: Box::new(default_print), 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(); engine.register_core_lib();
@ -110,9 +123,32 @@ impl Engine<'_> {
engine engine
} }
/// Control whether the `Engine` will optimize an AST after compilation /// Control whether and how the `Engine` will optimize an AST after compilation
pub fn set_optimization(&mut self, optimize: bool) { #[cfg(not(feature = "no_optimize"))]
self.optimize = optimize pub fn set_optimization_level(&mut self, optimization_level: OptimizationLevel) {
self.optimization_level = optimization_level
}
/// Call a registered function
#[cfg(not(feature = "no_optimize"))]
pub(crate) fn call_ext_fn_raw(
&self,
fn_name: &str,
args: FnCallArgs,
pos: Position,
) -> Result<Option<Dynamic>, EvalAltResult> {
let spec = FnSpec {
name: fn_name.into(),
args: Some(args.iter().map(|a| Any::type_id(&**a)).collect()),
};
// Search built-in's and external functions
if let Some(func) = self.ext_functions.get(&spec) {
// Run external function
Ok(Some(func(args, pos)?))
} else {
Ok(None)
}
} }
/// Universal method for calling functions, that are either /// Universal method for calling functions, that are either
@ -148,12 +184,13 @@ impl Engine<'_> {
fn_def fn_def
.params .params
.iter() .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 // Evaluate
// Convert return statement to return value
return match self.eval_stmt(&mut scope, &fn_def.body) { return match self.eval_stmt(&mut scope, &fn_def.body) {
// Convert return statement to return value
Err(EvalAltResult::Return(x, _)) => Ok(x), Err(EvalAltResult::Return(x, _)) => Ok(x),
other => other, other => other,
}; };
@ -164,13 +201,13 @@ impl Engine<'_> {
args: Some(args.iter().map(|a| Any::type_id(&**a)).collect()), args: Some(args.iter().map(|a| Any::type_id(&**a)).collect()),
}; };
// Then search built-in's and external functions // Search built-in's and external functions
if let Some(func) = self.ext_functions.get(&spec) { if let Some(func) = self.ext_functions.get(&spec) {
// Run external function // Run external function
let result = func(args, pos)?; let result = func(args, pos)?;
// See if the function match print/debug (which requires special processing) // See if the function match print/debug (which requires special processing)
let callback = match spec.name.as_ref() { let callback = match fn_name {
KEYWORD_PRINT => self.on_print.as_mut(), KEYWORD_PRINT => self.on_print.as_mut(),
KEYWORD_DEBUG => self.on_debug.as_mut(), KEYWORD_DEBUG => self.on_debug.as_mut(),
_ => return Ok(result), _ => return Ok(result),
@ -184,7 +221,7 @@ impl Engine<'_> {
return Ok(callback(val).into_dynamic()); return Ok(callback(val).into_dynamic());
} }
if spec.name == KEYWORD_TYPE_OF && args.len() == 1 { if fn_name == KEYWORD_TYPE_OF && args.len() == 1 {
// Handle `type_of` function // Handle `type_of` function
return Ok(self return Ok(self
.map_type_name(args[0].type_name()) .map_type_name(args[0].type_name())
@ -192,23 +229,23 @@ impl Engine<'_> {
.into_dynamic()); .into_dynamic());
} }
if spec.name.starts_with(FUNC_GETTER) { if fn_name.starts_with(FUNC_GETTER) {
// Getter function not found // Getter function not found
return Err(EvalAltResult::ErrorDotExpr( return Err(EvalAltResult::ErrorDotExpr(
format!( format!(
"- property '{}' unknown or write-only", "- property '{}' unknown or write-only",
&spec.name[FUNC_GETTER.len()..] &fn_name[FUNC_GETTER.len()..]
), ),
pos, pos,
)); ));
} }
if spec.name.starts_with(FUNC_SETTER) { if fn_name.starts_with(FUNC_SETTER) {
// Setter function not found // Setter function not found
return Err(EvalAltResult::ErrorDotExpr( return Err(EvalAltResult::ErrorDotExpr(
format!( format!(
"- property '{}' unknown or read-only", "- property '{}' unknown or read-only",
&spec.name[FUNC_SETTER.len()..] &fn_name[FUNC_SETTER.len()..]
), ),
pos, pos,
)); ));
@ -227,7 +264,7 @@ impl Engine<'_> {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
Err(EvalAltResult::ErrorFunctionNotFound( Err(EvalAltResult::ErrorFunctionNotFound(
format!("{} ({})", spec.name, types_list.join(", ")), format!("{} ({})", fn_name, types_list.join(", ")),
pos, pos,
)) ))
} }
@ -248,14 +285,14 @@ impl Engine<'_> {
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
let args = once(this_ptr) let args = once(this_ptr)
.chain(values.iter_mut().map(|b| b.as_mut())) .chain(values.iter_mut().map(Dynamic::as_mut))
.collect(); .collect();
self.call_fn_raw(fn_name, args, def_val.as_ref(), *pos) self.call_fn_raw(fn_name, args, def_val.as_ref(), *pos)
} }
// xxx.id // xxx.id
Expr::Identifier(id, pos) => { Expr::Property(id, pos) => {
let get_fn_name = format!("{}{}", FUNC_GETTER, id); let get_fn_name = format!("{}{}", FUNC_GETTER, id);
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) 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) => { Expr::Index(idx_lhs, idx_expr, idx_pos) => {
let (val, _) = match idx_lhs.as_ref() { let (val, _) = match idx_lhs.as_ref() {
// xxx.id[idx_expr] // xxx.id[idx_expr]
Expr::Identifier(id, pos) => { Expr::Property(id, pos) => {
let get_fn_name = format!("{}{}", FUNC_GETTER, id); let get_fn_name = format!("{}{}", FUNC_GETTER, id);
( (
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?, self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?,
@ -294,7 +331,7 @@ impl Engine<'_> {
// xxx.dot_lhs.rhs // xxx.dot_lhs.rhs
Expr::Dot(dot_lhs, rhs, _) => match dot_lhs.as_ref() { Expr::Dot(dot_lhs, rhs, _) => match dot_lhs.as_ref() {
// xxx.id.rhs // xxx.id.rhs
Expr::Identifier(id, pos) => { Expr::Property(id, pos) => {
let get_fn_name = format!("{}{}", FUNC_GETTER, id); let get_fn_name = format!("{}{}", FUNC_GETTER, id);
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) 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) => { Expr::Index(idx_lhs, idx_expr, idx_pos) => {
let (val, _) = match idx_lhs.as_ref() { let (val, _) = match idx_lhs.as_ref() {
// xxx.id[idx_expr].rhs // xxx.id[idx_expr].rhs
Expr::Identifier(id, pos) => { Expr::Property(id, pos) => {
let get_fn_name = format!("{}{}", FUNC_GETTER, id); let get_fn_name = format!("{}{}", FUNC_GETTER, id);
( (
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?, self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?,
@ -353,8 +390,8 @@ impl Engine<'_> {
) -> Result<Dynamic, EvalAltResult> { ) -> Result<Dynamic, EvalAltResult> {
match dot_lhs { match dot_lhs {
// id.??? // id.???
Expr::Identifier(id, pos) => { Expr::Variable(id, pos) => {
let (src_idx, mut target) = Self::search_scope(scope, id, Ok, *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); 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. // 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); 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. // 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 { if let Some((id, var_type, src_idx)) = src {
Self::update_indexed_var_in_scope( match var_type {
src_type, VariableType::Constant => {
scope, return Err(EvalAltResult::ErrorAssignmentToConstant(
id, id.to_string(),
src_idx, idx_lhs.position(),
idx, ));
target, }
idx_lhs.position(), VariableType::Normal => {
)?; Self::update_indexed_var_in_scope(
src_type,
scope,
id,
src_idx,
idx,
target,
dot_rhs.position(),
)?;
}
}
} }
val val
@ -400,11 +447,11 @@ impl Engine<'_> {
id: &str, id: &str,
map: impl FnOnce(Dynamic) -> Result<T, EvalAltResult>, map: impl FnOnce(Dynamic) -> Result<T, EvalAltResult>,
begin: Position, begin: Position,
) -> Result<(usize, T), EvalAltResult> { ) -> Result<(usize, VariableType, T), EvalAltResult> {
scope scope
.get(id) .get(id)
.ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.into(), begin)) .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) /// Evaluate the value of an index (must evaluate to INT)
@ -476,19 +523,32 @@ impl Engine<'_> {
lhs: &'a Expr, lhs: &'a Expr,
idx_expr: &Expr, idx_expr: &Expr,
idx_pos: Position, 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)?; let idx = self.eval_index_value(scope, idx_expr)?;
match lhs { match lhs {
// id[idx_expr] // id[idx_expr]
Expr::Identifier(id, _) => Self::search_scope( Expr::Variable(id, _) => Self::search_scope(
scope, scope,
&id, &id,
|val| self.get_indexed_value(&val, idx, idx_expr.position(), idx_pos), |val| self.get_indexed_value(&val, idx, idx_expr.position(), idx_pos),
lhs.position(), lhs.position(),
) )
.map(|(src_idx, (val, src_type))| { .map(|(src_idx, var_type, (val, src_type))| {
(src_type, Some((id.as_str(), src_idx)), idx as usize, val) (
src_type,
Some((id.as_str(), var_type, src_idx)),
idx as usize,
val,
)
}), }),
// (expr)[idx_expr] // (expr)[idx_expr]
@ -543,8 +603,7 @@ impl Engine<'_> {
Ok(Self::str_replace_char(s, idx as usize, ch).into_dynamic()) Ok(Self::str_replace_char(s, idx as usize, ch).into_dynamic())
} }
// All other variable types should be an error IndexSourceType::Expression => panic!("expression cannot be indexed for update"),
_ => panic!("array or string source type expected for indexing"),
} }
} }
@ -585,7 +644,7 @@ impl Engine<'_> {
) -> Result<Dynamic, EvalAltResult> { ) -> Result<Dynamic, EvalAltResult> {
match dot_rhs { match dot_rhs {
// xxx.id // xxx.id
Expr::Identifier(id, pos) => { Expr::Property(id, pos) => {
let set_fn_name = format!("{}{}", FUNC_SETTER, id); let set_fn_name = format!("{}{}", FUNC_SETTER, id);
self.call_fn_raw(&set_fn_name, vec![this_ptr, new_val.as_mut()], None, *pos) 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"))] #[cfg(not(feature = "no_index"))]
Expr::Index(lhs, idx_expr, idx_pos) => match lhs.as_ref() { Expr::Index(lhs, idx_expr, idx_pos) => match lhs.as_ref() {
// xxx.id[idx_expr] // xxx.id[idx_expr]
Expr::Identifier(id, pos) => { Expr::Property(id, pos) => {
let get_fn_name = format!("{}{}", FUNC_GETTER, id); let get_fn_name = format!("{}{}", FUNC_GETTER, id);
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
@ -620,7 +679,7 @@ impl Engine<'_> {
// xxx.lhs.{...} // xxx.lhs.{...}
Expr::Dot(lhs, rhs, _) => match lhs.as_ref() { Expr::Dot(lhs, rhs, _) => match lhs.as_ref() {
// xxx.id.rhs // xxx.id.rhs
Expr::Identifier(id, pos) => { Expr::Property(id, pos) => {
let get_fn_name = format!("{}{}", FUNC_GETTER, id); let get_fn_name = format!("{}{}", FUNC_GETTER, id);
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
@ -640,7 +699,7 @@ impl Engine<'_> {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Index(lhs, idx_expr, idx_pos) => match lhs.as_ref() { Expr::Index(lhs, idx_expr, idx_pos) => match lhs.as_ref() {
// xxx.id[idx_expr].rhs // xxx.id[idx_expr].rhs
Expr::Identifier(id, pos) => { Expr::Property(id, pos) => {
let get_fn_name = format!("{}{}", FUNC_GETTER, id); let get_fn_name = format!("{}{}", FUNC_GETTER, id);
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
@ -702,11 +761,23 @@ impl Engine<'_> {
dot_rhs: &Expr, dot_rhs: &Expr,
new_val: Dynamic, new_val: Dynamic,
val_pos: Position, val_pos: Position,
op_pos: Position,
) -> Result<Dynamic, EvalAltResult> { ) -> Result<Dynamic, EvalAltResult> {
match dot_lhs { match dot_lhs {
// id.??? // id.???
Expr::Identifier(id, pos) => { Expr::Variable(id, pos) => {
let (src_idx, mut target) = Self::search_scope(scope, id, Ok, *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 = let val =
self.set_dot_val_helper(scope, target.as_mut(), dot_rhs, new_val, val_pos); 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); 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. // 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 { if let Some((id, var_type, src_idx)) = src {
Self::update_indexed_var_in_scope( match var_type {
src_type, VariableType::Constant => {
scope, return Err(EvalAltResult::ErrorAssignmentToConstant(
id, id.to_string(),
src_idx, lhs.position(),
idx, ));
target, }
lhs.position(), VariableType::Normal => {
)?; Self::update_indexed_var_in_scope(
src_type, scope, id, src_idx, idx, target, val_pos,
)?;
}
}
} }
val val
@ -758,9 +833,10 @@ impl Engine<'_> {
Expr::IntegerConstant(i, _) => Ok(i.into_dynamic()), Expr::IntegerConstant(i, _) => Ok(i.into_dynamic()),
Expr::StringConstant(s, _) => Ok(s.into_dynamic()), Expr::StringConstant(s, _) => Ok(s.into_dynamic()),
Expr::CharConstant(c, _) => Ok(c.into_dynamic()), Expr::CharConstant(c, _) => Ok(c.into_dynamic()),
Expr::Identifier(id, pos) => { Expr::Variable(id, pos) => {
Self::search_scope(scope, id, Ok, *pos).map(|(_, val)| val) Self::search_scope(scope, id, Ok, *pos).map(|(_, _, val)| val)
} }
Expr::Property(_, _) => panic!("unexpected property."),
// lhs[idx_expr] // lhs[idx_expr]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -768,26 +844,25 @@ impl Engine<'_> {
.eval_index_expr(scope, lhs, idx_expr, *idx_pos) .eval_index_expr(scope, lhs, idx_expr, *idx_pos)
.map(|(_, _, _, x)| x), .map(|(_, _, _, x)| x),
#[cfg(feature = "no_index")]
Expr::Index(_, _, _) => panic!("encountered an index expression during no_index!"),
// Statement block // Statement block
Expr::Stmt(stmt, _) => self.eval_stmt(scope, stmt), Expr::Stmt(stmt, _) => self.eval_stmt(scope, stmt),
// lhs = rhs // lhs = rhs
Expr::Assignment(lhs, rhs, _) => { Expr::Assignment(lhs, rhs, op_pos) => {
let rhs_val = self.eval_expr(scope, rhs)?; let rhs_val = self.eval_expr(scope, rhs)?;
match lhs.as_ref() { match lhs.as_ref() {
// name = rhs // name = rhs
Expr::Identifier(name, pos) => { Expr::Variable(name, pos) => match scope.get(name) {
if let Some((idx, _, _)) = scope.get(name) { Some((idx, _, VariableType::Normal, _)) => {
*scope.get_mut(name, idx) = rhs_val; *scope.get_mut(name, idx) = rhs_val.clone();
Ok(().into_dynamic()) Ok(rhs_val)
} else {
Err(EvalAltResult::ErrorVariableNotFound(name.clone(), *pos))
} }
} Some((_, _, VariableType::Constant, _)) => Err(
EvalAltResult::ErrorAssignmentToConstant(name.to_string(), *op_pos),
),
_ => Err(EvalAltResult::ErrorVariableNotFound(name.clone(), *pos)),
},
// idx_lhs[idx_expr] = rhs // idx_lhs[idx_expr] = rhs
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -795,16 +870,24 @@ impl Engine<'_> {
let (src_type, src, idx, _) = let (src_type, src, idx, _) =
self.eval_index_expr(scope, idx_lhs, idx_expr, *idx_pos)?; self.eval_index_expr(scope, idx_lhs, idx_expr, *idx_pos)?;
if let Some((id, src_idx)) = src { if let Some((id, var_type, src_idx)) = src {
Ok(Self::update_indexed_var_in_scope( match var_type {
src_type, VariableType::Constant => {
scope, return Err(EvalAltResult::ErrorAssignmentToConstant(
&id, id.to_string(),
src_idx, idx_lhs.position(),
idx, ));
rhs_val, }
rhs.position(), VariableType::Normal => Ok(Self::update_indexed_var_in_scope(
)?) src_type,
scope,
&id,
src_idx,
idx,
rhs_val,
rhs.position(),
)?),
}
} else { } else {
Err(EvalAltResult::ErrorAssignmentToUnknownLHS( Err(EvalAltResult::ErrorAssignmentToUnknownLHS(
idx_lhs.position(), idx_lhs.position(),
@ -814,9 +897,15 @@ impl Engine<'_> {
// dot_lhs.dot_rhs = rhs // dot_lhs.dot_rhs = rhs
Expr::Dot(dot_lhs, dot_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 // Syntax error
_ => Err(EvalAltResult::ErrorAssignmentToUnknownLHS(lhs.position())), _ => Err(EvalAltResult::ErrorAssignmentToUnknownLHS(lhs.position())),
} }
@ -834,8 +923,6 @@ impl Engine<'_> {
Ok(Box::new(arr)) Ok(Box::new(arr))
} }
#[cfg(feature = "no_index")]
Expr::Array(_, _) => panic!("encountered an array during no_index!"),
// Dump AST // Dump AST
Expr::FunctionCall(fn_name, args_expr_list, _, pos) if fn_name == KEYWORD_DUMP_AST => { Expr::FunctionCall(fn_name, args_expr_list, _, pos) if fn_name == KEYWORD_DUMP_AST => {
@ -925,7 +1012,16 @@ impl Engine<'_> {
Stmt::Noop(_) => Ok(().into_dynamic()), Stmt::Noop(_) => Ok(().into_dynamic()),
// Expression as statement // 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 // Block scope
Stmt::Block(block, _) => { Stmt::Block(block, _) => {
@ -967,9 +1063,9 @@ impl Engine<'_> {
Ok(guard_val) => { Ok(guard_val) => {
if *guard_val { if *guard_val {
match self.eval_stmt(scope, body) { match self.eval_stmt(scope, body) {
Ok(_) => (),
Err(EvalAltResult::LoopBreak) => return Ok(().into_dynamic()), Err(EvalAltResult::LoopBreak) => return Ok(().into_dynamic()),
Err(x) => return Err(x), Err(x) => return Err(x),
_ => (),
} }
} else { } else {
return Ok(().into_dynamic()); return Ok(().into_dynamic());
@ -982,9 +1078,9 @@ impl Engine<'_> {
// Loop statement // Loop statement
Stmt::Loop(body) => loop { Stmt::Loop(body) => loop {
match self.eval_stmt(scope, body) { match self.eval_stmt(scope, body) {
Ok(_) => (),
Err(EvalAltResult::LoopBreak) => return Ok(().into_dynamic()), Err(EvalAltResult::LoopBreak) => return Ok(().into_dynamic()),
Err(x) => return Err(x), Err(x) => return Err(x),
_ => (),
} }
}, },
@ -1001,9 +1097,9 @@ impl Engine<'_> {
*scope.get_mut(name, idx) = a; *scope.get_mut(name, idx) = a;
match self.eval_stmt(scope, body) { match self.eval_stmt(scope, body) {
Ok(_) => (),
Err(EvalAltResult::LoopBreak) => break, Err(EvalAltResult::LoopBreak) => break,
Err(x) => return Err(x), Err(x) => return Err(x),
_ => (),
} }
} }
scope.pop(); scope.pop();
@ -1045,7 +1141,7 @@ impl Engine<'_> {
// Let statement // Let statement
Stmt::Let(name, Some(expr), _) => { Stmt::Let(name, Some(expr), _) => {
let val = self.eval_expr(scope, 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()) Ok(().into_dynamic())
} }
@ -1053,6 +1149,15 @@ impl Engine<'_> {
scope.push(name.clone(), ()); scope.push(name.clone(), ());
Ok(().into_dynamic()) 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!"),
} }
} }

View File

@ -18,20 +18,11 @@ pub enum LexError {
MalformedChar(String), MalformedChar(String),
/// Error in the script text. /// Error in the script text.
InputError(String), InputError(String),
/// An identifier is in an invalid format.
MalformedIdentifier(String),
} }
impl Error for LexError { 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 fmt::Display for LexError { impl fmt::Display for LexError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 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::MalformedEscapeSequence(s) => write!(f, "Invalid escape sequence: '{}'", s),
Self::MalformedNumber(s) => write!(f, "Invalid number: '{}'", s), Self::MalformedNumber(s) => write!(f, "Invalid number: '{}'", s),
Self::MalformedChar(s) => write!(f, "Invalid character: '{}'", 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), 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 `}`. /// An open `{` is missing the corresponding closing `}`.
MissingRightBrace(String), MissingRightBrace(String),
/// An open `[` is missing the corresponding closing `]`. /// An open `[` is missing the corresponding closing `]`.
#[cfg(not(feature = "no_index"))]
MissingRightBracket(String), MissingRightBracket(String),
/// An expression in function call arguments `()` has syntax error. /// An expression in function call arguments `()` has syntax error.
MalformedCallExpr(String), MalformedCallExpr(String),
/// An expression in indexing brackets `[]` has syntax error. /// An expression in indexing brackets `[]` has syntax error.
#[cfg(not(feature = "no_index"))]
MalformedIndexExpr(String), MalformedIndexExpr(String),
/// Missing a variable name after the `let` keyword. /// Invalid expression assigned to constant.
VarExpectsIdentifier, 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). /// Defining a function `fn` in an appropriate place (e.g. inside another function).
#[cfg(not(feature = "no_function"))]
WrongFnDefinition, WrongFnDefinition,
/// Missing a function name after the `fn` keyword. /// Missing a function name after the `fn` keyword.
#[cfg(not(feature = "no_function"))]
FnMissingName, FnMissingName,
/// A function definition is missing the parameters list. Wrapped value is the function name. /// A function definition is missing the parameters list. Wrapped value is the function name.
#[cfg(not(feature = "no_function"))]
FnMissingParams(String), FnMissingParams(String),
/// Assignment to an inappropriate LHS (left-hand-side) expression. /// Assignment to an inappropriate LHS (left-hand-side) expression.
AssignmentToInvalidLHS, AssignmentToInvalidLHS,
/// Assignment to a copy of a value.
AssignmentToCopy,
/// Assignment to an a constant variable.
AssignmentToConstant(String),
} }
/// Error when parsing a script. /// Error when parsing a script.
@ -98,10 +105,8 @@ impl ParseError {
pub fn position(&self) -> Position { pub fn position(&self) -> Position {
self.1 self.1
} }
}
impl Error for ParseError { pub(crate) fn desc(&self) -> &str {
fn description(&self) -> &str {
match self.0 { match self.0 {
ParseErrorType::BadInput(ref p) => p, ParseErrorType::BadInput(ref p) => p,
ParseErrorType::InputPastEndOfFile => "Script is incomplete", ParseErrorType::InputPastEndOfFile => "Script is incomplete",
@ -109,40 +114,64 @@ impl Error for ParseError {
ParseErrorType::MissingRightParen(_) => "Expecting ')'", ParseErrorType::MissingRightParen(_) => "Expecting ')'",
ParseErrorType::MissingLeftBrace => "Expecting '{'", ParseErrorType::MissingLeftBrace => "Expecting '{'",
ParseErrorType::MissingRightBrace(_) => "Expecting '}'", ParseErrorType::MissingRightBrace(_) => "Expecting '}'",
#[cfg(not(feature = "no_index"))]
ParseErrorType::MissingRightBracket(_) => "Expecting ']'", ParseErrorType::MissingRightBracket(_) => "Expecting ']'",
ParseErrorType::MalformedCallExpr(_) => "Invalid expression in function call arguments", ParseErrorType::MalformedCallExpr(_) => "Invalid expression in function call arguments",
#[cfg(not(feature = "no_index"))]
ParseErrorType::MalformedIndexExpr(_) => "Invalid index in indexing expression", 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", ParseErrorType::FnMissingName => "Expecting name in function declaration",
#[cfg(not(feature = "no_function"))]
ParseErrorType::FnMissingParams(_) => "Expecting parameters in function declaration", 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::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 { impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 { match self.0 {
ParseErrorType::BadInput(ref s) ParseErrorType::BadInput(ref s) | ParseErrorType::MalformedCallExpr(ref s) => {
| ParseErrorType::MalformedIndexExpr(ref s) write!(f, "{}", if s.is_empty() { self.desc() } else { s })?
| ParseErrorType::MalformedCallExpr(ref s) => {
write!(f, "{}", if s.is_empty() { self.description() } 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) => { ParseErrorType::FnMissingParams(ref s) => {
write!(f, "Expecting parameters for function '{}'", s)? write!(f, "Expecting parameters for function '{}'", s)?
} }
ParseErrorType::MissingRightParen(ref s)
| ParseErrorType::MissingRightBrace(ref s) ParseErrorType::MissingRightParen(ref s) | ParseErrorType::MissingRightBrace(ref s) => {
| ParseErrorType::MissingRightBracket(ref s) => { write!(f, "{} for {}", self.desc(), s)?
write!(f, "{} for {}", self.description(), 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() { if !self.1.is_eof() {

View File

@ -196,13 +196,10 @@ macro_rules! def_register {
// Call the user-supplied function using ($clone) to // Call the user-supplied function using ($clone) to
// potentially clone the value, otherwise pass the reference. // potentially clone the value, otherwise pass the reference.
match f($(($clone)($par)),*) { f($(($clone)($par)),*).map(|r| Box::new(r) as Dynamic).map_err(|mut err| {
Ok(r) => Ok(Box::new(r) as Dynamic), err.set_position(pos);
Err(mut err) => { err
err.set_position(pos); })
Err(err)
}
}
}; };
self.register_fn_raw(name, Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun)); self.register_fn_raw(name, Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun));
} }

View File

@ -78,9 +78,15 @@ pub use call::FuncArgs;
pub use engine::Engine; pub use engine::Engine;
pub use error::{ParseError, ParseErrorType}; pub use error::{ParseError, ParseErrorType};
pub use fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn}; 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 result::EvalAltResult;
pub use scope::Scope; pub use scope::{Scope, ScopeEntry, VariableType};
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
pub use engine::Array; pub use engine::Array;
#[cfg(not(feature = "no_float"))]
pub use parser::FLOAT;
#[cfg(not(feature = "no_optimize"))]
pub use optimize::OptimizationLevel;

View File

@ -1,48 +1,108 @@
use crate::engine::KEYWORD_DUMP_AST; #![cfg(not(feature = "no_optimize"))]
use crate::parser::{Expr, Stmt};
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 { match stmt {
Stmt::IfElse(expr, stmt1, None) if stmt1.is_noop() => { Stmt::IfElse(expr, stmt1, None) if stmt1.is_noop() => {
*changed = true; state.set_dirty();
let pos = expr.position(); let pos = expr.position();
let expr = optimize_expr(*expr, changed); let expr = optimize_expr(*expr, state);
match expr { if matches!(expr, Expr::False(_) | Expr::True(_)) {
Expr::False(_) | Expr::True(_) => Stmt::Noop(stmt1.position()), Stmt::Noop(stmt1.position())
expr => { } else {
let stmt = Stmt::Expr(Box::new(expr)); let stmt = Stmt::Expr(Box::new(expr));
if preserve_result { if preserve_result {
Stmt::Block(vec![stmt, *stmt1], pos) Stmt::Block(vec![stmt, *stmt1], pos)
} else { } else {
stmt stmt
}
} }
} }
} }
Stmt::IfElse(expr, stmt1, None) => match *expr { Stmt::IfElse(expr, stmt1, None) => match *expr {
Expr::False(pos) => { Expr::False(pos) => {
*changed = true; state.set_dirty();
Stmt::Noop(pos) Stmt::Noop(pos)
} }
Expr::True(_) => optimize_stmt(*stmt1, changed, true), Expr::True(_) => optimize_stmt(*stmt1, state, true),
expr => Stmt::IfElse( expr => Stmt::IfElse(
Box::new(optimize_expr(expr, changed)), Box::new(optimize_expr(expr, state)),
Box::new(optimize_stmt(*stmt1, changed, true)), Box::new(optimize_stmt(*stmt1, state, true)),
None, None,
), ),
}, },
Stmt::IfElse(expr, stmt1, Some(stmt2)) => match *expr { Stmt::IfElse(expr, stmt1, Some(stmt2)) => match *expr {
Expr::False(_) => optimize_stmt(*stmt2, changed, true), Expr::False(_) => optimize_stmt(*stmt2, state, true),
Expr::True(_) => optimize_stmt(*stmt1, changed, true), Expr::True(_) => optimize_stmt(*stmt1, state, true),
expr => Stmt::IfElse( expr => Stmt::IfElse(
Box::new(optimize_expr(expr, changed)), Box::new(optimize_expr(expr, state)),
Box::new(optimize_stmt(*stmt1, changed, true)), Box::new(optimize_stmt(*stmt1, state, true)),
match optimize_stmt(*stmt2, changed, true) { match optimize_stmt(*stmt2, state, true) {
stmt if stmt.is_noop() => None, stmt if stmt.is_noop() => None,
stmt => Some(Box::new(stmt)), 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 { Stmt::While(expr, stmt) => match *expr {
Expr::False(pos) => { Expr::False(pos) => {
*changed = true; state.set_dirty();
Stmt::Noop(pos) 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( expr => Stmt::While(
Box::new(optimize_expr(expr, changed)), Box::new(optimize_expr(expr, state)),
Box::new(optimize_stmt(*stmt, changed, false)), 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( Stmt::For(id, expr, stmt) => Stmt::For(
id, id,
Box::new(optimize_expr(*expr, changed)), Box::new(optimize_expr(*expr, state)),
Box::new(optimize_stmt(*stmt, changed, false)), Box::new(optimize_stmt(*stmt, state, false)),
), ),
Stmt::Let(id, Some(expr), pos) => { 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::Let(_, None, _) => stmt,
Stmt::Block(statements, pos) => { Stmt::Block(statements, pos) => {
let orig_len = statements.len(); let orig_len = statements.len();
let orig_constants_len = state.constants.len();
let mut result: Vec<_> = statements let mut result: Vec<_> = statements
.into_iter() // For each statement .into_iter() // For each statement
.rev() // Scan in reverse .map(|stmt| {
.map(|s| optimize_stmt(s, changed, preserve_result)) // Optimize the statement 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() .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 .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(|(_, s)| s) .map(|(_, stmt)| stmt)
.rev()
.collect(); .collect();
// Remove all raw expression statements that are pure except for the very last statement // Remove all raw expression statements that are pure except for the very last statement
let last_stmt = if preserve_result { result.pop() } else { None }; let last_stmt = if preserve_result { result.pop() } else { None };
result.retain(|stmt| match stmt { result.retain(|stmt| !matches!(stmt, Stmt::Expr(expr) if expr.is_pure()));
Stmt::Expr(expr) if expr.is_pure() => false,
_ => true,
});
if let Some(stmt) = last_stmt { if let Some(stmt) = last_stmt {
result.push(stmt); result.push(stmt);
@ -106,7 +170,6 @@ fn optimize_stmt(stmt: Stmt, changed: &mut bool, preserve_result: bool) -> Stmt
match expr { match expr {
Stmt::Let(_, None, _) => removed = true, Stmt::Let(_, None, _) => removed = true,
Stmt::Let(_, Some(val_expr), _) if val_expr.is_pure() => removed = true, Stmt::Let(_, Some(val_expr), _) if val_expr.is_pure() => removed = true,
_ => { _ => {
result.push(expr); result.push(expr);
break; break;
@ -123,59 +186,82 @@ fn optimize_stmt(stmt: Stmt, changed: &mut bool, preserve_result: bool) -> Stmt
.into_iter() .into_iter()
.rev() .rev()
.enumerate() .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() .rev()
.collect(); .collect();
} }
*changed = *changed || orig_len != result.len(); if orig_len != result.len() {
state.set_dirty();
}
state.restore_constants(orig_constants_len);
match result[..] { match result[..] {
// No statements in block - change to No-op // No statements in block - change to No-op
[] => { [] => {
*changed = true; state.set_dirty();
Stmt::Noop(pos) Stmt::Noop(pos)
} }
// Only one statement - promote // Only one statement - promote
[_] => { [_] => {
*changed = true; state.set_dirty();
result.remove(0) result.remove(0)
} }
_ => Stmt::Block(result, pos), _ => 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( Stmt::ReturnWithVal(Some(expr), is_return, pos) => {
Some(Box::new(optimize_expr(*expr, changed))), Stmt::ReturnWithVal(Some(Box::new(optimize_expr(*expr, state))), is_return, pos)
is_return, }
pos,
),
stmt => stmt, stmt => stmt,
} }
} }
fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr { fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
match 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(_) => { Stmt::Noop(_) => {
*changed = true; state.set_dirty();
Expr::Unit(pos) Expr::Unit(pos)
} }
Stmt::Expr(expr) => { Stmt::Expr(expr) => {
*changed = true; state.set_dirty();
*expr *expr
} }
stmt => Expr::Stmt(Box::new(stmt), pos), stmt => Expr::Stmt(Box::new(stmt), pos),
}, },
Expr::Assignment(id, expr, pos) => { Expr::Assignment(id1, expr1, pos1) => match *expr1 {
Expr::Assignment(id, Box::new(optimize_expr(*expr, changed)), pos) 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( Expr::Dot(lhs, rhs, pos) => Expr::Dot(
Box::new(optimize_expr(*lhs, changed)), Box::new(optimize_expr(*lhs, state)),
Box::new(optimize_expr(*rhs, changed)), Box::new(optimize_expr(*rhs, state)),
pos, pos,
), ),
@ -184,19 +270,25 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
(Expr::Array(mut items, _), Expr::IntegerConstant(i, _)) (Expr::Array(mut items, _), Expr::IntegerConstant(i, _))
if i >= 0 && (i as usize) < items.len() && items.iter().all(|x| x.is_pure()) => 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. // All other items can be thrown away.
*changed = true; state.set_dirty();
items.remove(i as usize) 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( (lhs, rhs) => Expr::Index(
Box::new(optimize_expr(lhs, changed)), Box::new(optimize_expr(lhs, state)),
Box::new(optimize_expr(rhs, changed)), Box::new(optimize_expr(rhs, state)),
pos, pos,
), ),
}, },
#[cfg(feature = "no_index")]
Expr::Index(_, _, _) => panic!("encountered an index expression during no_index!"),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Array(items, pos) => { Expr::Array(items, pos) => {
@ -204,95 +296,164 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
let items: Vec<_> = items let items: Vec<_> = items
.into_iter() .into_iter()
.map(|expr| optimize_expr(expr, changed)) .map(|expr| optimize_expr(expr, state))
.collect(); .collect();
*changed = *changed || orig_len != items.len(); if orig_len != items.len() {
state.set_dirty();
}
Expr::Array(items, pos) Expr::Array(items, pos)
} }
#[cfg(feature = "no_index")]
Expr::Array(_, _) => panic!("encountered an array during no_index!"),
Expr::And(lhs, rhs) => match (*lhs, *rhs) { Expr::And(lhs, rhs) => match (*lhs, *rhs) {
(Expr::True(_), rhs) => { (Expr::True(_), rhs) => {
*changed = true; state.set_dirty();
rhs rhs
} }
(Expr::False(pos), _) => { (Expr::False(pos), _) => {
*changed = true; state.set_dirty();
Expr::False(pos) Expr::False(pos)
} }
(lhs, Expr::True(_)) => { (lhs, Expr::True(_)) => {
*changed = true; state.set_dirty();
lhs lhs
} }
(lhs, rhs) => Expr::And( (lhs, rhs) => Expr::And(
Box::new(optimize_expr(lhs, changed)), Box::new(optimize_expr(lhs, state)),
Box::new(optimize_expr(rhs, changed)), Box::new(optimize_expr(rhs, state)),
), ),
}, },
Expr::Or(lhs, rhs) => match (*lhs, *rhs) { Expr::Or(lhs, rhs) => match (*lhs, *rhs) {
(Expr::False(_), rhs) => { (Expr::False(_), rhs) => {
*changed = true; state.set_dirty();
rhs rhs
} }
(Expr::True(pos), _) => { (Expr::True(pos), _) => {
*changed = true; state.set_dirty();
Expr::True(pos) Expr::True(pos)
} }
(lhs, Expr::False(_)) => { (lhs, Expr::False(_)) => {
*changed = true; state.set_dirty();
lhs lhs
} }
(lhs, rhs) => Expr::Or( (lhs, rhs) => Expr::Or(
Box::new(optimize_expr(lhs, changed)), Box::new(optimize_expr(lhs, state)),
Box::new(optimize_expr(rhs, changed)), 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) if id == KEYWORD_DUMP_AST => {
Expr::FunctionCall(id, args, def_value, pos) Expr::FunctionCall(id, args, def_value, pos)
} }
// Actually call function to optimize it
Expr::FunctionCall(id, args, def_value, pos)
if id != KEYWORD_DEBUG // not debug
&& id != KEYWORD_PRINT // not print
&& state.engine.map(|eng| eng.optimization_level == OptimizationLevel::Full).unwrap_or(false) // full optimizations
&& args.iter().all(|expr| expr.is_constant()) // all arguments are constants
=>
{
let engine = state.engine.expect("engine should be Some");
let mut arg_values: Vec<_> = args.iter().map(Expr::get_constant_value).collect();
let call_args: FnCallArgs = arg_values.iter_mut().map(Dynamic::as_mut).collect();
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) => { Expr::FunctionCall(id, args, def_value, pos) => {
let orig_len = args.len(); let orig_len = args.len();
let args: Vec<_> = args let args: Vec<_> = args.into_iter().map(|a| optimize_expr(a, state)).collect();
.into_iter()
.map(|a| optimize_expr(a, changed))
.collect();
*changed = *changed || orig_len != args.len(); if orig_len != args.len() {
state.set_dirty();
}
Expr::FunctionCall(id, args, def_value, pos) 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, 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; let mut result = statements;
loop { loop {
let mut changed = false; state.reset();
state.restore_constants(orig_constants_len);
let num_statements = result.len();
result = result result = result
.into_iter() .into_iter()
.rev() // Scan in reverse
.enumerate() .enumerate()
.map(|(i, stmt)| { .map(|(i, stmt)| {
// Keep all variable declarations at this level if let Stmt::Const(name, value, _) = &stmt {
let keep = stmt.is_var(); // 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 state, keep)
optimize_stmt(stmt, &mut changed, keep || i == 0) }
}) })
.rev()
.collect(); .collect();
if !changed { if !state.is_dirty() {
break; break;
} }
} }
@ -301,10 +462,7 @@ pub(crate) fn optimize(statements: Vec<Stmt>) -> Vec<Stmt> {
let last_stmt = result.pop(); let last_stmt = result.pop();
// Remove all pure statements at top level // Remove all pure statements at top level
result.retain(|stmt| match stmt { result.retain(|stmt| !matches!(stmt, Stmt::Expr(expr) if expr.is_pure()));
Stmt::Expr(expr) if expr.is_pure() => false,
_ => true,
});
if let Some(stmt) = last_stmt { if let Some(stmt) = last_stmt {
result.push(stmt); // Add back the last statement result.push(stmt); // Add back the last statement
@ -312,3 +470,32 @@ pub(crate) fn optimize(statements: Vec<Stmt>) -> Vec<Stmt> {
result result
} }
pub fn optimize_ast(
engine: &Engine,
scope: &Scope,
statements: Vec<Stmt>,
functions: Vec<FnDef>,
) -> AST {
AST(
match engine.optimization_level {
OptimizationLevel::None => statements,
OptimizationLevel::Simple => optimize(statements, None, &scope),
OptimizationLevel::Full => optimize(statements, Some(engine), &scope),
},
functions
.into_iter()
.map(|mut fn_def| {
match engine.optimization_level {
OptimizationLevel::None => (),
OptimizationLevel::Simple | OptimizationLevel::Full => {
let pos = fn_def.body.position();
let mut body = optimize(vec![fn_def.body], None, &Scope::new());
fn_def.body = body.pop().unwrap_or_else(|| Stmt::Noop(pos));
}
}
Arc::new(fn_def)
})
.collect(),
)
}

View File

@ -1,8 +1,12 @@
//! Main module defining the lexer and parser. //! Main module defining the lexer and parser.
use crate::any::Dynamic; use crate::any::{Any, AnyExt, Dynamic};
use crate::engine::Engine;
use crate::error::{LexError, ParseError, ParseErrorType}; use crate::error::{LexError, ParseError, ParseErrorType};
use crate::optimize::optimize; use crate::scope::{Scope, VariableType};
#[cfg(not(feature = "no_optimize"))]
use crate::optimize::optimize_ast;
use std::{ use std::{
borrow::Cow, char, cmp::Ordering, fmt, iter::Peekable, str::Chars, str::FromStr, sync::Arc, borrow::Cow, char, cmp::Ordering, fmt, iter::Peekable, str::Chars, str::FromStr, sync::Arc,
@ -22,6 +26,7 @@ pub type INT = i64;
pub type INT = i32; pub type INT = i32;
/// The system floating-point type /// The system floating-point type
#[cfg(not(feature = "no_float"))]
pub type FLOAT = f64; pub type FLOAT = f64;
type LERR = LexError; type LERR = LexError;
@ -143,12 +148,9 @@ impl fmt::Debug for Position {
/// Compiled AST (abstract syntax tree) of a Rhai script. /// Compiled AST (abstract syntax tree) of a Rhai script.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct AST( pub struct AST(pub(crate) Vec<Stmt>, pub(crate) Vec<Arc<FnDef>>);
pub(crate) Vec<Stmt>,
#[cfg(not(feature = "no_function"))] pub(crate) Vec<Arc<FnDef>>,
);
#[derive(Debug)] // Do not derive Clone because it is expensive #[derive(Debug, Clone)]
pub struct FnDef { pub struct FnDef {
pub name: String, pub name: String,
pub params: Vec<String>, pub params: Vec<String>,
@ -179,6 +181,7 @@ pub enum Stmt {
Loop(Box<Stmt>), Loop(Box<Stmt>),
For(String, Box<Expr>, Box<Stmt>), For(String, Box<Expr>, Box<Stmt>),
Let(String, Option<Box<Expr>>, Position), Let(String, Option<Box<Expr>>, Position),
Const(String, Box<Expr>, Position),
Block(Vec<Stmt>, Position), Block(Vec<Stmt>, Position),
Expr(Box<Expr>), Expr(Box<Expr>),
Break(Position), Break(Position),
@ -187,30 +190,22 @@ pub enum Stmt {
impl Stmt { impl Stmt {
pub fn is_noop(&self) -> bool { pub fn is_noop(&self) -> bool {
match self { matches!(self, Stmt::Noop(_))
Stmt::Noop(_) => true,
_ => false,
}
} }
pub fn is_op(&self) -> bool { pub fn is_op(&self) -> bool {
match self { !matches!(self, Stmt::Noop(_))
Stmt::Noop(_) => false,
_ => true,
}
} }
pub fn is_var(&self) -> bool { pub fn is_var(&self) -> bool {
match self { matches!(self, Stmt::Let(_, _, _))
Stmt::Let(_, _, _) => true,
_ => false,
}
} }
pub fn position(&self) -> Position { pub fn position(&self) -> Position {
match self { match self {
Stmt::Noop(pos) Stmt::Noop(pos)
| Stmt::Let(_, _, pos) | Stmt::Let(_, _, pos)
| Stmt::Const(_, _, pos)
| Stmt::Block(_, pos) | Stmt::Block(_, pos)
| Stmt::Break(pos) | Stmt::Break(pos)
| Stmt::ReturnWithVal(_, _, pos) => *pos, | Stmt::ReturnWithVal(_, _, pos) => *pos,
@ -225,14 +220,17 @@ pub enum Expr {
IntegerConstant(INT, Position), IntegerConstant(INT, Position),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
FloatConstant(FLOAT, Position), FloatConstant(FLOAT, Position),
Identifier(String, Position), Variable(String, Position),
Property(String, Position),
CharConstant(char, Position), CharConstant(char, Position),
StringConstant(String, Position), StringConstant(String, Position),
Stmt(Box<Stmt>, Position), Stmt(Box<Stmt>, Position),
FunctionCall(String, Vec<Expr>, Option<Dynamic>, Position), FunctionCall(String, Vec<Expr>, Option<Dynamic>, Position),
Assignment(Box<Expr>, Box<Expr>, Position), Assignment(Box<Expr>, Box<Expr>, Position),
Dot(Box<Expr>, Box<Expr>, Position), Dot(Box<Expr>, Box<Expr>, Position),
#[cfg(not(feature = "no_index"))]
Index(Box<Expr>, Box<Expr>, Position), Index(Box<Expr>, Box<Expr>, Position),
#[cfg(not(feature = "no_index"))]
Array(Vec<Expr>, Position), Array(Vec<Expr>, Position),
And(Box<Expr>, Box<Expr>), And(Box<Expr>, Box<Expr>),
Or(Box<Expr>, Box<Expr>), Or(Box<Expr>, Box<Expr>),
@ -242,27 +240,73 @@ pub enum Expr {
} }
impl 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 { pub fn position(&self) -> Position {
match self { match self {
Expr::IntegerConstant(_, pos) Expr::IntegerConstant(_, pos)
| Expr::Identifier(_, pos)
| Expr::CharConstant(_, pos) | Expr::CharConstant(_, pos)
| Expr::StringConstant(_, pos) | Expr::StringConstant(_, pos)
| Expr::Variable(_, pos)
| Expr::Property(_, pos)
| Expr::Stmt(_, pos) | Expr::Stmt(_, pos)
| Expr::FunctionCall(_, _, _, pos) | Expr::FunctionCall(_, _, _, pos)
| Expr::Array(_, pos)
| Expr::True(pos) | Expr::True(pos)
| Expr::False(pos) | Expr::False(pos)
| Expr::Unit(pos) => *pos, | Expr::Unit(pos) => *pos,
Expr::Assignment(e, _, _) Expr::Assignment(e, _, _) | Expr::Dot(e, _, _) | Expr::And(e, _) | Expr::Or(e, _) => {
| Expr::Dot(e, _, _) e.position()
| Expr::Index(e, _, _) }
| Expr::And(e, _)
| Expr::Or(e, _) => e.position(),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Expr::FloatConstant(_, pos) => *pos, Expr::FloatConstant(_, pos) => *pos,
#[cfg(not(feature = "no_index"))]
Expr::Array(_, pos) => *pos,
#[cfg(not(feature = "no_index"))]
Expr::Index(e, _, _) => e.position(),
} }
} }
@ -271,9 +315,15 @@ impl Expr {
/// A pure expression has no side effects. /// A pure expression has no side effects.
pub fn is_pure(&self) -> bool { pub fn is_pure(&self) -> bool {
match self { match self {
#[cfg(not(feature = "no_index"))]
Expr::Array(expressions, _) => expressions.iter().all(Expr::is_pure), Expr::Array(expressions, _) => expressions.iter().all(Expr::is_pure),
Expr::And(x, y) | Expr::Or(x, y) | Expr::Index(x, y, _) => x.is_pure() && y.is_pure(),
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"))] #[cfg(not(feature = "no_float"))]
Expr::FloatConstant(_, _) => true, Expr::FloatConstant(_, _) => true,
_ => false, #[cfg(not(feature = "no_index"))]
} Expr::Array(expressions, _) => expressions.iter().all(Expr::is_constant),
}
pub fn is_identifier(&self) -> bool {
match self {
Expr::Identifier(_, _) => true,
_ => false, _ => false,
} }
} }
@ -312,7 +359,9 @@ pub enum Token {
RightBrace, RightBrace,
LeftParen, LeftParen,
RightParen, RightParen,
#[cfg(not(feature = "no_index"))]
LeftBracket, LeftBracket,
#[cfg(not(feature = "no_index"))]
RightBracket, RightBracket,
Plus, Plus,
UnaryPlus, UnaryPlus,
@ -328,6 +377,7 @@ pub enum Token {
True, True,
False, False,
Let, Let,
Const,
If, If,
Else, Else,
While, While,
@ -343,6 +393,7 @@ pub enum Token {
Or, Or,
Ampersand, Ampersand,
And, And,
#[cfg(not(feature = "no_function"))]
Fn, Fn,
Break, Break,
Return, Return,
@ -386,7 +437,9 @@ impl Token {
RightBrace => "}", RightBrace => "}",
LeftParen => "(", LeftParen => "(",
RightParen => ")", RightParen => ")",
#[cfg(not(feature = "no_index"))]
LeftBracket => "[", LeftBracket => "[",
#[cfg(not(feature = "no_index"))]
RightBracket => "]", RightBracket => "]",
Plus => "+", Plus => "+",
UnaryPlus => "+", UnaryPlus => "+",
@ -402,6 +455,7 @@ impl Token {
True => "true", True => "true",
False => "false", False => "false",
Let => "let", Let => "let",
Const => "const",
If => "if", If => "if",
Else => "else", Else => "else",
While => "while", While => "while",
@ -417,6 +471,7 @@ impl Token {
Or => "||", Or => "||",
Ampersand => "&", Ampersand => "&",
And => "&&", And => "&&",
#[cfg(not(feature = "no_function"))]
Fn => "fn", Fn => "fn",
Break => "break", Break => "break",
Return => "return", Return => "return",
@ -456,8 +511,6 @@ impl Token {
// RightBrace | {expr} - expr not unary & is closing // RightBrace | {expr} - expr not unary & is closing
LeftParen | // {-expr} - is unary LeftParen | // {-expr} - is unary
// RightParen | (expr) - expr not unary & is closing // RightParen | (expr) - expr not unary & is closing
LeftBracket | // [-expr] - is unary
// RightBracket | [expr] - expr not unary & is closing
Plus | Plus |
UnaryPlus | UnaryPlus |
Minus | Minus |
@ -501,6 +554,10 @@ impl Token {
In | In |
PowerOfAssign => true, PowerOfAssign => true,
#[cfg(not(feature = "no_index"))]
LeftBracket => true, // [-expr] - is unary
// RightBracket | [expr] - expr not unary & is closing
_ => false, _ => false,
} }
} }
@ -510,9 +567,12 @@ impl Token {
use self::Token::*; use self::Token::*;
match *self { match *self {
RightBrace | RightParen | RightBracket | Plus | Minus | Multiply | Divide | Comma RightParen | Plus | Minus | Multiply | Divide | Comma | Equals | LessThan
| Equals | LessThan | GreaterThan | LessThanEqualsTo | GreaterThanEqualsTo | GreaterThan | LessThanEqualsTo | GreaterThanEqualsTo | EqualsTo | NotEqualsTo
| EqualsTo | NotEqualsTo | Pipe | Or | Ampersand | And | PowerOf => true, | Pipe | Or | Ampersand | And | PowerOf => true,
#[cfg(not(feature = "no_index"))]
RightBrace | RightBracket => true,
_ => false, _ => false,
} }
@ -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(( return Some((
match out.as_str() { match identifier.as_str() {
"true" => Token::True, "true" => Token::True,
"false" => Token::False, "false" => Token::False,
"let" => Token::Let, "let" => Token::Let,
"const" => Token::Const,
"if" => Token::If, "if" => Token::If,
"else" => Token::Else, "else" => Token::Else,
"while" => Token::While, "while" => Token::While,
@ -836,10 +898,15 @@ impl<'a> TokenIterator<'a> {
"break" => Token::Break, "break" => Token::Break,
"return" => Token::Return, "return" => Token::Return,
"throw" => Token::Throw, "throw" => Token::Throw,
"fn" => Token::Fn,
"for" => Token::For, "for" => Token::For,
"in" => Token::In, "in" => Token::In,
_ => Token::Identifier(out),
#[cfg(not(feature = "no_function"))]
"fn" => Token::Fn,
_ if has_letter => Token::Identifier(identifier),
_ => Token::LexError(LERR::MalformedIdentifier(identifier)),
}, },
pos, pos,
)); ));
@ -873,8 +940,12 @@ impl<'a> TokenIterator<'a> {
'}' => return Some((Token::RightBrace, pos)), '}' => return Some((Token::RightBrace, pos)),
'(' => return Some((Token::LeftParen, pos)), '(' => return Some((Token::LeftParen, pos)),
')' => return Some((Token::RightParen, pos)), ')' => return Some((Token::RightParen, pos)),
#[cfg(not(feature = "no_index"))]
'[' => return Some((Token::LeftBracket, pos)), '[' => return Some((Token::LeftBracket, pos)),
#[cfg(not(feature = "no_index"))]
']' => return Some((Token::RightBracket, pos)), ']' => return Some((Token::RightBracket, pos)),
'+' => { '+' => {
return Some(( return Some((
match self.char_stream.peek() { match self.char_stream.peek() {
@ -1146,7 +1217,7 @@ pub fn lex(input: &str) -> TokenIterator<'_> {
} }
} }
fn get_precedence(token: &Token) -> i8 { fn get_precedence(token: &Token) -> u8 {
match *token { match *token {
Token::Equals Token::Equals
| Token::PlusAssign | Token::PlusAssign
@ -1161,28 +1232,49 @@ fn get_precedence(token: &Token) -> i8 {
| Token::ModuloAssign | Token::ModuloAssign
| Token::PowerOfAssign => 10, | 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::LessThan
| Token::LessThanEqualsTo | Token::LessThanEqualsTo
| Token::GreaterThan | Token::GreaterThan
| Token::GreaterThanEqualsTo | Token::GreaterThanEqualsTo
| Token::EqualsTo | 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"))] #[cfg(not(feature = "no_index"))]
Some(&(Token::LeftBracket, pos)) => { Some(&(Token::LeftBracket, pos)) => {
input.next(); 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)), Some(_) => Ok(Expr::Variable(id, begin)),
None => Ok(Expr::Identifier(id, Position::eof())), 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 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 { 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"))] #[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"))] #[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::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"))] #[cfg(not(feature = "no_index"))]
Expr::Index(idx_lhs, _, _) if idx_lhs.is_identifier() => { Expr::Index(idx_lhs, _, _)
valid_assignment_chain(dot_rhs) if matches!(idx_lhs.as_ref(), &Expr::Variable(_, _)) && is_top =>
{
valid_assignment_chain(dot_rhs, false)
} }
#[cfg(not(feature = "no_index"))] #[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); //println!("{:#?} = {:#?}", lhs, rhs);
match valid_assignment_chain(&lhs) { match valid_assignment_chain(&lhs, true) {
(true, _) => Ok(Expr::Assignment(Box::new(lhs), Box::new(rhs), pos)), None => Ok(Expr::Assignment(Box::new(lhs), Box::new(rhs), pos)),
(false, pos) => Err(ParseError::new(PERR::AssignmentToInvalidLHS, pos)), Some(err) => Err(err),
} }
} }
@ -1573,39 +1703,47 @@ fn parse_op_assignment(
fn parse_binary_op<'a>( fn parse_binary_op<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
precedence: i8, parent_precedence: u8,
lhs: Expr, lhs: Expr,
) -> Result<Expr, ParseError> { ) -> Result<Expr, ParseError> {
let mut current_lhs = lhs; let mut current_lhs = lhs;
loop { 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() { // Bind left to the parent lhs expression if precedence is higher
current_precedence = get_precedence(current_op); // If same precedence, then check if the operator binds right
} if current_precedence < parent_precedence
|| (current_precedence == parent_precedence && !bind_right)
if current_precedence < precedence { {
return Ok(current_lhs); return Ok(current_lhs);
} }
if let Some((op_token, pos)) = input.next() { if let Some((op_token, pos)) = input.next() {
input.peek(); 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() { // Bind to right if the next operator has higher precedence
next_precedence = get_precedence(next_op); // If same precedence, then check if the operator binds right
} let rhs = if (current_precedence == next_precedence && bind_right)
|| current_precedence < next_precedence
if current_precedence < next_precedence { {
rhs = parse_binary_op(input, current_precedence + 1, rhs)?; parse_binary_op(input, current_precedence, rhs)?
} else if current_precedence >= 100 { } else {
// Always bind right to left for precedence over 100 // Otherwise bind to left (even if next operator has the same precedence)
rhs = parse_binary_op(input, current_precedence, rhs)?; rhs
} };
current_lhs = match op_token { current_lhs = match op_token {
Token::Plus => Expr::FunctionCall("+".into(), vec![current_lhs, rhs], None, pos), 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::PlusAssign => parse_op_assignment("+", current_lhs, rhs, pos)?,
Token::MinusAssign => 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 // Comparison operators default to false when passed invalid operands
Token::EqualsTo => Expr::FunctionCall( 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> { fn parse_expr<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, ParseError> {
let lhs = parse_unary(input)?; 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> { 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, _)) => { Some(&(Token::Else, _)) => {
input.next(); input.next();
let else_body = match input.peek() { let else_body = if matches!(input.peek(), Some(&(Token::If, _))) {
Some(&(Token::If, _)) => parse_if(input)?, parse_if(input)?
_ => parse_block(input)?, } else {
parse_block(input)?
}; };
Ok(Stmt::IfElse( Ok(Stmt::IfElse(
@ -1746,14 +1907,17 @@ fn parse_for<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseE
let name = match input.next() { let name = match input.next() {
Some((Token::Identifier(s), _)) => s, Some((Token::Identifier(s), _)) => s,
Some((_, pos)) => return Err(ParseError::new(PERR::VarExpectsIdentifier, pos)), Some((Token::LexError(s), pos)) => {
None => return Err(ParseError::new(PERR::VarExpectsIdentifier, Position::eof())), 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() { match input.next() {
Some((Token::In, _)) => {} Some((Token::In, _)) => (),
Some((_, pos)) => return Err(ParseError::new(PERR::VarExpectsIdentifier, pos)), Some((_, pos)) => return Err(ParseError::new(PERR::MissingIn, pos)),
None => return Err(ParseError::new(PERR::VarExpectsIdentifier, Position::eof())), None => return Err(ParseError::new(PERR::MissingIn, Position::eof())),
} }
let expr = parse_expr(input)?; 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))) 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() { let pos = match input.next() {
Some((_, tok_pos)) => tok_pos, Some((_, tok_pos)) => tok_pos,
_ => return Err(ParseError::new(PERR::InputPastEndOfFile, Position::eof())), _ => 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() { let name = match input.next() {
Some((Token::Identifier(s), _)) => s, Some((Token::Identifier(s), _)) => s,
Some((_, pos)) => return Err(ParseError::new(PERR::VarExpectsIdentifier, pos)), Some((Token::LexError(s), pos)) => {
None => return Err(ParseError::new(PERR::VarExpectsIdentifier, Position::eof())), 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() { if matches!(input.peek(), Some(&(Token::Equals, _))) {
Some(&(Token::Equals, _)) => { input.next();
input.next(); let init_value = parse_expr(input)?;
let init_value = parse_expr(input)?;
Ok(Stmt::Let(name, Some(Box::new(init_value)), pos)) 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() { match input.peek() {
Some(&(Token::RightBrace, _)) => (), // empty block Some(&(Token::RightBrace, _)) => (), // empty block
#[cfg(not(feature = "no_function"))]
Some(&(Token::Fn, pos)) => return Err(ParseError::new(PERR::WrongFnDefinition, pos)), Some(&(Token::Fn, pos)) => return Err(ParseError::new(PERR::WrongFnDefinition, pos)),
_ => { _ => {
@ -1849,7 +2032,7 @@ fn parse_stmt<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Parse
let return_type = match token { let return_type = match token {
Token::Return => ReturnType::Return, Token::Return => ReturnType::Return,
Token::Throw => ReturnType::Exception, Token::Throw => ReturnType::Exception,
_ => panic!("unexpected token!"), _ => panic!("token should be return or throw"),
}; };
input.next(); input.next();
@ -1867,7 +2050,8 @@ fn parse_stmt<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Parse
} }
} }
Some(&(Token::LeftBrace, _)) => parse_block(input), 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), _ => 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(); let mut params = Vec::new();
match input.peek() { if matches!(input.peek(), Some(&(Token::RightParen, _))) {
Some(&(Token::RightParen, _)) => { input.next();
input.next(); } else {
} loop {
_ => loop {
match input.next() { match input.next() {
Some((Token::RightParen, _)) => break, Some((Token::RightParen, _)) => break,
Some((Token::Comma, _)) => (), Some((Token::Comma, _)) => (),
@ -1928,7 +2111,7 @@ fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseE
)) ))
} }
} }
}, }
} }
let body = parse_block(input)?; 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>>, input: &mut Peekable<TokenIterator<'a>>,
optimize_ast: bool, ) -> Result<(Vec<Stmt>, Vec<FnDef>), ParseError> {
) -> Result<AST, ParseError> {
let mut statements = Vec::<Stmt>::new(); let mut statements = Vec::<Stmt>::new();
#[cfg(not(feature = "no_function"))]
let mut functions = Vec::<FnDef>::new(); let mut functions = Vec::<FnDef>::new();
while input.peek().is_some() { while input.peek().is_some() {
@ -1971,30 +2151,79 @@ fn parse_top_level<'a>(
} }
} }
return Ok(AST( Ok((statements, functions))
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(),
));
} }
pub fn parse<'a>( pub fn parse<'a, 'e>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
optimize_ast: bool, engine: &Engine<'e>,
scope: &Scope,
) -> Result<AST, ParseError> { ) -> 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)
}
} }

View File

@ -41,6 +41,8 @@ pub enum EvalAltResult {
ErrorVariableNotFound(String, Position), ErrorVariableNotFound(String, Position),
/// Assignment to an inappropriate LHS (left-hand-side) expression. /// Assignment to an inappropriate LHS (left-hand-side) expression.
ErrorAssignmentToUnknownLHS(Position), ErrorAssignmentToUnknownLHS(Position),
/// Assignment to a constant variable.
ErrorAssignmentToConstant(String, Position),
/// Returned type is not the same as the required output type. /// Returned type is not the same as the required output type.
/// Wrapped value is the type of the actual result. /// Wrapped value is the type of the actual result.
ErrorMismatchOutputType(String, Position), ErrorMismatchOutputType(String, Position),
@ -59,10 +61,10 @@ pub enum EvalAltResult {
Return(Dynamic, Position), Return(Dynamic, Position),
} }
impl Error for EvalAltResult { impl EvalAltResult {
fn description(&self) -> &str { pub(crate) fn desc(&self) -> &str {
match self { match self {
Self::ErrorParsing(p) => p.description(), Self::ErrorParsing(p) => p.desc(),
Self::ErrorFunctionNotFound(_, _) => "Function not found", Self::ErrorFunctionNotFound(_, _) => "Function not found",
Self::ErrorFunctionArgsMismatch(_, _, _, _) => { Self::ErrorFunctionArgsMismatch(_, _, _, _) => {
"Function call with wrong number of arguments" "Function call with wrong number of arguments"
@ -89,6 +91,7 @@ impl Error for EvalAltResult {
Self::ErrorAssignmentToUnknownLHS(_) => { Self::ErrorAssignmentToUnknownLHS(_) => {
"Assignment to an unsupported left-hand side expression" "Assignment to an unsupported left-hand side expression"
} }
Self::ErrorAssignmentToConstant(_, _) => "Assignment to a constant variable",
Self::ErrorMismatchOutputType(_, _) => "Output type is incorrect", Self::ErrorMismatchOutputType(_, _) => "Output type is incorrect",
Self::ErrorReadingScriptFile(_, _) => "Cannot read from script file", Self::ErrorReadingScriptFile(_, _) => "Cannot read from script file",
Self::ErrorDotExpr(_, _) => "Malformed dot expression", Self::ErrorDotExpr(_, _) => "Malformed dot expression",
@ -98,15 +101,13 @@ impl Error for EvalAltResult {
Self::Return(_, _) => "[Not Error] Function returns value", Self::Return(_, _) => "[Not Error] Function returns value",
} }
} }
fn cause(&self) -> Option<&dyn Error> {
None
}
} }
impl Error for EvalAltResult {}
impl fmt::Display for EvalAltResult { impl fmt::Display for EvalAltResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let desc = self.description(); let desc = self.desc();
match self { match self {
Self::ErrorFunctionNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos), 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::ErrorIfGuard(pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorFor(pos) => write!(f, "{} ({})", desc, pos), Self::ErrorFor(pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorAssignmentToUnknownLHS(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::ErrorMismatchOutputType(s, pos) => write!(f, "{}: {} ({})", desc, s, pos),
Self::ErrorDotExpr(s, pos) if !s.is_empty() => 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), Self::ErrorDotExpr(_, pos) => write!(f, "{} ({})", desc, pos),
@ -213,6 +215,7 @@ impl EvalAltResult {
| Self::ErrorFor(pos) | Self::ErrorFor(pos)
| Self::ErrorVariableNotFound(_, pos) | Self::ErrorVariableNotFound(_, pos)
| Self::ErrorAssignmentToUnknownLHS(pos) | Self::ErrorAssignmentToUnknownLHS(pos)
| Self::ErrorAssignmentToConstant(_, pos)
| Self::ErrorMismatchOutputType(_, pos) | Self::ErrorMismatchOutputType(_, pos)
| Self::ErrorDotExpr(_, pos) | Self::ErrorDotExpr(_, pos)
| Self::ErrorArithmetic(_, pos) | Self::ErrorArithmetic(_, pos)
@ -238,6 +241,7 @@ impl EvalAltResult {
| Self::ErrorFor(ref mut pos) | Self::ErrorFor(ref mut pos)
| Self::ErrorVariableNotFound(_, ref mut pos) | Self::ErrorVariableNotFound(_, ref mut pos)
| Self::ErrorAssignmentToUnknownLHS(ref mut pos) | Self::ErrorAssignmentToUnknownLHS(ref mut pos)
| Self::ErrorAssignmentToConstant(_, ref mut pos)
| Self::ErrorMismatchOutputType(_, ref mut pos) | Self::ErrorMismatchOutputType(_, ref mut pos)
| Self::ErrorDotExpr(_, ref mut pos) | Self::ErrorDotExpr(_, ref mut pos)
| Self::ErrorArithmetic(_, ref mut pos) | Self::ErrorArithmetic(_, ref mut pos)

View File

@ -1,11 +1,33 @@
//! Module that defines the `Scope` type representing a function call-stack scope. //! Module that defines the `Scope` type representing a function call-stack scope.
use crate::any::{Any, Dynamic}; use crate::any::{Any, Dynamic};
use crate::parser::{map_dynamic_to_expr, Expr, Position};
use std::borrow::Cow; use std::borrow::Cow;
/// A type containing information about current scope. /// Type of a variable in the Scope.
/// Useful for keeping state between `Engine` runs. #[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 /// # Example
/// ///
@ -25,7 +47,7 @@ use std::borrow::Cow;
/// ///
/// When searching for variables, newly-added variables are found before similarly-named but older variables, /// When searching for variables, newly-added variables are found before similarly-named but older variables,
/// allowing for automatic _shadowing_ of 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> { impl<'a> Scope<'a> {
/// Create a new Scope. /// Create a new Scope.
@ -44,18 +66,67 @@ impl<'a> Scope<'a> {
} }
/// Add (push) a new variable to the Scope. /// Add (push) a new variable to the Scope.
pub fn push<K: Into<Cow<'a, str>>, T: Any>(&mut self, key: K, value: T) { pub fn push<K: Into<Cow<'a, str>>, T: Any>(&mut self, name: K, value: T) {
self.0.push((key.into(), Box::new(value))); 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. /// Add (push) a new constant 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)); /// 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. /// Remove (pop) the last variable from the Scope.
pub fn pop(&mut self) -> Option<(String, Dynamic)> { pub fn pop(&mut self) -> Option<(String, VariableType, Dynamic)> {
self.0.pop().map(|(key, value)| (key.to_string(), value)) self.0.pop().map(
|ScopeEntry {
name,
var_type,
value,
..
}| (name.to_string(), var_type, value),
)
} }
/// Truncate (rewind) the Scope to a previous size. /// 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. /// 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 self.0
.iter() .iter()
.enumerate() .enumerate()
.rev() // Always search a Scope in reverse order .rev() // Always search a Scope in reverse order
.find(|(_, (name, _))| name == key) .find(|(_, ScopeEntry { name, .. })| name == key)
.map(|(i, (name, value))| (i, name.as_ref(), value.clone())) .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. /// Get the value of a variable in the Scope, starting from the last.
@ -79,53 +160,50 @@ impl<'a> Scope<'a> {
.iter() .iter()
.enumerate() .enumerate()
.rev() // Always search a Scope in reverse order .rev() // Always search a Scope in reverse order
.find(|(_, (name, _))| name == key) .find(|(_, ScopeEntry { name, .. })| name == key)
.and_then(|(_, (_, value))| value.downcast_ref::<T>()) .and_then(|(_, ScopeEntry { value, .. })| value.downcast_ref::<T>())
.map(|value| value.clone()) .map(T::clone)
} }
/// Get a mutable reference to a variable in the Scope. /// 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"); 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 /// Get a mutable reference to a variable in the Scope and downcast it to a specific type
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
pub(crate) fn get_mut_by_type<T: Any + Clone>(&mut self, key: &str, index: usize) -> &mut T { pub(crate) fn get_mut_by_type<T: Any + Clone>(&mut self, name: &str, index: usize) -> &mut T {
self.get_mut(key, index) self.get_mut(name, index)
.downcast_mut::<T>() .downcast_mut::<T>()
.expect("wrong type cast") .expect("wrong type cast")
} }
/// Get an iterator to variables in the Scope. /// Get an iterator to variables in the Scope.
pub fn iter(&self) -> impl Iterator<Item = (&str, &Dynamic)> { pub fn iter(&self) -> impl Iterator<Item = &ScopeEntry> {
self.0 self.0.iter().rev() // Always search a Scope in reverse order
.iter()
.rev() // Always search a Scope in reverse order
.map(|(key, value)| (key.as_ref(), value))
} }
/*
/// 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 where
K: Into<Cow<'a, str>>, 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 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,
}));
} }
} }

View File

@ -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 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::<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(()) Ok(())
} }
@ -48,10 +52,12 @@ fn test_array_with_structs() -> Result<(), EvalAltResult> {
assert_eq!( assert_eq!(
engine.eval::<INT>( engine.eval::<INT>(
"let a = [new_ts()]; \ r"
a[0].x = 100; \ let a = [new_ts()];
a[0].update(); \ a[0].x = 100;
a[0].x", a[0].update();
a[0].x
"
)?, )?,
1100 1100
); );

View File

@ -1,25 +1,24 @@
#![cfg(not(feature = "no_stdlib"))]
#![cfg(not(feature = "no_function"))] #![cfg(not(feature = "no_function"))]
use rhai::{Engine, EvalAltResult, INT}; use rhai::{Engine, EvalAltResult, INT};
#[test] #[test]
fn test_engine_call_fn() -> Result<(), EvalAltResult> { fn test_call_fn() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
engine.consume( engine.consume(
true,
r" r"
fn hello(x, y) { fn hello(x, y) {
x.len() + y x + y
} }
fn hello(x) { fn hello(x) {
x * 2 x * 2
} }
", ",
true,
)?; )?;
let r: i64 = engine.call_fn("hello", (String::from("abc"), 123 as INT))?; let r: i64 = engine.call_fn("hello", (42 as INT, 123 as INT))?;
assert_eq!(r, 126); assert_eq!(r, 165);
let r: i64 = engine.call_fn("hello", 123 as INT)?; let r: i64 = engine.call_fn("hello", 123 as INT)?;
assert_eq!(r, 246); assert_eq!(r, 246);

View File

@ -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::<char>(r#"let x="hello"; x[2]"#)?, 'l');
assert_eq!( 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() "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()); assert!(engine.eval::<char>("''").is_err());
Ok(()) Ok(())

21
tests/constants.rs Normal file
View 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(())
}

View File

@ -6,12 +6,9 @@ fn test_decrement() -> Result<(), EvalAltResult> {
assert_eq!(engine.eval::<INT>("let x = 10; x -= 7; x")?, 3); assert_eq!(engine.eval::<INT>("let x = 10; x -= 7; x")?, 3);
let r = engine.eval::<String>("let s = \"test\"; s -= \"ing\"; s"); assert!(matches!(engine
.eval::<String>(r#"let s = "test"; s -= "ing"; s"#)
match r { .expect_err("expects error"), EvalAltResult::ErrorFunctionNotFound(err, _) if err == "- (string, string)"));
Err(EvalAltResult::ErrorFunctionNotFound(err, _)) if err == "- (string, string)" => (),
_ => panic!(),
}
Ok(()) Ok(())
} }

View File

@ -24,54 +24,76 @@ fn test_math() -> Result<(), EvalAltResult> {
{ {
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
{ {
match engine.eval::<INT>("(-9223372036854775808).abs()") { assert!(matches!(
Err(EvalAltResult::ErrorArithmetic(_, _)) => (), engine
r => panic!("should return overflow error: {:?}", r), .eval::<INT>("(-9223372036854775808).abs()")
} .expect_err("expects negation overflow"),
match engine.eval::<INT>("9223372036854775807 + 1") { EvalAltResult::ErrorArithmetic(_, _)
Err(EvalAltResult::ErrorArithmetic(_, _)) => (), ));
r => panic!("should return overflow error: {:?}", r), assert!(matches!(
} engine
match engine.eval::<INT>("-9223372036854775808 - 1") { .eval::<INT>("9223372036854775807 + 1")
Err(EvalAltResult::ErrorArithmetic(_, _)) => (), .expect_err("expects overflow"),
r => panic!("should return underflow error: {:?}", r), EvalAltResult::ErrorArithmetic(_, _)
} ));
match engine.eval::<INT>("9223372036854775807 * 9223372036854775807") { assert!(matches!(
Err(EvalAltResult::ErrorArithmetic(_, _)) => (), engine
r => panic!("should return overflow error: {:?}", r), .eval::<INT>("-9223372036854775808 - 1")
} .expect_err("expects underflow"),
match engine.eval::<INT>("9223372036854775807 / 0") { EvalAltResult::ErrorArithmetic(_, _)
Err(EvalAltResult::ErrorArithmetic(_, _)) => (), ));
r => panic!("should return division by zero error: {:?}", r), assert!(matches!(
} engine
match engine.eval::<INT>("9223372036854775807 % 0") { .eval::<INT>("9223372036854775807 * 9223372036854775807")
Err(EvalAltResult::ErrorArithmetic(_, _)) => (), .expect_err("expects overflow"),
r => panic!("should return division by zero error: {:?}", r), 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")] #[cfg(feature = "only_i32")]
{ {
match engine.eval::<INT>("2147483647 + 1") { assert!(matches!(
Err(EvalAltResult::ErrorArithmetic(_, _)) => (), engine
r => panic!("should return overflow error: {:?}", r), .eval::<INT>("2147483647 + 1")
} .expect_err("expects overflow"),
match engine.eval::<INT>("-2147483648 - 1") { EvalAltResult::ErrorArithmetic(_, _)
Err(EvalAltResult::ErrorArithmetic(_, _)) => (), ));
r => panic!("should return underflow error: {:?}", r), assert!(matches!(
} engine
match engine.eval::<INT>("2147483647 * 2147483647") { .eval::<INT>("-2147483648 - 1")
Err(EvalAltResult::ErrorArithmetic(_, _)) => (), .expect_err("expects underflow"),
r => panic!("should return overflow error: {:?}", r), EvalAltResult::ErrorArithmetic(_, _)
} ));
match engine.eval::<INT>("2147483647 / 0") { assert!(matches!(
Err(EvalAltResult::ErrorArithmetic(_, _)) => (), engine
r => panic!("should return division by zero error: {:?}", r), .eval::<INT>("2147483647 * 2147483647")
} .expect_err("expects overflow"),
match engine.eval::<INT>("2147483647 % 0") { EvalAltResult::ErrorArithmetic(_, _)
Err(EvalAltResult::ErrorArithmetic(_, _)) => (), ));
r => panic!("should return division by zero error: {:?}", r), 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(_, _)
));
} }
} }

View File

@ -5,12 +5,10 @@ use rhai::{Engine, EvalAltResult, RegisterFn, INT};
fn test_mismatched_op() { fn test_mismatched_op() {
let mut engine = Engine::new(); let mut engine = Engine::new();
let r = engine.eval::<INT>("60 + \"hello\""); assert!(
matches!(engine.eval::<INT>(r#"60 + "hello""#).expect_err("expects error"),
match r { EvalAltResult::ErrorMismatchOutputType(err, _) if err == "string")
Err(EvalAltResult::ErrorMismatchOutputType(err, _)) if err == "string" => (), );
_ => panic!(),
}
} }
#[test] #[test]
@ -30,15 +28,17 @@ fn test_mismatched_op_custom_type() {
engine.register_type_with_name::<TestStruct>("TestStruct"); engine.register_type_with_name::<TestStruct>("TestStruct");
engine.register_fn("new_ts", TestStruct::new); 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")]
#[cfg(feature = "only_i32")] assert!(
Err(EvalAltResult::ErrorFunctionNotFound(err, _)) if err == "+ (i32, TestStruct)" => (), matches!(r, EvalAltResult::ErrorFunctionNotFound(err, _) if err == "+ (i32, TestStruct)")
);
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
Err(EvalAltResult::ErrorFunctionNotFound(err, _)) if err == "+ (i64, TestStruct)" => (), assert!(
matches!(r, EvalAltResult::ErrorFunctionNotFound(err, _) if err == "+ (i64, TestStruct)")
_ => panic!(), );
}
} }

32
tests/optimizer.rs Normal file
View 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(())
}

View File

@ -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] #[test]
fn test_power_of() -> Result<(), EvalAltResult> { fn test_power_of() -> Result<(), EvalAltResult> {
@ -9,9 +15,8 @@ fn test_power_of() -> Result<(), EvalAltResult> {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
{ {
assert_eq!( assert!(
engine.eval::<FLOAT>("2.2 ~ 3.3")?, (engine.eval::<FLOAT>("2.2 ~ 3.3")? - 13.489468760533386 as FLOAT).abs() <= EPSILON
13.489468760533386 as FLOAT
); );
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);
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"))] #[cfg(not(feature = "no_float"))]
{ {
assert_eq!( assert!(
engine.eval::<FLOAT>("let x = 2.2; x ~= 3.3; x")?, (engine.eval::<FLOAT>("let x = 2.2; x ~= 3.3; x")? - 13.489468760533386 as FLOAT).abs()
13.489468760533386 as FLOAT <= EPSILON
); );
assert_eq!( assert_eq!(
engine.eval::<FLOAT>("let x = 2.0; x ~= -2.0; x")?, engine.eval::<FLOAT>("let x = 2.0; x ~= -2.0; x")?,

View File

@ -1,18 +1,14 @@
use rhai::{Engine, EvalAltResult, INT}; use rhai::{Engine, EvalAltResult};
#[test] #[test]
fn test_throw() { fn test_throw() {
let mut engine = Engine::new(); let mut engine = Engine::new();
match engine.eval::<INT>(r#"if true { throw "hello" }"#) { assert!(matches!(
Ok(_) => panic!("not an error"), engine.eval::<()>(r#"if true { throw "hello" }"#).expect_err("expects error"),
Err(EvalAltResult::ErrorRuntime(s, _)) if s == "hello" => (), EvalAltResult::ErrorRuntime(s, _) if s == "hello"));
Err(err) => panic!("wrong error: {}", err),
}
match engine.eval::<INT>(r#"throw;"#) { assert!(matches!(
Ok(_) => panic!("not an error"), engine.eval::<()>(r#"throw;"#).expect_err("expects error"),
Err(EvalAltResult::ErrorRuntime(s, _)) if s == "" => (), EvalAltResult::ErrorRuntime(s, _) if s == ""));
Err(err) => panic!("wrong error: {}", err),
}
} }