diff --git a/src/api.rs b/src/api.rs index f5c7e0ce..c1c42f83 100644 --- a/src/api.rs +++ b/src/api.rs @@ -2,7 +2,7 @@ use crate::any::{Any, AnyExt, Dynamic}; use crate::call::FuncArgs; -use crate::engine::{Engine, FnAny, FnCallArgs, FnIntExt, FnSpec}; +use crate::engine::{Engine, FnAny, FnCallArgs, FnSpec}; use crate::error::ParseError; use crate::fn_register::RegisterFn; use crate::parser::{lex, parse, Position, AST}; @@ -12,7 +12,6 @@ use std::{ any::{type_name, TypeId}, fs::File, io::prelude::*, - sync::Arc, }; impl<'e> Engine<'e> { @@ -41,7 +40,7 @@ impl<'e> Engine<'e> { args, }; - self.ext_functions.insert(spec, Arc::new(FnIntExt::Ext(f))); + self.ext_functions.insert(spec, f); } /// Register a custom type for use with the `Engine`. @@ -63,7 +62,7 @@ impl<'e> Engine<'e> { where F: Fn(&Dynamic) -> Box> + 'static, { - self.type_iterators.insert(TypeId::of::(), Arc::new(f)); + self.type_iterators.insert(TypeId::of::(), Box::new(f)); } /// Register a getter function for a member of a registered type with the `Engine`. @@ -176,13 +175,7 @@ impl<'e> Engine<'e> { let AST(statements, functions) = ast; functions.iter().for_each(|f| { - engine.script_functions.insert( - FnSpec { - name: f.name.clone().into(), - args: None, - }, - Arc::new(FnIntExt::Int(f.clone())), - ); + engine.script_functions.push(f.clone()); }); statements @@ -261,13 +254,7 @@ impl<'e> Engine<'e> { let AST(ref statements, ref functions) = ast; functions.iter().for_each(|f| { - self.script_functions.insert( - FnSpec { - name: f.name.clone().into(), - args: None, - }, - Arc::new(FnIntExt::Int(f.clone())), - ); + self.script_functions.push(f.clone()); }); statements @@ -322,13 +309,7 @@ impl<'e> Engine<'e> { args: FnCallArgs, ) -> Result { ast.1.iter().for_each(|f| { - engine.script_functions.insert( - FnSpec { - name: f.name.clone().into(), - args: None, - }, - Arc::new(FnIntExt::Int(f.clone())), - ); + engine.script_functions.push(f.clone()); }); let result = engine.call_fn_raw(name, args, None, Position::none()); diff --git a/src/engine.rs b/src/engine.rs index 62acd255..7cf8b114 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,7 +1,7 @@ //! Main module defining the script evaluation `Engine`. use crate::any::{Any, AnyExt, Dynamic, Variant}; -use crate::parser::{Expr, FnDef, Position, Stmt}; +use crate::parser::{Expr, FnDef, Position, ReturnType, Stmt}; use crate::result::EvalAltResult; use crate::scope::Scope; @@ -11,7 +11,6 @@ use crate::INT; use std::{ any::{type_name, TypeId}, borrow::Cow, - cmp::{PartialEq, PartialOrd}, collections::HashMap, iter::once, sync::Arc, @@ -34,7 +33,7 @@ pub(crate) const KEYWORD_TYPE_OF: &'static str = "type_of"; pub(crate) const FUNC_GETTER: &'static str = "get$"; pub(crate) const FUNC_SETTER: &'static str = "set$"; -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)] #[cfg(not(feature = "no_index"))] enum IndexSourceType { Array, @@ -42,7 +41,7 @@ enum IndexSourceType { Expression, } -#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +#[derive(Debug, Eq, PartialEq, Hash)] pub struct FnSpec<'a> { pub name: Cow<'a, str>, pub args: Option>, @@ -66,11 +65,11 @@ pub struct Engine<'e> { /// Optimize the AST after compilation pub(crate) optimize: bool, /// A hashmap containing all compiled functions known to the engine - pub(crate) ext_functions: HashMap, Arc>>, + pub(crate) ext_functions: HashMap, Box>, /// A hashmap containing all script-defined functions - pub(crate) script_functions: HashMap, Arc>>, + pub(crate) script_functions: Vec>, /// A hashmap containing all iterators known to the engine - pub(crate) type_iterators: HashMap>, + pub(crate) type_iterators: HashMap>, pub(crate) type_names: HashMap, // Closures for implementing the print/debug commands @@ -78,11 +77,6 @@ pub struct Engine<'e> { pub(crate) on_debug: Box, } -pub enum FnIntExt<'a> { - Ext(Box), - Int(FnDef<'a>), -} - impl Engine<'_> { /// Create a new `Engine` pub fn new() -> Self { @@ -101,7 +95,7 @@ impl Engine<'_> { let mut engine = Engine { optimize: true, ext_functions: HashMap::new(), - script_functions: HashMap::new(), + script_functions: Vec::new(), type_iterators: HashMap::new(), type_names, on_print: Box::new(default_print), // default print/debug implementations @@ -140,113 +134,112 @@ impl Engine<'_> { .join(", ") ); - let mut spec = FnSpec { + // First search in script-defined functions (can override built-in) + if let Some(func) = self + .script_functions + .iter() + .rev() + .find(|fn_def| fn_def.name == fn_name) + .map(|fn_def| fn_def.clone()) + { + // First check number of parameters + if func.params.len() != args.len() { + return Err(EvalAltResult::ErrorFunctionArgsMismatch( + fn_name.into(), + func.params.len(), + args.len(), + pos, + )); + } + + let mut scope = Scope::new(); + + scope.extend( + // Put arguments into scope as variables + func.params + .iter() + .zip(args.iter().map(|x| (*x).into_dynamic())), + ); + + // Evaluate + return match self.eval_stmt(&mut scope, &func.body) { + // Convert return statement to return value + Err(EvalAltResult::Return(x, _)) => Ok(x), + other => other, + }; + } + + let spec = FnSpec { name: fn_name.into(), - args: None, + args: Some(args.iter().map(|a| Any::type_id(&**a)).collect()), }; - // First search in script-defined functions (can override built-in), - // then built-in's and external functions - let fn_def = self - .script_functions - .get(&spec) - .or_else(|| { - spec.args = Some(args.iter().map(|a| Any::type_id(&**a)).collect()); - self.ext_functions.get(&spec) - }) - .map(|f| f.clone()); + // Then search built-in's and external functions + if let Some(func) = self.ext_functions.get(&spec) { + // Run external function + let result = func(args, pos)?; - if let Some(f) = fn_def { - match *f { - // Run external function - FnIntExt::Ext(ref func) => { - let result = func(args, pos)?; + // See if the function match print/debug (which requires special processing) + let callback = match spec.name.as_ref() { + KEYWORD_PRINT => self.on_print.as_mut(), + KEYWORD_DEBUG => self.on_debug.as_mut(), + _ => return Ok(result), + }; - // See if the function match print/debug (which requires special processing) - let callback = match spec.name.as_ref() { - KEYWORD_PRINT => self.on_print.as_mut(), - KEYWORD_DEBUG => self.on_debug.as_mut(), - _ => return Ok(result), - }; + let val = &result + .downcast::() + .map(|s| *s) + .unwrap_or("error: not a string".into()); - let val = &result - .downcast::() - .map(|s| *s) - .unwrap_or("error: not a string".into()); + return Ok(callback(val).into_dynamic()); + } - Ok(callback(val).into_dynamic()) - } - - // Run script-defined function - FnIntExt::Int(ref func) => { - // First check number of parameters - if func.params.len() != args.len() { - return Err(EvalAltResult::ErrorFunctionArgsMismatch( - spec.name.into(), - func.params.len(), - args.len(), - pos, - )); - } - - let mut scope = Scope::new(); - - scope.extend( - // Put arguments into scope as variables - func.params - .iter() - .cloned() - .zip(args.iter().map(|x| (*x).into_dynamic())), - ); - - // Evaluate - match self.eval_stmt(&mut scope, &func.body) { - // Convert return statement to return value - Err(EvalAltResult::Return(x, _)) => Ok(x), - other => other, - } - } - } - } else if spec.name == KEYWORD_TYPE_OF && args.len() == 1 { + if spec.name == KEYWORD_TYPE_OF && args.len() == 1 { // Handle `type_of` function - Ok(self + return Ok(self .map_type_name(args[0].type_name()) .to_string() - .into_dynamic()) - } else if spec.name.starts_with(FUNC_GETTER) { + .into_dynamic()); + } + + if spec.name.starts_with(FUNC_GETTER) { // Getter function not found - Err(EvalAltResult::ErrorDotExpr( + return Err(EvalAltResult::ErrorDotExpr( format!( "- property '{}' unknown or write-only", &spec.name[FUNC_GETTER.len()..] ), pos, - )) - } else if spec.name.starts_with(FUNC_SETTER) { + )); + } + + if spec.name.starts_with(FUNC_SETTER) { // Setter function not found - Err(EvalAltResult::ErrorDotExpr( + return Err(EvalAltResult::ErrorDotExpr( format!( "- property '{}' unknown or read-only", &spec.name[FUNC_SETTER.len()..] ), pos, - )) - } else if let Some(val) = def_val { - // Return default value - Ok(val.clone()) - } else { - // Raise error - let types_list = args - .iter() - .map(|x| (*x).type_name()) - .map(|name| self.map_type_name(name)) - .collect::>(); - - Err(EvalAltResult::ErrorFunctionNotFound( - format!("{} ({})", spec.name, types_list.join(", ")), - pos, - )) + )); } + + if let Some(val) = def_val { + // Return default value + return Ok(val.clone()); + } + + // Raise error + let types_list = args + .iter() + .map(|x| (*x).type_name()) + .map(|name| self.map_type_name(name)) + .collect::>(); + + Err(EvalAltResult::ErrorFunctionNotFound( + format!("{} ({})", spec.name, types_list.join(", ")), + pos, + )) } /// Chain-evaluate a dot setter @@ -281,7 +274,7 @@ impl Engine<'_> { // xxx.idx_lhs[idx_expr] #[cfg(not(feature = "no_index"))] Expr::Index(idx_lhs, idx_expr, idx_pos) => { - let (expr, _) = match idx_lhs.as_ref() { + let (val, _) = match idx_lhs.as_ref() { // xxx.id[idx_expr] Expr::Identifier(id, pos) => { let get_fn_name = format!("{}{}", FUNC_GETTER, id); @@ -304,7 +297,7 @@ impl Engine<'_> { }; let idx = self.eval_index_value(scope, idx_expr)?; - self.get_indexed_value(expr, idx, idx_expr.position(), *idx_pos) + self.get_indexed_value(&val, idx, idx_expr.position(), *idx_pos) .map(|(v, _)| v) } @@ -320,7 +313,7 @@ impl Engine<'_> { // xxx.idx_lhs[idx_expr].rhs #[cfg(not(feature = "no_index"))] Expr::Index(idx_lhs, idx_expr, idx_pos) => { - let (expr, _) = match idx_lhs.as_ref() { + let (val, _) = match idx_lhs.as_ref() { // xxx.id[idx_expr].rhs Expr::Identifier(id, pos) => { let get_fn_name = format!("{}{}", FUNC_GETTER, id); @@ -343,7 +336,7 @@ impl Engine<'_> { }; let idx = self.eval_index_value(scope, idx_expr)?; - self.get_indexed_value(expr, idx, idx_expr.position(), *idx_pos) + self.get_indexed_value(&val, idx, idx_expr.position(), *idx_pos) .and_then(|(mut v, _)| self.get_dot_val_helper(scope, v.as_mut(), rhs)) } // Syntax error @@ -441,14 +434,14 @@ impl Engine<'_> { #[cfg(not(feature = "no_index"))] fn get_indexed_value( &self, - val: Dynamic, + val: &Dynamic, idx: INT, val_pos: Position, idx_pos: Position, ) -> Result<(Dynamic, IndexSourceType), EvalAltResult> { if val.is::() { // val_array[idx] - let arr = val.downcast::().expect("array expected"); + let arr = val.downcast_ref::().expect("array expected"); if idx >= 0 { arr.get(idx as usize) @@ -460,7 +453,7 @@ impl Engine<'_> { } } else if val.is::() { // val_string[idx] - let s = val.downcast::().expect("string expected"); + let s = val.downcast_ref::().expect("string expected"); if idx >= 0 { s.chars() @@ -501,7 +494,7 @@ impl Engine<'_> { Expr::Identifier(id, _) => Self::search_scope( scope, &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(), ) .map(|(src_idx, (val, src_type))| { @@ -512,7 +505,7 @@ impl Engine<'_> { expr => { let val = self.eval_expr(scope, expr)?; - self.get_indexed_value(val, idx, idx_expr.position(), idx_pos) + self.get_indexed_value(&val, idx, idx_expr.position(), idx_pos) .map(|(v, _)| (IndexSourceType::Expression, None, idx as usize, v)) } } @@ -663,12 +656,8 @@ impl Engine<'_> { self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) .and_then(|v| { let idx = self.eval_index_value(scope, idx_expr)?; - let (mut target, _) = self.get_indexed_value( - v.clone(), // TODO - Avoid cloning this - idx, - idx_expr.position(), - *idx_pos, - )?; + let (mut target, _) = + self.get_indexed_value(&v, idx, idx_expr.position(), *idx_pos)?; self.set_dot_val_helper( scope, @@ -1042,22 +1031,22 @@ impl Engine<'_> { Stmt::Break(_) => Err(EvalAltResult::LoopBreak), // Empty return - Stmt::ReturnWithVal(None, true, pos) => { + Stmt::ReturnWithVal(None, ReturnType::Return, pos) => { Err(EvalAltResult::Return(().into_dynamic(), *pos)) } // Return value - Stmt::ReturnWithVal(Some(a), true, pos) => { + Stmt::ReturnWithVal(Some(a), ReturnType::Return, pos) => { Err(EvalAltResult::Return(self.eval_expr(scope, a)?, *pos)) } // Empty throw - Stmt::ReturnWithVal(None, false, pos) => { + Stmt::ReturnWithVal(None, ReturnType::Exception, pos) => { Err(EvalAltResult::ErrorRuntime("".into(), *pos)) } // Throw value - Stmt::ReturnWithVal(Some(a), false, pos) => { + Stmt::ReturnWithVal(Some(a), ReturnType::Exception, pos) => { let val = self.eval_expr(scope, a)?; Err(EvalAltResult::ErrorRuntime( val.downcast::() @@ -1068,13 +1057,14 @@ impl Engine<'_> { } // Let statement - Stmt::Let(name, init, _) => { - if let Some(v) = init { - let val = self.eval_expr(scope, v)?; - scope.push_dynamic(name.clone(), val); - } else { - scope.push(name.clone(), ()); - } + Stmt::Let(name, Some(expr), _) => { + let val = self.eval_expr(scope, expr)?; + scope.push_dynamic(name.clone(), val); + Ok(().into_dynamic()) + } + + Stmt::Let(name, None, _) => { + scope.push(name.clone(), ()); Ok(().into_dynamic()) } } diff --git a/src/parser.rs b/src/parser.rs index 5aec9d46..45ecc42b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -4,7 +4,7 @@ use crate::any::Dynamic; use crate::error::{LexError, ParseError, ParseErrorType}; use crate::optimize::optimize; -use std::{borrow::Cow, char, fmt, iter::Peekable, str::Chars, str::FromStr, usize}; +use std::{borrow::Cow, char, fmt, iter::Peekable, str::Chars, str::FromStr, sync::Arc, usize}; /// The system integer type. /// @@ -139,19 +139,26 @@ impl fmt::Debug for Position { } /// Compiled AST (abstract syntax tree) of a Rhai script. +#[derive(Debug, Clone)] pub struct AST( pub(crate) Vec, - #[cfg(not(feature = "no_function"))] pub(crate) Vec>, + #[cfg(not(feature = "no_function"))] pub(crate) Vec>, ); -#[derive(Debug, Clone)] -pub struct FnDef<'a> { - pub name: Cow<'a, str>, - pub params: Vec>, +#[derive(Debug)] // Do not derive Clone because it is expensive +pub struct FnDef { + pub name: String, + pub params: Vec, pub body: Stmt, pub pos: Position, } +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub enum ReturnType { + Return, + Exception, +} + #[derive(Debug, Clone)] pub enum Stmt { Noop(Position), @@ -163,7 +170,7 @@ pub enum Stmt { Block(Vec, Position), Expr(Box), Break(Position), - ReturnWithVal(Option>, bool, Position), + ReturnWithVal(Option>, ReturnType, Position), } impl Stmt { @@ -173,6 +180,25 @@ impl Stmt { _ => true, } } + + pub fn is_var(&self) -> bool { + match self { + Stmt::Let(_, _, _) => true, + _ => false, + } + } + + pub fn position(&self) -> Position { + match self { + Stmt::Noop(pos) + | Stmt::Let(_, _, pos) + | Stmt::Block(_, pos) + | Stmt::Break(pos) + | Stmt::ReturnWithVal(_, _, pos) => *pos, + Stmt::IfElse(expr, _, _) | Stmt::Expr(expr) => expr.position(), + Stmt::While(_, stmt) | Stmt::Loop(stmt) | Stmt::For(_, _, stmt) => stmt.position(), + } + } } #[derive(Debug, Clone)] @@ -221,9 +247,13 @@ impl Expr { } } - pub fn is_constant(&self) -> bool { + /// Is this expression pure? + /// + /// A pure expression has no side effects. + pub fn is_pure(&self) -> bool { match self { - Expr::IntegerConstant(_, _) + Expr::Identifier(_, _) + | Expr::IntegerConstant(_, _) | Expr::CharConstant(_, _) | Expr::StringConstant(_, _) | Expr::True(_) @@ -233,6 +263,8 @@ impl Expr { #[cfg(not(feature = "no_float"))] Expr::FloatConstant(_, _) => true, + Expr::Array(expressions, _) => expressions.iter().all(Expr::is_pure), + _ => false, } } @@ -1425,17 +1457,16 @@ fn parse_unary<'a>(input: &mut Peekable>) -> Result Ok(i - .checked_neg() - .map(|x| Expr::IntegerConstant(x, pos)) - .unwrap_or_else(|| Expr::FloatConstant(-(i as FLOAT), pos))), - - // Negative integer - #[cfg(feature = "no_float")] Ok(Expr::IntegerConstant(i, _)) => i .checked_neg() .map(|x| Expr::IntegerConstant(x, pos)) + .or_else(|| { + #[cfg(not(feature = "no_float"))] + return Some(Expr::FloatConstant(-(i as FLOAT), pos)); + + #[cfg(feature = "no_float")] + return None; + }) .ok_or_else(|| { ParseError::new( PERR::BadInput(LERR::MalformedNumber(format!("-{}", i)).to_string()), @@ -1485,10 +1516,11 @@ fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result valid_assignment_chain(dot_rhs), #[cfg(not(feature = "no_index"))] - Expr::Index(idx_lhs, _, _) => match idx_lhs.as_ref() { - Expr::Identifier(_, _) => valid_assignment_chain(dot_rhs), - _ => (false, idx_lhs.position()), - }, + Expr::Index(idx_lhs, _, _) if idx_lhs.is_identifier() => { + valid_assignment_chain(dot_rhs) + } + #[cfg(not(feature = "no_index"))] + Expr::Index(idx_lhs, _, _) => (false, idx_lhs.position()), _ => (false, dot_lhs.position()), }, @@ -1497,7 +1529,7 @@ fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result Ok(Expr::Assignment(Box::new(lhs), Box::new(rhs), pos)), @@ -1797,23 +1829,23 @@ fn parse_stmt<'a>(input: &mut Peekable>) -> Result { - let is_return = match token { - Token::Return => true, - Token::Throw => false, - _ => panic!(), + let return_type = match token { + Token::Return => ReturnType::Return, + Token::Throw => ReturnType::Exception, + _ => panic!("unexpected token!"), }; input.next(); match input.peek() { // return; or throw; - Some(&(Token::SemiColon, pos)) => Ok(Stmt::ReturnWithVal(None, is_return, pos)), + Some(&(Token::SemiColon, pos)) => Ok(Stmt::ReturnWithVal(None, return_type, pos)), // Just a return/throw without anything at the end of script - None => Ok(Stmt::ReturnWithVal(None, is_return, Position::eof())), + None => Ok(Stmt::ReturnWithVal(None, return_type, Position::eof())), // return or throw with expression Some(&(_, pos)) => { let ret = parse_expr(input)?; - Ok(Stmt::ReturnWithVal(Some(Box::new(ret)), is_return, pos)) + Ok(Stmt::ReturnWithVal(Some(Box::new(ret)), return_type, pos)) } } } @@ -1824,7 +1856,7 @@ fn parse_stmt<'a>(input: &mut Peekable>) -> Result(input: &mut Peekable>) -> Result, ParseError> { +fn parse_fn<'a>(input: &mut Peekable>) -> Result { let pos = match input.next() { Some((_, tok_pos)) => tok_pos, _ => return Err(ParseError::new(PERR::InputPastEndOfFile, Position::eof())), @@ -1885,7 +1917,7 @@ fn parse_fn<'a>(input: &mut Peekable>) -> Result( } } - return Ok(if optimize_ast { - AST( - optimize(statements), - #[cfg(not(feature = "no_function"))] - functions - .into_iter() - .map(|mut fn_def| { + return Ok(AST( + if optimize_ast { + optimize(statements) + } else { + statements + }, + #[cfg(not(feature = "no_function"))] + functions + .into_iter() + .map(|mut fn_def| { + if optimize_ast { + let pos = fn_def.body.position(); let mut body = optimize(vec![fn_def.body]); - fn_def.body = body.pop().unwrap(); - fn_def - }) - .collect(), - ) - } else { - AST( - statements, - #[cfg(not(feature = "no_function"))] - functions, - ) - }); + fn_def.body = body.pop().unwrap_or_else(|| Stmt::Noop(pos)); + } + Arc::new(fn_def) + }) + .collect(), + )); } pub fn parse<'a>(