diff --git a/Cargo.toml b/Cargo.toml index b2330d24..3148eb3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +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 +no_import = [] # no 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/README.md b/README.md index b453b04c..36337932 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Rhai's current features set: * [`no-std`](#optional-features) support * Support for [function overloading](#function-overloading) * Support for [operator overloading](#operator-overloading) +* Support for loading external [modules] * Compiled script is [optimized](#script-optimization) for repeat evaluations * Support for [minimal builds](#minimal-builds) by excluding unneeded language [features](#optional-features) * Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/) @@ -69,6 +70,7 @@ Optional features | `no_object` | Disable support for custom types and objects. | | `no_float` | Disable floating-point numbers and math if not needed. | | `no_optimize` | Disable the script optimizer. | +| `no_import` | Disable modules. | | `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. | | `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. | | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | @@ -84,6 +86,7 @@ Excluding unneeded functionalities can result in smaller, faster builds as well [`no_function`]: #optional-features [`no_object`]: #optional-features [`no_optimize`]: #optional-features +[`no_import`]: #optional-features [`only_i32`]: #optional-features [`only_i64`]: #optional-features [`no_std`]: #optional-features @@ -1998,6 +2001,22 @@ for entry in logbook.read().unwrap().iter() { } ``` +Using external modules +---------------------- + +[module]: #using-external-modules +[modules]: #using-external-modules + +```rust +import "crypto" as crypto; // Import an external script file as a module + +crypto::encrypt(secret); // Use functions defined under the module via '::' + +print(crypto::status); // Module variables are constants + +crypto::hash::sha256(key); // Sub-modules are also supported +``` + Script optimization =================== diff --git a/src/engine.rs b/src/engine.rs index 6af55b69..8ec80117 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -476,25 +476,23 @@ fn default_print(s: &str) { fn search_scope<'a>( scope: &'a mut Scope, name: &str, - namespaces: Option<&Box>>, + modules: Option<&Box>>, pos: Position, ) -> Result<(&'a mut Dynamic, ScopeEntryType), Box> { - if let Some(namespaces) = namespaces { - let (id, root_pos) = namespaces.get(0); // First namespace + if let Some(modules) = modules { + let (id, root_pos) = modules.get(0); // First module let mut sub_scope = scope .find_sub_scope(id) - .ok_or_else(|| Box::new(EvalAltResult::ErrorNamespaceNotFound(id.into(), *root_pos)))?; + .ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos)))?; - for x in 1..namespaces.len() { - let (id, id_pos) = namespaces.get(x); + for x in 1..modules.len() { + let (id, id_pos) = modules.get(x); sub_scope = sub_scope .get_mut(id) .unwrap() .downcast_mut::() - .ok_or_else(|| { - Box::new(EvalAltResult::ErrorNamespaceNotFound(id.into(), *id_pos)) - })?; + .ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *id_pos)))?; } sub_scope @@ -503,7 +501,7 @@ fn search_scope<'a>( .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), pos))) } else { let (index, _) = scope - .get(name) + .get_index(name) .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), pos)))?; Ok(scope.get_mut(index)) @@ -863,7 +861,7 @@ 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 + // xxx.module::fn_name(...) - syntax error Expr::FnCall(_,_,_,_,_) => unreachable!(), // {xxx:map}.id = ??? Expr::Property(id, pos) if obj.is::() && new_val.is_some() => { @@ -934,7 +932,7 @@ impl Engine { if may_be_changed { if let Expr::Property(id, pos) = dot_lhs.as_ref() { let fn_name = make_setter(id); - // Reuse args because the first &mut parameter will not be consumed + // Re-use args because the first &mut parameter will not be consumed args[1] = indexed_val; self.exec_fn_call(fn_lib, &fn_name, &mut args, None, *pos, 0).or_else(|err| match *err { // If there is no setter, no need to feed it back because the property is read-only @@ -974,10 +972,10 @@ impl Engine { match dot_lhs { // id.??? or id[???] - Expr::Variable(id, namespaces, index, pos) => { + Expr::Variable(id, modules, index, pos) => { let (target, typ) = match index { Some(i) if !state.always_search => scope.get_mut(scope.len() - i.get()), - _ => search_scope(scope, id, namespaces.as_ref(), *pos)?, + _ => search_scope(scope, id, modules.as_ref(), *pos)?, }; // Constants cannot be modified @@ -1211,8 +1209,8 @@ impl Engine { Expr::Variable(_, None, Some(index), _) if !state.always_search => { Ok(scope.get_mut(scope.len() - index.get()).0.clone()) } - Expr::Variable(id, namespaces, _, pos) => { - search_scope(scope, id, namespaces.as_ref(), *pos).map(|(v, _)| v.clone()) + Expr::Variable(id, modules, _, pos) => { + search_scope(scope, id, modules.as_ref(), *pos).map(|(v, _)| v.clone()) } Expr::Property(_, _) => unreachable!(), @@ -1225,8 +1223,8 @@ impl Engine { match lhs.as_ref() { // name = rhs - Expr::Variable(name, namespaces, _, pos) => { - match search_scope(scope, name, namespaces.as_ref(), *pos)? { + Expr::Variable(name, modules, _, pos) => { + match search_scope(scope, name, modules.as_ref(), *pos)? { (_, ScopeEntryType::Constant) => Err(Box::new( EvalAltResult::ErrorAssignmentToConstant(name.to_string(), *op_pos), )), @@ -1299,8 +1297,8 @@ impl Engine { .collect::, _>>()?, )))), - // TODO - handle namespaced function call - Expr::FnCall(fn_name, namespaces, arg_exprs, def_val, pos) => { + // TODO - handle moduled function call + Expr::FnCall(fn_name, modules, arg_exprs, def_val, pos) => { let mut arg_values = arg_exprs .iter() .map(|expr| self.eval_expr(scope, state, fn_lib, expr, level)) @@ -1546,9 +1544,6 @@ impl Engine { Ok(Default::default()) } - // Import statement - Stmt::Import(_name_expr, _alias) => unimplemented!(), - // Const statement Stmt::Const(name, expr, _) if expr.is_constant() => { let val = self.eval_expr(scope, state, fn_lib, expr, level)?; @@ -1560,6 +1555,25 @@ impl Engine { // Const expression not constant Stmt::Const(_, _, _) => unreachable!(), + + // Import statement + Stmt::Import(expr, name, _) => { + if let Some(path) = self + .eval_expr(scope, state, fn_lib, expr, level)? + .try_cast::() + { + let mut module = Map::new(); + module.insert("kitty".to_string(), "foo".to_string().into()); + module.insert("path".to_string(), path.into()); + + // TODO - avoid copying module name in inner block? + let mod_name = name.as_ref().clone(); + scope.push_sub_scope(mod_name, module); + Ok(Default::default()) + } else { + Err(Box::new(EvalAltResult::ErrorImportExpr(expr.position()))) + } + } } } diff --git a/src/optimize.rs b/src/optimize.rs index 5143c480..57425c9a 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -596,8 +596,8 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { } // id(args ..) -> optimize function call arguments - 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), + Expr::FnCall(id, modules, args, def_value, pos) => + Expr::FnCall(id, modules, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos), // constant-name Expr::Variable(name, None, _, pos) if state.contains_constant(&name) => { diff --git a/src/parser.rs b/src/parser.rs index 782fcb66..7dcb4d65 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -186,7 +186,7 @@ pub enum ReturnType { /// A type that encapsulates a local stack with variable names to simulate an actual runtime scope. #[derive(Debug, Clone)] -struct Stack(Vec); +struct Stack(Vec<(String, ScopeEntryType)>); impl Stack { /// Create a new `Stack`. @@ -202,13 +202,31 @@ impl Stack { .iter() .rev() .enumerate() - .find(|(_, n)| *n == name) + .find(|(_, (n, typ))| match typ { + ScopeEntryType::Normal | ScopeEntryType::Constant => *n == name, + ScopeEntryType::SubScope => false, + }) + .and_then(|(i, _)| NonZeroUsize::new(i + 1)) + } + /// Find a sub-scope by name in the `Stack`, searching in reverse. + /// The return value is the offset to be deducted from `Stack::len`, + /// i.e. the top element of the `Stack` is offset 1. + /// Return zero when the variable name is not found in the `Stack`. + pub fn find_sub_scope(&self, name: &str) -> Option { + self.0 + .iter() + .rev() + .enumerate() + .find(|(_, (n, typ))| match typ { + ScopeEntryType::SubScope => *n == name, + ScopeEntryType::Normal | ScopeEntryType::Constant => false, + }) .and_then(|(i, _)| NonZeroUsize::new(i + 1)) } } impl Deref for Stack { - type Target = Vec; + type Target = Vec<(String, ScopeEntryType)>; fn deref(&self) -> &Self::Target { &self.0 @@ -248,8 +266,8 @@ pub enum Stmt { Break(Position), /// return/throw ReturnWithVal(Option>, ReturnType, Position), - /// import expr - Import(Box, Option), + /// import expr as module + Import(Box, Box, Position), } impl Stmt { @@ -259,14 +277,13 @@ impl Stmt { Stmt::Noop(pos) | Stmt::Let(_, _, pos) | Stmt::Const(_, _, pos) + | Stmt::Import(_, _, pos) | Stmt::Block(_, pos) | Stmt::Continue(pos) | Stmt::Break(pos) | Stmt::ReturnWithVal(_, _, pos) => *pos, - Stmt::IfThenElse(expr, _, _) | Stmt::Expr(expr) | Stmt::Import(expr, _) => { - expr.position() - } + Stmt::IfThenElse(expr, _, _) | Stmt::Expr(expr) => expr.position(), Stmt::While(_, stmt) | Stmt::Loop(stmt) | Stmt::For(_, _, stmt) => stmt.position(), } @@ -279,14 +296,14 @@ impl Stmt { | Stmt::While(_, _) | Stmt::Loop(_) | Stmt::For(_, _, _) - | Stmt::Block(_, _) - | Stmt::Import(_, _) => true, + | Stmt::Block(_, _) => true, // A No-op requires a semicolon in order to know it is an empty statement! Stmt::Noop(_) => false, Stmt::Let(_, _, _) | Stmt::Const(_, _, _) + | Stmt::Import(_, _, _) | Stmt::Expr(_) | Stmt::Continue(_) | Stmt::Break(_) @@ -310,7 +327,7 @@ impl Stmt { Stmt::Let(_, _, _) | Stmt::Const(_, _, _) => false, Stmt::Block(statements, _) => statements.iter().all(Stmt::is_pure), Stmt::Continue(_) | Stmt::Break(_) | Stmt::ReturnWithVal(_, _, _) => false, - Stmt::Import(_, _) => false, + Stmt::Import(_, _, _) => false, } } } @@ -326,7 +343,7 @@ pub enum Expr { CharConstant(char, Position), /// String constant. StringConstant(String, Position), - /// Variable access - (variable name, optional namespaces, optional index, position) + /// Variable access - (variable name, optional modules, optional index, position) Variable( Box, Option>>, @@ -337,7 +354,7 @@ pub enum Expr { Property(String, Position), /// { stmt } Stmt(Box, Position), - /// func(expr, ... ) - (function name, optional namespaces, arguments, optional default value, position) + /// func(expr, ... ) - (function name, optional modules, 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( @@ -614,11 +631,11 @@ fn match_token(input: &mut Peekable, token: Token) -> Result( input: &mut Peekable>, stack: &mut Stack, - begin: Position, + pos: Position, allow_stmt_expr: bool, ) -> Result> { if match_token(input, Token::RightParen)? { - return Ok(Expr::Unit(begin)); + return Ok(Expr::Unit(pos)); } let expr = parse_expr(input, stack, allow_stmt_expr)?; @@ -630,7 +647,7 @@ fn parse_paren_expr<'a>( (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), // ( xxx ??? (_, pos) => Err(PERR::MissingToken( - ")".into(), + Token::RightParen.into(), "for a matching ( in this expression".into(), ) .into_err(pos)), @@ -642,7 +659,7 @@ fn parse_call_expr<'a>( input: &mut Peekable>, stack: &mut Stack, id: String, - namespaces: Option>>, + modules: Option>>, begin: Position, allow_stmt_expr: bool, ) -> Result> { @@ -652,7 +669,7 @@ fn parse_call_expr<'a>( // id (Token::EOF, pos) => { return Err(PERR::MissingToken( - ")".into(), + Token::RightParen.into(), format!("to close the arguments list of this function call '{}'", id), ) .into_err(*pos)) @@ -664,7 +681,7 @@ fn parse_call_expr<'a>( eat_token(input, Token::RightParen); return Ok(Expr::FnCall( Box::new(id.into()), - namespaces, + modules, Box::new(args), None, begin, @@ -683,7 +700,7 @@ fn parse_call_expr<'a>( return Ok(Expr::FnCall( Box::new(id.into()), - namespaces, + modules, Box::new(args), None, begin, @@ -694,7 +711,7 @@ fn parse_call_expr<'a>( } (Token::EOF, pos) => { return Err(PERR::MissingToken( - ")".into(), + Token::RightParen.into(), format!("to close the arguments list of this function call '{}'", id), ) .into_err(*pos)) @@ -704,7 +721,7 @@ fn parse_call_expr<'a>( } (_, pos) => { return Err(PERR::MissingToken( - ",".into(), + Token::Comma.into(), format!("to separate the arguments to function call '{}'", id), ) .into_err(*pos)) @@ -848,7 +865,7 @@ fn parse_index_chain<'a>( } (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(*pos)), (_, pos) => Err(PERR::MissingToken( - "]".into(), + Token::RightBracket.into(), "for a matching [ in this index expression".into(), ) .into_err(*pos)), @@ -859,7 +876,7 @@ fn parse_index_chain<'a>( fn parse_array_literal<'a>( input: &mut Peekable>, stack: &mut Stack, - begin: Position, + pos: Position, allow_stmt_expr: bool, ) -> Result> { let mut arr = Vec::new(); @@ -875,17 +892,18 @@ fn parse_array_literal<'a>( break; } (Token::EOF, pos) => { - return Err( - PERR::MissingToken("]".into(), "to end this array literal".into()) - .into_err(*pos), + return Err(PERR::MissingToken( + Token::RightBracket.into(), + "to end this array literal".into(), ) + .into_err(*pos)) } (Token::LexError(err), pos) => { return Err(PERR::BadInput(err.to_string()).into_err(*pos)) } (_, pos) => { return Err(PERR::MissingToken( - ",".into(), + Token::Comma.into(), "to separate the items of this array literal".into(), ) .into_err(*pos)) @@ -894,14 +912,14 @@ fn parse_array_literal<'a>( } } - Ok(Expr::Array(arr, begin)) + Ok(Expr::Array(arr, pos)) } /// Parse a map literal. fn parse_map_literal<'a>( input: &mut Peekable>, stack: &mut Stack, - begin: Position, + pos: Position, allow_stmt_expr: bool, ) -> Result> { let mut map = Vec::new(); @@ -917,10 +935,16 @@ fn parse_map_literal<'a>( return Err(PERR::BadInput(err.to_string()).into_err(pos)) } (_, pos) if map.is_empty() => { - return Err(PERR::MissingToken("}".into(), MISSING_RBRACE.into()).into_err(pos)) + return Err( + PERR::MissingToken(Token::RightBrace.into(), MISSING_RBRACE.into()) + .into_err(pos), + ) } (Token::EOF, pos) => { - return Err(PERR::MissingToken("}".into(), MISSING_RBRACE.into()).into_err(pos)) + return Err( + PERR::MissingToken(Token::RightBrace.into(), MISSING_RBRACE.into()) + .into_err(pos), + ) } (_, pos) => return Err(PERR::PropertyExpected.into_err(pos)), }; @@ -932,7 +956,7 @@ fn parse_map_literal<'a>( } (_, pos) => { return Err(PERR::MissingToken( - ":".into(), + Token::Colon.into(), format!( "to follow the property '{}' in this object map literal", name @@ -956,7 +980,7 @@ fn parse_map_literal<'a>( } (Token::Identifier(_), pos) => { return Err(PERR::MissingToken( - ",".into(), + Token::Comma.into(), "to separate the items of this object map literal".into(), ) .into_err(*pos)) @@ -965,7 +989,10 @@ fn parse_map_literal<'a>( return Err(PERR::BadInput(err.to_string()).into_err(*pos)) } (_, pos) => { - return Err(PERR::MissingToken("}".into(), MISSING_RBRACE.into()).into_err(*pos)) + return Err( + PERR::MissingToken(Token::RightBrace.into(), MISSING_RBRACE.into()) + .into_err(*pos), + ) } } } @@ -982,7 +1009,7 @@ fn parse_map_literal<'a>( }) .map_err(|(key, pos)| PERR::DuplicatedProperty(key.to_string()).into_err(pos))?; - Ok(Expr::Map(map, begin)) + Ok(Expr::Map(map, pos)) } /// Parse a primary expression. @@ -1037,25 +1064,28 @@ fn parse_primary<'a>( root_expr = match (root_expr, token) { // Function call - (Expr::Variable(id, namespaces, _, pos), Token::LeftParen) => { - parse_call_expr(input, stack, *id, namespaces, pos, allow_stmt_expr)? + (Expr::Variable(id, modules, _, pos), Token::LeftParen) => { + parse_call_expr(input, stack, *id, modules, pos, allow_stmt_expr)? } (Expr::Property(id, pos), Token::LeftParen) => { parse_call_expr(input, stack, id, None, pos, allow_stmt_expr)? } - // Namespaced + // moduled #[cfg(not(feature = "no_import"))] - (Expr::Variable(id, mut namespaces, _, pos), Token::DoubleColon) => { + (Expr::Variable(id, mut modules, _, pos), Token::DoubleColon) => { match input.next().unwrap() { (Token::Identifier(id2), pos2) => { - if let Some(ref mut namespaces) = namespaces { - namespaces.push((*id, pos)); + if let Some(ref mut modules) = modules { + modules.push((*id, pos)); } else { let mut vec = StaticVec::new(); vec.push((*id, pos)); - namespaces = Some(Box::new(vec)); + modules = Some(Box::new(vec)); } - Expr::Variable(Box::new(id2), namespaces, None, pos2) + + let root = modules.as_ref().unwrap().get(0); + let index = stack.find_sub_scope(&root.0); + Expr::Variable(Box::new(id2), modules, index, pos2) } (_, pos2) => return Err(PERR::VariableExpected.into_err(pos2)), } @@ -1213,9 +1243,9 @@ fn make_dot_expr( 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.module::id - syntax error + (_, Expr::Variable(_, Some(modules), _, _)) => { + return Err(PERR::PropertyExpected.into_err(modules.get(0).1)) } // lhs.dot_lhs.dot_rhs (lhs, Expr::Dot(dot_lhs, dot_rhs, dot_pos)) => Expr::Dot( @@ -1546,7 +1576,7 @@ fn parse_binary_op<'a>( #[cfg(not(feature = "no_object"))] Token::Period => make_dot_expr(current_lhs, rhs, pos, false)?, - token => return Err(PERR::UnknownOperator(token.syntax().into()).into_err(pos)), + token => return Err(PERR::UnknownOperator(token.into()).into_err(pos)), }; } } @@ -1701,7 +1731,7 @@ fn parse_for<'a>( (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), (_, pos) => { return Err( - PERR::MissingToken("in".into(), "after the iteration variable".into()) + PERR::MissingToken(Token::In.into(), "after the iteration variable".into()) .into_err(pos), ) } @@ -1712,7 +1742,7 @@ fn parse_for<'a>( let expr = parse_expr(input, stack, allow_stmt_expr)?; let prev_len = stack.len(); - stack.push(name.clone()); + stack.push((name.clone(), ScopeEntryType::Normal)); let body = parse_block(input, stack, true, allow_stmt_expr)?; @@ -1746,12 +1776,12 @@ fn parse_let<'a>( match var_type { // let name = expr ScopeEntryType::Normal => { - stack.push(name.clone()); + stack.push((name.clone(), ScopeEntryType::Normal)); Ok(Stmt::Let(Box::new(name), Some(Box::new(init_value)), pos)) } // const name = { expr:constant } ScopeEntryType::Constant if init_value.is_constant() => { - stack.push(name.clone()); + stack.push((name.clone(), ScopeEntryType::Constant)); Ok(Stmt::Const(Box::new(name), Box::new(init_value), pos)) } // const name = expr - error @@ -1767,6 +1797,40 @@ fn parse_let<'a>( } } +/// Parse an import statement. +#[cfg(not(feature = "no_import"))] +fn parse_import<'a>( + input: &mut Peekable>, + stack: &mut Stack, + allow_stmt_expr: bool, +) -> Result> { + // import ... + let pos = eat_token(input, Token::Import); + + // import expr ... + let expr = parse_expr(input, stack, allow_stmt_expr)?; + + // import expr as ... + match input.next().unwrap() { + (Token::As, _) => (), + (_, pos) => { + return Err( + PERR::MissingToken(Token::As.into(), "in this import statement".into()) + .into_err(pos), + ) + } + } + + // import expr as name ... + let (name, _) = match input.next().unwrap() { + (Token::Identifier(s), pos) => (s, pos), + (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), + (_, pos) => return Err(PERR::VariableExpected.into_err(pos)), + }; + + Ok(Stmt::Import(Box::new(expr), Box::new(name), pos)) +} + /// Parse a statement block. fn parse_block<'a>( input: &mut Peekable>, @@ -1779,9 +1843,11 @@ fn parse_block<'a>( (Token::LeftBrace, pos) => pos, (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), (_, pos) => { - return Err( - PERR::MissingToken("{".into(), "to start a statement block".into()).into_err(pos), + return Err(PERR::MissingToken( + Token::LeftBrace.into(), + "to start a statement block".into(), ) + .into_err(pos)) } }; @@ -1818,10 +1884,11 @@ fn parse_block<'a>( // { ... stmt ??? (_, pos) => { // Semicolons are not optional between statements - return Err( - PERR::MissingToken(";".into(), "to terminate this statement".into()) - .into_err(*pos), - ); + return Err(PERR::MissingToken( + Token::SemiColon.into(), + "to terminate this statement".into(), + ) + .into_err(*pos)); } } } @@ -1905,6 +1972,8 @@ fn parse_stmt<'a>( Token::Let => parse_let(input, stack, ScopeEntryType::Normal, allow_stmt_expr), Token::Const => parse_let(input, stack, ScopeEntryType::Constant, allow_stmt_expr), + Token::Import => parse_import(input, stack, allow_stmt_expr), + _ => parse_expr_stmt(input, stack, allow_stmt_expr), } } @@ -1936,25 +2005,29 @@ fn parse_fn<'a>( loop { match input.next().unwrap() { (Token::Identifier(s), pos) => { - stack.push(s.clone()); + stack.push((s.clone(), ScopeEntryType::Normal)); params.push((s, pos)) } (Token::LexError(err), pos) => { return Err(PERR::BadInput(err.to_string()).into_err(pos)) } - (_, pos) => return Err(PERR::MissingToken(")".into(), end_err).into_err(pos)), + (_, pos) => { + return Err(PERR::MissingToken(Token::RightParen.into(), end_err).into_err(pos)) + } } match input.next().unwrap() { (Token::RightParen, _) => break, (Token::Comma, _) => (), (Token::Identifier(_), pos) => { - return Err(PERR::MissingToken(",".into(), sep_err).into_err(pos)) + return Err(PERR::MissingToken(Token::Comma.into(), sep_err).into_err(pos)) } (Token::LexError(err), pos) => { return Err(PERR::BadInput(err.to_string()).into_err(pos)) } - (_, pos) => return Err(PERR::MissingToken(",".into(), sep_err).into_err(pos)), + (_, pos) => { + return Err(PERR::MissingToken(Token::Comma.into(), sep_err).into_err(pos)) + } } } } @@ -2064,10 +2137,11 @@ fn parse_global_level<'a>( // stmt ??? (_, pos) => { // Semicolons are not optional between statements - return Err( - PERR::MissingToken(";".into(), "to terminate this statement".into()) - .into_err(*pos), - ); + return Err(PERR::MissingToken( + Token::SemiColon.into(), + "to terminate this statement".into(), + ) + .into_err(*pos)); } } } diff --git a/src/result.rs b/src/result.rs index dbb5ec59..645432b3 100644 --- a/src/result.rs +++ b/src/result.rs @@ -53,6 +53,8 @@ pub enum EvalAltResult { ErrorNumericIndexExpr(Position), /// Trying to index into a map with an index that is not `String`. ErrorStringIndexExpr(Position), + /// Trying to import with an expression that is not `String`. + ErrorImportExpr(Position), /// Invalid arguments for `in` operator. ErrorInExpr(Position), /// The guard expression in an `if` or `while` statement does not return a boolean value. @@ -61,8 +63,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), + /// Usage of an unknown module. Wrapped value is the name of the module. + ErrorModuleNotFound(String, Position), /// Assignment to an inappropriate LHS (left-hand-side) expression. ErrorAssignmentToUnknownLHS(Position), /// Assignment to a constant variable. @@ -108,6 +110,7 @@ impl EvalAltResult { Self::ErrorIndexingType(_, _) => { "Indexing can only be performed on an array, an object map, or a string" } + Self::ErrorImportExpr(_) => "Importing a module expects a string path", Self::ErrorArrayBounds(_, index, _) if *index < 0 => { "Array access expects non-negative index" } @@ -121,7 +124,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::ErrorModuleNotFound(_, _) => "module not found", Self::ErrorAssignmentToUnknownLHS(_) => { "Assignment to an unsupported left-hand side expression" } @@ -155,13 +158,14 @@ impl fmt::Display for EvalAltResult { Self::ErrorFunctionNotFound(s, pos) | Self::ErrorVariableNotFound(s, pos) - | Self::ErrorNamespaceNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos), + | Self::ErrorModuleNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos), Self::ErrorDotExpr(s, pos) if !s.is_empty() => write!(f, "{} {} ({})", desc, s, pos), Self::ErrorIndexingType(_, pos) | Self::ErrorNumericIndexExpr(pos) | Self::ErrorStringIndexExpr(pos) + | Self::ErrorImportExpr(pos) | Self::ErrorLogicGuard(pos) | Self::ErrorFor(pos) | Self::ErrorAssignmentToUnknownLHS(pos) @@ -270,10 +274,11 @@ impl EvalAltResult { | Self::ErrorIndexingType(_, pos) | Self::ErrorNumericIndexExpr(pos) | Self::ErrorStringIndexExpr(pos) + | Self::ErrorImportExpr(pos) | Self::ErrorLogicGuard(pos) | Self::ErrorFor(pos) | Self::ErrorVariableNotFound(_, pos) - | Self::ErrorNamespaceNotFound(_, pos) + | Self::ErrorModuleNotFound(_, pos) | Self::ErrorAssignmentToUnknownLHS(pos) | Self::ErrorAssignmentToConstant(_, pos) | Self::ErrorMismatchOutputType(_, pos) @@ -305,10 +310,11 @@ impl EvalAltResult { | Self::ErrorIndexingType(_, pos) | Self::ErrorNumericIndexExpr(pos) | Self::ErrorStringIndexExpr(pos) + | Self::ErrorImportExpr(pos) | Self::ErrorLogicGuard(pos) | Self::ErrorFor(pos) | Self::ErrorVariableNotFound(_, pos) - | Self::ErrorNamespaceNotFound(_, pos) + | Self::ErrorModuleNotFound(_, pos) | Self::ErrorAssignmentToUnknownLHS(pos) | Self::ErrorAssignmentToConstant(_, pos) | Self::ErrorMismatchOutputType(_, pos) diff --git a/src/scope.rs b/src/scope.rs index d4ad0cb8..3f55aeaa 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -316,7 +316,7 @@ impl<'a> Scope<'a> { /// Find an entry in the Scope, starting from the last. /// /// Sub-scopes are ignored. - pub(crate) fn get(&self, name: &str) -> Option<(usize, EntryType)> { + pub(crate) fn get_index(&self, name: &str) -> Option<(usize, EntryType)> { self.0 .iter() .enumerate() @@ -333,13 +333,28 @@ impl<'a> Scope<'a> { }) } + /// Find a sub-scope in the Scope, starting from the last. + pub(crate) fn get_sub_scope_index(&self, name: &str) -> Option { + self.0 + .iter() + .enumerate() + .rev() // Always search a Scope in reverse order + .find_map(|(index, Entry { name: key, typ, .. })| match typ { + EntryType::SubScope => { + if name == key { + Some(index) + } else { + None + } + } + EntryType::Normal | EntryType::Constant => None, + }) + } + /// 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_mut() - .rev() - .find(|Entry { name: key, typ, .. }| name == key && *typ == EntryType::SubScope) - .and_then(|Entry { value, .. }| value.downcast_mut::()) + let index = self.get_sub_scope_index(name)?; + self.get_mut(index).0.downcast_mut() } /// Get the value of an entry in the Scope, starting from the last. @@ -389,7 +404,7 @@ impl<'a> Scope<'a> { /// assert_eq!(my_scope.get_value::("x").unwrap(), 0); /// ``` pub fn set_value(&mut self, name: &'a str, value: T) { - match self.get(name) { + match self.get_index(name) { None => self.push(name, value), Some((_, EntryType::Constant)) => panic!("variable {} is constant", name), Some((index, EntryType::Normal)) => { diff --git a/src/token.rs b/src/token.rs index 698cb4f4..86a3b820 100644 --- a/src/token.rs +++ b/src/token.rs @@ -422,6 +422,12 @@ impl Token { } } +impl From for String { + fn from(token: Token) -> Self { + token.syntax().into() + } +} + /// An iterator on a `Token` stream. pub struct TokenIterator<'a> { /// Can the next token be a unary operator?