diff --git a/src/engine.rs b/src/engine.rs index 007adf40..6db8d849 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1188,7 +1188,8 @@ impl Engine { 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"))] @@ -1497,6 +1498,11 @@ 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)?; diff --git a/src/parser.rs b/src/parser.rs index df7889c9..716b3580 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -248,6 +248,8 @@ pub enum Stmt { Break(Position), /// `return`/`throw` ReturnWithVal(Option>, ReturnType, Position), + /// import expr + Import(Box, Option) } impl Stmt { @@ -261,7 +263,11 @@ impl Stmt { | Stmt::Continue(pos) | Stmt::Break(pos) | Stmt::ReturnWithVal(_, _, pos) => *pos, - Stmt::IfThenElse(expr, _, _) | Stmt::Expr(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(), } } @@ -273,7 +279,8 @@ impl Stmt { | Stmt::While(_, _) | Stmt::Loop(_) | Stmt::For(_, _, _) - | Stmt::Block(_, _) => true, + | Stmt::Block(_, _) + | Stmt::Import(_, _) => true, // A No-op requires a semicolon in order to know it is an empty statement! Stmt::Noop(_) => false, @@ -303,6 +310,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, } } } @@ -333,6 +341,16 @@ pub enum Expr { 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>, + Box>, + Option>, + Position, + ), /// expr = expr Assignment(Box, Box, Position), /// lhs.rhs @@ -430,6 +448,7 @@ impl Expr { | Self::Property(_, pos) | Self::Stmt(_, pos) | Self::FnCall(_, _, _, pos) + | Self::SubscopeFnCall(_, _, _, _, pos) | Self::And(_, _, pos) | Self::Or(_, _, pos) | Self::In(_, _, pos) @@ -456,6 +475,7 @@ impl Expr { | Self::Property(_, pos) | Self::Stmt(_, pos) | Self::FnCall(_, _, _, pos) + | Self::SubscopeFnCall(_, _, _, _, pos) | Self::And(_, _, pos) | Self::Or(_, _, pos) | Self::In(_, _, pos) @@ -533,6 +553,7 @@ impl Expr { Self::StringConstant(_, _) | Self::Stmt(_, _) | Self::FnCall(_, _, _, _) + | Self::SubscopeFnCall(_, _, _, _, _) | Self::Assignment(_, _, _) | Self::Dot(_, _, _) | Self::Index(_, _, _) @@ -1683,6 +1704,8 @@ fn parse_let<'a>( ScopeEntryType::Constant => { Err(PERR::ForbiddenConstantExpr(name).into_err(init_value.position())) } + + ScopeEntryType::Subscope => unreachable!(), } } else { // let name diff --git a/src/scope.rs b/src/scope.rs index b50d1af3..937efe1b 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -13,6 +13,8 @@ pub enum EntryType { Normal, /// Immutable constant value. Constant, + /// Name of a subscope, allowing member access with the :: operator. + Subscope, } /// An entry in the Scope. @@ -165,6 +167,24 @@ impl<'a> Scope<'a> { self.push_dynamic_value(name, EntryType::Normal, value, false); } + /// Add (push) a new subscope to the Scope. + /// + /// Subscopes are used for access to members in modules and plugins. + /// + /// # Examples + /// + /// ``` + /// use rhai::Scope; + /// + /// 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"); + /// ``` + pub fn push_subscope(&mut self, name: String, value: String) { + self.push_dynamic_value(name, EntryType::Subscope, Dynamic::from(value), true); + } + /// Add (push) a new constant to the Scope. /// /// Constants are immutable and cannot be assigned to. Their values never change. @@ -297,6 +317,17 @@ impl<'a> Scope<'a> { }) } + /// Get the subscope of an entry in the Scope, starting from the last. + /// + pub fn get_subscope(&self, name: &str) -> Option { + self.0 + .iter() + .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()) + } + /// Get the value of an entry in the Scope, starting from the last. /// /// # Examples @@ -344,6 +375,9 @@ impl<'a> Scope<'a> { 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), } } diff --git a/src/token.rs b/src/token.rs index f202bccd..d136d172 100644 --- a/src/token.rs +++ b/src/token.rs @@ -153,6 +153,7 @@ pub enum Token { RightShift, SemiColon, Colon, + DoubleColon, Comma, Period, #[cfg(not(feature = "no_object"))] @@ -230,6 +231,7 @@ impl Token { Divide => "/", SemiColon => ";", Colon => ":", + DoubleColon => "::", Comma => ",", Period => ".", #[cfg(not(feature = "no_object"))] @@ -874,7 +876,6 @@ impl<'a> TokenIterator<'a> { ('/', _) => return Some((Token::Divide, pos)), (';', _) => return Some((Token::SemiColon, pos)), - (':', _) => return Some((Token::Colon, pos)), (',', _) => return Some((Token::Comma, pos)), ('.', _) => return Some((Token::Period, pos)), @@ -896,6 +897,12 @@ impl<'a> TokenIterator<'a> { } ('=', _) => return Some((Token::Equals, pos)), + (':', ':') => { + self.eat_next(); + return Some((Token::DoubleColon, pos)); + } + (':', _) => return Some((Token::Colon, pos)), + ('<', '=') => { self.eat_next(); return Some((Token::LessThanEqualsTo, pos));