diff --git a/Cargo.toml b/Cargo.toml index cc04ffed..0b27d863 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ smallvec = { version = "1.6", default-features = false, features = ["union"] } ahash = { version = "0.7", default-features = false } num-traits = { version = "0.2", default-features = false } smartstring = { version = "0.2.7", default-features = false } -rhai_codegen = { version = "1.1", path = "codegen", default-features = false } +rhai_codegen = { version = "1.2", path = "codegen", default-features = false } [features] default = ["ahash/std", "num-traits/std"] diff --git a/src/ast.rs b/src/ast.rs index 1269c170..992c1d21 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1667,11 +1667,11 @@ impl Stmt { pub struct CustomExpr { /// List of keywords. pub inputs: StaticVec, + /// List of tokens actually parsed. + pub tokens: StaticVec, /// Is the current [`Scope`][crate::Scope] possibly modified by this custom statement /// (e.g. introducing a new variable)? pub scope_may_be_changed: bool, - /// List of tokens actually parsed. - pub tokens: StaticVec, /// Is this custom syntax self-terminated? pub self_terminated: bool, } @@ -1693,7 +1693,7 @@ impl CustomExpr { /// # Volatile Data Structure /// /// This type is volatile and may change. -#[derive(Clone, Hash)] +#[derive(Debug, Clone, Hash)] pub struct BinaryExpr { /// LHS expression. pub lhs: Expr, @@ -1711,7 +1711,7 @@ pub struct BinaryExpr { pub struct OpAssignment<'a> { /// Hash of the op-assignment call. pub hash_op_assign: u64, - /// Hash of the underlying operator call. + /// Hash of the underlying operator call (for fallback). pub hash_op: u64, /// Op-assignment operator. pub op: &'a str, @@ -1739,31 +1739,31 @@ impl OpAssignment<'_> { } } -/// _(internals)_ An set of function call hashes. -/// Exported under the `internals` feature only. +/// _(internals)_ A set of function call hashes. Exported under the `internals` feature only. /// -/// Two separate hashes are pre-calculated because of the following pattern: +/// Two separate hashes are pre-calculated because of the following patterns: /// /// ```ignore -/// func(a, b, c); // Native: func(a, b, c) - 3 parameters -/// // Script: func(a, b, c) - 3 parameters +/// func(a, b, c); // Native: func(a, b, c) - 3 parameters +/// // Script: func(a, b, c) - 3 parameters /// -/// a.func(b, c); // Native: func(&mut a, b, c) - 3 parameters -/// // Script: func(b, c) - 2 parameters +/// a.func(b, c); // Native: func(&mut a, b, c) - 3 parameters +/// // Script: func(b, c) - 2 parameters /// ``` /// /// For normal function calls, the native hash equals the script hash. +/// /// For method-style calls, the script hash contains one fewer parameter. /// /// Function call hashes are used in the following manner: /// /// * First, the script hash is tried, which contains only the called function's name plus the -/// of parameters. +/// number of parameters. /// /// * Next, the actual types of arguments are hashed and _combined_ with the native hash, which is -/// then used to search for a native function. -/// In other words, a native function call hash always contains the called function's name plus -/// the types of the arguments. This is to due to possible function overloading for different parameter types. +/// then used to search for a native function. In other words, a complete native function call +/// hash always contains the called function's name plus the types of the arguments. This is due +/// to possible function overloading for different parameter types. /// /// # Volatile Data Structure /// diff --git a/src/parser.rs b/src/parser.rs index 004261fa..e311e2ea 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -422,12 +422,11 @@ fn parse_paren_expr( lib: &mut FunctionsLib, settings: ParseSettings, ) -> Result { - let mut settings = settings; - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; // ( ... + let mut settings = settings; settings.pos = eat_token(input, Token::LeftParen); if match_token(input, Token::RightParen).0 { @@ -597,11 +596,11 @@ fn parse_index_chain( lhs: Expr, settings: ParseSettings, ) -> Result { - let mut settings = settings; - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; + let mut settings = settings; + let idx_expr = parse_expr(input, state, lib, settings.level_up())?; // Check type of indexing - must be integer or string @@ -760,12 +759,11 @@ fn parse_array_literal( lib: &mut FunctionsLib, settings: ParseSettings, ) -> Result { - let mut settings = settings; - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; // [ ... + let mut settings = settings; settings.pos = eat_token(input, Token::LeftBracket); let mut arr = StaticVec::new(); @@ -834,12 +832,11 @@ fn parse_map_literal( lib: &mut FunctionsLib, settings: ParseSettings, ) -> Result { - let mut settings = settings; - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; // #{ ... + let mut settings = settings; settings.pos = eat_token(input, Token::MapStart); let mut map = StaticVec::<(Ident, Expr)>::new(); @@ -952,12 +949,11 @@ fn parse_switch( lib: &mut FunctionsLib, settings: ParseSettings, ) -> Result { - let mut settings = settings; - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; // switch ... + let mut settings = settings; settings.pos = eat_token(input, Token::Switch); let item = parse_expr(input, state, lib, settings.level_up())?; @@ -1099,12 +1095,12 @@ fn parse_primary( lib: &mut FunctionsLib, settings: ParseSettings, ) -> Result { - let mut settings = settings; - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; let (token, token_pos) = input.peek().expect(NEVER_ENDS); + + let mut settings = settings; settings.pos = *token_pos; let mut root_expr = match token { @@ -1358,48 +1354,48 @@ fn parse_primary( root_expr = match (root_expr, tail_token) { // Qualified function call with ! (Expr::Variable(_, _, x), Token::Bang) if x.1.is_some() => { - return Err(if !match_token(input, Token::LeftParen).0 { - LexError::UnexpectedInput(Token::Bang.syntax().to_string()).into_err(tail_pos) + return if !match_token(input, Token::LeftParen).0 { + Err(LexError::UnexpectedInput(Token::Bang.syntax().to_string()) + .into_err(tail_pos)) } else { - LexError::ImproperSymbol( + Err(LexError::ImproperSymbol( "!".to_string(), "'!' cannot be used to call module functions".to_string(), ) - .into_err(tail_pos) - }); + .into_err(tail_pos)) + }; } // Function call with ! - (Expr::Variable(_, var_pos, x), Token::Bang) => { - let (matched, pos) = match_token(input, Token::LeftParen); - if !matched { - return Err(PERR::MissingToken( - Token::LeftParen.syntax().into(), - "to start arguments list of function call".into(), - ) - .into_err(pos)); + (Expr::Variable(_, pos, x), Token::Bang) => { + match match_token(input, Token::LeftParen) { + (false, pos) => { + return Err(PERR::MissingToken( + Token::LeftParen.syntax().into(), + "to start arguments list of function call".into(), + ) + .into_err(pos)) + } + _ => (), } let (_, namespace, name) = *x; - settings.pos = var_pos; + settings.pos = pos; let ns = namespace.map(|(ns, _)| ns); parse_fn_call(input, state, lib, name, true, ns, settings.level_up())? } // Function call - (Expr::Variable(_, var_pos, x), Token::LeftParen) => { + (Expr::Variable(_, pos, x), Token::LeftParen) => { let (_, namespace, name) = *x; - settings.pos = var_pos; let ns = namespace.map(|(ns, _)| ns); + settings.pos = pos; parse_fn_call(input, state, lib, name, false, ns, settings.level_up())? } // module access #[cfg(not(feature = "no_module"))] - (Expr::Variable(_, var_pos, x), Token::DoubleColon) => { + (Expr::Variable(_, pos, x), Token::DoubleColon) => { let (id2, pos2) = parse_var_name(input)?; - let (_, mut namespace, var_name) = *x; - let var_name_def = Ident { - name: var_name, - pos: var_pos, - }; + let (_, mut namespace, name) = *x; + let var_name_def = Ident { name, pos }; if let Some((ref mut namespace, _)) = namespace { namespace.push(var_name_def); @@ -1437,7 +1433,6 @@ fn parse_primary( } let rhs = parse_primary(input, state, lib, settings.level_up())?; - make_dot_expr(state, expr, false, rhs, tail_pos)? } // Unknown postfix operator @@ -1482,14 +1477,14 @@ fn parse_unary( lib: &mut FunctionsLib, settings: ParseSettings, ) -> Result { - let mut settings = settings; - - let (token, token_pos) = input.peek().expect(NEVER_ENDS); - settings.pos = *token_pos; - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; + let (token, token_pos) = input.peek().expect(NEVER_ENDS); + + let mut settings = settings; + settings.pos = *token_pos; + match token { // -expr Token::Minus | Token::UnaryMinus => { @@ -1676,7 +1671,7 @@ fn make_assignment_stmt( } } -/// Parse an operator-assignment expression. +/// Parse an operator-assignment expression (if any). fn parse_op_assignment_stmt( input: &mut TokenStream, state: &mut ParseState, @@ -1684,23 +1679,24 @@ fn parse_op_assignment_stmt( lhs: Expr, settings: ParseSettings, ) -> Result { - let mut settings = settings; - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; - let (token, token_pos) = input.peek().expect(NEVER_ENDS); - settings.pos = *token_pos; - - let (op, pos) = match token { - Token::Equals => (None, input.next().expect(NEVER_ENDS).1), - _ if token.is_op_assignment() => input + let (op, pos) = match input.peek().expect(NEVER_ENDS) { + // var = ... + (Token::Equals, _) => (None, eat_token(input, Token::Equals)), + // var op= ... + (token, _) if token.is_op_assignment() => input .next() .map(|(op, pos)| (Some(op), pos)) .expect(NEVER_ENDS), + // Not op-assignment _ => return Ok(Stmt::Expr(lhs)), }; + let mut settings = settings; + settings.pos = pos; + let rhs = parse_expr(input, state, lib, settings.level_up())?; make_assignment_stmt(op, state, lhs, rhs, pos) } @@ -1714,25 +1710,27 @@ fn make_dot_expr( rhs: Expr, op_pos: Position, ) -> Result { - Ok(match (lhs, rhs) { + match (lhs, rhs) { // lhs[idx_expr].rhs (Expr::Index(mut x, term, pos), rhs) => { x.rhs = make_dot_expr(state, x.rhs, term || terminate_chaining, rhs, op_pos)?; - Expr::Index(x, false, pos) + Ok(Expr::Index(x, false, pos)) } // lhs.id (lhs, var_expr @ Expr::Variable(_, _, _)) if var_expr.is_variable_access(true) => { let rhs = var_expr.into_property(state); - Expr::Dot(BinaryExpr { lhs, rhs }.into(), false, op_pos) + Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), false, op_pos)) } // lhs.module::id - syntax error (_, Expr::Variable(_, _, x)) => { - return Err(PERR::PropertyExpected.into_err(x.1.expect("`Some`").0[0].pos)) + Err(PERR::PropertyExpected.into_err(x.1.expect("`Some`").0[0].pos)) } // lhs.prop - (lhs, prop @ Expr::Property(_)) => { - Expr::Dot(BinaryExpr { lhs, rhs: prop }.into(), false, op_pos) - } + (lhs, prop @ Expr::Property(_)) => Ok(Expr::Dot( + BinaryExpr { lhs, rhs: prop }.into(), + false, + op_pos, + )), // lhs.dot_lhs.dot_rhs or lhs.dot_lhs[idx_rhs] (lhs, rhs @ Expr::Dot(_, _, _)) | (lhs, rhs @ Expr::Index(_, _, _)) => { let (x, term, pos, is_dot) = match rhs { @@ -1754,7 +1752,7 @@ fn make_dot_expr( } else { Expr::Index(new_lhs, term, pos) }; - Expr::Dot(BinaryExpr { lhs, rhs }.into(), false, op_pos) + Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), false, op_pos)) } Expr::FnCall(mut func, func_pos) => { // Recalculate hash @@ -1775,7 +1773,7 @@ fn make_dot_expr( } else { Expr::Index(new_lhs, term, pos) }; - Expr::Dot(BinaryExpr { lhs, rhs }.into(), false, op_pos) + Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), false, op_pos)) } _ => unreachable!("invalid dot expression: {:?}", x.lhs), } @@ -1790,7 +1788,7 @@ fn make_dot_expr( && [crate::engine::KEYWORD_FN_PTR, crate::engine::KEYWORD_EVAL] .contains(&x.name.as_ref()) => { - return Err(LexError::ImproperSymbol( + Err(LexError::ImproperSymbol( x.name.to_string(), format!( "'{}' should not be called in method style. Try {}(...);", @@ -1800,12 +1798,10 @@ fn make_dot_expr( .into_err(pos)) } // lhs.func!(...) - (_, Expr::FnCall(x, pos)) if x.capture_parent_scope => { - return Err(PERR::MalformedCapture( - "method-call style does not support running within the caller's scope".into(), - ) - .into_err(pos)) - } + (_, Expr::FnCall(x, pos)) if x.capture_parent_scope => Err(PERR::MalformedCapture( + "method-call style does not support running within the caller's scope".into(), + ) + .into_err(pos)), // lhs.func(...) (lhs, Expr::FnCall(mut func, func_pos)) => { // Recalculate hash @@ -1816,14 +1812,14 @@ fn make_dot_expr( ); let rhs = Expr::FnCall(func, func_pos); - Expr::Dot(BinaryExpr { lhs, rhs }.into(), false, op_pos) + Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), false, op_pos)) } // lhs.rhs - (_, rhs) => return Err(PERR::PropertyExpected.into_err(rhs.position())), - }) + (_, rhs) => Err(PERR::PropertyExpected.into_err(rhs.position())), + } } -/// Parse a binary expression. +/// Parse a binary expression (if any). fn parse_binary_op( input: &mut TokenStream, state: &mut ParseState, @@ -1832,11 +1828,10 @@ fn parse_binary_op( lhs: Expr, settings: ParseSettings, ) -> Result { - let mut settings = settings; - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; + let mut settings = settings; settings.pos = lhs.position(); let mut root = lhs; @@ -2177,11 +2172,10 @@ fn parse_expr( lib: &mut FunctionsLib, settings: ParseSettings, ) -> Result { - let mut settings = settings; - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; + let mut settings = settings; settings.pos = input.peek().expect(NEVER_ENDS).1; // Check if it is a custom syntax. @@ -2222,12 +2216,11 @@ fn parse_if( lib: &mut FunctionsLib, settings: ParseSettings, ) -> Result { - let mut settings = settings; - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; // if ... + let mut settings = settings; settings.pos = eat_token(input, Token::If); // if guard { if_body } @@ -2263,11 +2256,11 @@ fn parse_while_loop( lib: &mut FunctionsLib, settings: ParseSettings, ) -> Result { - let mut settings = settings; - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; + let mut settings = settings; + // while|loops ... let (guard, token_pos) = match input.next().expect(NEVER_ENDS) { (Token::While, pos) => { @@ -2294,12 +2287,11 @@ fn parse_do( lib: &mut FunctionsLib, settings: ParseSettings, ) -> Result { - let mut settings = settings; - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; // do ... + let mut settings = settings; settings.pos = eat_token(input, Token::Do); // do { body } [while|until] guard @@ -2338,12 +2330,11 @@ fn parse_for( lib: &mut FunctionsLib, settings: ParseSettings, ) -> Result { - let mut settings = settings; - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; // for ... + let mut settings = settings; settings.pos = eat_token(input, Token::For); // for name ... @@ -2398,16 +2389,20 @@ fn parse_for( let prev_stack_len = state.stack.len(); let counter_var = if let Some(name) = counter_name { - let counter_var = state.get_identifier(name); - state - .stack - .push((counter_var.clone(), AccessMode::ReadWrite)); - Some(counter_var) + let name = state.get_identifier(name); + let pos = counter_pos.expect("`Some`"); + state.stack.push((name.clone(), AccessMode::ReadWrite)); + Some(Ident { name, pos }) } else { None }; + let loop_var = state.get_identifier(name); state.stack.push((loop_var.clone(), AccessMode::ReadWrite)); + let loop_var = Ident { + name: loop_var, + pos: name_pos, + }; settings.is_breakable = true; let body = parse_block(input, state, lib, settings.level_up())?; @@ -2416,17 +2411,7 @@ fn parse_for( Ok(Stmt::For( expr, - Box::new(( - Ident { - name: loop_var, - pos: name_pos, - }, - counter_var.map(|name| Ident { - name, - pos: counter_pos.expect("`Some`"), - }), - body.into(), - )), + Box::new((loop_var, counter_var, body.into())), settings.pos, )) } @@ -2440,12 +2425,11 @@ fn parse_let( is_export: bool, settings: ParseSettings, ) -> Result { - let mut settings = settings; - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; // let/const... (specified in `var_type`) + let mut settings = settings; settings.pos = input.next().expect(NEVER_ENDS).1; // let name ... @@ -2494,12 +2478,11 @@ fn parse_import( lib: &mut FunctionsLib, settings: ParseSettings, ) -> Result { - let mut settings = settings; - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; // import ... + let mut settings = settings; settings.pos = eat_token(input, Token::Import); // import expr ... @@ -2511,19 +2494,13 @@ fn parse_import( } // import expr as name ... - let (name, name_pos) = parse_var_name(input)?; + let (name, pos) = parse_var_name(input)?; let name = state.get_identifier(name); state.modules.push(name.clone()); Ok(Stmt::Import( expr, - Some( - Ident { - name, - pos: name_pos, - } - .into(), - ), + Some(Ident { name, pos }.into()), settings.pos, )) } @@ -2536,11 +2513,10 @@ fn parse_export( lib: &mut FunctionsLib, settings: ParseSettings, ) -> Result { - let mut settings = settings; - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; + let mut settings = settings; settings.pos = eat_token(input, Token::Export); match input.peek().expect(NEVER_ENDS) { @@ -2610,9 +2586,11 @@ fn parse_block( lib: &mut FunctionsLib, settings: ParseSettings, ) -> Result { - let mut settings = settings; + #[cfg(not(feature = "unchecked"))] + settings.ensure_level_within_max_limit(state.max_expr_depth)?; // Must start with { + let mut settings = settings; settings.pos = match input.next().expect(NEVER_ENDS) { (Token::LeftBrace, pos) => pos, (Token::LexError(err), pos) => return Err(err.into_err(pos)), @@ -2625,9 +2603,6 @@ fn parse_block( } }; - #[cfg(not(feature = "unchecked"))] - settings.ensure_level_within_max_limit(state.max_expr_depth)?; - let mut statements = Vec::with_capacity(8); let prev_entry_stack_len = state.entry_stack_len; @@ -2713,11 +2688,10 @@ fn parse_expr_stmt( lib: &mut FunctionsLib, settings: ParseSettings, ) -> Result { - let mut settings = settings; - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; + let mut settings = settings; settings.pos = input.peek().expect(NEVER_ENDS).1; let expr = parse_expr(input, state, lib, settings.level_up())?; @@ -2934,12 +2908,11 @@ fn parse_try_catch( lib: &mut FunctionsLib, settings: ParseSettings, ) -> Result { - let mut settings = settings; - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; // try ... + let mut settings = settings; settings.pos = eat_token(input, Token::Try); // try { body } @@ -3001,11 +2974,11 @@ fn parse_fn( #[cfg(feature = "metadata")] comments: Vec>, ) -> Result { - let mut settings = settings; - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; + let mut settings = settings; + let (token, pos) = input.next().expect(NEVER_ENDS); let name = match token.into_function_name_for_override() { @@ -3142,11 +3115,10 @@ fn parse_anon_fn( lib: &mut FunctionsLib, settings: ParseSettings, ) -> Result<(Expr, ScriptFnDef), ParseError> { - let mut settings = settings; - #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; + let mut settings = settings; let mut params_list = StaticVec::new(); if input.next().expect(NEVER_ENDS).0 != Token::Or && !match_token(input, Token::Pipe).0 {