diff --git a/Cargo.toml b/Cargo.toml index a50bc0b0..b2330d24 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ no_float = [] # no floating-point no_function = [] # no script-defined functions no_object = [] # no custom objects no_optimize = [] # no script optimizer +no_import = [] # no namespaces/modules only_i32 = [] # set INT=i32 (useful for 32-bit systems) only_i64 = [] # set INT=i64 (default) and disable support for all other integer types sync = [] # restrict to only types that implement Send + Sync diff --git a/examples/repl.rs b/examples/repl.rs index 4ae1c44f..d2281b6a 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -1,4 +1,4 @@ -use rhai::{Dynamic, Engine, EvalAltResult, Scope, AST}; +use rhai::{Dynamic, Engine, EvalAltResult, Map, Scope, AST, INT}; #[cfg(not(feature = "no_optimize"))] use rhai::OptimizationLevel; diff --git a/src/engine.rs b/src/engine.rs index 6db8d849..a0c4be00 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -120,7 +120,7 @@ impl Target<'_> { chars.iter().for_each(|&ch| s.push(ch)); } } - _ => panic!("should be String"), + _ => unreachable!(), }, } @@ -139,9 +139,10 @@ impl> From for Target<'_> { } } -/// A type to hold a number of `Dynamic` values in static storage for speed, +/// A type to hold a number of values in static storage for speed, /// and any spill-overs in a `Vec`. -struct StaticVec { +#[derive(Debug, Clone)] +pub struct StaticVec { /// Total number of values held. len: usize, /// Static storage. 4 slots should be enough for most cases - i.e. four levels of indirection. @@ -150,7 +151,7 @@ struct StaticVec { more: Vec, } -impl StaticVec { +impl StaticVec { /// Create a new `StaticVec`. pub fn new() -> Self { Self { @@ -191,6 +192,21 @@ impl StaticVec { result } + /// Get the number of items in this `StaticVec`. + pub fn len(&self) -> usize { + self.len + } + pub fn get(&self, index: usize) -> &T { + if index >= self.len { + panic!("index OOB in StaticVec"); + } + + if index < self.list.len() { + self.list.get(index).unwrap() + } else { + self.more.get(index - self.list.len()).unwrap() + } + } } /// A type that holds all the current states of the Engine. @@ -460,13 +476,38 @@ fn default_print(s: &str) { fn search_scope<'a>( scope: &'a mut Scope, name: &str, - begin: Position, + namespaces: Option<&Box>>, + pos: Position, ) -> Result<(&'a mut Dynamic, ScopeEntryType), Box> { - let (index, _) = scope - .get(name) - .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), begin)))?; + if let Some(namespaces) = namespaces { + let (id, root_pos) = namespaces.get(0); // First namespace + let mut sub_scope = scope + .find_sub_scope(id) + .ok_or_else(|| Box::new(EvalAltResult::ErrorNamespaceNotFound(id.into(), *root_pos)))?; - Ok(scope.get_mut(index)) + for x in 1..namespaces.len() { + let (id, id_pos) = namespaces.get(x); + + sub_scope = sub_scope + .get_mut(id) + .unwrap() + .downcast_mut::() + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorNamespaceNotFound(id.into(), *id_pos)) + })?; + } + + sub_scope + .get_mut(name) + .map(|v| (v, ScopeEntryType::Constant)) + .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), pos))) + } else { + let (index, _) = scope + .get(name) + .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), pos)))?; + + Ok(scope.get_mut(index)) + } } impl Engine { @@ -813,7 +854,7 @@ impl Engine { } else { match rhs { // xxx.fn_name(arg_expr_list) - Expr::FnCall(fn_name, _, def_val, pos) => { + Expr::FnCall(fn_name, None,_, def_val, pos) => { let mut args: Vec<_> = once(obj) .chain(idx_val.downcast_mut::().unwrap().iter_mut()) .collect(); @@ -822,6 +863,8 @@ impl Engine { // TODO - Remove assumption of side effects by checking whether the first parameter is &mut self.exec_fn_call(fn_lib, fn_name, &mut args, def_val, *pos, 0).map(|v| (v, true)) } + // xxx.namespace::fn_name(...) - syntax error + Expr::FnCall(_,_,_,_,_) => unreachable!(), // {xxx:map}.id = ??? Expr::Property(id, pos) if obj.is::() && new_val.is_some() => { let mut indexed_val = @@ -927,10 +970,10 @@ impl Engine { match dot_lhs { // id.??? or id[???] - Expr::Variable(id, index, pos) => { + Expr::Variable(id, namespaces, index, pos) => { let (target, typ) = match index { Some(i) if !state.always_search => scope.get_mut(scope.len() - i.get()), - _ => search_scope(scope, id, *pos)?, + _ => search_scope(scope, id, namespaces.as_ref(), *pos)?, }; // Constants cannot be modified @@ -984,7 +1027,7 @@ impl Engine { level: usize, ) -> Result<(), Box> { match expr { - Expr::FnCall(_, arg_exprs, _, _) => { + Expr::FnCall(_, None, arg_exprs, _, _) => { let arg_values = arg_exprs .iter() .map(|arg_expr| self.eval_expr(scope, state, fn_lib, arg_expr, level)) @@ -992,6 +1035,7 @@ impl Engine { idx_values.push(arg_values) } + Expr::FnCall(_, _, _, _, _) => unreachable!(), Expr::Property(_, _) => idx_values.push(()), // Store a placeholder - no need to copy the property name Expr::Index(lhs, rhs, _) | Expr::Dot(lhs, rhs, _) => { // Evaluate in left-to-right order @@ -1160,11 +1204,13 @@ impl Engine { Expr::FloatConstant(f, _) => Ok((*f).into()), Expr::StringConstant(s, _) => Ok(s.to_string().into()), Expr::CharConstant(c, _) => Ok((*c).into()), - Expr::Variable(_, Some(index), _) if !state.always_search => { + Expr::Variable(_, None, Some(index), _) if !state.always_search => { Ok(scope.get_mut(scope.len() - index.get()).0.clone()) } - Expr::Variable(id, _, pos) => search_scope(scope, id, *pos).map(|(v, _)| v.clone()), - Expr::Property(_, _) => panic!("unexpected property."), + Expr::Variable(id, namespaces, _, pos) => { + search_scope(scope, id, namespaces.as_ref(), *pos).map(|(v, _)| v.clone()) + } + Expr::Property(_, _) => unreachable!(), // Statement block Expr::Stmt(stmt, _) => self.eval_stmt(scope, state, fn_lib, stmt, level), @@ -1175,22 +1221,19 @@ impl Engine { match lhs.as_ref() { // name = rhs - Expr::Variable(name, _, pos) => match scope.get(name) { - None => { - return Err(Box::new(EvalAltResult::ErrorVariableNotFound( - name.to_string(), - *pos, - ))) + Expr::Variable(name, namespaces, _, pos) => { + match search_scope(scope, name, namespaces.as_ref(), *pos)? { + (_, ScopeEntryType::Constant) => Err(Box::new( + EvalAltResult::ErrorAssignmentToConstant(name.to_string(), *op_pos), + )), + (value_ptr, ScopeEntryType::Normal) => { + *value_ptr = rhs_val; + Ok(Default::default()) + } + // End variable cannot be a sub-scope + (_, ScopeEntryType::SubScope) => unreachable!(), } - Some((_, ScopeEntryType::Constant)) => Err(Box::new( - EvalAltResult::ErrorAssignmentToConstant(name.to_string(), *op_pos), - )), - Some((index, ScopeEntryType::Normal)) => { - *scope.get_mut(index).0 = rhs_val; - Ok(Default::default()) - }, - Some((_, ScopeEntryType::Subscope)) => unreachable!(), - }, + } // idx_lhs[idx_expr] = rhs #[cfg(not(feature = "no_index"))] Expr::Index(idx_lhs, idx_expr, op_pos) => { @@ -1252,7 +1295,8 @@ impl Engine { .collect::, _>>()?, )))), - Expr::FnCall(fn_name, arg_exprs, def_val, pos) => { + // TODO - handle namespaced function call + Expr::FnCall(fn_name, namespaces, arg_exprs, def_val, pos) => { let mut arg_values = arg_exprs .iter() .map(|expr| self.eval_expr(scope, state, fn_lib, expr, level)) @@ -1322,7 +1366,7 @@ impl Engine { Expr::False(_) => Ok(false.into()), Expr::Unit(_) => Ok(().into()), - _ => panic!("should not appear: {:?}", expr), + _ => unreachable!(), } } @@ -1499,9 +1543,7 @@ impl Engine { } // Import statement - Stmt::Import(_name_expr, _alias) => { - unimplemented!() - } + Stmt::Import(_name_expr, _alias) => unimplemented!(), // Const statement Stmt::Const(name, expr, _) if expr.is_constant() => { @@ -1512,7 +1554,8 @@ impl Engine { Ok(Default::default()) } - Stmt::Const(_, _, _) => panic!("constant expression not constant!"), + // Const expression not constant + Stmt::Const(_, _, _) => unreachable!(), } } diff --git a/src/optimize.rs b/src/optimize.rs index d018b4c5..5143c480 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -368,10 +368,12 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { //id = id2 = expr2 Expr::Assignment(id2, expr2, pos2) => match (*id, *id2) { // var = var = expr2 -> var = expr2 - (Expr::Variable(var, sp, _), Expr::Variable(var2, sp2, _)) if var == var2 && sp == sp2 => { + (Expr::Variable(var, None, sp, _), Expr::Variable(var2, None, sp2, _)) + if var == var2 && sp == sp2 => + { // Assignment to the same variable - fold state.set_dirty(); - Expr::Assignment(Box::new(Expr::Variable(var, sp, pos)), Box::new(optimize_expr(*expr2, state)), pos) + Expr::Assignment(Box::new(Expr::Variable(var, None, sp, pos)), Box::new(optimize_expr(*expr2, state)), pos) } // id1 = id2 = expr2 (id1, id2) => Expr::Assignment( @@ -547,18 +549,18 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { }, // Do not call some special keywords - Expr::FnCall(id, args, def_value, pos) if DONT_EVAL_KEYWORDS.contains(&id.as_ref().as_ref())=> - Expr::FnCall(id, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos), + Expr::FnCall(id, None, args, def_value, pos) if DONT_EVAL_KEYWORDS.contains(&id.as_ref().as_ref())=> + Expr::FnCall(id, None, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos), // Eagerly call functions - Expr::FnCall(id, args, def_value, pos) + Expr::FnCall(id, None, args, def_value, pos) if state.optimization_level == OptimizationLevel::Full // full optimizations && args.iter().all(|expr| expr.is_constant()) // all arguments are constants => { // First search in script-defined functions (can override built-in) if state.fn_lib.iter().find(|(name, len)| name == id.as_ref() && *len == args.len()).is_some() { // A script-defined function overrides the built-in function - do not make the call - return Expr::FnCall(id, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos); + return Expr::FnCall(id, None, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos); } let mut arg_values: Vec<_> = args.iter().map(Expr::get_constant_value).collect(); @@ -589,16 +591,16 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { }) ).unwrap_or_else(|| // Optimize function call arguments - Expr::FnCall(id, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos) + Expr::FnCall(id, None, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos) ) } // id(args ..) -> optimize function call arguments - Expr::FnCall(id, args, def_value, pos) => - Expr::FnCall(id, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos), + Expr::FnCall(id, namespaces, args, def_value, pos) => + Expr::FnCall(id, namespaces, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos), // constant-name - Expr::Variable(name, _, pos) if state.contains_constant(&name) => { + Expr::Variable(name, None, _, pos) if state.contains_constant(&name) => { state.set_dirty(); // Replace constant with value diff --git a/src/parser.rs b/src/parser.rs index 716b3580..782fcb66 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,7 +1,7 @@ //! Main module defining the lexer and parser. use crate::any::{Dynamic, Union}; -use crate::engine::{calc_fn_def, Engine, FunctionsLib}; +use crate::engine::{calc_fn_def, Engine, FunctionsLib, StaticVec}; use crate::error::{LexError, ParseError, ParseErrorType}; use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::scope::{EntryType as ScopeEntryType, Scope}; @@ -246,10 +246,10 @@ pub enum Stmt { Continue(Position), /// break Break(Position), - /// `return`/`throw` + /// return/throw ReturnWithVal(Option>, ReturnType, Position), /// import expr - Import(Box, Option) + Import(Box, Option), } impl Stmt { @@ -264,9 +264,9 @@ impl Stmt { | Stmt::Break(pos) | Stmt::ReturnWithVal(_, _, pos) => *pos, - Stmt::IfThenElse(expr, _, _) - | Stmt::Expr(expr) - | Stmt::Import(expr, _) => expr.position(), + Stmt::IfThenElse(expr, _, _) | Stmt::Expr(expr) | Stmt::Import(expr, _) => { + expr.position() + } Stmt::While(_, stmt) | Stmt::Loop(stmt) | Stmt::For(_, _, stmt) => stmt.position(), } @@ -326,27 +326,23 @@ pub enum Expr { CharConstant(char, Position), /// String constant. StringConstant(String, Position), - /// Variable access. - Variable(Box, Option, Position), + /// Variable access - (variable name, optional namespaces, optional index, position) + Variable( + Box, + Option>>, + Option, + Position, + ), /// Property access. Property(String, Position), /// { stmt } Stmt(Box, Position), - /// func(expr, ... ) + /// func(expr, ... ) - (function name, optional namespaces, arguments, optional default value, position) /// Use `Cow<'static, str>` because a lot of operators (e.g. `==`, `>=`) are implemented as function calls /// and the function names are predictable, so no need to allocate a new `String`. FnCall( Box>, - Box>, - Option>, - Position, - ), - /// subscope::func(expr, ... ) - /// Use `Cow<'static, str>` because a lot of operators (e.g. `==`, `>=`) are implemented as function calls - /// and the function names are predictable, so no need to allocate a new `String`. - SubscopeFnCall( - String, - Box>, + Option>>, Box>, Option>, Position, @@ -444,11 +440,10 @@ impl Expr { | Self::StringConstant(_, pos) | Self::Array(_, pos) | Self::Map(_, pos) - | Self::Variable(_, _, pos) + | Self::Variable(_, _, _, pos) | Self::Property(_, pos) | Self::Stmt(_, pos) - | Self::FnCall(_, _, _, pos) - | Self::SubscopeFnCall(_, _, _, _, pos) + | Self::FnCall(_, _, _, _, pos) | Self::And(_, _, pos) | Self::Or(_, _, pos) | Self::In(_, _, pos) @@ -471,11 +466,10 @@ impl Expr { | Self::StringConstant(_, pos) | Self::Array(_, pos) | Self::Map(_, pos) - | Self::Variable(_, _, pos) + | Self::Variable(_, _, _, pos) | Self::Property(_, pos) | Self::Stmt(_, pos) - | Self::FnCall(_, _, _, pos) - | Self::SubscopeFnCall(_, _, _, _, pos) + | Self::FnCall(_, _, _, _, pos) | Self::And(_, _, pos) | Self::Or(_, _, pos) | Self::In(_, _, pos) @@ -503,7 +497,7 @@ impl Expr { Self::Stmt(stmt, _) => stmt.is_pure(), - Self::Variable(_, _, _) => true, + Self::Variable(_, _, _, _) => true, expr => expr.is_constant(), } @@ -552,8 +546,7 @@ impl Expr { Self::StringConstant(_, _) | Self::Stmt(_, _) - | Self::FnCall(_, _, _, _) - | Self::SubscopeFnCall(_, _, _, _, _) + | Self::FnCall(_, _, _, _, _) | Self::Assignment(_, _, _) | Self::Dot(_, _, _) | Self::Index(_, _, _) @@ -563,7 +556,19 @@ impl Expr { _ => false, }, - Self::Variable(_, _, _) | Self::Property(_, _) => match token { + Self::Variable(_, None, _, _) => match token { + Token::LeftBracket | Token::LeftParen => true, + #[cfg(not(feature = "no_import"))] + Token::DoubleColon => true, + _ => false, + }, + Self::Variable(_, _, _, _) => match token { + #[cfg(not(feature = "no_import"))] + Token::DoubleColon => true, + _ => false, + }, + + Self::Property(_, _) => match token { Token::LeftBracket | Token::LeftParen => true, _ => false, }, @@ -573,7 +578,7 @@ impl Expr { /// Convert a `Variable` into a `Property`. All other variants are untouched. pub(crate) fn into_property(self) -> Self { match self { - Self::Variable(id, _, pos) => Self::Property(*id, pos), + Self::Variable(id, None, _, pos) => Self::Property(*id, pos), _ => self, } } @@ -637,6 +642,7 @@ fn parse_call_expr<'a>( input: &mut Peekable>, stack: &mut Stack, id: String, + namespaces: Option>>, begin: Position, allow_stmt_expr: bool, ) -> Result> { @@ -658,6 +664,7 @@ fn parse_call_expr<'a>( eat_token(input, Token::RightParen); return Ok(Expr::FnCall( Box::new(id.into()), + namespaces, Box::new(args), None, begin, @@ -673,8 +680,10 @@ fn parse_call_expr<'a>( match input.peek().unwrap() { (Token::RightParen, _) => { eat_token(input, Token::RightParen); + return Ok(Expr::FnCall( Box::new(id.into()), + namespaces, Box::new(args), None, begin, @@ -1001,7 +1010,7 @@ fn parse_primary<'a>( Token::StringConst(s) => Expr::StringConstant(s, pos), Token::Identifier(s) => { let index = stack.find(&s); - Expr::Variable(Box::new(s), index, pos) + Expr::Variable(Box::new(s), None, index, pos) } Token::LeftParen => parse_paren_expr(input, stack, pos, allow_stmt_expr)?, #[cfg(not(feature = "no_index"))] @@ -1024,19 +1033,36 @@ fn parse_primary<'a>( break; } - let (token, pos) = input.next().unwrap(); + let (token, token_pos) = input.next().unwrap(); root_expr = match (root_expr, token) { // Function call - (Expr::Variable(id, _, pos), Token::LeftParen) => { - parse_call_expr(input, stack, *id, pos, allow_stmt_expr)? + (Expr::Variable(id, namespaces, _, pos), Token::LeftParen) => { + parse_call_expr(input, stack, *id, namespaces, pos, allow_stmt_expr)? } (Expr::Property(id, pos), Token::LeftParen) => { - parse_call_expr(input, stack, id, pos, allow_stmt_expr)? + parse_call_expr(input, stack, id, None, pos, allow_stmt_expr)? + } + // Namespaced + #[cfg(not(feature = "no_import"))] + (Expr::Variable(id, mut namespaces, _, pos), Token::DoubleColon) => { + match input.next().unwrap() { + (Token::Identifier(id2), pos2) => { + if let Some(ref mut namespaces) = namespaces { + namespaces.push((*id, pos)); + } else { + let mut vec = StaticVec::new(); + vec.push((*id, pos)); + namespaces = Some(Box::new(vec)); + } + Expr::Variable(Box::new(id2), namespaces, None, pos2) + } + (_, pos2) => return Err(PERR::VariableExpected.into_err(pos2)), + } } // Indexing (expr, Token::LeftBracket) => { - parse_index_chain(input, stack, expr, pos, allow_stmt_expr)? + parse_index_chain(input, stack, expr, token_pos, allow_stmt_expr)? } // Unknown postfix operator (expr, token) => panic!("unknown postfix operator {:?} for {:?}", token, expr), @@ -1092,6 +1118,7 @@ fn parse_unary<'a>( // Call negative function e => Ok(Expr::FnCall( Box::new("-".into()), + None, Box::new(vec![e]), None, pos, @@ -1108,6 +1135,7 @@ fn parse_unary<'a>( let pos = eat_token(input, Token::Bang); Ok(Expr::FnCall( Box::new("!".into()), + None, Box::new(vec![parse_primary(input, stack, allow_stmt_expr)?]), Some(Box::new(false.into())), // NOT operator, when operating on invalid operand, defaults to false pos, @@ -1161,25 +1189,34 @@ fn parse_op_assignment_stmt<'a>( // lhs op= rhs -> lhs = op(lhs, rhs) let args = vec![lhs_copy, rhs]; - let rhs_expr = Expr::FnCall(Box::new(op.into()), Box::new(args), None, pos); + let rhs_expr = Expr::FnCall(Box::new(op.into()), None, Box::new(args), None, pos); Ok(Expr::Assignment(Box::new(lhs), Box::new(rhs_expr), pos)) } /// Make a dot expression. -fn make_dot_expr(lhs: Expr, rhs: Expr, op_pos: Position, is_index: bool) -> Expr { - match (lhs, rhs) { +fn make_dot_expr( + lhs: Expr, + rhs: Expr, + op_pos: Position, + is_index: bool, +) -> Result> { + Ok(match (lhs, rhs) { // idx_lhs[idx_rhs].rhs // Attach dot chain to the bottom level of indexing chain (Expr::Index(idx_lhs, idx_rhs, idx_pos), rhs) => Expr::Index( idx_lhs, - Box::new(make_dot_expr(*idx_rhs, rhs, op_pos, true)), + Box::new(make_dot_expr(*idx_rhs, rhs, op_pos, true)?), idx_pos, ), // lhs.id - (lhs, rhs @ Expr::Variable(_, _, _)) | (lhs, rhs @ Expr::Property(_, _)) => { + (lhs, rhs @ Expr::Variable(_, None, _, _)) | (lhs, rhs @ Expr::Property(_, _)) => { let lhs = if is_index { lhs.into_property() } else { lhs }; Expr::Dot(Box::new(lhs), Box::new(rhs.into_property()), op_pos) } + // lhs.namespace::id - syntax error + (_, Expr::Variable(_, Some(namespaces), _, _)) => { + return Err(PERR::PropertyExpected.into_err(namespaces.get(0).1)) + } // lhs.dot_lhs.dot_rhs (lhs, Expr::Dot(dot_lhs, dot_rhs, dot_pos)) => Expr::Dot( Box::new(lhs), @@ -1202,7 +1239,7 @@ fn make_dot_expr(lhs: Expr, rhs: Expr, op_pos: Position, is_index: bool) -> Expr ), // lhs.rhs (lhs, rhs) => Expr::Dot(Box::new(lhs), Box::new(rhs.into_property()), op_pos), - } + }) } /// Make an 'in' expression. @@ -1380,24 +1417,28 @@ fn parse_binary_op<'a>( current_lhs = match op_token { Token::Plus => Expr::FnCall( Box::new("+".into()), + None, Box::new(vec![current_lhs, rhs]), None, pos, ), Token::Minus => Expr::FnCall( Box::new("-".into()), + None, Box::new(vec![current_lhs, rhs]), None, pos, ), Token::Multiply => Expr::FnCall( Box::new("*".into()), + None, Box::new(vec![current_lhs, rhs]), None, pos, ), Token::Divide => Expr::FnCall( Box::new("/".into()), + None, Box::new(vec![current_lhs, rhs]), None, pos, @@ -1405,24 +1446,28 @@ fn parse_binary_op<'a>( Token::LeftShift => Expr::FnCall( Box::new("<<".into()), + None, Box::new(vec![current_lhs, rhs]), None, pos, ), Token::RightShift => Expr::FnCall( Box::new(">>".into()), + None, Box::new(vec![current_lhs, rhs]), None, pos, ), Token::Modulo => Expr::FnCall( Box::new("%".into()), + None, Box::new(vec![current_lhs, rhs]), None, pos, ), Token::PowerOf => Expr::FnCall( Box::new("~".into()), + None, Box::new(vec![current_lhs, rhs]), None, pos, @@ -1431,36 +1476,42 @@ fn parse_binary_op<'a>( // Comparison operators default to false when passed invalid operands Token::EqualsTo => Expr::FnCall( Box::new("==".into()), + None, Box::new(vec![current_lhs, rhs]), cmp_default, pos, ), Token::NotEqualsTo => Expr::FnCall( Box::new("!=".into()), + None, Box::new(vec![current_lhs, rhs]), cmp_default, pos, ), Token::LessThan => Expr::FnCall( Box::new("<".into()), + None, Box::new(vec![current_lhs, rhs]), cmp_default, pos, ), Token::LessThanEqualsTo => Expr::FnCall( Box::new("<=".into()), + None, Box::new(vec![current_lhs, rhs]), cmp_default, pos, ), Token::GreaterThan => Expr::FnCall( Box::new(">".into()), + None, Box::new(vec![current_lhs, rhs]), cmp_default, pos, ), Token::GreaterThanEqualsTo => Expr::FnCall( Box::new(">=".into()), + None, Box::new(vec![current_lhs, rhs]), cmp_default, pos, @@ -1470,18 +1521,21 @@ fn parse_binary_op<'a>( Token::And => Expr::And(Box::new(current_lhs), Box::new(rhs), pos), Token::Ampersand => Expr::FnCall( Box::new("&".into()), + None, Box::new(vec![current_lhs, rhs]), None, pos, ), Token::Pipe => Expr::FnCall( Box::new("|".into()), + None, Box::new(vec![current_lhs, rhs]), None, pos, ), Token::XOr => Expr::FnCall( Box::new("^".into()), + None, Box::new(vec![current_lhs, rhs]), None, pos, @@ -1490,7 +1544,7 @@ fn parse_binary_op<'a>( Token::In => make_in_expr(current_lhs, rhs, pos)?, #[cfg(not(feature = "no_object"))] - Token::Period => make_dot_expr(current_lhs, rhs, pos, false), + Token::Period => make_dot_expr(current_lhs, rhs, pos, false)?, token => return Err(PERR::UnknownOperator(token.syntax().into()).into_err(pos)), }; @@ -1704,8 +1758,8 @@ fn parse_let<'a>( ScopeEntryType::Constant => { Err(PERR::ForbiddenConstantExpr(name).into_err(init_value.position())) } - - ScopeEntryType::Subscope => unreachable!(), + // Variable cannot be a sub-scope + ScopeEntryType::SubScope => unreachable!(), } } else { // let name diff --git a/src/result.rs b/src/result.rs index 17fe389a..dbb5ec59 100644 --- a/src/result.rs +++ b/src/result.rs @@ -61,6 +61,8 @@ pub enum EvalAltResult { ErrorFor(Position), /// Usage of an unknown variable. Wrapped value is the name of the variable. ErrorVariableNotFound(String, Position), + /// Usage of an unknown namespace. Wrapped value is the name of the namespace. + ErrorNamespaceNotFound(String, Position), /// Assignment to an inappropriate LHS (left-hand-side) expression. ErrorAssignmentToUnknownLHS(Position), /// Assignment to a constant variable. @@ -119,6 +121,7 @@ impl EvalAltResult { Self::ErrorLogicGuard(_) => "Boolean value expected", Self::ErrorFor(_) => "For loop expects an array, object map, or range", Self::ErrorVariableNotFound(_, _) => "Variable not found", + Self::ErrorNamespaceNotFound(_, _) => "Namespace not found", Self::ErrorAssignmentToUnknownLHS(_) => { "Assignment to an unsupported left-hand side expression" } @@ -150,9 +153,10 @@ impl fmt::Display for EvalAltResult { Self::ErrorParsing(p) => write!(f, "Syntax error: {}", p), - Self::ErrorFunctionNotFound(s, pos) | Self::ErrorVariableNotFound(s, pos) => { - write!(f, "{}: '{}' ({})", desc, s, pos) - } + Self::ErrorFunctionNotFound(s, pos) + | Self::ErrorVariableNotFound(s, pos) + | Self::ErrorNamespaceNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos), + Self::ErrorDotExpr(s, pos) if !s.is_empty() => write!(f, "{} {} ({})", desc, s, pos), Self::ErrorIndexingType(_, pos) @@ -269,6 +273,7 @@ impl EvalAltResult { | Self::ErrorLogicGuard(pos) | Self::ErrorFor(pos) | Self::ErrorVariableNotFound(_, pos) + | Self::ErrorNamespaceNotFound(_, pos) | Self::ErrorAssignmentToUnknownLHS(pos) | Self::ErrorAssignmentToConstant(_, pos) | Self::ErrorMismatchOutputType(_, pos) @@ -303,6 +308,7 @@ impl EvalAltResult { | Self::ErrorLogicGuard(pos) | Self::ErrorFor(pos) | Self::ErrorVariableNotFound(_, pos) + | Self::ErrorNamespaceNotFound(_, pos) | Self::ErrorAssignmentToUnknownLHS(pos) | Self::ErrorAssignmentToConstant(_, pos) | Self::ErrorMismatchOutputType(_, pos) diff --git a/src/scope.rs b/src/scope.rs index 937efe1b..d4ad0cb8 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,6 +1,7 @@ //! Module that defines the `Scope` type representing a function call-stack scope. use crate::any::{Dynamic, Variant}; +use crate::engine::Map; use crate::parser::{map_dynamic_to_expr, Expr}; use crate::token::Position; @@ -13,8 +14,8 @@ pub enum EntryType { Normal, /// Immutable constant value. Constant, - /// Name of a subscope, allowing member access with the :: operator. - Subscope, + /// Name of a sub-scope, allowing member access with the :: operator. + SubScope, } /// An entry in the Scope. @@ -167,22 +168,27 @@ impl<'a> Scope<'a> { self.push_dynamic_value(name, EntryType::Normal, value, false); } - /// Add (push) a new subscope to the Scope. + /// Add (push) a new sub-scope to the Scope. /// - /// Subscopes are used for access to members in modules and plugins. + /// Sub-scopes are used for accessing members in modules and plugins under a namespace. /// /// # Examples /// /// ``` - /// use rhai::Scope; + /// use rhai::{Scope, Map}; /// /// let mut my_scope = Scope::new(); /// - /// my_scope.push_subscope("x".to_string(), "My Plugin".to_string()); - /// assert_eq!(my_scope.get_subscope("x").unwrap(), "My Plugin"); + /// let mut sub_scope = Map::new(); + /// sub_scope.insert("x".to_string(), 42_i64.into()); + /// + /// my_scope.push_sub_scope("my_plugin", sub_scope); + /// + /// let s = my_scope.find_sub_scope("my_plugin").unwrap(); + /// assert_eq!(*s.get("x").unwrap().downcast_ref::().unwrap(), 42); /// ``` - pub fn push_subscope(&mut self, name: String, value: String) { - self.push_dynamic_value(name, EntryType::Subscope, Dynamic::from(value), true); + pub fn push_sub_scope>>(&mut self, name: K, value: Map) { + self.push_dynamic_value(name, EntryType::SubScope, value.into(), true); } /// Add (push) a new constant to the Scope. @@ -284,6 +290,8 @@ impl<'a> Scope<'a> { /// Does the scope contain the entry? /// + /// Sub-scopes are ignored. + /// /// # Examples /// /// ``` @@ -299,37 +307,45 @@ impl<'a> Scope<'a> { self.0 .iter() .rev() // Always search a Scope in reverse order - .any(|Entry { name: key, .. }| name == key) + .any(|Entry { name: key, typ, .. }| match typ { + EntryType::Normal | EntryType::Constant => name == key, + EntryType::SubScope => false, + }) } /// Find an entry in the Scope, starting from the last. + /// + /// Sub-scopes are ignored. pub(crate) fn get(&self, name: &str) -> Option<(usize, EntryType)> { self.0 .iter() .enumerate() .rev() // Always search a Scope in reverse order - .find_map(|(index, Entry { name: key, typ, .. })| { - if name == key { - Some((index, *typ)) - } else { - None + .find_map(|(index, Entry { name: key, typ, .. })| match typ { + EntryType::Normal | EntryType::Constant => { + if name == key { + Some((index, *typ)) + } else { + None + } } + EntryType::SubScope => None, }) } - /// Get the subscope of an entry in the Scope, starting from the last. - /// - pub fn get_subscope(&self, name: &str) -> Option { + /// Find a sub-scope in the Scope, starting from the last entry. + pub fn find_sub_scope(&mut self, name: &str) -> Option<&mut Map> { self.0 - .iter() + .iter_mut() .rev() - .find(|Entry { name: key, typ, .. }| name == key && - std::mem::discriminant(typ) == std::mem::discriminant(&EntryType::Subscope)) - .and_then(|Entry { value, .. }| value.downcast_ref::().cloned()) + .find(|Entry { name: key, typ, .. }| name == key && *typ == EntryType::SubScope) + .and_then(|Entry { value, .. }| value.downcast_mut::()) } /// Get the value of an entry in the Scope, starting from the last. /// + /// Sub-scopes are ignored. + /// /// # Examples /// /// ``` @@ -344,7 +360,10 @@ impl<'a> Scope<'a> { self.0 .iter() .rev() - .find(|Entry { name: key, .. }| name == key) + .find(|Entry { name: key, typ, .. }| match typ { + EntryType::Normal | EntryType::Constant => name == key, + EntryType::SubScope => false, + }) .and_then(|Entry { value, .. }| value.downcast_ref::().cloned()) } @@ -371,14 +390,13 @@ impl<'a> Scope<'a> { /// ``` pub fn set_value(&mut self, name: &'a str, value: T) { match self.get(name) { + None => self.push(name, value), Some((_, EntryType::Constant)) => panic!("variable {} is constant", name), Some((index, EntryType::Normal)) => { self.0.get_mut(index).unwrap().value = Dynamic::from(value) } - Some((index, EntryType::Subscope)) => { - self.0.get_mut(index).unwrap().value = Dynamic::from(value) - } - None => self.push(name, value), + // Sub-scopes cannot be modified + Some((_, EntryType::SubScope)) => unreachable!(), } } diff --git a/src/token.rs b/src/token.rs index d136d172..698cb4f4 100644 --- a/src/token.rs +++ b/src/token.rs @@ -153,6 +153,7 @@ pub enum Token { RightShift, SemiColon, Colon, + #[cfg(not(feature = "no_import"))] DoubleColon, Comma, Period, @@ -198,6 +199,12 @@ pub enum Token { XOrAssign, ModuloAssign, PowerOfAssign, + #[cfg(not(feature = "no_import"))] + Import, + #[cfg(not(feature = "no_import"))] + Export, + #[cfg(not(feature = "no_import"))] + As, LexError(Box), EOF, } @@ -231,6 +238,7 @@ impl Token { Divide => "/", SemiColon => ";", Colon => ":", + #[cfg(not(feature = "no_import"))] DoubleColon => "::", Comma => ",", Period => ".", @@ -245,6 +253,8 @@ impl Token { Else => "else", While => "while", Loop => "loop", + For => "for", + In => "in", LessThan => "<", GreaterThan => ">", Bang => "!", @@ -278,8 +288,12 @@ impl Token { ModuloAssign => "%=", PowerOf => "~", PowerOfAssign => "~=", - For => "for", - In => "in", + #[cfg(not(feature = "no_import"))] + Import => "import", + #[cfg(not(feature = "no_import"))] + Export => "export", + #[cfg(not(feature = "no_import"))] + As => "as", EOF => "{EOF}", _ => panic!("operator should be match in outer scope"), }) @@ -743,6 +757,13 @@ impl<'a> TokenIterator<'a> { "for" => Token::For, "in" => Token::In, + #[cfg(not(feature = "no_import"))] + "import" => Token::Import, + #[cfg(not(feature = "no_import"))] + "export" => Token::Export, + #[cfg(not(feature = "no_import"))] + "as" => Token::As, + #[cfg(not(feature = "no_function"))] "fn" => Token::Fn, @@ -897,6 +918,7 @@ impl<'a> TokenIterator<'a> { } ('=', _) => return Some((Token::Equals, pos)), + #[cfg(not(feature = "no_import"))] (':', ':') => { self.eat_next(); return Some((Token::DoubleColon, pos));