//! Main module defining the lexer and parser. use crate::any::{Dynamic, Union}; use crate::calc_fn_hash; use crate::engine::{make_getter, make_setter, Engine}; use crate::error::{LexError, ParseError, ParseErrorType}; use crate::module::{Module, ModuleRef}; use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::token::{Position, Token, TokenStream}; use crate::utils::{StaticVec, StraightHasherBuilder}; use crate::stdlib::{ borrow::Cow, boxed::Box, char, collections::HashMap, format, iter::empty, mem, num::NonZeroUsize, ops::{Add, Deref, DerefMut}, string::{String, ToString}, vec, vec::Vec, }; /// The system integer type. /// /// If the `only_i32` feature is enabled, this will be `i32` instead. #[cfg(not(feature = "only_i32"))] pub type INT = i64; /// The system integer type. /// /// If the `only_i32` feature is not enabled, this will be `i64` instead. #[cfg(feature = "only_i32")] pub type INT = i32; /// The system floating-point type. /// /// Not available under the `no_float` feature. #[cfg(not(feature = "no_float"))] pub type FLOAT = f64; type PERR = ParseErrorType; pub use crate::utils::ImmutableString; /// Compiled AST (abstract syntax tree) of a Rhai script. /// /// Currently, `AST` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. #[derive(Debug, Clone, Default)] pub struct AST( /// Global statements. Vec, /// Script-defined functions. Module, ); impl AST { /// Create a new `AST`. pub fn new(statements: Vec, lib: Module) -> Self { Self(statements, lib) } /// Get the statements. pub(crate) fn statements(&self) -> &Vec { &self.0 } /// Get a mutable reference to the statements. pub(crate) fn statements_mut(&mut self) -> &mut Vec { &mut self.0 } /// Get the script-defined functions. pub(crate) fn lib(&self) -> &Module { &self.1 } /// Merge two `AST` into one. Both `AST`'s are untouched and a new, merged, version /// is returned. /// /// The second `AST` is simply appended to the end of the first _without any processing_. /// Thus, the return value of the first `AST` (if using expression-statement syntax) is buried. /// Of course, if the first `AST` uses a `return` statement at the end, then /// the second `AST` will essentially be dead code. /// /// All script-defined functions in the second `AST` overwrite similarly-named functions /// in the first `AST` with the same number of parameters. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// # #[cfg(not(feature = "no_function"))] /// # { /// use rhai::Engine; /// /// let engine = Engine::new(); /// /// let ast1 = engine.compile(r#"fn foo(x) { 42 + x } foo(1)"#)?; /// let ast2 = engine.compile(r#"fn foo(n) { "hello" + n } foo("!")"#)?; /// /// let ast = ast1.merge(&ast2); // Merge 'ast2' into 'ast1' /// /// // Notice that using the '+' operator also works: /// // let ast = &ast1 + &ast2; /// /// // 'ast' is essentially: /// // /// // fn foo(n) { "hello" + n } // <- definition of first 'foo' is overwritten /// // foo(1) // <- notice this will be "hello1" instead of 43, /// // // but it is no longer the return value /// // foo("!") // returns "hello!" /// /// // Evaluate it /// assert_eq!(engine.eval_ast::(&ast)?, "hello!"); /// # } /// # Ok(()) /// # } /// ``` pub fn merge(&self, other: &Self) -> Self { let Self(statements, functions) = self; let ast = match (statements.is_empty(), other.0.is_empty()) { (false, false) => { let mut statements = statements.clone(); statements.extend(other.0.iter().cloned()); statements } (false, true) => statements.clone(), (true, false) => other.0.clone(), (true, true) => vec![], }; let mut functions = functions.clone(); functions.merge(&other.1); Self::new(ast, functions) } /// Clear all function definitions in the `AST`. #[cfg(not(feature = "no_function"))] pub fn clear_functions(&mut self) { self.1 = Default::default(); } /// Clear all statements in the `AST`, leaving only function definitions. #[cfg(not(feature = "no_function"))] pub fn retain_functions(&mut self) { self.0 = vec![]; } } impl Add for &AST { type Output = AST; fn add(self, rhs: Self) -> Self::Output { self.merge(rhs) } } /// A type representing the access mode of a scripted function. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub enum FnAccess { /// Private function. Private, /// Public function. Public, } /// A scripted function definition. #[derive(Debug, Clone)] pub struct ScriptFnDef { /// Function name. pub name: String, /// Function access mode. pub access: FnAccess, /// Names of function parameters. pub params: StaticVec, /// Function body. pub body: Stmt, /// Position of the function definition. pub pos: Position, } /// `return`/`throw` statement. #[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)] pub enum ReturnType { /// `return` statement. Return, /// `throw` statement. Exception, } #[derive(Debug, Clone, Eq, PartialEq, Hash, Default)] struct ParseState { /// Encapsulates a local stack with variable names to simulate an actual runtime scope. stack: Vec<(String, ScopeEntryType)>, /// Maximum levels of expression nesting. max_expr_depth: usize, /// Maximum length of a string. pub max_string_size: usize, /// Maximum length of an array. pub max_array_size: usize, /// Maximum number of properties in a map. pub max_map_size: usize, } impl ParseState { /// Create a new `ParseState`. pub fn new( max_expr_depth: usize, max_string_size: usize, max_array_size: usize, max_map_size: usize, ) -> Self { Self { max_expr_depth, max_string_size, max_array_size, max_map_size, ..Default::default() } } /// Find a variable by name in the `ParseState`, searching in reverse. /// The return value is the offset to be deducted from `Stack::len`, /// i.e. the top element of the `ParseState` is offset 1. /// Return zero when the variable name is not found in the `ParseState`. pub fn find(&self, name: &str) -> Option { self.stack .iter() .rev() .enumerate() .find(|(_, (n, typ))| match typ { ScopeEntryType::Normal | ScopeEntryType::Constant => *n == name, ScopeEntryType::Module => false, }) .and_then(|(i, _)| NonZeroUsize::new(i + 1)) } /// Find a module by name in the `ParseState`, searching in reverse. /// The return value is the offset to be deducted from `Stack::len`, /// i.e. the top element of the `ParseState` is offset 1. /// Return zero when the variable name is not found in the `ParseState`. pub fn find_module(&self, name: &str) -> Option { self.stack .iter() .rev() .enumerate() .find(|(_, (n, typ))| match typ { ScopeEntryType::Module => *n == name, ScopeEntryType::Normal | ScopeEntryType::Constant => false, }) .and_then(|(i, _)| NonZeroUsize::new(i + 1)) } } impl Deref for ParseState { type Target = Vec<(String, ScopeEntryType)>; fn deref(&self) -> &Self::Target { &self.stack } } impl DerefMut for ParseState { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.stack } } #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] /// A type that encapsulates all the settings for a particular parsing function. struct ParseSettings { /// Current position. pos: Position, /// Is the construct being parsed located at global level? is_global: bool, /// Is the current position inside a loop? is_breakable: bool, /// Is if-expression allowed? allow_if_expr: bool, /// Is statement-expression allowed? allow_stmt_expr: bool, /// Current expression nesting level. level: usize, } impl ParseSettings { /// Create a new `ParseSettings` with one higher expression level. pub fn level_up(&self) -> Self { Self { level: self.level + 1, ..*self } } /// Make sure that the current level of expression nesting is within the maximum limit. pub fn ensure_level_within_max_limit(&self, limit: usize) -> Result<(), ParseError> { if limit == 0 { Ok(()) } else if self.level > limit { Err(PERR::ExprTooDeep.into_err(self.pos)) } else { Ok(()) } } } /// A statement. /// /// Each variant is at most one pointer in size (for speed), /// with everything being allocated together in one single tuple. #[derive(Debug, Clone)] pub enum Stmt { /// No-op. Noop(Position), /// if expr { stmt } else { stmt } IfThenElse(Box<(Expr, Stmt, Option)>), /// while expr { stmt } While(Box<(Expr, Stmt)>), /// loop { stmt } Loop(Box), /// for id in expr { stmt } For(Box<(String, Expr, Stmt)>), /// let id = expr Let(Box<((String, Position), Option)>), /// const id = expr Const(Box<((String, Position), Expr)>), /// { stmt; ... } Block(Box<(StaticVec, Position)>), /// { stmt } Expr(Box), /// continue Continue(Position), /// break Break(Position), /// return/throw ReturnWithVal(Box<((ReturnType, Position), Option)>), /// import expr as module Import(Box<(Expr, (String, Position))>), /// expr id as name, ... Export(Box)>>), } impl Default for Stmt { fn default() -> Self { Self::Noop(Default::default()) } } impl Stmt { /// Get the `Position` of this statement. pub fn position(&self) -> Position { match self { Stmt::Noop(pos) | Stmt::Continue(pos) | Stmt::Break(pos) => *pos, Stmt::Let(x) => (x.0).1, Stmt::Const(x) => (x.0).1, Stmt::ReturnWithVal(x) => (x.0).1, Stmt::Block(x) => x.1, Stmt::IfThenElse(x) => x.0.position(), Stmt::Expr(x) => x.position(), Stmt::While(x) => x.1.position(), Stmt::Loop(x) => x.position(), Stmt::For(x) => x.2.position(), Stmt::Import(x) => (x.1).1, Stmt::Export(x) => (x.get(0).0).1, } } /// Is this statement self-terminated (i.e. no need for a semicolon terminator)? pub fn is_self_terminated(&self) -> bool { match self { Stmt::IfThenElse(_) | Stmt::While(_) | Stmt::Loop(_) | Stmt::For(_) | 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::Export(_) | Stmt::Expr(_) | Stmt::Continue(_) | Stmt::Break(_) | Stmt::ReturnWithVal(_) => false, } } /// Is this statement _pure_? pub fn is_pure(&self) -> bool { match self { Stmt::Noop(_) => true, Stmt::Expr(expr) => expr.is_pure(), Stmt::IfThenElse(x) if x.2.is_some() => { x.0.is_pure() && x.1.is_pure() && x.2.as_ref().unwrap().is_pure() } Stmt::IfThenElse(x) => x.1.is_pure(), Stmt::While(x) => x.0.is_pure() && x.1.is_pure(), Stmt::Loop(x) => x.is_pure(), Stmt::For(x) => x.1.is_pure() && x.2.is_pure(), Stmt::Let(_) | Stmt::Const(_) => false, Stmt::Block(x) => x.0.iter().all(Stmt::is_pure), Stmt::Continue(_) | Stmt::Break(_) | Stmt::ReturnWithVal(_) => false, Stmt::Import(_) => false, Stmt::Export(_) => false, } } } /// An expression. /// /// Each variant is at most one pointer in size (for speed), /// with everything being allocated together in one single tuple. #[derive(Debug, Clone)] pub enum Expr { /// Integer constant. IntegerConstant(Box<(INT, Position)>), /// Floating-point constant. #[cfg(not(feature = "no_float"))] FloatConstant(Box<(FLOAT, Position)>), /// Character constant. CharConstant(Box<(char, Position)>), /// String constant. StringConstant(Box<(ImmutableString, Position)>), /// Variable access - ((variable name, position), optional modules, hash, optional index) Variable( Box<( (String, Position), Option>, u64, Option, )>, ), /// Property access. Property(Box<((String, String, String), Position)>), /// { stmt } Stmt(Box<(Stmt, Position)>), /// Wrapped expression - should not be optimized away. Expr(Box), /// func(expr, ... ) - ((function name, native_only, position), optional modules, hash, arguments, optional default value) /// 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<( (Cow<'static, str>, bool, Position), Option>, u64, StaticVec, Option, )>, ), /// expr op= expr Assignment(Box<(Expr, Cow<'static, str>, Expr, Position)>), /// lhs.rhs Dot(Box<(Expr, Expr, Position)>), /// expr[expr] Index(Box<(Expr, Expr, Position)>), /// [ expr, ... ] Array(Box<(StaticVec, Position)>), /// #{ name:expr, ... } Map(Box<(StaticVec<((String, Position), Expr)>, Position)>), /// lhs in rhs In(Box<(Expr, Expr, Position)>), /// lhs && rhs And(Box<(Expr, Expr, Position)>), /// lhs || rhs Or(Box<(Expr, Expr, Position)>), /// true True(Position), /// false False(Position), /// () Unit(Position), } impl Default for Expr { fn default() -> Self { Self::Unit(Default::default()) } } impl Expr { /// Get the `Dynamic` value of a constant expression. /// /// # Panics /// /// Panics when the expression is not constant. pub fn get_constant_value(&self) -> Dynamic { match self { Self::Expr(x) => x.get_constant_value(), Self::IntegerConstant(x) => x.0.into(), #[cfg(not(feature = "no_float"))] Self::FloatConstant(x) => x.0.into(), Self::CharConstant(x) => x.0.into(), Self::StringConstant(x) => x.0.clone().into(), Self::True(_) => true.into(), Self::False(_) => false.into(), Self::Unit(_) => ().into(), #[cfg(not(feature = "no_index"))] Self::Array(x) if x.0.iter().all(Self::is_constant) => Dynamic(Union::Array(Box::new( x.0.iter().map(Self::get_constant_value).collect::>(), ))), #[cfg(not(feature = "no_object"))] Self::Map(x) if x.0.iter().all(|(_, v)| v.is_constant()) => { Dynamic(Union::Map(Box::new( x.0.iter() .map(|((k, _), v)| (k.clone(), v.get_constant_value())) .collect::>(), ))) } _ => panic!("cannot get value of non-constant expression"), } } /// Get the display value of a constant expression. /// /// # Panics /// /// Panics when the expression is not constant. pub fn get_constant_str(&self) -> String { match self { Self::Expr(x) => x.get_constant_str(), #[cfg(not(feature = "no_float"))] Self::FloatConstant(x) => x.0.to_string(), Self::IntegerConstant(x) => x.0.to_string(), Self::CharConstant(x) => x.0.to_string(), Self::StringConstant(_) => "string".to_string(), Self::True(_) => "true".to_string(), Self::False(_) => "false".to_string(), Self::Unit(_) => "()".to_string(), Self::Array(x) if x.0.iter().all(Self::is_constant) => "array".to_string(), _ => panic!("cannot get value of non-constant expression"), } } /// Get the `Position` of the expression. pub fn position(&self) -> Position { match self { Self::Expr(x) => x.position(), #[cfg(not(feature = "no_float"))] Self::FloatConstant(x) => x.1, Self::IntegerConstant(x) => x.1, Self::CharConstant(x) => x.1, Self::StringConstant(x) => x.1, Self::Array(x) => x.1, Self::Map(x) => x.1, Self::Property(x) => x.1, Self::Stmt(x) => x.1, Self::Variable(x) => (x.0).1, Self::FnCall(x) => (x.0).2, Self::Assignment(x) => x.0.position(), Self::And(x) | Self::Or(x) | Self::In(x) => x.2, Self::True(pos) | Self::False(pos) | Self::Unit(pos) => *pos, Self::Dot(x) | Self::Index(x) => x.0.position(), } } /// Override the `Position` of the expression. pub(crate) fn set_position(mut self, new_pos: Position) -> Self { match &mut self { Self::Expr(ref mut x) => { let expr = mem::take(x); *x = Box::new(expr.set_position(new_pos)); } #[cfg(not(feature = "no_float"))] Self::FloatConstant(x) => x.1 = new_pos, Self::IntegerConstant(x) => x.1 = new_pos, Self::CharConstant(x) => x.1 = new_pos, Self::StringConstant(x) => x.1 = new_pos, Self::Array(x) => x.1 = new_pos, Self::Map(x) => x.1 = new_pos, Self::Variable(x) => (x.0).1 = new_pos, Self::Property(x) => x.1 = new_pos, Self::Stmt(x) => x.1 = new_pos, Self::FnCall(x) => (x.0).2 = new_pos, Self::And(x) => x.2 = new_pos, Self::Or(x) => x.2 = new_pos, Self::In(x) => x.2 = new_pos, Self::True(pos) => *pos = new_pos, Self::False(pos) => *pos = new_pos, Self::Unit(pos) => *pos = new_pos, Self::Assignment(x) => x.3 = new_pos, Self::Dot(x) => x.2 = new_pos, Self::Index(x) => x.2 = new_pos, } self } /// Is the expression pure? /// /// A pure expression has no side effects. pub fn is_pure(&self) -> bool { match self { Self::Expr(x) => x.is_pure(), Self::Array(x) => x.0.iter().all(Self::is_pure), Self::Index(x) | Self::And(x) | Self::Or(x) | Self::In(x) => { let (lhs, rhs, _) = x.as_ref(); lhs.is_pure() && rhs.is_pure() } Self::Stmt(x) => x.0.is_pure(), Self::Variable(_) => true, _ => self.is_constant(), } } /// Is the expression a constant? pub fn is_constant(&self) -> bool { match self { Self::Expr(x) => x.is_constant(), #[cfg(not(feature = "no_float"))] Self::FloatConstant(_) => true, Self::IntegerConstant(_) | Self::CharConstant(_) | Self::StringConstant(_) | Self::True(_) | Self::False(_) | Self::Unit(_) => true, // An array literal is constant if all items are constant Self::Array(x) => x.0.iter().all(Self::is_constant), // An map literal is constant if all items are constant Self::Map(x) => x.0.iter().map(|(_, expr)| expr).all(Self::is_constant), // Check in expression Self::In(x) => match (&x.0, &x.1) { (Self::StringConstant(_), Self::StringConstant(_)) | (Self::CharConstant(_), Self::StringConstant(_)) => true, _ => false, }, _ => false, } } /// Is a particular token allowed as a postfix operator to this expression? pub fn is_valid_postfix(&self, token: &Token) -> bool { match self { Self::Expr(x) => x.is_valid_postfix(token), #[cfg(not(feature = "no_float"))] Self::FloatConstant(_) => false, Self::IntegerConstant(_) | Self::CharConstant(_) | Self::In(_) | Self::And(_) | Self::Or(_) | Self::True(_) | Self::False(_) | Self::Unit(_) | Self::Assignment(_) => false, Self::StringConstant(_) | Self::Stmt(_) | Self::FnCall(_) | Self::Dot(_) | Self::Index(_) | Self::Array(_) | Self::Map(_) => match token { #[cfg(not(feature = "no_index"))] Token::LeftBracket => true, _ => false, }, Self::Variable(_) => match token { #[cfg(not(feature = "no_index"))] Token::LeftBracket => true, Token::LeftParen => true, Token::DoubleColon => true, _ => false, }, Self::Property(_) => match token { #[cfg(not(feature = "no_index"))] Token::LeftBracket => true, Token::LeftParen => true, _ => false, }, } } /// Convert a `Variable` into a `Property`. All other variants are untouched. pub(crate) fn into_property(self) -> Self { match self { Self::Variable(x) if x.1.is_none() => { let (name, pos) = x.0; let getter = make_getter(&name); let setter = make_setter(&name); Self::Property(Box::new(((name.clone(), getter, setter), pos))) } _ => self, } } } /// Consume a particular token, checking that it is the expected one. fn eat_token(input: &mut TokenStream, token: Token) -> Position { let (t, pos) = input.next().unwrap(); if t != token { panic!( "expecting {} (found {}) at {}", token.syntax(), t.syntax(), pos ); } pos } /// Match a particular token, consuming it if matched. fn match_token(input: &mut TokenStream, token: Token) -> Result { let (t, _) = input.peek().unwrap(); if *t == token { eat_token(input, token); Ok(true) } else { Ok(false) } } /// Parse ( expr ) fn parse_paren_expr( input: &mut TokenStream, state: &mut ParseState, settings: ParseSettings, ) -> Result { settings.ensure_level_within_max_limit(state.max_expr_depth)?; if match_token(input, Token::RightParen)? { return Ok(Expr::Unit(settings.pos)); } let expr = parse_expr(input, state, settings.level_up())?; match input.next().unwrap() { // ( xxx ) (Token::RightParen, _) => Ok(expr), // ( (Token::LexError(err), pos) => return Err(err.into_err(pos)), // ( xxx ??? (_, pos) => Err(PERR::MissingToken( Token::RightParen.into(), "for a matching ( in this expression".into(), ) .into_err(pos)), } } /// Parse a function call. fn parse_call_expr( input: &mut TokenStream, state: &mut ParseState, id: String, mut modules: Option>, settings: ParseSettings, ) -> Result { let (token, _) = input.peek().unwrap(); settings.ensure_level_within_max_limit(state.max_expr_depth)?; let mut args = StaticVec::new(); match token { // id Token::EOF => { return Err(PERR::MissingToken( Token::RightParen.into(), format!("to close the arguments list of this function call '{}'", id), ) .into_err(settings.pos)) } // id Token::LexError(err) => return Err(err.into_err(settings.pos)), // id() Token::RightParen => { eat_token(input, Token::RightParen); let hash_script = if let Some(modules) = modules.as_mut() { modules.set_index(state.find_module(&modules.get(0).0)); // Rust functions are indexed in two steps: // 1) Calculate a hash in a similar manner to script-defined functions, // i.e. qualifiers + function name + number of arguments. // 2) Calculate a second hash with no qualifiers, empty function name, // zero number of arguments, and the actual list of argument `TypeId`'s. // 3) The final hash is the XOR of the two hashes. let qualifiers = modules.iter().map(|(m, _)| m.as_str()); calc_fn_hash(qualifiers, &id, 0, empty()) } else { // Qualifiers (none) + function name + no parameters. calc_fn_hash(empty(), &id, 0, empty()) }; return Ok(Expr::FnCall(Box::new(( (id.into(), false, settings.pos), modules, hash_script, args, None, )))); } // id... _ => (), } let settings = settings.level_up(); loop { args.push(parse_expr(input, state, settings)?); match input.peek().unwrap() { // id(...args) (Token::RightParen, _) => { eat_token(input, Token::RightParen); let hash_script = if let Some(modules) = modules.as_mut() { modules.set_index(state.find_module(&modules.get(0).0)); // Rust functions are indexed in two steps: // 1) Calculate a hash in a similar manner to script-defined functions, // i.e. qualifiers + function name + number of arguments. // 2) Calculate a second hash with no qualifiers, empty function name, // zero number of arguments, and the actual list of argument `TypeId`'s. // 3) The final hash is the XOR of the two hashes. let qualifiers = modules.iter().map(|(m, _)| m.as_str()); calc_fn_hash(qualifiers, &id, args.len(), empty()) } else { // Qualifiers (none) + function name + number of arguments. calc_fn_hash(empty(), &id, args.len(), empty()) }; return Ok(Expr::FnCall(Box::new(( (id.into(), false, settings.pos), modules, hash_script, args, None, )))); } // id(...args, (Token::Comma, _) => { eat_token(input, Token::Comma); } // id(...args (Token::EOF, pos) => { return Err(PERR::MissingToken( Token::RightParen.into(), format!("to close the arguments list of this function call '{}'", id), ) .into_err(*pos)) } // id(...args (Token::LexError(err), pos) => return Err(err.into_err(*pos)), // id(...args ??? (_, pos) => { return Err(PERR::MissingToken( Token::Comma.into(), format!("to separate the arguments to function call '{}'", id), ) .into_err(*pos)) } } } } /// Parse an indexing chain. /// Indexing binds to the right, so this call parses all possible levels of indexing following in the input. fn parse_index_chain( input: &mut TokenStream, state: &mut ParseState, lhs: Expr, mut settings: ParseSettings, ) -> Result { settings.ensure_level_within_max_limit(state.max_expr_depth)?; let idx_expr = parse_expr(input, state, settings.level_up())?; // Check type of indexing - must be integer or string match &idx_expr { // lhs[int] Expr::IntegerConstant(x) if x.0 < 0 => { return Err(PERR::MalformedIndexExpr(format!( "Array access expects non-negative index: {} < 0", x.0 )) .into_err(x.1)) } Expr::IntegerConstant(x) => match lhs { Expr::Array(_) | Expr::StringConstant(_) => (), Expr::Map(_) => { return Err(PERR::MalformedIndexExpr( "Object map access expects string index, not a number".into(), ) .into_err(x.1)) } #[cfg(not(feature = "no_float"))] Expr::FloatConstant(_) => { return Err(PERR::MalformedIndexExpr( "Only arrays, object maps and strings can be indexed".into(), ) .into_err(lhs.position())) } Expr::CharConstant(_) | Expr::Assignment(_) | Expr::And(_) | Expr::Or(_) | Expr::In(_) | Expr::True(_) | Expr::False(_) | Expr::Unit(_) => { return Err(PERR::MalformedIndexExpr( "Only arrays, object maps and strings can be indexed".into(), ) .into_err(lhs.position())) } _ => (), }, // lhs[string] Expr::StringConstant(x) => match lhs { Expr::Map(_) => (), Expr::Array(_) | Expr::StringConstant(_) => { return Err(PERR::MalformedIndexExpr( "Array or string expects numeric index, not a string".into(), ) .into_err(x.1)) } #[cfg(not(feature = "no_float"))] Expr::FloatConstant(_) => { return Err(PERR::MalformedIndexExpr( "Only arrays, object maps and strings can be indexed".into(), ) .into_err(lhs.position())) } Expr::CharConstant(_) | Expr::Assignment(_) | Expr::And(_) | Expr::Or(_) | Expr::In(_) | Expr::True(_) | Expr::False(_) | Expr::Unit(_) => { return Err(PERR::MalformedIndexExpr( "Only arrays, object maps and strings can be indexed".into(), ) .into_err(lhs.position())) } _ => (), }, // lhs[float] #[cfg(not(feature = "no_float"))] x @ Expr::FloatConstant(_) => { return Err(PERR::MalformedIndexExpr( "Array access expects integer index, not a float".into(), ) .into_err(x.position())) } // lhs[char] x @ Expr::CharConstant(_) => { return Err(PERR::MalformedIndexExpr( "Array access expects integer index, not a character".into(), ) .into_err(x.position())) } // lhs[??? = ??? ] x @ Expr::Assignment(_) => { return Err(PERR::MalformedIndexExpr( "Array access expects integer index, not an assignment".into(), ) .into_err(x.position())) } // lhs[()] x @ Expr::Unit(_) => { return Err(PERR::MalformedIndexExpr( "Array access expects integer index, not ()".into(), ) .into_err(x.position())) } // lhs[??? && ???], lhs[??? || ???], lhs[??? in ???] x @ Expr::And(_) | x @ Expr::Or(_) | x @ Expr::In(_) => { return Err(PERR::MalformedIndexExpr( "Array access expects integer index, not a boolean".into(), ) .into_err(x.position())) } // lhs[true], lhs[false] x @ Expr::True(_) | x @ Expr::False(_) => { return Err(PERR::MalformedIndexExpr( "Array access expects integer index, not a boolean".into(), ) .into_err(x.position())) } // All other expressions _ => (), } // Check if there is a closing bracket match input.peek().unwrap() { (Token::RightBracket, _) => { eat_token(input, Token::RightBracket); // Any more indexing following? match input.peek().unwrap() { // If another indexing level, right-bind it (Token::LeftBracket, _) => { let prev_pos = settings.pos; settings.pos = eat_token(input, Token::LeftBracket); // Recursively parse the indexing chain, right-binding each let idx_expr = parse_index_chain(input, state, idx_expr, settings.level_up())?; // Indexing binds to right Ok(Expr::Index(Box::new((lhs, idx_expr, prev_pos)))) } // Otherwise terminate the indexing chain _ => { match idx_expr { // Terminate with an `Expr::Expr` wrapper to prevent the last index expression // inside brackets to be mis-parsed as another level of indexing, or a // dot expression/function call to be mis-parsed as following the indexing chain. Expr::Index(_) | Expr::Dot(_) | Expr::FnCall(_) => Ok(Expr::Index( Box::new((lhs, Expr::Expr(Box::new(idx_expr)), settings.pos)), )), _ => Ok(Expr::Index(Box::new((lhs, idx_expr, settings.pos)))), } } } } (Token::LexError(err), pos) => return Err(err.into_err(*pos)), (_, pos) => Err(PERR::MissingToken( Token::RightBracket.into(), "for a matching [ in this index expression".into(), ) .into_err(*pos)), } } /// Parse an array literal. fn parse_array_literal( input: &mut TokenStream, state: &mut ParseState, settings: ParseSettings, ) -> Result { settings.ensure_level_within_max_limit(state.max_expr_depth)?; let mut arr = StaticVec::new(); if !match_token(input, Token::RightBracket)? { while !input.peek().unwrap().0.is_eof() { if state.max_array_size > 0 && arr.len() >= state.max_array_size { return Err(PERR::LiteralTooLarge( "Size of array literal".to_string(), state.max_array_size, ) .into_err(input.peek().unwrap().1)); } let expr = parse_expr(input, state, settings.level_up())?; arr.push(expr); match input.peek().unwrap() { (Token::Comma, _) => eat_token(input, Token::Comma), (Token::RightBracket, _) => { eat_token(input, Token::RightBracket); break; } (Token::EOF, pos) => { return Err(PERR::MissingToken( Token::RightBracket.into(), "to end this array literal".into(), ) .into_err(*pos)) } (Token::LexError(err), pos) => return Err(err.into_err(*pos)), (_, pos) => { return Err(PERR::MissingToken( Token::Comma.into(), "to separate the items of this array literal".into(), ) .into_err(*pos)) } }; } } Ok(Expr::Array(Box::new((arr, settings.pos)))) } /// Parse a map literal. fn parse_map_literal( input: &mut TokenStream, state: &mut ParseState, settings: ParseSettings, ) -> Result { settings.ensure_level_within_max_limit(state.max_expr_depth)?; let mut map = StaticVec::new(); if !match_token(input, Token::RightBrace)? { while !input.peek().unwrap().0.is_eof() { const MISSING_RBRACE: &str = "to end this object map literal"; let (name, pos) = match input.next().unwrap() { (Token::Identifier(s), pos) => (s, pos), (Token::StringConst(s), pos) => (s, pos), (Token::LexError(err), pos) => return Err(err.into_err(pos)), (_, pos) if map.is_empty() => { return Err( PERR::MissingToken(Token::RightBrace.into(), MISSING_RBRACE.into()) .into_err(pos), ) } (Token::EOF, pos) => { return Err( PERR::MissingToken(Token::RightBrace.into(), MISSING_RBRACE.into()) .into_err(pos), ) } (_, pos) => return Err(PERR::PropertyExpected.into_err(pos)), }; match input.next().unwrap() { (Token::Colon, _) => (), (Token::LexError(err), pos) => return Err(err.into_err(pos)), (_, pos) => { return Err(PERR::MissingToken( Token::Colon.into(), format!( "to follow the property '{}' in this object map literal", name ), ) .into_err(pos)) } }; if state.max_map_size > 0 && map.len() >= state.max_map_size { return Err(PERR::LiteralTooLarge( "Number of properties in object map literal".to_string(), state.max_map_size, ) .into_err(input.peek().unwrap().1)); } let expr = parse_expr(input, state, settings.level_up())?; map.push(((name, pos), expr)); match input.peek().unwrap() { (Token::Comma, _) => { eat_token(input, Token::Comma); } (Token::RightBrace, _) => { eat_token(input, Token::RightBrace); break; } (Token::Identifier(_), pos) => { return Err(PERR::MissingToken( Token::Comma.into(), "to separate the items of this object map literal".into(), ) .into_err(*pos)) } (Token::LexError(err), pos) => return Err(err.into_err(*pos)), (_, pos) => { return Err( PERR::MissingToken(Token::RightBrace.into(), MISSING_RBRACE.into()) .into_err(*pos), ) } } } } // Check for duplicating properties map.iter() .enumerate() .try_for_each(|(i, ((k1, _), _))| { map.iter() .skip(i + 1) .find(|((k2, _), _)| k2 == k1) .map_or_else(|| Ok(()), |((k2, pos), _)| Err((k2, *pos))) }) .map_err(|(key, pos)| PERR::DuplicatedProperty(key.to_string()).into_err(pos))?; Ok(Expr::Map(Box::new((map, settings.pos)))) } /// Parse a primary expression. fn parse_primary( input: &mut TokenStream, state: &mut ParseState, mut settings: ParseSettings, ) -> Result { let (token, pos1) = input.peek().unwrap(); settings.pos = *pos1; settings.ensure_level_within_max_limit(state.max_expr_depth)?; let (token, _) = match token { // { - block statement as expression Token::LeftBrace if settings.allow_stmt_expr => { return parse_block(input, state, settings.level_up()) .map(|block| Expr::Stmt(Box::new((block, settings.pos)))) } Token::EOF => return Err(PERR::UnexpectedEOF.into_err(settings.pos)), _ => input.next().unwrap(), }; let mut root_expr = match token { Token::IntegerConstant(x) => Expr::IntegerConstant(Box::new((x, settings.pos))), #[cfg(not(feature = "no_float"))] Token::FloatConstant(x) => Expr::FloatConstant(Box::new((x, settings.pos))), Token::CharConstant(c) => Expr::CharConstant(Box::new((c, settings.pos))), Token::StringConst(s) => Expr::StringConstant(Box::new((s.into(), settings.pos))), Token::Identifier(s) => { let index = state.find(&s); Expr::Variable(Box::new(((s, settings.pos), None, 0, index))) } Token::LeftParen => parse_paren_expr(input, state, settings.level_up())?, #[cfg(not(feature = "no_index"))] Token::LeftBracket => parse_array_literal(input, state, settings.level_up())?, #[cfg(not(feature = "no_object"))] Token::MapStart => parse_map_literal(input, state, settings.level_up())?, Token::True => Expr::True(settings.pos), Token::False => Expr::False(settings.pos), Token::LexError(err) => return Err(err.into_err(settings.pos)), _ => { return Err( PERR::BadInput(format!("Unexpected '{}'", token.syntax())).into_err(settings.pos) ) } }; // Tail processing all possible postfix operators loop { let (token, _) = input.peek().unwrap(); if !root_expr.is_valid_postfix(token) { break; } let (token, token_pos) = input.next().unwrap(); settings.pos = token_pos; root_expr = match (root_expr, token) { // Function call (Expr::Variable(x), Token::LeftParen) => { let ((name, pos), modules, _, _) = *x; settings.pos = pos; parse_call_expr(input, state, name, modules, settings.level_up())? } (Expr::Property(_), _) => unreachable!(), // module access (Expr::Variable(x), Token::DoubleColon) => match input.next().unwrap() { (Token::Identifier(id2), pos2) => { let ((name, pos), mut modules, _, index) = *x; if let Some(ref mut modules) = modules { modules.push((name, pos)); } else { let mut m: ModuleRef = Default::default(); m.push((name, pos)); modules = Some(Box::new(m)); } Expr::Variable(Box::new(((id2, pos2), modules, 0, index))) } (_, pos2) => return Err(PERR::VariableExpected.into_err(pos2)), }, // Indexing #[cfg(not(feature = "no_index"))] (expr, Token::LeftBracket) => { parse_index_chain(input, state, expr, settings.level_up())? } // Unknown postfix operator (expr, token) => panic!( "unknown postfix operator '{}' for {:?}", token.syntax(), expr ), } } match &mut root_expr { // Cache the hash key for module-qualified variables Expr::Variable(x) if x.1.is_some() => { let ((name, _), modules, hash, _) = x.as_mut(); let modules = modules.as_mut().unwrap(); // Qualifiers + variable name *hash = calc_fn_hash(modules.iter().map(|(v, _)| v.as_str()), name, 0, empty()); modules.set_index(state.find_module(&modules.get(0).0)); } _ => (), } Ok(root_expr) } /// Parse a potential unary operator. fn parse_unary( input: &mut TokenStream, state: &mut ParseState, mut settings: ParseSettings, ) -> Result { let (token, token_pos) = input.peek().unwrap(); settings.pos = *token_pos; settings.ensure_level_within_max_limit(state.max_expr_depth)?; match token { // If statement is allowed to act as expressions Token::If if settings.allow_if_expr => Ok(Expr::Stmt(Box::new(( parse_if(input, state, settings.level_up())?, settings.pos, )))), // -expr Token::UnaryMinus => { let pos = eat_token(input, Token::UnaryMinus); match parse_unary(input, state, settings.level_up())? { // Negative integer Expr::IntegerConstant(x) => { let (num, pos) = *x; num.checked_neg() .map(|i| Expr::IntegerConstant(Box::new((i, pos)))) .or_else(|| { #[cfg(not(feature = "no_float"))] { Some(Expr::FloatConstant(Box::new((-(x.0 as FLOAT), pos)))) } #[cfg(feature = "no_float")] { None } }) .ok_or_else(|| LexError::MalformedNumber(format!("-{}", x.0)).into_err(pos)) } // Negative float #[cfg(not(feature = "no_float"))] Expr::FloatConstant(x) => Ok(Expr::FloatConstant(Box::new((-x.0, x.1)))), // Call negative function expr => { let op = "-"; let hash = calc_fn_hash(empty(), op, 2, empty()); let mut args = StaticVec::new(); args.push(expr); Ok(Expr::FnCall(Box::new(( (op.into(), true, pos), None, hash, args, None, )))) } } } // +expr Token::UnaryPlus => { eat_token(input, Token::UnaryPlus); parse_unary(input, state, settings.level_up()) } // !expr Token::Bang => { let pos = eat_token(input, Token::Bang); let mut args = StaticVec::new(); let expr = parse_primary(input, state, settings.level_up())?; args.push(expr); let op = "!"; let hash = calc_fn_hash(empty(), op, 2, empty()); Ok(Expr::FnCall(Box::new(( (op.into(), true, pos), None, hash, args, Some(false.into()), // NOT operator, when operating on invalid operand, defaults to false )))) } // Token::EOF => Err(PERR::UnexpectedEOF.into_err(settings.pos)), // All other tokens _ => parse_primary(input, state, settings.level_up()), } } fn make_assignment_stmt<'a>( fn_name: Cow<'static, str>, state: &mut ParseState, lhs: Expr, rhs: Expr, pos: Position, ) -> Result { match &lhs { // var (non-indexed) = rhs Expr::Variable(x) if x.3.is_none() => { Ok(Expr::Assignment(Box::new((lhs, fn_name.into(), rhs, pos)))) } // var (indexed) = rhs Expr::Variable(x) => { let ((name, name_pos), _, _, index) = x.as_ref(); match state.stack[(state.len() - index.unwrap().get())].1 { ScopeEntryType::Normal => { Ok(Expr::Assignment(Box::new((lhs, fn_name.into(), rhs, pos)))) } // Constant values cannot be assigned to ScopeEntryType::Constant => { Err(PERR::AssignmentToConstant(name.clone()).into_err(*name_pos)) } ScopeEntryType::Module => unreachable!(), } } // xxx[???] = rhs, xxx.??? = rhs Expr::Index(x) | Expr::Dot(x) => match &x.0 { // var[???] (non-indexed) = rhs, var.??? (non-indexed) = rhs Expr::Variable(x) if x.3.is_none() => { Ok(Expr::Assignment(Box::new((lhs, fn_name.into(), rhs, pos)))) } // var[???] (indexed) = rhs, var.??? (indexed) = rhs Expr::Variable(x) => { let ((name, name_pos), _, _, index) = x.as_ref(); match state.stack[(state.len() - index.unwrap().get())].1 { ScopeEntryType::Normal => { Ok(Expr::Assignment(Box::new((lhs, fn_name.into(), rhs, pos)))) } // Constant values cannot be assigned to ScopeEntryType::Constant => { Err(PERR::AssignmentToConstant(name.clone()).into_err(*name_pos)) } ScopeEntryType::Module => unreachable!(), } } // expr[???] = rhs, expr.??? = rhs _ => Err(PERR::AssignmentToCopy.into_err(x.0.position())), }, // const_expr = rhs expr if expr.is_constant() => { Err(PERR::AssignmentToConstant("".into()).into_err(lhs.position())) } // ??? && ??? = rhs, ??? || ??? = rhs Expr::And(_) | Expr::Or(_) => { Err(PERR::BadInput("Possibly a typo of '=='?".to_string()).into_err(pos)) } // expr = rhs _ => Err(PERR::AssignmentToCopy.into_err(lhs.position())), } } /// Parse an operator-assignment expression. fn parse_op_assignment_stmt( input: &mut TokenStream, state: &mut ParseState, lhs: Expr, mut settings: ParseSettings, ) -> Result { let (token, token_pos) = input.peek().unwrap(); settings.pos = *token_pos; settings.ensure_level_within_max_limit(state.max_expr_depth)?; let op = match token { Token::Equals => "".into(), Token::PlusAssign | Token::MinusAssign | Token::MultiplyAssign | Token::DivideAssign | Token::LeftShiftAssign | Token::RightShiftAssign | Token::ModuloAssign | Token::PowerOfAssign | Token::AndAssign | Token::OrAssign | Token::XOrAssign => token.syntax(), _ => return Ok(lhs), }; let (_, pos) = input.next().unwrap(); let rhs = parse_expr(input, state, settings.level_up())?; make_assignment_stmt(op, state, lhs, rhs, pos) } /// Make a dot expression. fn make_dot_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result { Ok(match (lhs, rhs) { // idx_lhs[idx_expr].rhs // Attach dot chain to the bottom level of indexing chain (Expr::Index(x), rhs) => { let (idx_lhs, idx_expr, pos) = *x; Expr::Index(Box::new(( idx_lhs, make_dot_expr(idx_expr, rhs, op_pos)?, pos, ))) } // lhs.id (lhs, Expr::Variable(x)) if x.1.is_none() => { let (name, pos) = x.0; let getter = make_getter(&name); let setter = make_setter(&name); let rhs = Expr::Property(Box::new(((name, getter, setter), pos))); Expr::Dot(Box::new((lhs, rhs, op_pos))) } // lhs.module::id - syntax error (_, Expr::Variable(x)) if x.1.is_some() => { return Err(PERR::PropertyExpected.into_err(x.1.unwrap().get(0).1)); } // lhs.prop (lhs, prop @ Expr::Property(_)) => Expr::Dot(Box::new((lhs, prop, op_pos))), // lhs.dot_lhs.dot_rhs (lhs, Expr::Dot(x)) => { let (dot_lhs, dot_rhs, pos) = *x; Expr::Dot(Box::new(( lhs, Expr::Dot(Box::new((dot_lhs.into_property(), dot_rhs, pos))), op_pos, ))) } // lhs.idx_lhs[idx_rhs] (lhs, Expr::Index(x)) => { let (dot_lhs, dot_rhs, pos) = *x; Expr::Dot(Box::new(( lhs, Expr::Index(Box::new((dot_lhs.into_property(), dot_rhs, pos))), op_pos, ))) } // lhs.func() (lhs, func @ Expr::FnCall(_)) => Expr::Dot(Box::new((lhs, func, op_pos))), // lhs.rhs _ => unreachable!(), }) } /// Make an 'in' expression. fn make_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result { match (&lhs, &rhs) { (_, x @ Expr::IntegerConstant(_)) | (_, x @ Expr::And(_)) | (_, x @ Expr::Or(_)) | (_, x @ Expr::In(_)) | (_, x @ Expr::Assignment(_)) | (_, x @ Expr::True(_)) | (_, x @ Expr::False(_)) | (_, x @ Expr::Unit(_)) => { return Err(PERR::MalformedInExpr( "'in' expression expects a string, array or object map".into(), ) .into_err(x.position())) } #[cfg(not(feature = "no_float"))] (_, x @ Expr::FloatConstant(_)) => { return Err(PERR::MalformedInExpr( "'in' expression expects a string, array or object map".into(), ) .into_err(x.position())) } // "xxx" in "xxxx", 'x' in "xxxx" - OK! (Expr::StringConstant(_), Expr::StringConstant(_)) | (Expr::CharConstant(_), Expr::StringConstant(_)) => (), // 123.456 in "xxxx" #[cfg(not(feature = "no_float"))] (x @ Expr::FloatConstant(_), Expr::StringConstant(_)) => { return Err(PERR::MalformedInExpr( "'in' expression for a string expects a string, not a float".into(), ) .into_err(x.position())) } // 123 in "xxxx" (x @ Expr::IntegerConstant(_), Expr::StringConstant(_)) => { return Err(PERR::MalformedInExpr( "'in' expression for a string expects a string, not a number".into(), ) .into_err(x.position())) } // (??? && ???) in "xxxx", (??? || ???) in "xxxx", (??? in ???) in "xxxx", // true in "xxxx", false in "xxxx" (x @ Expr::And(_), Expr::StringConstant(_)) | (x @ Expr::Or(_), Expr::StringConstant(_)) | (x @ Expr::In(_), Expr::StringConstant(_)) | (x @ Expr::True(_), Expr::StringConstant(_)) | (x @ Expr::False(_), Expr::StringConstant(_)) => { return Err(PERR::MalformedInExpr( "'in' expression for a string expects a string, not a boolean".into(), ) .into_err(x.position())) } // [???, ???, ???] in "xxxx" (x @ Expr::Array(_), Expr::StringConstant(_)) => { return Err(PERR::MalformedInExpr( "'in' expression for a string expects a string, not an array".into(), ) .into_err(x.position())) } // #{...} in "xxxx" (x @ Expr::Map(_), Expr::StringConstant(_)) => { return Err(PERR::MalformedInExpr( "'in' expression for a string expects a string, not an object map".into(), ) .into_err(x.position())) } // (??? = ???) in "xxxx" (x @ Expr::Assignment(_), Expr::StringConstant(_)) => { return Err(PERR::MalformedInExpr( "'in' expression for a string expects a string, not an assignment".into(), ) .into_err(x.position())) } // () in "xxxx" (x @ Expr::Unit(_), Expr::StringConstant(_)) => { return Err(PERR::MalformedInExpr( "'in' expression for a string expects a string, not ()".into(), ) .into_err(x.position())) } // "xxx" in #{...}, 'x' in #{...} - OK! (Expr::StringConstant(_), Expr::Map(_)) | (Expr::CharConstant(_), Expr::Map(_)) => (), // 123.456 in #{...} #[cfg(not(feature = "no_float"))] (x @ Expr::FloatConstant(_), Expr::Map(_)) => { return Err(PERR::MalformedInExpr( "'in' expression for an object map expects a string, not a float".into(), ) .into_err(x.position())) } // 123 in #{...} (x @ Expr::IntegerConstant(_), Expr::Map(_)) => { return Err(PERR::MalformedInExpr( "'in' expression for an object map expects a string, not a number".into(), ) .into_err(x.position())) } // (??? && ???) in #{...}, (??? || ???) in #{...}, (??? in ???) in #{...}, // true in #{...}, false in #{...} (x @ Expr::And(_), Expr::Map(_)) | (x @ Expr::Or(_), Expr::Map(_)) | (x @ Expr::In(_), Expr::Map(_)) | (x @ Expr::True(_), Expr::Map(_)) | (x @ Expr::False(_), Expr::Map(_)) => { return Err(PERR::MalformedInExpr( "'in' expression for an object map expects a string, not a boolean".into(), ) .into_err(x.position())) } // [???, ???, ???] in #{..} (x @ Expr::Array(_), Expr::Map(_)) => { return Err(PERR::MalformedInExpr( "'in' expression for an object map expects a string, not an array".into(), ) .into_err(x.position())) } // #{...} in #{..} (x @ Expr::Map(_), Expr::Map(_)) => { return Err(PERR::MalformedInExpr( "'in' expression for an object map expects a string, not an object map".into(), ) .into_err(x.position())) } // (??? = ???) in #{...} (x @ Expr::Assignment(_), Expr::Map(_)) => { return Err(PERR::MalformedInExpr( "'in' expression for an object map expects a string, not an assignment".into(), ) .into_err(x.position())) } // () in #{...} (x @ Expr::Unit(_), Expr::Map(_)) => { return Err(PERR::MalformedInExpr( "'in' expression for an object map expects a string, not ()".into(), ) .into_err(x.position())) } _ => (), } Ok(Expr::In(Box::new((lhs, rhs, op_pos)))) } /// Parse a binary expression. fn parse_binary_op( input: &mut TokenStream, state: &mut ParseState, parent_precedence: u8, lhs: Expr, mut settings: ParseSettings, ) -> Result { settings.pos = lhs.position(); settings.ensure_level_within_max_limit(state.max_expr_depth)?; let mut root = lhs; loop { let (current_op, _) = input.peek().unwrap(); let precedence = current_op.precedence(); let bind_right = current_op.is_bind_right(); // Bind left to the parent lhs expression if precedence is higher // If same precedence, then check if the operator binds right if precedence < parent_precedence || (precedence == parent_precedence && !bind_right) { return Ok(root); } let (op_token, pos) = input.next().unwrap(); let rhs = parse_unary(input, state, settings)?; let next_precedence = input.peek().unwrap().0.precedence(); // Bind to right if the next operator has higher precedence // If same precedence, then check if the operator binds right let rhs = if (precedence == next_precedence && bind_right) || precedence < next_precedence { parse_binary_op(input, state, precedence, rhs, settings)? } else { // Otherwise bind to left (even if next operator has the same precedence) rhs }; settings = settings.level_up(); settings.pos = pos; settings.ensure_level_within_max_limit(state.max_expr_depth)?; let cmp_def = Some(false.into()); let op = op_token.syntax(); let hash = calc_fn_hash(empty(), &op, 2, empty()); let op = (op, true, pos); let mut args = StaticVec::new(); args.push(root); args.push(rhs); root = match op_token { Token::Plus | Token::Minus | Token::Multiply | Token::Divide | Token::LeftShift | Token::RightShift | Token::Modulo | Token::PowerOf | Token::Ampersand | Token::Pipe | Token::XOr => Expr::FnCall(Box::new((op, None, hash, args, None))), // '!=' defaults to true when passed invalid operands Token::NotEqualsTo => Expr::FnCall(Box::new((op, None, hash, args, Some(true.into())))), // Comparison operators default to false when passed invalid operands Token::EqualsTo | Token::LessThan | Token::LessThanEqualsTo | Token::GreaterThan | Token::GreaterThanEqualsTo => Expr::FnCall(Box::new((op, None, hash, args, cmp_def))), Token::Or => { let rhs = args.pop(); let current_lhs = args.pop(); Expr::Or(Box::new((current_lhs, rhs, pos))) } Token::And => { let rhs = args.pop(); let current_lhs = args.pop(); Expr::And(Box::new((current_lhs, rhs, pos))) } Token::In => { let rhs = args.pop(); let current_lhs = args.pop(); make_in_expr(current_lhs, rhs, pos)? } #[cfg(not(feature = "no_object"))] Token::Period => { let mut rhs = args.pop(); let current_lhs = args.pop(); match &mut rhs { // current_lhs.rhs(...) - method call Expr::FnCall(x) => { let ((id, _, _), _, hash, args, _) = x.as_mut(); // Recalculate function call hash because there is an additional argument *hash = calc_fn_hash(empty(), id, args.len() + 1, empty()); } _ => (), } make_dot_expr(current_lhs, rhs, pos)? } op_token => return Err(PERR::UnknownOperator(op_token.into()).into_err(pos)), }; } } /// Parse an expression. fn parse_expr( input: &mut TokenStream, state: &mut ParseState, mut settings: ParseSettings, ) -> Result { settings.pos = input.peek().unwrap().1; settings.ensure_level_within_max_limit(state.max_expr_depth)?; let lhs = parse_unary(input, state, settings.level_up())?; parse_binary_op(input, state, 1, lhs, settings.level_up()) } /// Make sure that the expression is not a statement expression (i.e. wrapped in `{}`). fn ensure_not_statement_expr(input: &mut TokenStream, type_name: &str) -> Result<(), ParseError> { match input.peek().unwrap() { // Disallow statement expressions (Token::LeftBrace, pos) | (Token::EOF, pos) => { Err(PERR::ExprExpected(type_name.to_string()).into_err(*pos)) } // No need to check for others at this time - leave it for the expr parser _ => Ok(()), } } /// Make sure that the expression is not a mis-typed assignment (i.e. `a = b` instead of `a == b`). fn ensure_not_assignment(input: &mut TokenStream) -> Result<(), ParseError> { match input.peek().unwrap() { (Token::Equals, pos) => { return Err(PERR::BadInput("Possibly a typo of '=='?".to_string()).into_err(*pos)) } (Token::PlusAssign, pos) | (Token::MinusAssign, pos) | (Token::MultiplyAssign, pos) | (Token::DivideAssign, pos) | (Token::LeftShiftAssign, pos) | (Token::RightShiftAssign, pos) | (Token::ModuloAssign, pos) | (Token::PowerOfAssign, pos) | (Token::AndAssign, pos) | (Token::OrAssign, pos) | (Token::XOrAssign, pos) => { return Err(PERR::BadInput( "Expecting a boolean expression, not an assignment".to_string(), ) .into_err(*pos)) } _ => Ok(()), } } /// Parse an if statement. fn parse_if( input: &mut TokenStream, state: &mut ParseState, mut settings: ParseSettings, ) -> Result { // if ... settings.pos = eat_token(input, Token::If); settings.ensure_level_within_max_limit(state.max_expr_depth)?; // if guard { if_body } ensure_not_statement_expr(input, "a boolean")?; let guard = parse_expr(input, state, settings.level_up())?; ensure_not_assignment(input)?; let if_body = parse_block(input, state, settings.level_up())?; // if guard { if_body } else ... let else_body = if match_token(input, Token::Else).unwrap_or(false) { Some(if let (Token::If, _) = input.peek().unwrap() { // if guard { if_body } else if ... parse_if(input, state, settings.level_up())? } else { // if guard { if_body } else { else-body } parse_block(input, state, settings.level_up())? }) } else { None }; Ok(Stmt::IfThenElse(Box::new((guard, if_body, else_body)))) } /// Parse a while loop. fn parse_while( input: &mut TokenStream, state: &mut ParseState, mut settings: ParseSettings, ) -> Result { // while ... settings.pos = eat_token(input, Token::While); settings.ensure_level_within_max_limit(state.max_expr_depth)?; // while guard { body } ensure_not_statement_expr(input, "a boolean")?; let guard = parse_expr(input, state, settings.level_up())?; ensure_not_assignment(input)?; settings.is_breakable = true; let body = parse_block(input, state, settings.level_up())?; Ok(Stmt::While(Box::new((guard, body)))) } /// Parse a loop statement. fn parse_loop( input: &mut TokenStream, state: &mut ParseState, mut settings: ParseSettings, ) -> Result { // loop ... settings.pos = eat_token(input, Token::Loop); settings.ensure_level_within_max_limit(state.max_expr_depth)?; // loop { body } settings.is_breakable = true; let body = parse_block(input, state, settings.level_up())?; Ok(Stmt::Loop(Box::new(body))) } /// Parse a for loop. fn parse_for( input: &mut TokenStream, state: &mut ParseState, mut settings: ParseSettings, ) -> Result { // for ... settings.pos = eat_token(input, Token::For); settings.ensure_level_within_max_limit(state.max_expr_depth)?; // for name ... let name = match input.next().unwrap() { // Variable name (Token::Identifier(s), _) => s, // Bad identifier (Token::LexError(err), pos) => return Err(err.into_err(pos)), // EOF (Token::EOF, pos) => return Err(PERR::VariableExpected.into_err(pos)), // Not a variable name (_, pos) => return Err(PERR::VariableExpected.into_err(pos)), }; // for name in ... match input.next().unwrap() { (Token::In, _) => (), (Token::LexError(err), pos) => return Err(err.into_err(pos)), (_, pos) => { return Err( PERR::MissingToken(Token::In.into(), "after the iteration variable".into()) .into_err(pos), ) } } // for name in expr { body } ensure_not_statement_expr(input, "a boolean")?; let expr = parse_expr(input, state, settings.level_up())?; let prev_len = state.len(); state.push((name.clone(), ScopeEntryType::Normal)); settings.is_breakable = true; let body = parse_block(input, state, settings.level_up())?; state.truncate(prev_len); Ok(Stmt::For(Box::new((name, expr, body)))) } /// Parse a variable definition statement. fn parse_let( input: &mut TokenStream, state: &mut ParseState, var_type: ScopeEntryType, mut settings: ParseSettings, ) -> Result { // let/const... (specified in `var_type`) settings.pos = input.next().unwrap().1; settings.ensure_level_within_max_limit(state.max_expr_depth)?; // let name ... let (name, pos) = match input.next().unwrap() { (Token::Identifier(s), pos) => (s, pos), (Token::LexError(err), pos) => return Err(err.into_err(pos)), (_, pos) => return Err(PERR::VariableExpected.into_err(pos)), }; // let name = ... if match_token(input, Token::Equals)? { // let name = expr let init_value = parse_expr(input, state, settings.level_up())?; match var_type { // let name = expr ScopeEntryType::Normal => { state.push((name.clone(), ScopeEntryType::Normal)); Ok(Stmt::Let(Box::new(((name, pos), Some(init_value))))) } // const name = { expr:constant } ScopeEntryType::Constant if init_value.is_constant() => { state.push((name.clone(), ScopeEntryType::Constant)); Ok(Stmt::Const(Box::new(((name, pos), init_value)))) } // const name = expr - error ScopeEntryType::Constant => { Err(PERR::ForbiddenConstantExpr(name).into_err(init_value.position())) } // Variable cannot be a module ScopeEntryType::Module => unreachable!(), } } else { // let name match var_type { ScopeEntryType::Normal => { state.push((name.clone(), ScopeEntryType::Normal)); Ok(Stmt::Let(Box::new(((name, pos), None)))) } ScopeEntryType::Constant => { state.push((name.clone(), ScopeEntryType::Constant)); Ok(Stmt::Const(Box::new(((name, pos), Expr::Unit(pos))))) } // Variable cannot be a module ScopeEntryType::Module => unreachable!(), } } } /// Parse an import statement. fn parse_import( input: &mut TokenStream, state: &mut ParseState, mut settings: ParseSettings, ) -> Result { // import ... settings.pos = eat_token(input, Token::Import); settings.ensure_level_within_max_limit(state.max_expr_depth)?; // import expr ... let expr = parse_expr(input, state, settings.level_up())?; // 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(err.into_err(pos)), (_, pos) => return Err(PERR::VariableExpected.into_err(pos)), }; state.push((name.clone(), ScopeEntryType::Module)); Ok(Stmt::Import(Box::new((expr, (name, settings.pos))))) } /// Parse an export statement. #[cfg(not(feature = "no_module"))] fn parse_export( input: &mut TokenStream, state: &mut ParseState, mut settings: ParseSettings, ) -> Result { settings.pos = eat_token(input, Token::Export); settings.ensure_level_within_max_limit(state.max_expr_depth)?; let mut exports = StaticVec::new(); loop { let (id, id_pos) = match input.next().unwrap() { (Token::Identifier(s), pos) => (s.clone(), pos), (Token::LexError(err), pos) => return Err(err.into_err(pos)), (_, pos) => return Err(PERR::VariableExpected.into_err(pos)), }; let rename = if match_token(input, Token::As)? { match input.next().unwrap() { (Token::Identifier(s), pos) => Some((s.clone(), pos)), (_, pos) => return Err(PERR::VariableExpected.into_err(pos)), } } else { None }; exports.push(((id, id_pos), rename)); match input.peek().unwrap() { (Token::Comma, _) => { eat_token(input, Token::Comma); } (Token::Identifier(_), pos) => { return Err(PERR::MissingToken( Token::Comma.into(), "to separate the list of exports".into(), ) .into_err(*pos)) } _ => break, } } // Check for duplicating parameters exports .iter() .enumerate() .try_for_each(|(i, ((id1, _), _))| { exports .iter() .skip(i + 1) .find(|((id2, _), _)| id2 == id1) .map_or_else(|| Ok(()), |((id2, pos), _)| Err((id2, *pos))) }) .map_err(|(id2, pos)| PERR::DuplicatedExport(id2.to_string()).into_err(pos))?; Ok(Stmt::Export(Box::new(exports))) } /// Parse a statement block. fn parse_block( input: &mut TokenStream, state: &mut ParseState, mut settings: ParseSettings, ) -> Result { // Must start with { settings.pos = match input.next().unwrap() { (Token::LeftBrace, pos) => pos, (Token::LexError(err), pos) => return Err(err.into_err(pos)), (_, pos) => { return Err(PERR::MissingToken( Token::LeftBrace.into(), "to start a statement block".into(), ) .into_err(pos)) } }; settings.ensure_level_within_max_limit(state.max_expr_depth)?; let mut statements = StaticVec::new(); let prev_len = state.len(); while !match_token(input, Token::RightBrace)? { // Parse statements inside the block settings.is_global = false; let stmt = parse_stmt(input, state, settings.level_up())?; // See if it needs a terminating semicolon let need_semicolon = !stmt.is_self_terminated(); statements.push(stmt); match input.peek().unwrap() { // { ... stmt } (Token::RightBrace, _) => { eat_token(input, Token::RightBrace); break; } // { ... stmt; (Token::SemiColon, _) if need_semicolon => { eat_token(input, Token::SemiColon); } // { ... { stmt } ; (Token::SemiColon, _) if !need_semicolon => (), // { ... { stmt } ??? (_, _) if !need_semicolon => (), // { ... stmt (Token::LexError(err), pos) => return Err(err.into_err(*pos)), // { ... stmt ??? (_, pos) => { // Semicolons are not optional between statements return Err(PERR::MissingToken( Token::SemiColon.into(), "to terminate this statement".into(), ) .into_err(*pos)); } } } state.truncate(prev_len); Ok(Stmt::Block(Box::new((statements, settings.pos)))) } /// Parse an expression as a statement. fn parse_expr_stmt( input: &mut TokenStream, state: &mut ParseState, mut settings: ParseSettings, ) -> Result { settings.pos = input.peek().unwrap().1; settings.ensure_level_within_max_limit(state.max_expr_depth)?; let expr = parse_expr(input, state, settings.level_up())?; let expr = parse_op_assignment_stmt(input, state, expr, settings.level_up())?; Ok(Stmt::Expr(Box::new(expr))) } /// Parse a single statement. fn parse_stmt( input: &mut TokenStream, state: &mut ParseState, mut settings: ParseSettings, ) -> Result { use ScopeEntryType::{Constant, Normal}; let (token, token_pos) = match input.peek().unwrap() { (Token::EOF, pos) => return Ok(Stmt::Noop(*pos)), x => x, }; settings.pos = *token_pos; settings.ensure_level_within_max_limit(state.max_expr_depth)?; match token { // Semicolon - empty statement Token::SemiColon => Ok(Stmt::Noop(settings.pos)), Token::LeftBrace => parse_block(input, state, settings.level_up()), // fn ... #[cfg(not(feature = "no_function"))] Token::Fn if !settings.is_global => Err(PERR::WrongFnDefinition.into_err(settings.pos)), #[cfg(not(feature = "no_function"))] Token::Fn => unreachable!(), Token::If => parse_if(input, state, settings.level_up()), Token::While => parse_while(input, state, settings.level_up()), Token::Loop => parse_loop(input, state, settings.level_up()), Token::For => parse_for(input, state, settings.level_up()), Token::Continue if settings.is_breakable => { let pos = eat_token(input, Token::Continue); Ok(Stmt::Continue(pos)) } Token::Break if settings.is_breakable => { let pos = eat_token(input, Token::Break); Ok(Stmt::Break(pos)) } Token::Continue | Token::Break => Err(PERR::LoopBreak.into_err(settings.pos)), Token::Return | Token::Throw => { let return_type = match input.next().unwrap() { (Token::Return, _) => ReturnType::Return, (Token::Throw, _) => ReturnType::Exception, _ => panic!("token should be return or throw"), }; match input.peek().unwrap() { // `return`/`throw` at (Token::EOF, pos) => Ok(Stmt::ReturnWithVal(Box::new(((return_type, *pos), None)))), // `return;` or `throw;` (Token::SemiColon, _) => Ok(Stmt::ReturnWithVal(Box::new(( (return_type, settings.pos), None, )))), // `return` or `throw` with expression (_, _) => { let expr = parse_expr(input, state, settings.level_up())?; let pos = expr.position(); Ok(Stmt::ReturnWithVal(Box::new(( (return_type, pos), Some(expr), )))) } } } Token::Let => parse_let(input, state, Normal, settings.level_up()), Token::Const => parse_let(input, state, Constant, settings.level_up()), Token::Import => parse_import(input, state, settings.level_up()), #[cfg(not(feature = "no_module"))] Token::Export if !settings.is_global => Err(PERR::WrongExport.into_err(settings.pos)), #[cfg(not(feature = "no_module"))] Token::Export => parse_export(input, state, settings.level_up()), _ => parse_expr_stmt(input, state, settings.level_up()), } } /// Parse a function definition. #[cfg(not(feature = "no_function"))] fn parse_fn( input: &mut TokenStream, state: &mut ParseState, access: FnAccess, mut settings: ParseSettings, ) -> Result { settings.pos = eat_token(input, Token::Fn); settings.ensure_level_within_max_limit(state.max_expr_depth)?; let name = match input.next().unwrap() { (Token::Identifier(s), _) => s, (_, pos) => return Err(PERR::FnMissingName.into_err(pos)), }; match input.peek().unwrap() { (Token::LeftParen, _) => eat_token(input, Token::LeftParen), (_, pos) => return Err(PERR::FnMissingParams(name).into_err(*pos)), }; let mut params = Vec::new(); if !match_token(input, Token::RightParen)? { let end_err = format!("to close the parameters list of function '{}'", name); let sep_err = format!("to separate the parameters of function '{}'", name); loop { match input.next().unwrap() { (Token::Identifier(s), pos) => { state.push((s.clone(), ScopeEntryType::Normal)); params.push((s, pos)) } (Token::LexError(err), pos) => return Err(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(Token::Comma.into(), sep_err).into_err(pos)) } (Token::LexError(err), pos) => return Err(err.into_err(pos)), (_, pos) => { return Err(PERR::MissingToken(Token::Comma.into(), sep_err).into_err(pos)) } } } } // Check for duplicating parameters params .iter() .enumerate() .try_for_each(|(i, (p1, _))| { params .iter() .skip(i + 1) .find(|(p2, _)| p2 == p1) .map_or_else(|| Ok(()), |(p2, pos)| Err((p2, *pos))) }) .map_err(|(p, pos)| { PERR::FnDuplicatedParam(name.to_string(), p.to_string()).into_err(pos) })?; // Parse function body let body = match input.peek().unwrap() { (Token::LeftBrace, _) => { settings.is_breakable = false; parse_block(input, state, settings.level_up())? } (_, pos) => return Err(PERR::FnMissingBody(name).into_err(*pos)), }; let params = params.into_iter().map(|(p, _)| p).collect(); Ok(ScriptFnDef { name, access, params, body, pos: settings.pos, }) } impl Engine { pub(crate) fn parse_global_expr( &self, input: &mut TokenStream, scope: &Scope, optimization_level: OptimizationLevel, ) -> Result { let mut state = ParseState::new( self.max_expr_depth, self.max_string_size, self.max_array_size, self.max_map_size, ); let settings = ParseSettings { allow_if_expr: false, allow_stmt_expr: false, is_global: true, is_breakable: false, level: 0, pos: Position::none(), }; let expr = parse_expr(input, &mut state, settings)?; match input.peek().unwrap() { (Token::EOF, _) => (), // Return error if the expression doesn't end (token, pos) => { return Err( PERR::BadInput(format!("Unexpected '{}'", token.syntax())).into_err(*pos) ) } } let expr = vec![Stmt::Expr(Box::new(expr))]; Ok( // Optimize AST optimize_into_ast(self, scope, expr, Default::default(), optimization_level), ) } /// Parse the global level statements. fn parse_global_level( &self, input: &mut TokenStream, ) -> Result<(Vec, Vec), ParseError> { let mut statements = Vec::::new(); let mut functions = HashMap::::with_hasher(StraightHasherBuilder); let mut state = ParseState::new( self.max_expr_depth, self.max_string_size, self.max_array_size, self.max_map_size, ); while !input.peek().unwrap().0.is_eof() { // Collect all the function definitions #[cfg(not(feature = "no_function"))] { let (access, must_be_fn) = if match_token(input, Token::Private)? { (FnAccess::Private, true) } else { (FnAccess::Public, false) }; match input.peek().unwrap() { #[cfg(not(feature = "no_function"))] (Token::Fn, pos) => { let mut state = ParseState::new( self.max_function_expr_depth, self.max_string_size, self.max_array_size, self.max_map_size, ); let settings = ParseSettings { allow_if_expr: true, allow_stmt_expr: true, is_global: false, is_breakable: false, level: 0, pos: *pos, }; let func = parse_fn(input, &mut state, access, settings)?; // Qualifiers (none) + function name + number of arguments. let hash = calc_fn_hash(empty(), &func.name, func.params.len(), empty()); functions.insert(hash, func); continue; } (_, pos) if must_be_fn => { return Err(PERR::MissingToken( Token::Fn.into(), format!("following '{}'", Token::Private.syntax()), ) .into_err(*pos)) } _ => (), } } // Actual statement let settings = ParseSettings { allow_if_expr: true, allow_stmt_expr: true, is_global: true, is_breakable: false, level: 0, pos: Position::none(), }; let stmt = parse_stmt(input, &mut state, settings)?; let need_semicolon = !stmt.is_self_terminated(); statements.push(stmt); match input.peek().unwrap() { // EOF (Token::EOF, _) => break, // stmt ; (Token::SemiColon, _) if need_semicolon => { eat_token(input, Token::SemiColon); } // stmt ; (Token::SemiColon, _) if !need_semicolon => (), // { stmt } ??? (_, _) if !need_semicolon => (), // stmt (Token::LexError(err), pos) => return Err(err.into_err(*pos)), // stmt ??? (_, pos) => { // Semicolons are not optional between statements return Err(PERR::MissingToken( Token::SemiColon.into(), "to terminate this statement".into(), ) .into_err(*pos)); } } } Ok((statements, functions.into_iter().map(|(_, v)| v).collect())) } /// Run the parser on an input stream, returning an AST. pub(crate) fn parse( &self, input: &mut TokenStream, scope: &Scope, optimization_level: OptimizationLevel, ) -> Result { let (statements, lib) = self.parse_global_level(input)?; Ok( // Optimize AST optimize_into_ast(self, scope, statements, lib, optimization_level), ) } } /// Map a `Dynamic` value to an expression. /// /// Returns Some(expression) if conversion is successful. Otherwise None. pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option { match value.0 { #[cfg(not(feature = "no_float"))] Union::Float(value) => Some(Expr::FloatConstant(Box::new((value, pos)))), Union::Unit(_) => Some(Expr::Unit(pos)), Union::Int(value) => Some(Expr::IntegerConstant(Box::new((value, pos)))), Union::Char(value) => Some(Expr::CharConstant(Box::new((value, pos)))), Union::Str(value) => Some(Expr::StringConstant(Box::new((value.clone(), pos)))), Union::Bool(true) => Some(Expr::True(pos)), Union::Bool(false) => Some(Expr::False(pos)), #[cfg(not(feature = "no_index"))] Union::Array(array) => { let items: Vec<_> = array .into_iter() .map(|x| map_dynamic_to_expr(x, pos)) .collect(); if items.iter().all(Option::is_some) { Some(Expr::Array(Box::new(( items.into_iter().map(Option::unwrap).collect(), pos, )))) } else { None } } #[cfg(not(feature = "no_object"))] Union::Map(map) => { let items: Vec<_> = map .into_iter() .map(|(k, v)| ((k, pos), map_dynamic_to_expr(v, pos))) .collect(); if items.iter().all(|(_, expr)| expr.is_some()) { Some(Expr::Map(Box::new(( items .into_iter() .map(|((k, pos), expr)| ((k, pos), expr.unwrap())) .collect(), pos, )))) } else { None } } _ => None, } }