From dee66a409fe907ac0064442bde753bbc1657ad61 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 4 Jul 2022 17:42:24 +0800 Subject: [PATCH 1/6] Add case alternatives for switch. --- CHANGELOG.md | 9 +++ src/ast/mod.rs | 4 +- src/ast/stmt.rs | 139 ++++++++++++++++++++++++++++++----- src/eval/stmt.rs | 16 ++-- src/lib.rs | 3 +- src/optimizer.rs | 133 +++++++++++++++------------------- src/parser.rs | 185 ++++++++++++++++++++++++++++++----------------- src/tokenizer.rs | 8 +- tests/switch.rs | 7 ++ 9 files changed, 334 insertions(+), 170 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6adfdff0..bb039a1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ Rhai Release Notes ================== +Version 1.9.0 +============= + +Enhancements +------------ + +* `switch` cases can now include multiple values separated by `|`. + + Version 1.8.0 ============= diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 32a84497..6dfb077b 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -20,8 +20,8 @@ pub use script_fn::EncapsulatedEnviron; #[cfg(not(feature = "no_function"))] pub use script_fn::{ScriptFnDef, ScriptFnMetadata}; pub use stmt::{ - ConditionalStmtBlock, OpAssignment, Stmt, StmtBlock, StmtBlockContainer, SwitchCases, - TryCatchBlock, + ConditionalStmtBlock, OpAssignment, RangeCase, Stmt, StmtBlock, StmtBlockContainer, + SwitchCases, TryCatchBlock, }; #[cfg(not(feature = "no_float"))] diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index 57da95dd..7cab128f 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -12,7 +12,7 @@ use std::{ hash::Hash, mem, num::NonZeroUsize, - ops::{Deref, DerefMut}, + ops::{Deref, DerefMut, Range, RangeInclusive}, }; /// _(internals)_ An op-assignment operator. @@ -125,7 +125,7 @@ impl fmt::Debug for OpAssignment { /// A statements block with a condition. /// /// The condition may simply be [`Expr::BoolConstant`] with `true` if there is actually no condition. -#[derive(Debug, Clone, Hash)] +#[derive(Debug, Clone, Default, Hash)] pub struct ConditionalStmtBlock { /// Condition. pub condition: Expr, @@ -153,16 +153,110 @@ impl> From<(Expr, B)> for ConditionalStmtBlock { } } +/// _(internals)_ A type containing a range case for a `switch` statement. +/// Exported under the `internals` feature only. +#[derive(Clone, Hash)] +pub enum RangeCase { + /// Exclusive range. + ExclusiveInt(Range, usize), + /// Inclusive range. + InclusiveInt(RangeInclusive, usize), +} + +impl fmt::Debug for RangeCase { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::ExclusiveInt(r, n) => write!(f, "{}..{} => {}", r.start, r.end, n), + Self::InclusiveInt(r, n) => write!(f, "{}..={} => {}", *r.start(), *r.end(), n), + } + } +} + +impl From> for RangeCase { + #[inline(always)] + fn from(value: Range) -> Self { + Self::ExclusiveInt(value, 0) + } +} + +impl From> for RangeCase { + #[inline(always)] + fn from(value: RangeInclusive) -> Self { + Self::InclusiveInt(value, 0) + } +} + +impl RangeCase { + /// Is the range empty? + #[inline(always)] + #[must_use] + pub fn is_empty(&self) -> bool { + match self { + Self::ExclusiveInt(r, ..) => r.is_empty(), + Self::InclusiveInt(r, ..) => r.is_empty(), + } + } + /// Is the specified number within this range? + #[inline(always)] + #[must_use] + pub fn contains(&self, n: INT) -> bool { + match self { + Self::ExclusiveInt(r, ..) => r.contains(&n), + Self::InclusiveInt(r, ..) => r.contains(&n), + } + } + /// If the range contains only of a single [`INT`], return it; + /// otherwise return [`None`]. + #[inline(always)] + #[must_use] + pub fn single_int(&self) -> Option { + match self { + Self::ExclusiveInt(r, ..) if r.end.checked_sub(r.start) == Some(1) => Some(r.start), + Self::InclusiveInt(r, ..) if r.end().checked_sub(*r.start()) == Some(0) => { + Some(*r.start()) + } + _ => None, + } + } + /// Is the specified range inclusive? + #[inline(always)] + #[must_use] + pub fn is_inclusive(&self) -> bool { + match self { + Self::ExclusiveInt(..) => false, + Self::InclusiveInt(..) => true, + } + } + /// Get the index to the [`ConditionalStmtBlock`]. + #[inline(always)] + #[must_use] + pub fn index(&self) -> usize { + match self { + Self::ExclusiveInt(.., n) | Self::InclusiveInt(.., n) => *n, + } + } + /// Set the index to the [`ConditionalStmtBlock`]. + #[inline(always)] + pub fn set_index(&mut self, index: usize) { + match self { + Self::ExclusiveInt(.., n) | Self::InclusiveInt(.., n) => *n = index, + } + } +} + /// _(internals)_ A type containing all cases for a `switch` statement. /// Exported under the `internals` feature only. #[derive(Debug, Clone, Hash)] pub struct SwitchCases { + /// List of [`ConditionalStmtBlock`]'s. + pub blocks: StaticVec, /// Dictionary mapping value hashes to [`ConditionalStmtBlock`]'s. - pub cases: BTreeMap>, + pub cases: BTreeMap, /// Statements block for the default case (there can be no condition for the default case). - pub def_case: Box, + pub def_case: usize, /// List of range cases. - pub ranges: StaticVec<(INT, INT, bool, Box)>, + pub ranges: StaticVec, } /// _(internals)_ A `try-catch` block. @@ -195,7 +289,9 @@ pub type StmtBlockContainer = StaticVec; /// Exported under the `internals` feature only. #[derive(Clone, Hash, Default)] pub struct StmtBlock { + /// List of [statements][Stmt]. block: StmtBlockContainer, + /// [Position] of the statements block. span: Span, } @@ -634,14 +730,17 @@ impl Stmt { x.0.is_pure() && x.1.iter().all(Stmt::is_pure) && x.2.iter().all(Stmt::is_pure) } Self::Switch(x, ..) => { - x.0.is_pure() - && x.1.cases.values().all(|block| { + let (expr, sw) = x.as_ref(); + expr.is_pure() + && sw.cases.values().all(|&c| { + let block = &sw.blocks[c]; block.condition.is_pure() && block.statements.iter().all(Stmt::is_pure) }) - && x.1.ranges.iter().all(|(.., block)| { + && sw.ranges.iter().all(|r| { + let block = &sw.blocks[r.index()]; block.condition.is_pure() && block.statements.iter().all(Stmt::is_pure) }) - && x.1.def_case.iter().all(Stmt::is_pure) + && sw.blocks[sw.def_case].statements.iter().all(Stmt::is_pure) } // Loops that exit can be pure because it can never be infinite. @@ -777,30 +876,36 @@ impl Stmt { } } Self::Switch(x, ..) => { - if !x.0.walk(path, on_node) { + let (expr, sw) = x.as_ref(); + + if !expr.walk(path, on_node) { return false; } - for b in x.1.cases.values() { - if !b.condition.walk(path, on_node) { + for (.., &b) in sw.cases.iter() { + let block = &sw.blocks[b]; + + if !block.condition.walk(path, on_node) { return false; } - for s in b.statements.iter() { + for s in block.statements.iter() { if !s.walk(path, on_node) { return false; } } } - for (.., b) in &x.1.ranges { - if !b.condition.walk(path, on_node) { + for r in sw.ranges.iter() { + let block = &sw.blocks[r.index()]; + + if !block.condition.walk(path, on_node) { return false; } - for s in b.statements.iter() { + for s in block.statements.iter() { if !s.walk(path, on_node) { return false; } } } - for s in x.1.def_case.iter() { + for s in sw.blocks[sw.def_case].statements.iter() { if !s.walk(path, on_node) { return false; } diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index b5171d8e..7b2a485a 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -394,6 +394,7 @@ impl Engine { let ( expr, SwitchCases { + blocks, cases, def_case, ranges, @@ -410,7 +411,9 @@ impl Engine { let hash = hasher.finish(); // First check hashes - if let Some(case_block) = cases.get(&hash) { + if let Some(&case_block) = cases.get(&hash) { + let case_block = &blocks[case_block]; + let cond_result = match case_block.condition { Expr::BoolConstant(b, ..) => Ok(b), ref c => self @@ -432,12 +435,9 @@ impl Engine { let value = value.as_int().expect("`INT`"); let mut result = Ok(None); - for (.., block) in - ranges.iter().filter(|&&(start, end, inclusive, ..)| { - (!inclusive && (start..end).contains(&value)) - || (inclusive && (start..=end).contains(&value)) - }) - { + for r in ranges.iter().filter(|r| r.contains(value)) { + let block = &blocks[r.index()]; + let cond_result = match block.condition { Expr::BoolConstant(b, ..) => Ok(b), ref c => self @@ -481,6 +481,8 @@ impl Engine { } } else if let Ok(None) = stmt_block_result { // Default match clause + let def_case = &blocks[*def_case].statements; + if !def_case.is_empty() { self.eval_stmt_block( scope, global, caches, lib, this_ptr, def_case, true, level, diff --git a/src/lib.rs b/src/lib.rs index a63cac6a..a6825ab0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -283,7 +283,8 @@ pub use parser::ParseState; #[cfg(feature = "internals")] pub use ast::{ ASTFlags, ASTNode, BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, - FnCallHashes, Ident, OpAssignment, ScriptFnDef, Stmt, StmtBlock, SwitchCases, TryCatchBlock, + FnCallHashes, Ident, OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlock, SwitchCases, + TryCatchBlock, }; #[cfg(feature = "internals")] diff --git a/src/optimizer.rs b/src/optimizer.rs index 89fae709..d5f8c029 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -525,6 +525,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b let ( match_expr, SwitchCases { + blocks: blocks_list, cases, ranges, def_case, @@ -537,34 +538,30 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b let hash = hasher.finish(); // First check hashes - if let Some(block) = cases.get_mut(&hash) { - match mem::take(&mut block.condition) { + if let Some(block) = cases.remove(&hash) { + let mut block = mem::take(&mut blocks_list[block]); + cases.clear(); + + match block.condition { Expr::BoolConstant(true, ..) => { // Promote the matched case - let statements = optimize_stmt_block( - mem::take(&mut block.statements), - state, - true, - true, - false, - ); + let statements: StmtBlockContainer = mem::take(&mut block.statements); + let statements = optimize_stmt_block(statements, state, true, true, false); *stmt = (statements, block.statements.span()).into(); } - mut condition => { + ref mut condition => { // switch const { case if condition => stmt, _ => def } => if condition { stmt } else { def } - optimize_expr(&mut condition, state, false); - - let def_stmt = - optimize_stmt_block(mem::take(def_case), state, true, true, false); + optimize_expr(condition, state, false); + let def_case = &mut blocks_list[*def_case].statements; + let def_span = def_case.span_or_else(*pos, Position::NONE); + let def_case: StmtBlockContainer = mem::take(def_case); + let def_stmt = optimize_stmt_block(def_case, state, true, true, false); *stmt = Stmt::If( ( - condition, + mem::take(condition), mem::take(&mut block.statements), - StmtBlock::new_with_span( - def_stmt, - def_case.span_or_else(*pos, Position::NONE), - ), + StmtBlock::new_with_span(def_stmt, def_span), ) .into(), match_expr.start_position(), @@ -582,21 +579,20 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b // Only one range or all ranges without conditions if ranges.len() == 1 - || ranges - .iter() - .all(|(.., c)| matches!(c.condition, Expr::BoolConstant(true, ..))) + || ranges.iter().all(|r| { + matches!( + blocks_list[r.index()].condition, + Expr::BoolConstant(true, ..) + ) + }) { - for (.., block) in - ranges - .iter_mut() - .filter(|&&mut (start, end, inclusive, ..)| { - (!inclusive && (start..end).contains(&value)) - || (inclusive && (start..=end).contains(&value)) - }) - { - match mem::take(&mut block.condition) { + for r in ranges.iter().filter(|r| r.contains(value)) { + let condition = mem::take(&mut blocks_list[r.index()].condition); + + match condition { Expr::BoolConstant(true, ..) => { // Promote the matched case + let block = &mut blocks_list[r.index()]; let statements = mem::take(&mut *block.statements); let statements = optimize_stmt_block(statements, state, true, true, false); @@ -606,21 +602,19 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b // switch const { range if condition => stmt, _ => def } => if condition { stmt } else { def } optimize_expr(&mut condition, state, false); - let def_stmt = optimize_stmt_block( - mem::take(def_case), - state, - true, - true, - false, - ); + let def_case = &mut blocks_list[*def_case].statements; + let def_span = def_case.span_or_else(*pos, Position::NONE); + let def_case: StmtBlockContainer = mem::take(def_case); + let def_stmt = + optimize_stmt_block(def_case, state, true, true, false); + + let statements = mem::take(&mut blocks_list[r.index()].statements); + *stmt = Stmt::If( ( condition, - mem::take(&mut block.statements), - StmtBlock::new_with_span( - def_stmt, - def_case.span_or_else(*pos, Position::NONE), - ), + statements, + StmtBlock::new_with_span(def_stmt, def_span), ) .into(), match_expr.start_position(), @@ -640,16 +634,14 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b let old_ranges_len = ranges.len(); - ranges.retain(|&mut (start, end, inclusive, ..)| { - (!inclusive && (start..end).contains(&value)) - || (inclusive && (start..=end).contains(&value)) - }); + ranges.retain(|r| r.contains(value)); if ranges.len() != old_ranges_len { state.set_dirty(); } - for (.., block) in ranges.iter_mut() { + for r in ranges.iter() { + let block = &mut blocks_list[r.index()]; let statements = mem::take(&mut *block.statements); *block.statements = optimize_stmt_block(statements, state, preserve_result, true, false); @@ -670,14 +662,18 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b // Promote the default case state.set_dirty(); - let def_stmt = optimize_stmt_block(mem::take(def_case), state, true, true, false); - *stmt = (def_stmt, def_case.span_or_else(*pos, Position::NONE)).into(); + let def_case = &mut blocks_list[*def_case].statements; + let def_span = def_case.span_or_else(*pos, Position::NONE); + let def_case: StmtBlockContainer = mem::take(def_case); + let def_stmt = optimize_stmt_block(def_case, state, true, true, false); + *stmt = (def_stmt, def_span).into(); } // switch Stmt::Switch(x, ..) => { let ( match_expr, SwitchCases { + blocks: blocks_list, cases, ranges, def_case, @@ -687,8 +683,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b optimize_expr(match_expr, state, false); - // Optimize cases - for block in cases.values_mut() { + // Optimize blocks + for block in blocks_list.iter_mut() { let statements = mem::take(&mut *block.statements); *block.statements = optimize_stmt_block(statements, state, preserve_result, true, false); @@ -700,38 +696,26 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b block.condition = Expr::BoolConstant(true, pos); state.set_dirty(); } + Expr::BoolConstant(false, ..) => { + if !block.statements.is_empty() { + block.statements = StmtBlock::NONE; + state.set_dirty(); + } + } _ => (), } } // Remove false cases - cases.retain(|_, block| match block.condition { + cases.retain(|_, &mut block| match blocks_list[block].condition { Expr::BoolConstant(false, ..) => { state.set_dirty(); false } _ => true, }); - - // Optimize ranges - for (.., block) in ranges.iter_mut() { - let statements = mem::take(&mut *block.statements); - *block.statements = - optimize_stmt_block(statements, state, preserve_result, true, false); - - optimize_expr(&mut block.condition, state, false); - - match block.condition { - Expr::Unit(pos) => { - block.condition = Expr::BoolConstant(true, pos); - state.set_dirty(); - } - _ => (), - } - } - // Remove false ranges - ranges.retain(|(.., block)| match block.condition { + ranges.retain(|r| match blocks_list[r.index()].condition { Expr::BoolConstant(false, ..) => { state.set_dirty(); false @@ -739,8 +723,9 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b _ => true, }); - let def_block = mem::take(&mut ***def_case); - ***def_case = optimize_stmt_block(def_block, state, preserve_result, true, false); + let def_case = &mut blocks_list[*def_case].statements; + let def_block = mem::take(&mut **def_case); + **def_case = optimize_stmt_block(def_block, state, preserve_result, true, false); } // while false { block } -> Noop diff --git a/src/parser.rs b/src/parser.rs index d19c3d2d..d1c2aff4 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -5,7 +5,7 @@ use crate::api::events::VarDefInfo; use crate::api::options::LangOptions; use crate::ast::{ ASTFlags, BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, - OpAssignment, ScriptFnDef, Stmt, StmtBlock, StmtBlockContainer, SwitchCases, TryCatchBlock, + OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlockContainer, SwitchCases, TryCatchBlock, }; use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS}; use crate::eval::GlobalRuntimeState; @@ -25,6 +25,7 @@ use crate::{ use std::prelude::v1::*; use std::{ collections::BTreeMap, + fmt, hash::{Hash, Hasher}, num::{NonZeroU8, NonZeroUsize}, }; @@ -41,7 +42,6 @@ const NEVER_ENDS: &str = "`Token`"; /// _(internals)_ A type that encapsulates the current state of the parser. /// Exported under the `internals` feature only. -#[derive(Debug)] pub struct ParseState<'e> { /// Input stream buffer containing the next character to read. pub tokenizer_control: TokenizerControl, @@ -55,6 +55,8 @@ pub struct ParseState<'e> { pub stack: Scope<'e>, /// Size of the local variables stack upon entry of the current block scope. pub block_stack_len: usize, + /// Controls whether parsing of an expression should stop given the next token. + pub expr_filter: fn(&Token) -> bool, /// Tracks a list of external variables (variables that are not explicitly declared in the scope). #[cfg(not(feature = "no_closure"))] pub external_vars: Vec, @@ -72,6 +74,23 @@ pub struct ParseState<'e> { pub max_expr_depth: usize, } +impl fmt::Debug for ParseState<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ParseState") + .field("tokenizer_control", &self.tokenizer_control) + .field("interned_strings", &self.interned_strings) + .field("scope", &self.scope) + .field("global", &self.global) + .field("stack", &self.stack) + .field("block_stack_len", &self.block_stack_len) + .field("external_vars", &self.external_vars) + .field("allow_capture", &self.allow_capture) + .field("imports", &self.imports) + .field("max_expr_depth", &self.max_expr_depth) + .finish() + } +} + impl<'e> ParseState<'e> { /// Create a new [`ParseState`]. #[inline(always)] @@ -79,6 +98,7 @@ impl<'e> ParseState<'e> { pub fn new(engine: &Engine, scope: &'e Scope, tokenizer_control: TokenizerControl) -> Self { Self { tokenizer_control, + expr_filter: |_| true, #[cfg(not(feature = "no_closure"))] external_vars: Vec::new(), #[cfg(not(feature = "no_closure"))] @@ -1027,15 +1047,16 @@ impl Engine { } } - let mut cases = BTreeMap::>::new(); - let mut ranges = StaticVec::<(INT, INT, bool, Box)>::new(); + let mut blocks = StaticVec::::new(); + let mut cases = BTreeMap::::new(); + let mut ranges = StaticVec::::new(); let mut def_pos = Position::NONE; let mut def_stmt = None; loop { const MISSING_RBRACE: &str = "to end this switch block"; - let (expr, condition) = match input.peek().expect(NEVER_ENDS) { + let (case_expr_list, condition) = match input.peek().expect(NEVER_ENDS) { (Token::RightBrace, ..) => { eat_token(input, Token::RightBrace); break; @@ -1056,7 +1077,7 @@ impl Engine { return Err(PERR::WrongSwitchCaseCondition.into_err(if_pos)); } - (None, Expr::BoolConstant(true, Position::NONE)) + (Default::default(), Expr::BoolConstant(true, Position::NONE)) } (Token::Underscore, pos) => return Err(PERR::DuplicatedSwitchCase.into_err(*pos)), @@ -1065,8 +1086,25 @@ impl Engine { } _ => { - let case_expr = - Some(self.parse_expr(input, state, lib, settings.level_up())?); + let mut case_expr_list = StaticVec::new(); + + loop { + let filter = state.expr_filter; + state.expr_filter = |t| t != &Token::Pipe; + let expr = self.parse_expr(input, state, lib, settings.level_up()); + state.expr_filter = filter; + + match expr { + Ok(expr) => case_expr_list.push(expr), + Err(err) => { + return Err(PERR::ExprExpected("literal".into()).into_err(err.1)) + } + } + + if !match_token(input, Token::Pipe).0 { + break; + } + } let condition = if match_token(input, Token::If).0 { ensure_not_statement_expr(input, "a boolean")?; @@ -1078,37 +1116,10 @@ impl Engine { } else { Expr::BoolConstant(true, Position::NONE) }; - (case_expr, condition) + (case_expr_list, condition) } }; - let (hash, range) = if let Some(expr) = expr { - let value = expr.get_literal_value().ok_or_else(|| { - PERR::ExprExpected("a literal".to_string()).into_err(expr.start_position()) - })?; - - let guard = value.read_lock::(); - - if let Some(range) = guard { - (None, Some((range.start, range.end, false))) - } else if let Some(range) = value.read_lock::() { - (None, Some((*range.start(), *range.end(), true))) - } else if value.is::() && !ranges.is_empty() { - return Err(PERR::WrongSwitchIntegerCase.into_err(expr.start_position())); - } else { - let hasher = &mut get_hasher(); - value.hash(hasher); - let hash = hasher.finish(); - - if !cases.is_empty() && cases.contains_key(&hash) { - return Err(PERR::DuplicatedSwitchCase.into_err(expr.start_position())); - } - (Some(hash), None) - } - } else { - (None, None) - }; - match input.next().expect(NEVER_ENDS) { (Token::DoubleArrow, ..) => (), (Token::LexError(err), pos) => return Err(err.into_err(pos)), @@ -1122,48 +1133,61 @@ impl Engine { }; let stmt = self.parse_stmt(input, state, lib, settings.level_up())?; - let need_comma = !stmt.is_self_terminated(); - def_stmt = match (hash, range) { - (None, Some(range)) => { - let is_empty = if range.2 { - (range.0..=range.1).is_empty() - } else { - (range.0..range.1).is_empty() - }; + blocks.push((condition, stmt).into()); + let index = blocks.len() - 1; - if !is_empty { - match (range.1.checked_sub(range.0), range.2) { - // Unroll single range - (Some(1), false) | (Some(0), true) => { - let value = Dynamic::from_int(range.0); + if !case_expr_list.is_empty() { + for expr in case_expr_list { + let value = expr.get_literal_value().ok_or_else(|| { + PERR::ExprExpected("a literal".to_string()).into_err(expr.start_position()) + })?; + + let mut range_value: Option = None; + + let guard = value.read_lock::(); + if let Some(range) = guard { + range_value = Some(range.clone().into()); + } else if let Some(range) = value.read_lock::() { + range_value = Some(range.clone().into()); + } + + if let Some(mut r) = range_value { + if !r.is_empty() { + if let Some(n) = r.single_int() { + // Unroll single range + let value = Dynamic::from_int(n); let hasher = &mut get_hasher(); value.hash(hasher); let hash = hasher.finish(); - cases.entry(hash).or_insert_with(|| { - let block: ConditionalStmtBlock = (condition, stmt).into(); - block.into() - }); - } - // Other range - _ => { - let block: ConditionalStmtBlock = (condition, stmt).into(); - ranges.push((range.0, range.1, range.2, block.into())) + cases.entry(hash).or_insert(index); + } else { + // Other range + r.set_index(index); + ranges.push(r); } } + continue; } - None + + if value.is::() && !ranges.is_empty() { + return Err(PERR::WrongSwitchIntegerCase.into_err(expr.start_position())); + } + + let hasher = &mut get_hasher(); + value.hash(hasher); + let hash = hasher.finish(); + + if cases.contains_key(&hash) { + return Err(PERR::DuplicatedSwitchCase.into_err(expr.start_position())); + } + cases.insert(hash, index); } - (Some(hash), None) => { - let block: ConditionalStmtBlock = (condition, stmt).into(); - cases.insert(hash, block.into()); - None - } - (None, None) => Some(Box::new(stmt.into())), - _ => unreachable!("both hash and range in switch statement case"), - }; + } else { + def_stmt = Some(index); + } match input.peek().expect(NEVER_ENDS) { (Token::Comma, ..) => { @@ -1188,9 +1212,15 @@ impl Engine { } } + let def_case = def_stmt.unwrap_or_else(|| { + blocks.push(Default::default()); + blocks.len() - 1 + }); + let cases = SwitchCases { + blocks, cases, - def_case: def_stmt.unwrap_or_else(|| StmtBlock::NONE.into()), + def_case, ranges, }; @@ -1214,6 +1244,12 @@ impl Engine { settings.pos = *token_pos; let root_expr = match token { + _ if !(state.expr_filter)(token) => { + return Err( + LexError::UnexpectedInput(token.syntax().to_string()).into_err(settings.pos) + ) + } + Token::EOF => return Err(PERR::UnexpectedEOF.into_err(settings.pos)), Token::Unit => { @@ -1538,6 +1574,10 @@ impl Engine { } }; + if !(state.expr_filter)(&input.peek().expect(NEVER_ENDS).0) { + return Ok(root_expr); + } + self.parse_postfix(input, state, lib, root_expr, settings) } @@ -1738,6 +1778,10 @@ impl Engine { let (token, token_pos) = input.peek().expect(NEVER_ENDS); + if !(state.expr_filter)(token) { + return Err(LexError::UnexpectedInput(token.syntax().to_string()).into_err(*token_pos)); + } + let mut settings = settings; settings.pos = *token_pos; @@ -2130,6 +2174,11 @@ impl Engine { loop { let (current_op, current_pos) = input.peek().expect(NEVER_ENDS); + + if !(state.expr_filter)(current_op) { + return Ok(root); + } + let precedence = match current_op { Token::Custom(c) => self .custom_keywords diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 59a63bb5..fa74a647 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -283,7 +283,7 @@ impl AddAssign for Position { /// _(internals)_ A span consisting of a starting and an ending [positions][Position]. /// Exported under the `internals` feature only. -#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, Default)] +#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] pub struct Span { /// Starting [position][Position]. start: Position, @@ -291,6 +291,12 @@ pub struct Span { end: Position, } +impl Default for Span { + fn default() -> Self { + Self::NONE + } +} + impl Span { /// Empty [`Span`]. pub const NONE: Self = Self::new(Position::NONE, Position::NONE); diff --git a/tests/switch.rs b/tests/switch.rs index 6d91c809..301401a8 100644 --- a/tests/switch.rs +++ b/tests/switch.rs @@ -45,6 +45,13 @@ fn test_switch() -> Result<(), Box> { engine.eval_with_scope::<()>(&mut scope, "switch x { 1 => 123, 2 => 'a' }")?, () ); + assert_eq!( + engine.eval_with_scope::( + &mut scope, + "switch x { 1 | 2 | 3 | 5..50 | 'x' | true => 123, 'z' => 'a' }" + )?, + 123 + ); assert_eq!( engine.eval::("let x = timestamp(); switch x { 1 => 123, _ => 42 }")?, 42 From 9319f87a7b247fbf766e1c4dab0673596a6a728d Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 4 Jul 2022 17:47:59 +0800 Subject: [PATCH 2/6] Fix builds. --- src/parser.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index d1c2aff4..cfa594a5 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -76,18 +76,22 @@ pub struct ParseState<'e> { impl fmt::Debug for ParseState<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("ParseState") - .field("tokenizer_control", &self.tokenizer_control) + let mut f = f.debug_struct("ParseState"); + + f.field("tokenizer_control", &self.tokenizer_control) .field("interned_strings", &self.interned_strings) .field("scope", &self.scope) .field("global", &self.global) .field("stack", &self.stack) - .field("block_stack_len", &self.block_stack_len) - .field("external_vars", &self.external_vars) - .field("allow_capture", &self.allow_capture) - .field("imports", &self.imports) - .field("max_expr_depth", &self.max_expr_depth) - .finish() + .field("block_stack_len", &self.block_stack_len); + #[cfg(not(feature = "no_closure"))] + f.field("external_vars", &self.external_vars) + .field("allow_capture", &self.allow_capture); + #[cfg(not(feature = "no_module"))] + f.field("imports", &self.imports); + #[cfg(not(feature = "unchecked"))] + f.field("max_expr_depth", &self.max_expr_depth); + f.finish() } } From b6528bd51d0d2447ec3f033ab8078f75573db115 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 5 Jul 2022 16:26:38 +0800 Subject: [PATCH 3/6] Reduce usage of as_ref and as_mut. --- codegen/src/module.rs | 4 +-- codegen/src/rhai_module.rs | 2 +- src/api/call_fn.rs | 2 +- src/api/compile.rs | 4 +-- src/api/eval.rs | 2 +- src/api/mod.rs | 13 +++++---- src/api/register.rs | 5 ++-- src/api/run.rs | 2 +- src/ast/ast.rs | 44 +++++++++++++++--------------- src/ast/expr.rs | 4 +-- src/ast/script_fn.rs | 2 +- src/ast/stmt.rs | 39 +++++++++++++++++--------- src/eval/chaining.rs | 22 +++++++-------- src/eval/debugger.rs | 6 ++-- src/eval/expr.rs | 8 +++--- src/eval/global_state.rs | 17 +++++++++++- src/eval/stmt.rs | 24 ++++++++-------- src/func/callable_function.rs | 2 +- src/func/native.rs | 2 +- src/func/script.rs | 2 +- src/module/mod.rs | 17 ++++++------ src/module/resolvers/collection.rs | 12 +++++++- src/module/resolvers/file.rs | 2 +- src/module/resolvers/stat.rs | 17 +++++++++++- src/optimizer.rs | 16 +++++------ src/packages/lang_core.rs | 4 +-- src/packages/map_basic.rs | 2 +- src/parser.rs | 10 +++---- src/serde/metadata.rs | 4 +-- src/tokenizer.rs | 20 +++++++------- src/types/fn_ptr.rs | 4 +-- src/types/interner.rs | 15 ++++++---- src/types/scope.rs | 19 +++++++++++-- 33 files changed, 211 insertions(+), 137 deletions(-) diff --git a/codegen/src/module.rs b/codegen/src/module.rs index 52b9716c..c1d7d6a3 100644 --- a/codegen/src/module.rs +++ b/codegen/src/module.rs @@ -137,7 +137,7 @@ impl Parse for Module { Ok(vec) })?; // Gather and parse constants definitions. - for item in content.iter() { + for item in &*content { match item { syn::Item::Const(syn::ItemConst { vis: syn::Visibility::Public(..), @@ -156,7 +156,7 @@ impl Parse for Module { } } // Gather and parse type definitions. - for item in content.iter() { + for item in &*content { match item { syn::Item::Type(syn::ItemType { vis: syn::Visibility::Public(..), diff --git a/codegen/src/rhai_module.rs b/codegen/src/rhai_module.rs index 0e03917f..10b61e20 100644 --- a/codegen/src/rhai_module.rs +++ b/codegen/src/rhai_module.rs @@ -300,7 +300,7 @@ pub fn check_rename_collisions(fns: &[ExportedFn]) -> Result<(), syn::Error> { let mut renames = BTreeMap::new(); let mut fn_defs = BTreeMap::new(); - for item_fn in fns.iter() { + for item_fn in fns { if !item_fn.params().name.is_empty() || item_fn.params().special != FnSpecialAccess::None { let mut names: Vec<_> = item_fn .params() diff --git a/src/api/call_fn.rs b/src/api/call_fn.rs index dcbfbf99..9ad2965d 100644 --- a/src/api/call_fn.rs +++ b/src/api/call_fn.rs @@ -259,7 +259,7 @@ impl Engine { } let mut this_ptr = this_ptr; - let mut args: StaticVec<_> = arg_values.as_mut().iter_mut().collect(); + let mut args: StaticVec<_> = arg_values.iter_mut().collect(); // Check for data race. #[cfg(not(feature = "no_closure"))] diff --git a/src/api/compile.rs b/src/api/compile.rs index 2250b641..9aed8eed 100644 --- a/src/api/compile.rs +++ b/src/api/compile.rs @@ -219,7 +219,7 @@ impl Engine { ) -> ParseResult { let (stream, tokenizer_control) = self.lex_raw( scripts.as_ref(), - self.token_mapper.as_ref().map(Box::as_ref), + self.token_mapper.as_ref().map(<_>::as_ref), ); let mut state = ParseState::new(self, scope, tokenizer_control); self.parse(&mut stream.peekable(), &mut state, optimization_level) @@ -288,7 +288,7 @@ impl Engine { ) -> ParseResult { let scripts = [script]; let (stream, tokenizer_control) = - self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref)); + self.lex_raw(&scripts, self.token_mapper.as_ref().map(<_>::as_ref)); let mut peekable = stream.peekable(); let mut state = ParseState::new(self, scope, tokenizer_control); diff --git a/src/api/eval.rs b/src/api/eval.rs index a90d88bc..cb351301 100644 --- a/src/api/eval.rs +++ b/src/api/eval.rs @@ -115,7 +115,7 @@ impl Engine { ) -> RhaiResultOf { let scripts = [script]; let (stream, tokenizer_control) = - self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref)); + self.lex_raw(&scripts, self.token_mapper.as_ref().map(<_>::as_ref)); let mut state = ParseState::new(self, scope, tokenizer_control); // No need to optimize a lone expression diff --git a/src/api/mod.rs b/src/api/mod.rs index b8e7084e..71a6527c 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -158,7 +158,9 @@ impl Engine { return Err("precedence cannot be zero".into()); } - match Token::lookup_from_syntax(keyword.as_ref()) { + let keyword = keyword.as_ref(); + + match Token::lookup_from_syntax(keyword) { // Standard identifiers, reserved keywords and custom keywords are OK None | Some(Token::Reserved(..)) | Some(Token::Custom(..)) => (), // Active standard keywords cannot be made custom @@ -167,7 +169,7 @@ impl Engine { if self.disabled_symbols.is_empty() || !self.disabled_symbols.contains(&*token.syntax()) { - return Err(format!("'{}' is a reserved keyword", keyword.as_ref())); + return Err(format!("'{}' is a reserved keyword", keyword)); } } // Active standard symbols cannot be made custom @@ -175,7 +177,7 @@ impl Engine { if self.disabled_symbols.is_empty() || !self.disabled_symbols.contains(&*token.syntax()) { - return Err(format!("'{}' is a reserved operator", keyword.as_ref())); + return Err(format!("'{}' is a reserved operator", keyword)); } } // Active standard symbols cannot be made custom @@ -183,15 +185,14 @@ impl Engine { if self.disabled_symbols.is_empty() || !self.disabled_symbols.contains(&*token.syntax()) => { - return Err(format!("'{}' is a reserved symbol", keyword.as_ref())) + return Err(format!("'{}' is a reserved symbol", keyword)) } // Disabled symbols are OK Some(_) => (), } // Add to custom keywords - self.custom_keywords - .insert(keyword.as_ref().into(), precedence); + self.custom_keywords.insert(keyword.into(), precedence); Ok(self) } diff --git a/src/api/register.rs b/src/api/register.rs index 3ee7eb39..485cec44 100644 --- a/src/api/register.rs +++ b/src/api/register.rs @@ -987,8 +987,9 @@ impl Engine { module: Shared, ) { let separator = crate::tokenizer::Token::DoubleColon.syntax(); + let separator = separator.as_ref(); - if !name.contains(separator.as_ref()) { + if !name.contains(separator) { if !module.is_indexed() { // Index the module (making a clone copy if necessary) if it is not indexed let mut module = crate::func::shared_take_or_clone(module); @@ -998,7 +999,7 @@ impl Engine { root.insert(name.into(), module); } } else { - let mut iter = name.splitn(2, separator.as_ref()); + let mut iter = name.splitn(2, separator); let sub_module = iter.next().expect("contains separator").trim(); let remainder = iter.next().expect("contains separator").trim(); diff --git a/src/api/run.rs b/src/api/run.rs index 673fa19e..1c937956 100644 --- a/src/api/run.rs +++ b/src/api/run.rs @@ -23,7 +23,7 @@ impl Engine { pub fn run_with_scope(&self, scope: &mut Scope, script: &str) -> RhaiResultOf<()> { let scripts = [script]; let (stream, tokenizer_control) = - self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref)); + self.lex_raw(&scripts, self.token_mapper.as_ref().map(<_>::as_ref)); let mut state = ParseState::new(self, scope, tokenizer_control); let ast = self.parse(&mut stream.peekable(), &mut state, self.optimization_level)?; diff --git a/src/ast/ast.rs b/src/ast/ast.rs index 67237b95..e7dcddf9 100644 --- a/src/ast/ast.rs +++ b/src/ast/ast.rs @@ -54,7 +54,7 @@ impl fmt::Debug for AST { #[cfg(not(feature = "no_function"))] if !self.lib.is_empty() { - for (.., ref fn_def) in self.lib.iter_script_fn() { + for (.., fn_def) in self.lib.iter_script_fn() { let sig = fn_def.to_string(); fp.field(&sig, &fn_def.body.as_slice()); } @@ -523,19 +523,19 @@ impl AST { #[cfg(not(feature = "no_module"))] match ( - self.resolver().map_or(0, |r| r.len()), - other.resolver().map_or(0, |r| r.len()), + self.resolver().map_or(true, |r| r.is_empty()), + other.resolver().map_or(true, |r| r.is_empty()), ) { - (0, 0) => (), - (_, 0) => { + (true, true) => (), + (false, true) => { _ast.set_resolver(self.resolver().unwrap().clone()); } - (0, _) => { + (true, false) => { _ast.set_resolver(other.resolver().unwrap().clone()); } - (_, _) => { - let mut resolver = (**self.resolver().unwrap()).clone(); - let other_resolver = (**other.resolver().unwrap()).clone(); + (false, false) => { + let mut resolver = self.resolver().unwrap().as_ref().clone(); + let other_resolver = other.resolver().unwrap().as_ref().clone(); for (k, v) in other_resolver { resolver.insert(k, crate::func::shared_take_or_clone(v)); } @@ -611,23 +611,16 @@ impl AST { other: Self, _filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, ) -> &mut Self { - self.body.extend(other.body.into_iter()); - - #[cfg(not(feature = "no_function"))] - if !other.lib.is_empty() { - crate::func::shared_make_mut(&mut self.lib).merge_filtered(&other.lib, &_filter); - } - #[cfg(not(feature = "no_module"))] match ( - self.resolver.as_ref().map_or(0, |r| r.len()), - other.resolver.as_ref().map_or(0, |r| r.len()), + self.resolver().map_or(true, |r| r.is_empty()), + other.resolver().map_or(true, |r| r.is_empty()), ) { - (_, 0) => (), - (0, _) => { + (_, true) => (), + (true, false) => { self.set_resolver(other.resolver.unwrap()); } - (_, _) => { + (false, false) => { let resolver = crate::func::shared_make_mut(self.resolver.as_mut().unwrap()); let other_resolver = crate::func::shared_take_or_clone(other.resolver.unwrap()); for (k, v) in other_resolver { @@ -636,6 +629,13 @@ impl AST { } } + self.body.extend(other.body.into_iter()); + + #[cfg(not(feature = "no_function"))] + if !other.lib.is_empty() { + crate::func::shared_make_mut(&mut self.lib).merge_filtered(&other.lib, &_filter); + } + self } /// Filter out the functions, retaining only some based on a filter predicate. @@ -792,7 +792,7 @@ impl AST { if options.contains(ASTFlags::CONSTANT) && include_constants || !options.contains(ASTFlags::CONSTANT) && include_variables => { - let (name, expr, ..) = x.as_ref(); + let (name, expr, ..) = &**x; if let Some(value) = expr.get_literal_value() { Some((name.as_str(), options.contains(ASTFlags::CONSTANT), value)) } else { diff --git a/src/ast/expr.rs b/src/ast/expr.rs index a802e688..6251dd0a 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -886,14 +886,14 @@ impl Expr { match self { Self::Stmt(x) => { - for s in x.iter() { + for s in &**x { if !s.walk(path, on_node) { return false; } } } Self::InterpolatedString(x, ..) | Self::Array(x, ..) => { - for e in x.as_ref() { + for e in &**x { if !e.walk(path, on_node) { return false; } diff --git a/src/ast/script_fn.rs b/src/ast/script_fn.rs index 2ebbc9ed..38edeea1 100644 --- a/src/ast/script_fn.rs +++ b/src/ast/script_fn.rs @@ -123,7 +123,7 @@ impl<'a> From<&'a ScriptFnDef> for ScriptFnMetadata<'a> { params: value.params.iter().map(|s| s.as_str()).collect(), access: value.access, #[cfg(feature = "metadata")] - comments: value.comments.iter().map(Box::as_ref).collect(), + comments: value.comments.iter().map(<_>::as_ref).collect(), } } } diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index 7cab128f..d5c1069c 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -175,6 +175,7 @@ impl fmt::Debug for RangeCase { impl From> for RangeCase { #[inline(always)] + #[must_use] fn from(value: Range) -> Self { Self::ExclusiveInt(value, 0) } @@ -182,6 +183,7 @@ impl From> for RangeCase { impl From> for RangeCase { #[inline(always)] + #[must_use] fn from(value: RangeInclusive) -> Self { Self::InclusiveInt(value, 0) } @@ -467,6 +469,17 @@ impl IntoIterator for StmtBlock { } } +impl<'a> IntoIterator for &'a StmtBlock { + type Item = &'a Stmt; + type IntoIter = std::slice::Iter<'a, Stmt>; + + #[inline(always)] + fn into_iter(self) -> Self::IntoIter { + let x = self.block.iter(); + x + } +} + impl Extend for StmtBlock { #[inline(always)] fn extend>(&mut self, iter: T) { @@ -730,7 +743,7 @@ impl Stmt { x.0.is_pure() && x.1.iter().all(Stmt::is_pure) && x.2.iter().all(Stmt::is_pure) } Self::Switch(x, ..) => { - let (expr, sw) = x.as_ref(); + let (expr, sw) = &**x; expr.is_pure() && sw.cases.values().all(|&c| { let block = &sw.blocks[c]; @@ -814,7 +827,7 @@ impl Stmt { match self { Self::Var(x, ..) => x.1.is_pure(), - Self::Expr(e) => match e.as_ref() { + Self::Expr(e) => match &**e { Expr::Stmt(s) => s.iter().all(Stmt::is_internally_pure), _ => self.is_pure(), }, @@ -864,48 +877,48 @@ impl Stmt { if !x.0.walk(path, on_node) { return false; } - for s in x.1.iter() { + for s in &x.1 { if !s.walk(path, on_node) { return false; } } - for s in x.2.iter() { + for s in &x.2 { if !s.walk(path, on_node) { return false; } } } Self::Switch(x, ..) => { - let (expr, sw) = x.as_ref(); + let (expr, sw) = &**x; if !expr.walk(path, on_node) { return false; } - for (.., &b) in sw.cases.iter() { + for (.., &b) in &sw.cases { let block = &sw.blocks[b]; if !block.condition.walk(path, on_node) { return false; } - for s in block.statements.iter() { + for s in &block.statements { if !s.walk(path, on_node) { return false; } } } - for r in sw.ranges.iter() { + for r in &sw.ranges { let block = &sw.blocks[r.index()]; if !block.condition.walk(path, on_node) { return false; } - for s in block.statements.iter() { + for s in &block.statements { if !s.walk(path, on_node) { return false; } } } - for s in sw.blocks[sw.def_case].statements.iter() { + for s in &sw.blocks[sw.def_case].statements { if !s.walk(path, on_node) { return false; } @@ -925,7 +938,7 @@ impl Stmt { if !x.2.walk(path, on_node) { return false; } - for s in x.3.iter() { + for s in &x.3 { if !s.walk(path, on_node) { return false; } @@ -954,12 +967,12 @@ impl Stmt { } } Self::TryCatch(x, ..) => { - for s in x.try_block.iter() { + for s in &x.try_block { if !s.walk(path, on_node) { return false; } } - for s in x.catch_block.iter() { + for s in &x.catch_block { if !s.walk(path, on_node) { return false; } diff --git a/src/eval/chaining.rs b/src/eval/chaining.rs index 810486a9..a1e39da3 100644 --- a/src/eval/chaining.rs +++ b/src/eval/chaining.rs @@ -205,7 +205,7 @@ impl Engine { let crate::ast::FnCallExpr { name, hashes, args, .. - } = x.as_ref(); + } = &**x; let offset = idx_values.len() - args.len(); let call_args = &mut idx_values[offset..]; @@ -266,7 +266,7 @@ impl Engine { #[cfg(feature = "debugging")] self.run_debugger(scope, global, lib, this_ptr, rhs, level)?; - let ((getter, hash_get), (setter, hash_set), name) = x.as_ref(); + let ((getter, hash_get), (setter, hash_set), name) = &**x; let (mut new_val, op_info) = new_val.expect("`Some`"); if op_info.is_op_assignment() { @@ -331,7 +331,7 @@ impl Engine { #[cfg(feature = "debugging")] self.run_debugger(scope, global, lib, this_ptr, rhs, level)?; - let ((getter, hash_get), _, name) = x.as_ref(); + let ((getter, hash_get), _, name) = &**x; let args = &mut [target.as_mut()]; self.call_native_fn( global, caches, lib, getter, *hash_get, args, is_ref_mut, false, *pos, @@ -382,7 +382,7 @@ impl Engine { let crate::ast::FnCallExpr { name, hashes, args, .. - } = x.as_ref(); + } = &**x; let offset = idx_values.len() - args.len(); let call_args = &mut idx_values[offset..]; @@ -425,7 +425,7 @@ impl Engine { #[cfg(feature = "debugging")] self.run_debugger(scope, global, lib, this_ptr, _node, level)?; - let ((getter, hash_get), (setter, hash_set), name) = p.as_ref(); + let ((getter, hash_get), (setter, hash_set), name) = &**p; let rhs_chain = rhs.into(); let mut arg_values = [target.as_mut(), &mut Dynamic::UNIT.clone()]; let args = &mut arg_values[..1]; @@ -507,7 +507,7 @@ impl Engine { let crate::ast::FnCallExpr { name, hashes, args, .. - } = f.as_ref(); + } = &**f; let rhs_chain = rhs.into(); let offset = idx_values.len() - args.len(); @@ -563,9 +563,9 @@ impl Engine { let chain_type = ChainType::from(expr); let (crate::ast::BinaryExpr { lhs, rhs }, options, op_pos) = match expr { #[cfg(not(feature = "no_index"))] - Expr::Index(x, options, pos) => (x.as_ref(), *options, *pos), + Expr::Index(x, options, pos) => (&**x, *options, *pos), #[cfg(not(feature = "no_object"))] - Expr::Dot(x, options, pos) => (x.as_ref(), *options, *pos), + Expr::Dot(x, options, pos) => (&**x, *options, *pos), expr => unreachable!("Expr::Index or Expr::Dot expected but gets {:?}", expr), }; @@ -666,7 +666,7 @@ impl Engine { Expr::MethodCall(x, ..) if _parent_chain_type == ChainType::Dotting && !x.is_qualified() => { - for arg_expr in x.args.as_ref() { + for arg_expr in &x.args { idx_values.push( self.get_arg_value(scope, global, caches, lib, this_ptr, arg_expr, level)? .0 @@ -686,7 +686,7 @@ impl Engine { Expr::Index(x, options, ..) | Expr::Dot(x, options, ..) if !parent_options.contains(ASTFlags::BREAK) => { - let crate::ast::BinaryExpr { lhs, rhs, .. } = x.as_ref(); + let crate::ast::BinaryExpr { lhs, rhs, .. } = &**x; let mut _arg_values = FnArgsVec::new_const(); @@ -700,7 +700,7 @@ impl Engine { Expr::MethodCall(x, ..) if _parent_chain_type == ChainType::Dotting && !x.is_qualified() => { - for arg_expr in x.args.as_ref() { + for arg_expr in &x.args { _arg_values.push( self.get_arg_value( scope, global, caches, lib, this_ptr, arg_expr, level, diff --git a/src/eval/debugger.rs b/src/eval/debugger.rs index 21f9e739..4954f923 100644 --- a/src/eval/debugger.rs +++ b/src/eval/debugger.rs @@ -357,7 +357,7 @@ impl Debugger { ASTNode::Expr(Expr::FnCall(x, ..)) | ASTNode::Stmt(Stmt::FnCall(x, ..)) => { x.name == *name } - ASTNode::Stmt(Stmt::Expr(e)) => match e.as_ref() { + ASTNode::Stmt(Stmt::Expr(e)) => match &**e { Expr::FnCall(x, ..) => x.name == *name, _ => false, }, @@ -367,7 +367,7 @@ impl Debugger { ASTNode::Expr(Expr::FnCall(x, ..)) | ASTNode::Stmt(Stmt::FnCall(x, ..)) => { x.args.len() == *args && x.name == *name } - ASTNode::Stmt(Stmt::Expr(e)) => match e.as_ref() { + ASTNode::Stmt(Stmt::Expr(e)) => match &**e { Expr::FnCall(x, ..) => x.args.len() == *args && x.name == *name, _ => false, }, @@ -560,7 +560,7 @@ impl Engine { ASTNode::Expr(Expr::FnCall(..)) | ASTNode::Stmt(Stmt::FnCall(..)) => { level + 1 } - ASTNode::Stmt(Stmt::Expr(e)) if matches!(e.as_ref(), Expr::FnCall(..)) => { + ASTNode::Stmt(Stmt::Expr(e)) if matches!(**e, Expr::FnCall(..)) => { level + 1 } _ => level, diff --git a/src/eval/expr.rs b/src/eval/expr.rs index 9e9aa7be..94da70e5 100644 --- a/src/eval/expr.rs +++ b/src/eval/expr.rs @@ -60,7 +60,7 @@ impl Engine { Expr::Variable(_, Some(_), _) => { self.search_scope_only(scope, global, lib, this_ptr, expr, level) } - Expr::Variable(v, None, _var_pos) => match v.as_ref() { + Expr::Variable(v, None, _var_pos) => match &**v { // Normal variable access #[cfg(not(feature = "no_module"))] (_, ns, ..) if ns.is_empty() => { @@ -323,7 +323,7 @@ impl Engine { let mut op_info = OpAssignment::new_op_assignment(OP_CONCAT, Position::NONE); let root = ("", Position::NONE); - for expr in x.iter() { + for expr in &**x { let item = match self.eval_expr(scope, global, caches, lib, this_ptr, expr, level) { Ok(r) => r, @@ -354,7 +354,7 @@ impl Engine { #[cfg(not(feature = "unchecked"))] let mut sizes = (0, 0, 0); - for item_expr in x.iter() { + for item_expr in &**x { let value = match self .eval_expr(scope, global, caches, lib, this_ptr, item_expr, level) { @@ -392,7 +392,7 @@ impl Engine { #[cfg(not(feature = "unchecked"))] let mut sizes = (0, 0, 0); - for (key, value_expr) in x.0.iter() { + for (key, value_expr) in &x.0 { let value = match self .eval_expr(scope, global, caches, lib, this_ptr, value_expr, level) { diff --git a/src/eval/global_state.rs b/src/eval/global_state.rs index 3c0a8c4a..1ed3b3af 100644 --- a/src/eval/global_state.rs +++ b/src/eval/global_state.rs @@ -197,7 +197,7 @@ impl GlobalRuntimeState<'_> { .iter() .rev() .zip(self.modules.iter().rev()) - .map(|(name, module)| (name.as_str(), module.as_ref())) + .map(|(name, module)| (name.as_str(), &**module)) } /// Get an iterator to the stack of globally-imported [modules][crate::Module] in reverse order. /// @@ -327,6 +327,21 @@ impl IntoIterator for GlobalRuntimeState<'_> { } } +#[cfg(not(feature = "no_module"))] +impl<'a> IntoIterator for &'a GlobalRuntimeState<'_> { + type Item = (&'a Identifier, &'a crate::Shared); + type IntoIter = std::iter::Zip< + std::iter::Rev>, + std::iter::Rev>>, + >; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + let x = self.keys.iter().rev().zip(self.modules.iter().rev()); + x + } +} + #[cfg(not(feature = "no_module"))] impl, M: Into>> Extend<(K, M)> for GlobalRuntimeState<'_> diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 7b2a485a..1fcef61f 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -248,7 +248,7 @@ impl Engine { self.inc_operations(&mut global.num_operations, stmt.position())?; let result = if x.1.lhs.is_variable_access(false) { - let (op_info, BinaryExpr { lhs, rhs }) = x.as_ref(); + let (op_info, BinaryExpr { lhs, rhs }) = &**x; let rhs_result = self .eval_expr(scope, global, caches, lib, this_ptr, rhs, level) @@ -294,7 +294,7 @@ impl Engine { rhs_result } } else { - let (op_info, BinaryExpr { lhs, rhs }) = x.as_ref(); + let (op_info, BinaryExpr { lhs, rhs }) = &**x; let rhs_result = self .eval_expr(scope, global, caches, lib, this_ptr, rhs, level) @@ -356,7 +356,7 @@ impl Engine { // If statement Stmt::If(x, ..) => { - let (expr, if_block, else_block) = x.as_ref(); + let (expr, if_block, else_block) = &**x; let guard_val = self .eval_expr(scope, global, caches, lib, this_ptr, expr, level) @@ -399,7 +399,7 @@ impl Engine { def_case, ranges, }, - ) = x.as_ref(); + ) = &**x; let value_result = self.eval_expr(scope, global, caches, lib, this_ptr, expr, level); @@ -500,7 +500,7 @@ impl Engine { // Loop Stmt::While(x, ..) if matches!(x.0, Expr::Unit(..)) => loop { - let (.., body) = x.as_ref(); + let (.., body) = &**x; if !body.is_empty() { match self @@ -521,7 +521,7 @@ impl Engine { // While loop Stmt::While(x, ..) => loop { - let (expr, body) = x.as_ref(); + let (expr, body) = &**x; let condition = self .eval_expr(scope, global, caches, lib, this_ptr, expr, level) @@ -552,7 +552,7 @@ impl Engine { // Do loop Stmt::Do(x, options, ..) => loop { - let (expr, body) = x.as_ref(); + let (expr, body) = &**x; let is_while = !options.contains(ASTFlags::NEGATED); if !body.is_empty() { @@ -585,7 +585,7 @@ impl Engine { // For loop Stmt::For(x, ..) => { - let (var_name, counter, expr, statements) = x.as_ref(); + let (var_name, counter, expr, statements) = &**x; let iter_result = self .eval_expr(scope, global, caches, lib, this_ptr, expr, level) @@ -728,7 +728,7 @@ impl Engine { name: catch_var, .. }, catch_block, - } = x.as_ref(); + } = &**x; let result = self .eval_stmt_block(scope, global, caches, lib, this_ptr, try_block, true, level) @@ -832,7 +832,7 @@ impl Engine { } // Let/const statement Stmt::Var(x, options, pos) => { - let (var_name, expr, index) = x.as_ref(); + let (var_name, expr, index) = &**x; let access = if options.contains(ASTFlags::CONSTANT) { AccessMode::ReadOnly @@ -926,7 +926,7 @@ impl Engine { // Import statement #[cfg(not(feature = "no_module"))] Stmt::Import(x, _pos) => { - let (expr, export) = x.as_ref(); + let (expr, export) = &**x; // Guard against too many modules #[cfg(not(feature = "unchecked"))] @@ -995,7 +995,7 @@ impl Engine { // Export statement #[cfg(not(feature = "no_module"))] Stmt::Export(x, ..) => { - let (Ident { name, pos, .. }, alias) = x.as_ref(); + let (Ident { name, pos, .. }, alias) = &**x; // Mark scope variables as public if let Some((index, ..)) = scope.get_index(name) { let alias = if alias.is_empty() { name } else { alias }.clone(); diff --git a/src/func/callable_function.rs b/src/func/callable_function.rs index 2b24b4a9..87691895 100644 --- a/src/func/callable_function.rs +++ b/src/func/callable_function.rs @@ -178,7 +178,7 @@ impl CallableFunction { #[must_use] pub fn get_iter_fn(&self) -> Option<&IteratorFn> { match self { - Self::Iterator(f) => Some(f.as_ref()), + Self::Iterator(f) => Some(&**f), Self::Pure(..) | Self::Method(..) | Self::Plugin(..) => None, #[cfg(not(feature = "no_function"))] diff --git a/src/func/native.rs b/src/func/native.rs index 64cd34dc..c88570ba 100644 --- a/src/func/native.rs +++ b/src/func/native.rs @@ -110,7 +110,7 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef + 'a + ?Sized> Self { engine: value.0, fn_name: value.1.as_ref(), - source: value.2.map(S::as_ref), + source: value.2.map(<_>::as_ref), global: Some(value.3), lib: value.4.as_ref(), pos: value.5, diff --git a/src/func/script.rs b/src/func/script.rs index ce6c3d95..985070f0 100644 --- a/src/func/script.rs +++ b/src/func/script.rs @@ -131,7 +131,7 @@ impl Engine { lib } else { caches.push_fn_resolution_cache(); - lib_merged.push(fn_lib.as_ref()); + lib_merged.push(&**fn_lib); lib_merged.extend(lib.iter().cloned()); &lib_merged }, diff --git a/src/module/mod.rs b/src/module/mod.rs index 9a5e8366..65c61253 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -781,7 +781,7 @@ impl Module { #[must_use] pub fn get_sub_module(&self, name: &str) -> Option<&Module> { if !self.modules.is_empty() { - self.modules.get(name).map(|m| m.as_ref()) + self.modules.get(name).map(|m| &**m) } else { None } @@ -1006,18 +1006,19 @@ impl Module { (names, return_type) }; - let hash_fn = calc_native_fn_hash(None, name.as_ref(), ¶m_types); + let name = name.as_ref(); + let hash_fn = calc_native_fn_hash(None, name, ¶m_types); if is_dynamic { self.dynamic_functions - .insert(calc_fn_hash(name.as_ref(), param_types.len())); + .insert(calc_fn_hash(name, param_types.len())); } self.functions.insert( hash_fn, FuncInfo { metadata: FnMetadata { - name: name.as_ref().into(), + name: name.into(), namespace, access, params: param_types.len(), @@ -1549,7 +1550,7 @@ impl Module { /// Sub-modules are flattened onto the root [`Module`], with higher level overriding lower level. #[inline] pub fn combine_flatten(&mut self, other: Self) -> &mut Self { - for (.., m) in other.modules.into_iter() { + for (.., m) in other.modules { self.combine_flatten(shared_take_or_clone(m)); } self.variables.extend(other.variables.into_iter()); @@ -1707,7 +1708,7 @@ impl Module { #[inline] #[allow(dead_code)] pub(crate) fn iter_fn(&self) -> impl Iterator { - self.functions.values().map(Box::as_ref) + self.functions.values().map(<_>::as_ref) } /// Get an iterator over all script-defined functions in the [`Module`]. @@ -2154,7 +2155,7 @@ impl Module { #[must_use] pub(crate) fn get_qualified_iter(&self, id: TypeId) -> Option<&IteratorFn> { if !self.all_type_iterators.is_empty() { - self.all_type_iterators.get(&id).map(|f| f.as_ref()) + self.all_type_iterators.get(&id).map(|f| &**f) } else { None } @@ -2165,7 +2166,7 @@ impl Module { #[must_use] pub(crate) fn get_iter(&self, id: TypeId) -> Option<&IteratorFn> { if !self.type_iterators.is_empty() { - self.type_iterators.get(&id).map(|f| f.as_ref()) + self.type_iterators.get(&id).map(|f| &**f) } else { None } diff --git a/src/module/resolvers/collection.rs b/src/module/resolvers/collection.rs index fe03612f..ca815968 100644 --- a/src/module/resolvers/collection.rs +++ b/src/module/resolvers/collection.rs @@ -1,7 +1,7 @@ use crate::{Engine, Module, ModuleResolver, Position, RhaiResultOf, Shared, ERR}; #[cfg(feature = "no_std")] use std::prelude::v1::*; -use std::{ops::AddAssign, vec::IntoIter}; +use std::{ops::AddAssign, slice::Iter, vec::IntoIter}; /// [Module] resolution service that holds a collection of module resolvers, /// to be searched in sequential order. @@ -116,6 +116,16 @@ impl IntoIterator for ModuleResolversCollection { } } +impl<'a> IntoIterator for &'a ModuleResolversCollection { + type Item = &'a Box; + type IntoIter = Iter<'a, Box>; + + #[inline(always)] + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + impl ModuleResolver for ModuleResolversCollection { fn resolve( &self, diff --git a/src/module/resolvers/file.rs b/src/module/resolvers/file.rs index 054168b6..52a87a4d 100644 --- a/src/module/resolvers/file.rs +++ b/src/module/resolvers/file.rs @@ -172,7 +172,7 @@ impl FileModuleResolver { #[inline(always)] #[must_use] pub fn base_path(&self) -> Option<&Path> { - self.base_path.as_ref().map(PathBuf::as_ref) + self.base_path.as_ref().map(<_>::as_ref) } /// Set the base path for script files. #[inline(always)] diff --git a/src/module/resolvers/stat.rs b/src/module/resolvers/stat.rs index 3e2e9659..60a89950 100644 --- a/src/module/resolvers/stat.rs +++ b/src/module/resolvers/stat.rs @@ -3,7 +3,11 @@ use crate::{ }; #[cfg(feature = "no_std")] use std::prelude::v1::*; -use std::{collections::btree_map::IntoIter, collections::BTreeMap, ops::AddAssign}; +use std::{ + collections::btree_map::{IntoIter, Iter}, + collections::BTreeMap, + ops::AddAssign, +}; /// A static [module][Module] resolution service that serves [modules][Module] added into it. /// @@ -122,11 +126,22 @@ impl IntoIterator for StaticModuleResolver { type Item = (Identifier, Shared); type IntoIter = IntoIter>; + #[inline(always)] fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } +impl<'a> IntoIterator for &'a StaticModuleResolver { + type Item = (&'a Identifier, &'a Shared); + type IntoIter = Iter<'a, SmartString, Shared>; + + #[inline(always)] + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + impl ModuleResolver for StaticModuleResolver { #[inline] fn resolve( diff --git a/src/optimizer.rs b/src/optimizer.rs index d5f8c029..8e540cf7 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -512,7 +512,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b } // if expr { if_block } else { else_block } Stmt::If(x, ..) => { - let (condition, body, other) = x.as_mut(); + let (condition, body, other) = &mut **x; optimize_expr(condition, state, false); **body = optimize_stmt_block(mem::take(&mut **body), state, preserve_result, true, false); @@ -530,7 +530,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b ranges, def_case, }, - ) = x.as_mut(); + ) = &mut **x; let value = match_expr.get_literal_value().unwrap(); let hasher = &mut get_hasher(); @@ -640,7 +640,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b state.set_dirty(); } - for r in ranges.iter() { + for r in &*ranges { let block = &mut blocks_list[r.index()]; let statements = mem::take(&mut *block.statements); *block.statements = @@ -679,7 +679,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b def_case, .. }, - ) = x.as_mut(); + ) = &mut **x; optimize_expr(match_expr, state, false); @@ -738,7 +738,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b }, // while expr { block } Stmt::While(x, ..) => { - let (condition, body) = x.as_mut(); + let (condition, body) = &mut **x; optimize_expr(condition, state, false); if let Expr::BoolConstant(true, pos) = condition { *condition = Expr::Unit(*pos); @@ -846,7 +846,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b Stmt::Expr(expr) => { optimize_expr(expr, state, false); - match expr.as_mut() { + match &mut **expr { // func(...) Expr::FnCall(x, pos) => { state.set_dirty(); @@ -892,7 +892,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { ***x = optimize_stmt_block(mem::take(&mut **x), state, true, true, false); // { Stmt(Expr) } - promote - match x.as_mut().as_mut() { + match &mut ****x { [ Stmt::Expr(e) ] => { state.set_dirty(); *expr = mem::take(e); } _ => () } @@ -1129,7 +1129,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { return; } // Overloaded operators can override built-in. - _ if x.args.len() == 2 && !has_native_fn_override(state.engine, x.hashes.native, arg_types.as_ref()) => { + _ if x.args.len() == 2 && !has_native_fn_override(state.engine, x.hashes.native, &arg_types) => { if let Some(result) = get_builtin_binary_op_fn(&x.name, &arg_values[0], &arg_values[1]) .and_then(|f| { #[cfg(not(feature = "no_function"))] diff --git a/src/packages/lang_core.rs b/src/packages/lang_core.rs index 4126b5ae..a82a5fa4 100644 --- a/src/packages/lang_core.rs +++ b/src/packages/lang_core.rs @@ -273,12 +273,12 @@ fn collect_fn_metadata( crate::tokenizer::Token::DoubleColon.literal_syntax(), ns ); - scan_module(list, dict, ns.into(), m.as_ref(), filter) + scan_module(list, dict, ns.into(), &**m, filter) } } for (ns, m) in ctx.iter_imports_raw() { - scan_module(&mut list, &dict, ns.clone(), m.as_ref(), filter) + scan_module(&mut list, &dict, ns.clone(), &**m, filter) } } diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index 771bc7aa..427146b9 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -191,7 +191,7 @@ mod map_functions { if !map1.is_empty() { let mut map2 = map2; - for (m1, v1) in map1.iter_mut() { + for (m1, v1) in map1 { if let Some(v2) = map2.get_mut(m1) { let equals = ctx .call_fn_raw(OP_EQUALS, true, false, &mut [v1, v2])? diff --git a/src/parser.rs b/src/parser.rs index cfa594a5..3c8e6c9f 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1729,9 +1729,9 @@ impl Engine { // Cache the hash key for namespace-qualified variables #[cfg(not(feature = "no_module"))] let namespaced_variable = match lhs { - Expr::Variable(ref mut x, ..) if !x.1.is_empty() => Some(x.as_mut()), + Expr::Variable(ref mut x, ..) if !x.1.is_empty() => Some(&mut **x), Expr::Index(ref mut x, ..) | Expr::Dot(ref mut x, ..) => match x.lhs { - Expr::Variable(ref mut x, ..) if !x.1.is_empty() => Some(x.as_mut()), + Expr::Variable(ref mut x, ..) if !x.1.is_empty() => Some(&mut **x), _ => None, }, _ => None, @@ -1933,7 +1933,7 @@ impl Engine { } // var (indexed) = rhs Expr::Variable(ref x, i, var_pos) => { - let (index, .., name) = x.as_ref(); + let (index, .., name) = &**x; let index = i.map_or_else( || index.expect("either long or short index is `None`").get(), |n| n.get() as usize, @@ -2067,7 +2067,7 @@ impl Engine { (.., Expr::FnCall(func, func_pos)) if func.args.is_empty() && [crate::engine::KEYWORD_FN_PTR, crate::engine::KEYWORD_EVAL] - .contains(&func.name.as_ref()) => + .contains(&func.name.as_str()) => { let err_msg = format!( "'{}' should not be called in method style. Try {}(...);", @@ -2370,7 +2370,7 @@ impl Engine { state.stack.push(marker, ()); } - let parse_func = syntax.parse.as_ref(); + let parse_func = &*syntax.parse; let mut required_token: ImmutableString = key.into(); tokens.push(required_token.clone().into()); diff --git a/src/serde/metadata.rs b/src/serde/metadata.rs index 8cd30eaa..f430e484 100644 --- a/src/serde/metadata.rs +++ b/src/serde/metadata.rs @@ -146,10 +146,10 @@ impl<'a> From<&'a FuncInfo> for FnMetadata<'a> { .expect("script-defined function") .comments .iter() - .map(Box::as_ref) + .map(<_>::as_ref) .collect() } else { - info.metadata.comments.iter().map(Box::as_ref).collect() + info.metadata.comments.iter().map(<_>::as_ref).collect() }, } } diff --git a/src/tokenizer.rs b/src/tokenizer.rs index fa74a647..39bd7627 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -329,20 +329,20 @@ impl Span { impl fmt::Display for Span { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match (self.start().is_none(), self.end().is_none()) { - (false, false) if self.start().line() != self.end().line() => { - write!(f, "{:?}-{:?}", self.start(), self.end()) + match (self.start(), self.end()) { + (Position::NONE, Position::NONE) => write!(f, "{:?}", Position::NONE), + (Position::NONE, end) => write!(f, "..{:?}", end), + (start, Position::NONE) => write!(f, "{:?}", start), + (start, end) if start.line() != end.line() => { + write!(f, "{:?}-{:?}", start, end) } - (false, false) => write!( + (start, end) => write!( f, "{}:{}-{}", - self.start().line().unwrap(), - self.start().position().unwrap_or(0), - self.end().position().unwrap_or(0) + start.line().unwrap(), + start.position().unwrap_or(0), + end.position().unwrap_or(0) ), - (true, false) => write!(f, "..{:?}", self.end()), - (false, true) => write!(f, "{:?}", self.start()), - (true, true) => write!(f, "{:?}", Position::NONE), } } } diff --git a/src/types/fn_ptr.rs b/src/types/fn_ptr.rs index 8e80cb35..719e8b0f 100644 --- a/src/types/fn_ptr.rs +++ b/src/types/fn_ptr.rs @@ -56,7 +56,7 @@ impl FnPtr { #[inline(always)] #[must_use] pub fn fn_name(&self) -> &str { - self.fn_name_raw().as_ref() + self.fn_name_raw().as_str() } /// Get the name of the function. #[inline(always)] @@ -223,7 +223,7 @@ impl FnPtr { args_data = StaticVec::with_capacity(self.curry().len() + arg_values.len()); args_data.extend(self.curry().iter().cloned()); args_data.extend(arg_values.iter_mut().map(mem::take)); - arg_values = args_data.as_mut(); + arg_values = &mut *args_data; }; let is_method = this_ptr.is_some(); diff --git a/src/types/interner.rs b/src/types/interner.rs index a5277207..f9e85acc 100644 --- a/src/types/interner.rs +++ b/src/types/interner.rs @@ -51,7 +51,10 @@ impl StringsInterner<'_> { #[inline] #[must_use] pub fn get(&mut self, prefix: impl AsRef, text: impl AsRef) -> ImmutableString { - let (dict, mapper): (_, fn(&str) -> Identifier) = match prefix.as_ref() { + let prefix = prefix.as_ref(); + let text = text.as_ref(); + + let (dict, mapper): (_, fn(&str) -> Identifier) = match prefix { "" => (&mut self.strings, |s| s.into()), #[cfg(not(feature = "no_object"))] @@ -59,14 +62,14 @@ impl StringsInterner<'_> { #[cfg(not(feature = "no_object"))] crate::engine::FN_SET => (&mut self.setters, crate::engine::make_setter), - _ => unreachable!("unsupported prefix {}", prefix.as_ref()), + _ => unreachable!("unsupported prefix {}", prefix), }; - if !dict.is_empty() && dict.contains_key(text.as_ref()) { - dict.get(text.as_ref()).unwrap().clone() + if !dict.is_empty() && dict.contains_key(text) { + dict.get(text).unwrap().clone() } else { - let value: ImmutableString = mapper(text.as_ref()).into(); - dict.insert(text.as_ref().into(), value.clone()); + let value: ImmutableString = mapper(text).into(); + dict.insert(text.into(), value.clone()); value } } diff --git a/src/types/scope.rs b/src/types/scope.rs index 6b96013b..aaa76fa3 100644 --- a/src/types/scope.rs +++ b/src/types/scope.rs @@ -132,6 +132,21 @@ impl IntoIterator for Scope<'_> { } } +impl<'a> IntoIterator for &'a Scope<'_> { + type Item = (&'a Identifier, &'a Dynamic, &'a Vec); + type IntoIter = Box + 'a>; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + Box::new( + self.values + .iter() + .zip(self.names.iter().zip(self.aliases.iter())) + .map(|(value, (name, alias))| (name, value, alias)), + ) + } +} + impl Scope<'_> { /// Create a new [`Scope`]. /// @@ -686,7 +701,7 @@ impl Scope<'_> { self.names .iter() .zip(self.values.iter()) - .map(|(name, value)| (name.as_ref(), value.is_read_only(), value)) + .map(|(name, value)| (name.as_str(), value.is_read_only(), value)) } /// Get a reverse iterator to entries in the [`Scope`]. /// Shared values are not expanded. @@ -696,7 +711,7 @@ impl Scope<'_> { .iter() .rev() .zip(self.values.iter().rev()) - .map(|(name, value)| (name.as_ref(), value.is_read_only(), value)) + .map(|(name, value)| (name.as_str(), value.is_read_only(), value)) } /// Remove a range of entries within the [`Scope`]. /// From b4dbc7619a1f0a7b4607eb8d80230aca8417a909 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 5 Jul 2022 22:59:03 +0800 Subject: [PATCH 4/6] Add no_custom_syntax. --- .github/workflows/build.yml | 5 +++-- CHANGELOG.md | 5 +++++ Cargo.toml | 1 + src/api/custom_syntax.rs | 5 +++++ src/api/deprecated.rs | 7 ++++--- src/api/mod.rs | 15 +++++++++++---- src/ast/expr.rs | 21 +++++++++++++++++---- src/ast/mod.rs | 4 +++- src/ast/stmt.rs | 1 + src/engine.rs | 31 ++++++++++++++++++------------- src/eval/eval_context.rs | 9 +++++---- src/eval/expr.rs | 6 ++++-- src/lib.rs | 13 +++++++++---- src/optimizer.rs | 1 + src/parser.rs | 15 +++++++++++---- src/tokenizer.rs | 21 +++++++++++++++++++-- tests/custom_syntax.rs | 2 ++ tests/tokens.rs | 2 ++ 18 files changed, 121 insertions(+), 43 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c889e006..090a8605 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,6 +27,7 @@ jobs: - "--features no_float,serde,metadata,internals,debugging" - "--features f32_float,serde,metadata,internals,debugging" - "--features decimal,serde,metadata,internals,debugging" + - "--features no_custom_syntax,serde,metadata,internals,debugging" - "--features no_float,decimal" - "--tests --features only_i32,serde,metadata,internals,debugging" - "--features only_i64,serde,metadata,internals,debugging" @@ -36,8 +37,8 @@ jobs: - "--features no_module,serde,metadata,internals,debugging" - "--features no_closure,serde,metadata,internals,debugging" - "--features unicode-xid-ident,serde,metadata,internals,debugging" - - "--features sync,no_function,no_float,no_position,no_optimize,no_module,no_closure,metadata,serde,unchecked,debugging" - - "--features no_function,no_float,no_position,no_index,no_object,no_optimize,no_module,no_closure,unchecked" + - "--features sync,no_function,no_float,no_position,no_optimize,no_module,no_closure,no_custom_syntax,metadata,serde,unchecked,debugging" + - "--features no_function,no_float,no_position,no_index,no_object,no_optimize,no_module,no_closure,no_custom_syntax,unchecked" toolchain: [stable] experimental: [false] include: diff --git a/CHANGELOG.md b/CHANGELOG.md index bb039a1c..04c06ec3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ Rhai Release Notes Version 1.9.0 ============= +New features +------------ + +* A new feature, `no_custom_syntax`, is added to remove custom syntax support from Rhai for applications that do not require it (which should be most). + Enhancements ------------ diff --git a/Cargo.toml b/Cargo.toml index 0328b394..9c22483f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ no_object = [] # no custom objects no_function = ["no_closure"] # no script-defined functions (meaning no closures) no_closure = [] # no automatic sharing and capture of anonymous functions to external variables no_module = [] # no modules +no_custom_syntax = [] # no custom syntax or custom operators unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers. metadata = ["serde", "serde_json", "rhai_codegen/metadata", "smartstring/serde"] # enable exporting functions metadata internals = [] # expose internal data structures diff --git a/src/api/custom_syntax.rs b/src/api/custom_syntax.rs index 4aacf153..5f365f2b 100644 --- a/src/api/custom_syntax.rs +++ b/src/api/custom_syntax.rs @@ -1,4 +1,5 @@ //! Module implementing custom syntax for [`Engine`]. +#![cfg(not(feature = "no_custom_syntax"))] use crate::ast::Expr; use crate::func::SendSync; @@ -153,6 +154,8 @@ pub struct CustomSyntax { impl Engine { /// Register a custom syntax with the [`Engine`]. /// + /// Not available under `no_custom_syntax`. + /// /// * `symbols` holds a slice of strings that define the custom syntax. /// * `scope_may_be_changed` specifies variables _may_ be added/removed by this custom syntax. /// * `func` is the implementation function. @@ -296,6 +299,8 @@ impl Engine { } /// Register a custom syntax with the [`Engine`]. /// + /// Not available under `no_custom_syntax`. + /// /// # WARNING - Low Level API /// /// This function is very low level. diff --git a/src/api/deprecated.rs b/src/api/deprecated.rs index 01f9331f..49dfb94f 100644 --- a/src/api/deprecated.rs +++ b/src/api/deprecated.rs @@ -1,8 +1,8 @@ //! Module containing all deprecated API that will be removed in the next major version. use crate::{ - Dynamic, Engine, EvalAltResult, Expression, FnPtr, ImmutableString, NativeCallContext, - Position, RhaiResult, RhaiResultOf, Scope, AST, + Dynamic, Engine, EvalAltResult, FnPtr, ImmutableString, NativeCallContext, Position, + RhaiResult, RhaiResultOf, Scope, AST, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -312,7 +312,8 @@ impl FnPtr { } } -impl Expression<'_> { +#[cfg(not(feature = "no_custom_syntax"))] +impl crate::Expression<'_> { /// If this expression is a variable name, return it. Otherwise [`None`]. /// /// # Deprecated diff --git a/src/api/mod.rs b/src/api/mod.rs index 71a6527c..4f5e96e1 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -28,10 +28,11 @@ pub mod custom_syntax; pub mod deprecated; -use crate::engine::Precedence; -use crate::tokenizer::Token; use crate::{Dynamic, Engine, Identifier}; +#[cfg(not(feature = "no_custom_syntax"))] +use crate::{engine::Precedence, tokenizer::Token}; + #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -121,6 +122,8 @@ impl Engine { /// Register a custom operator with a precedence into the language. /// + /// Not available under `no_custom_syntax`. + /// /// The operator can be a valid identifier, a reserved symbol, a disabled operator or a disabled keyword. /// /// The precedence cannot be zero. @@ -147,6 +150,7 @@ impl Engine { /// # Ok(()) /// # } /// ``` + #[cfg(not(feature = "no_custom_syntax"))] pub fn register_custom_operator( &mut self, keyword: impl AsRef, @@ -161,8 +165,11 @@ impl Engine { let keyword = keyword.as_ref(); match Token::lookup_from_syntax(keyword) { - // Standard identifiers, reserved keywords and custom keywords are OK - None | Some(Token::Reserved(..)) | Some(Token::Custom(..)) => (), + // Standard identifiers and reserved keywords are OK + None | Some(Token::Reserved(..)) => (), + // custom keywords are OK + #[cfg(not(feature = "no_custom_syntax"))] + Some(Token::Custom(..)) => (), // Active standard keywords cannot be made custom // Disabled keywords are OK Some(token) if token.is_standard_keyword() => { diff --git a/src/ast/expr.rs b/src/ast/expr.rs index 6251dd0a..edb66068 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -48,6 +48,9 @@ impl From<(Expr, Expr)> for BinaryExpr { /// _(internals)_ A custom syntax expression. /// Exported under the `internals` feature only. +/// +/// Not available under `no_custom_syntax`. +#[cfg(not(feature = "no_custom_syntax"))] #[derive(Debug, Clone, Hash)] pub struct CustomExpr { /// List of keywords. @@ -61,6 +64,7 @@ pub struct CustomExpr { pub self_terminated: bool, } +#[cfg(not(feature = "no_custom_syntax"))] impl CustomExpr { /// Is this custom syntax self-terminated (i.e. no need for a semicolon terminator)? /// @@ -421,6 +425,7 @@ pub enum Expr { /// lhs `??` rhs Coalesce(Box, Position), /// Custom syntax + #[cfg(not(feature = "no_custom_syntax"))] Custom(Box, Position), } @@ -530,6 +535,7 @@ impl fmt::Debug for Expr { .field("rhs", &x.rhs) .finish() } + #[cfg(not(feature = "no_custom_syntax"))] Self::Custom(x, ..) => f.debug_tuple("Custom").field(x).finish(), }?; @@ -703,10 +709,12 @@ impl Expr { | Self::Coalesce(.., pos) | Self::Index(.., pos) | Self::Dot(.., pos) - | Self::Custom(.., pos) | Self::InterpolatedString(.., pos) | Self::Property(.., pos) => *pos, + #[cfg(not(feature = "no_custom_syntax"))] + Self::Custom(.., pos) => *pos, + Self::FnCall(x, ..) | Self::MethodCall(x, ..) => x.pos, Self::Stmt(x) => x.position(), @@ -761,10 +769,12 @@ impl Expr { | Self::Variable(.., pos) | Self::FnCall(.., pos) | Self::MethodCall(.., pos) - | Self::Custom(.., pos) | Self::InterpolatedString(.., pos) | Self::Property(.., pos) => *pos = new_pos, + #[cfg(not(feature = "no_custom_syntax"))] + Self::Custom(.., pos) => *pos = new_pos, + Self::Stmt(x) => x.set_position(new_pos, Position::NONE), } @@ -853,8 +863,10 @@ impl Expr { | Self::Dot(..) | Self::Index(..) | Self::Array(..) - | Self::Map(..) - | Self::Custom(..) => false, + | Self::Map(..) => false, + + #[cfg(not(feature = "no_custom_syntax"))] + Self::Custom(..) => false, Self::Variable(..) => match token { Token::LeftParen => true, @@ -925,6 +937,7 @@ impl Expr { } } } + #[cfg(not(feature = "no_custom_syntax"))] Self::Custom(x, ..) => { for e in &x.inputs { if !e.walk(path, on_node) { diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 6dfb077b..cb525116 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -9,7 +9,9 @@ pub mod script_fn; pub mod stmt; pub use ast::{ASTNode, AST}; -pub use expr::{BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes}; +#[cfg(not(feature = "no_custom_syntax"))] +pub use expr::CustomExpr; +pub use expr::{BinaryExpr, Expr, FnCallExpr, FnCallHashes}; pub use flags::{ASTFlags, FnAccess}; pub use ident::Ident; #[cfg(not(feature = "no_module"))] diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index d5c1069c..5acbd892 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -713,6 +713,7 @@ impl Stmt { Self::Noop(..) => false, Self::Expr(e) => match &**e { + #[cfg(not(feature = "no_custom_syntax"))] Expr::Custom(x, ..) if x.is_self_terminated() => true, _ => false, }, diff --git a/src/engine.rs b/src/engine.rs index 2af23bac..10a80417 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,6 +1,5 @@ //! Main module defining the script evaluation [`Engine`]. -use crate::api::custom_syntax::CustomSyntax; use crate::api::options::LangOptions; use crate::func::native::{ OnDebugCallback, OnDefVarCallback, OnParseTokenCallback, OnPrintCallback, OnVarCallback, @@ -111,9 +110,11 @@ pub struct Engine { /// A set of symbols to disable. pub(crate) disabled_symbols: BTreeSet, /// A map containing custom keywords and precedence to recognize. + #[cfg(not(feature = "no_custom_syntax"))] pub(crate) custom_keywords: BTreeMap>, /// Custom syntax. - pub(crate) custom_syntax: BTreeMap, + #[cfg(not(feature = "no_custom_syntax"))] + pub(crate) custom_syntax: BTreeMap, /// Callback closure for filtering variable definition. pub(crate) def_var_filter: Option>, /// Callback closure for resolving variable access. @@ -160,17 +161,19 @@ impl fmt::Debug for Engine { #[cfg(not(feature = "no_module"))] f.field("global_sub_modules", &self.global_sub_modules); - f.field("disabled_symbols", &self.disabled_symbols) - .field("custom_keywords", &self.custom_keywords) - .field( - "custom_syntax", - &self - .custom_syntax - .keys() - .map(|s| s.as_str()) - .collect::(), - ) - .field("def_var_filter", &self.def_var_filter.is_some()) + f.field("disabled_symbols", &self.disabled_symbols); + + #[cfg(not(feature = "no_custom_syntax"))] + f.field("custom_keywords", &self.custom_keywords).field( + "custom_syntax", + &self + .custom_syntax + .keys() + .map(|s| s.as_str()) + .collect::(), + ); + + f.field("def_var_filter", &self.def_var_filter.is_some()) .field("resolve_var", &self.resolve_var.is_some()) .field("token_mapper", &self.token_mapper.is_some()); @@ -268,7 +271,9 @@ impl Engine { empty_string: ImmutableString::new(), disabled_symbols: BTreeSet::new(), + #[cfg(not(feature = "no_custom_syntax"))] custom_keywords: BTreeMap::new(), + #[cfg(not(feature = "no_custom_syntax"))] custom_syntax: BTreeMap::new(), def_var_filter: None, diff --git a/src/eval/eval_context.rs b/src/eval/eval_context.rs index 150b19e4..11245721 100644 --- a/src/eval/eval_context.rs +++ b/src/eval/eval_context.rs @@ -1,12 +1,12 @@ //! Evaluation context. use super::{Caches, GlobalRuntimeState}; -use crate::{Dynamic, Engine, Expression, Module, RhaiResult, Scope}; +use crate::{Dynamic, Engine, Module, Scope}; #[cfg(feature = "no_std")] use std::prelude::v1::*; /// Context of a script evaluation process. -#[derive(Debug)] +#[allow(dead_code)] pub struct EvalContext<'a, 's, 'ps, 'g, 'pg, 'c, 'pc, 't, 'pt> { /// The current [`Engine`]. engine: &'a Engine, @@ -142,13 +142,14 @@ impl<'a, 's, 'ps, 'g, 'pg, 'c, 'pc, 't, 'pt> EvalContext<'a, 's, 'ps, 'g, 'pg, ' self.level } - /// Evaluate an [expression tree][Expression] within this [evaluation context][`EvalContext`]. + /// Evaluate an [expression tree][crate::Expression] within this [evaluation context][`EvalContext`]. /// /// # WARNING - Low Level API /// /// This function is very low level. It evaluates an expression from an [`AST`][crate::AST]. + #[cfg(not(feature = "no_custom_syntax"))] #[inline(always)] - pub fn eval_expression_tree(&mut self, expr: &Expression) -> RhaiResult { + pub fn eval_expression_tree(&mut self, expr: &crate::Expression) -> crate::RhaiResult { let mut new_caches = Caches::new(); let caches = match self.caches.as_mut() { diff --git a/src/eval/expr.rs b/src/eval/expr.rs index 94da70e5..c4a0011f 100644 --- a/src/eval/expr.rs +++ b/src/eval/expr.rs @@ -4,7 +4,7 @@ use super::{Caches, EvalContext, GlobalRuntimeState, Target}; use crate::ast::{Expr, FnCallExpr, OpAssignment}; use crate::engine::{KEYWORD_THIS, OP_CONCAT}; use crate::types::dynamic::AccessMode; -use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, StaticVec, ERR}; +use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, ERR}; use std::num::NonZeroUsize; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -475,8 +475,10 @@ impl Engine { } } + #[cfg(not(feature = "no_custom_syntax"))] Expr::Custom(custom, pos) => { - let expressions: StaticVec<_> = custom.inputs.iter().map(Into::into).collect(); + let expressions: crate::StaticVec<_> = + custom.inputs.iter().map(Into::into).collect(); // The first token acts as the custom syntax's key let key_token = custom.tokens.first().unwrap(); // The key should exist, unless the AST is compiled in a different Engine diff --git a/src/lib.rs b/src/lib.rs index a6825ab0..c24ab2bc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -165,7 +165,6 @@ type ExclusiveRange = std::ops::Range; /// An inclusive integer range. type InclusiveRange = std::ops::RangeInclusive; -pub use api::custom_syntax::Expression; pub use api::events::VarDefInfo; pub use ast::{FnAccess, AST}; pub use engine::{Engine, OP_CONTAINS, OP_EQUALS}; @@ -179,6 +178,9 @@ pub use types::{ Dynamic, EvalAltResult, FnPtr, ImmutableString, LexError, ParseError, ParseErrorType, Scope, }; +#[cfg(not(feature = "no_custom_syntax"))] +pub use api::custom_syntax::Expression; + /// _(debugging)_ Module containing types for debugging. /// Exported under the `debugging` feature only. #[cfg(feature = "debugging")] @@ -282,11 +284,14 @@ pub use parser::ParseState; #[cfg(feature = "internals")] pub use ast::{ - ASTFlags, ASTNode, BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, - FnCallHashes, Ident, OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlock, SwitchCases, - TryCatchBlock, + ASTFlags, ASTNode, BinaryExpr, ConditionalStmtBlock, Expr, FnCallExpr, FnCallHashes, Ident, + OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlock, SwitchCases, TryCatchBlock, }; +#[cfg(feature = "internals")] +#[cfg(not(feature = "no_custom_syntax"))] +pub use ast::CustomExpr; + #[cfg(feature = "internals")] #[cfg(not(feature = "no_module"))] pub use ast::Namespace; diff --git a/src/optimizer.rs b/src/optimizer.rs index 8e540cf7..c1399f10 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -1231,6 +1231,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { } // Custom syntax + #[cfg(not(feature = "no_custom_syntax"))] Expr::Custom(x, ..) => { if x.scope_may_be_changed { state.propagate_constants = false; diff --git a/src/parser.rs b/src/parser.rs index 3c8e6c9f..119f207d 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,10 +1,9 @@ //! Main module defining the lexer and parser. -use crate::api::custom_syntax::{markers::*, CustomSyntax}; use crate::api::events::VarDefInfo; use crate::api::options::LangOptions; use crate::ast::{ - ASTFlags, BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, + ASTFlags, BinaryExpr, ConditionalStmtBlock, Expr, FnCallExpr, FnCallHashes, Ident, OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlockContainer, SwitchCases, TryCatchBlock, }; use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS}; @@ -428,6 +427,7 @@ fn parse_var_name(input: &mut TokenStream) -> ParseResult<(SmartString, Position } /// Parse a symbol. +#[cfg(not(feature = "no_custom_syntax"))] #[inline] fn parse_symbol(input: &mut TokenStream) -> ParseResult<(SmartString, Position)> { match input.next().expect(NEVER_ENDS) { @@ -1449,6 +1449,7 @@ impl Engine { Token::MapStart => self.parse_map_literal(input, state, lib, settings.level_up())?, // Custom syntax. + #[cfg(not(feature = "no_custom_syntax"))] Token::Custom(key) | Token::Reserved(key) | Token::Identifier(key) if !self.custom_syntax.is_empty() && self.custom_syntax.contains_key(&**key) => { @@ -2184,6 +2185,7 @@ impl Engine { } let precedence = match current_op { + #[cfg(not(feature = "no_custom_syntax"))] Token::Custom(c) => self .custom_keywords .get(c) @@ -2208,6 +2210,7 @@ impl Engine { let (next_op, next_pos) = input.peek().expect(NEVER_ENDS); let next_precedence = match next_op { + #[cfg(not(feature = "no_custom_syntax"))] Token::Custom(c) => self .custom_keywords .get(c) @@ -2317,6 +2320,7 @@ impl Engine { .into_fn_call_expr(pos) } + #[cfg(not(feature = "no_custom_syntax"))] Token::Custom(s) if self .custom_keywords @@ -2347,6 +2351,7 @@ impl Engine { } /// Parse a custom syntax. + #[cfg(not(feature = "no_custom_syntax"))] fn parse_custom_syntax( &self, input: &mut TokenStream, @@ -2354,9 +2359,11 @@ impl Engine { lib: &mut FnLib, settings: ParseSettings, key: impl Into, - syntax: &CustomSyntax, + syntax: &crate::api::custom_syntax::CustomSyntax, pos: Position, ) -> ParseResult { + use crate::api::custom_syntax::markers::*; + let mut settings = settings; let mut inputs = StaticVec::::new(); let mut segments = StaticVec::new_const(); @@ -2520,7 +2527,7 @@ impl Engine { }; Ok(Expr::Custom( - CustomExpr { + crate::ast::CustomExpr { inputs, tokens, scope_may_be_changed: syntax.scope_may_be_changed, diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 39bd7627..d7031e4d 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -562,6 +562,9 @@ pub enum Token { /// A reserved symbol. Reserved(SmartString), /// A custom keyword. + /// + /// Not available under the `no_custom_syntax` feature. + #[cfg(not(feature = "no_custom_syntax"))] Custom(SmartString), /// End of the input stream. EOF, @@ -682,6 +685,7 @@ impl Token { CharConstant(c) => c.to_string().into(), Identifier(s) => s.to_string().into(), Reserved(s) => s.to_string().into(), + #[cfg(not(feature = "no_custom_syntax"))] Custom(s) => s.to_string().into(), LexError(err) => err.to_string().into(), Comment(s) => s.to_string().into(), @@ -1069,12 +1073,15 @@ impl Token { #[inline] pub(crate) fn into_function_name_for_override(self) -> Result { match self { - Self::Custom(s) | Self::Identifier(s) if is_valid_function_name(&s) => Ok(s), + #[cfg(not(feature = "no_custom_syntax"))] + Self::Custom(s) if is_valid_function_name(&s) => Ok(s), + Self::Identifier(s) if is_valid_function_name(&s) => Ok(s), _ => Err(self), } } /// Is this token a custom keyword? + #[cfg(not(feature = "no_custom_syntax"))] #[inline(always)] #[must_use] pub const fn is_custom(&self) -> bool { @@ -2329,7 +2336,12 @@ impl<'a> Iterator for TokenIterator<'a> { } // Reserved keyword/symbol Some((Token::Reserved(s), pos)) => (match - (&*s, !self.engine.custom_keywords.is_empty() && self.engine.custom_keywords.contains_key(&*s)) + (&*s, + #[cfg(not(feature = "no_custom_syntax"))] + (!self.engine.custom_keywords.is_empty() && self.engine.custom_keywords.contains_key(&*s)), + #[cfg(feature = "no_custom_syntax")] + false + ) { ("===", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(), "'===' is not a valid operator. This is not JavaScript! Should it be '=='?".to_string(), @@ -2358,7 +2370,10 @@ impl<'a> Iterator for TokenIterator<'a> { "'#' is not a valid symbol. Should it be '#{'?".to_string(), ).into()), // Reserved keyword/operator that is custom. + #[cfg(not(feature = "no_custom_syntax"))] (.., true) => Token::Custom(s), + #[cfg(feature = "no_custom_syntax")] + (.., true) => unreachable!("no custom operators"), // Reserved keyword that is not custom and disabled. (token, false) if !self.engine.disabled_symbols.is_empty() && self.engine.disabled_symbols.contains(token) => { let msg = format!("reserved {} '{}' is disabled", if is_valid_identifier(token.chars()) { "keyword"} else {"symbol"}, token); @@ -2368,10 +2383,12 @@ impl<'a> Iterator for TokenIterator<'a> { (.., false) => Token::Reserved(s), }, pos), // Custom keyword + #[cfg(not(feature = "no_custom_syntax"))] Some((Token::Identifier(s), pos)) if !self.engine.custom_keywords.is_empty() && self.engine.custom_keywords.contains_key(&*s) => { (Token::Custom(s), pos) } // Custom keyword/symbol - must be disabled + #[cfg(not(feature = "no_custom_syntax"))] Some((token, pos)) if !self.engine.custom_keywords.is_empty() && self.engine.custom_keywords.contains_key(token.literal_syntax()) => { if !self.engine.disabled_symbols.is_empty() && self.engine.disabled_symbols.contains(token.literal_syntax()) { // Disabled standard keyword/symbol diff --git a/tests/custom_syntax.rs b/tests/custom_syntax.rs index 26cc2ae1..fbd24a93 100644 --- a/tests/custom_syntax.rs +++ b/tests/custom_syntax.rs @@ -1,3 +1,5 @@ +#![cfg(not(feature = "no_custom_syntax"))] + use rhai::{ Dynamic, Engine, EvalAltResult, ImmutableString, LexError, ParseErrorType, Position, Scope, INT, }; diff --git a/tests/tokens.rs b/tests/tokens.rs index 392f8953..1b9cb71a 100644 --- a/tests/tokens.rs +++ b/tests/tokens.rs @@ -30,6 +30,7 @@ fn test_tokens_disabled() { )); } +#[cfg(not(feature = "no_custom_syntax"))] #[test] fn test_tokens_custom_operator_identifiers() -> Result<(), Box> { let mut engine = Engine::new(); @@ -60,6 +61,7 @@ fn test_tokens_custom_operator_identifiers() -> Result<(), Box> { Ok(()) } +#[cfg(not(feature = "no_custom_syntax"))] #[test] fn test_tokens_custom_operator_symbol() -> Result<(), Box> { let mut engine = Engine::new(); From dda7bc7b8515daec815963d6b6d727aeb646d86f Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 6 Jul 2022 12:56:15 +0800 Subject: [PATCH 5/6] Add eval_expression_tree_raw. --- CHANGELOG.md | 1 + src/api/call_fn.rs | 5 ++++ src/api/custom_syntax.rs | 22 ++++++++++++++++ src/eval/eval_context.rs | 56 +++++++++++++++++++++++++++++++++------- src/optimizer.rs | 2 +- src/tokenizer.rs | 2 +- tests/custom_syntax.rs | 28 +++++++++++++++++++- 7 files changed, 104 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04c06ec3..e14595d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Enhancements ------------ * `switch` cases can now include multiple values separated by `|`. +* `EvalContext::eval_expression_tree_raw` and `Expression::eval_with_context_raw` are added to allow for not rewinding the `Scope` at the end of a statements block. Version 1.8.0 diff --git a/src/api/call_fn.rs b/src/api/call_fn.rs index 9ad2965d..8c5a9b22 100644 --- a/src/api/call_fn.rs +++ b/src/api/call_fn.rs @@ -185,6 +185,10 @@ impl Engine { /// /// Not available under `no_function`. /// + /// # WARNING - Unstable API + /// + /// This API is volatile and may change in the future. + /// /// # WARNING - Low Level API /// /// This function is _extremely_ low level. @@ -202,6 +206,7 @@ impl Engine { /// Do not use the arguments after this call. If they are needed afterwards, clone them _before_ /// calling this function. #[cfg(feature = "internals")] + #[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."] #[inline(always)] pub fn call_fn_raw_raw( &self, diff --git a/src/api/custom_syntax.rs b/src/api/custom_syntax.rs index 5f365f2b..f1128604 100644 --- a/src/api/custom_syntax.rs +++ b/src/api/custom_syntax.rs @@ -74,6 +74,28 @@ impl Expression<'_> { pub fn eval_with_context(&self, context: &mut EvalContext) -> RhaiResult { context.eval_expression_tree(self) } + /// Evaluate this [expression tree][Expression] within an [evaluation context][`EvalContext`]. + /// + /// The following option is available: + /// + /// * whether to rewind the [`Scope`] after evaluation if the expression is a [`StmtBlock`][crate::ast::StmtBlock] + /// + /// # WARNING - Unstable API + /// + /// This API is volatile and may change in the future. + /// + /// # WARNING - Low Level API + /// + /// This function is _extremely_ low level. It evaluates an expression from an [`AST`][crate::AST]. + #[inline(always)] + pub fn eval_with_context_raw( + &self, + context: &mut EvalContext, + rewind_scope: bool, + ) -> RhaiResult { + #[allow(deprecated)] + context.eval_expression_tree_raw(self, rewind_scope) + } /// Get the value of this expression if it is a variable name or a string constant. /// /// Returns [`None`] also if the constant is not of the specified type. diff --git a/src/eval/eval_context.rs b/src/eval/eval_context.rs index 11245721..2b0265f1 100644 --- a/src/eval/eval_context.rs +++ b/src/eval/eval_context.rs @@ -150,6 +150,32 @@ impl<'a, 's, 'ps, 'g, 'pg, 'c, 'pc, 't, 'pt> EvalContext<'a, 's, 'ps, 'g, 'pg, ' #[cfg(not(feature = "no_custom_syntax"))] #[inline(always)] pub fn eval_expression_tree(&mut self, expr: &crate::Expression) -> crate::RhaiResult { + #[allow(deprecated)] + self.eval_expression_tree_raw(expr, true) + } + /// Evaluate an [expression tree][crate::Expression] within this [evaluation context][`EvalContext`]. + /// + /// The following option is available: + /// + /// * whether to rewind the [`Scope`] after evaluation if the expression is a [`StmtBlock`][crate::ast::StmtBlock] + /// + /// # WARNING - Unstable API + /// + /// This API is volatile and may change in the future. + /// + /// # WARNING - Low Level API + /// + /// This function is _extremely_ low level. It evaluates an expression from an [`AST`][crate::AST]. + #[cfg(not(feature = "no_custom_syntax"))] + #[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."] + #[inline] + pub fn eval_expression_tree_raw( + &mut self, + expr: &crate::Expression, + rewind_scope: bool, + ) -> crate::RhaiResult { + let expr: &crate::ast::Expr = expr; + let mut new_caches = Caches::new(); let caches = match self.caches.as_mut() { @@ -157,14 +183,26 @@ impl<'a, 's, 'ps, 'g, 'pg, 'c, 'pc, 't, 'pt> EvalContext<'a, 's, 'ps, 'g, 'pg, ' None => &mut new_caches, }; - self.engine.eval_expr( - self.scope, - self.global, - caches, - self.lib, - self.this_ptr, - expr, - self.level, - ) + match expr { + crate::Expr::Stmt(statements) => self.engine.eval_stmt_block( + self.scope, + self.global, + caches, + self.lib, + self.this_ptr, + &statements, + rewind_scope, + self.level, + ), + _ => self.engine.eval_expr( + self.scope, + self.global, + caches, + self.lib, + self.this_ptr, + expr, + self.level, + ), + } } } diff --git a/src/optimizer.rs b/src/optimizer.rs index c1399f10..2a807055 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -1236,7 +1236,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { if x.scope_may_be_changed { state.propagate_constants = false; } - x.inputs.iter_mut().for_each(|expr| optimize_expr(expr, state, false)); + // Do not optimize custom syntax expressions as you won't know how they would be called } // All other expressions - skip diff --git a/src/tokenizer.rs b/src/tokenizer.rs index d7031e4d..fa1fcad9 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -2336,7 +2336,7 @@ impl<'a> Iterator for TokenIterator<'a> { } // Reserved keyword/symbol Some((Token::Reserved(s), pos)) => (match - (&*s, + (&*s, #[cfg(not(feature = "no_custom_syntax"))] (!self.engine.custom_keywords.is_empty() && self.engine.custom_keywords.contains_key(&*s)), #[cfg(feature = "no_custom_syntax")] diff --git a/tests/custom_syntax.rs b/tests/custom_syntax.rs index fbd24a93..a770acc6 100644 --- a/tests/custom_syntax.rs +++ b/tests/custom_syntax.rs @@ -52,7 +52,13 @@ fn test_custom_syntax() -> Result<(), Box> { break; } - context.eval_expression_tree(stmt)?; + // Do not rewind if the variable is upper-case + if var_name.to_uppercase() == var_name { + context.eval_expression_tree_raw(stmt, false)?; + } else { + context.eval_expression_tree(stmt)?; + } + count += 1; context @@ -125,6 +131,26 @@ fn test_custom_syntax() -> Result<(), Box> { )?, 144 ); + assert_eq!( + engine.eval::( + " + let foo = 123; + exec [x<15] -> { let foo = x; x += 1; } while x < 42; + foo + " + )?, + 123 + ); + assert_eq!( + engine.eval::( + " + let foo = 123; + exec [ABC<15] -> { let foo = ABC; ABC += 1; } while ABC < 42; + foo + " + )?, + 14 + ); // The first symbol must be an identifier assert_eq!( From fedd09053ad8d937d0e97059591dcc29516bbe3c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 6 Jul 2022 13:13:21 +0800 Subject: [PATCH 6/6] Fix typo. --- src/eval/eval_context.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/eval/eval_context.rs b/src/eval/eval_context.rs index 2b0265f1..3d83eb15 100644 --- a/src/eval/eval_context.rs +++ b/src/eval/eval_context.rs @@ -184,7 +184,7 @@ impl<'a, 's, 'ps, 'g, 'pg, 'c, 'pc, 't, 'pt> EvalContext<'a, 's, 'ps, 'g, 'pg, ' }; match expr { - crate::Expr::Stmt(statements) => self.engine.eval_stmt_block( + crate::ast::Expr::Stmt(statements) => self.engine.eval_stmt_block( self.scope, self.global, caches,