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