From a63f14b59c4cc41e2e250b645950aa451f079ccc Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 14 Nov 2020 23:43:36 +0800 Subject: [PATCH] Expr::Switch -> Stmt::Switch. --- src/ast.rs | 33 +++++++++++---------- src/engine.rs | 45 +++++++++++++++-------------- src/optimize.rs | 73 ++++++++++++++++++++++++----------------------- src/parser.rs | 26 +++++++++++++---- tests/comments.rs | 27 ++++++++++++------ tests/switch.rs | 2 +- 6 files changed, 119 insertions(+), 87 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index 5fcb2438..c1d44fe9 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -583,6 +583,12 @@ pub enum Stmt { Noop(Position), /// if expr { stmt } else { stmt } If(Expr, Box<(Stmt, Option)>, Position), + /// switch expr { literal or _ => stmt, ... } + Switch( + Expr, + Box<(HashMap, Option)>, + Position, + ), /// while expr { stmt } While(Expr, Box, Position), /// loop { stmt } @@ -642,6 +648,7 @@ impl Stmt { | Self::Block(_, pos) | Self::Assignment(_, pos) | Self::If(_, _, pos) + | Self::Switch(_, _, pos) | Self::While(_, _, pos) | Self::Loop(_, pos) | Self::For(_, _, pos) @@ -670,6 +677,7 @@ impl Stmt { | Self::Block(_, pos) | Self::Assignment(_, pos) | Self::If(_, _, pos) + | Self::Switch(_, _, pos) | Self::While(_, _, pos) | Self::Loop(_, pos) | Self::For(_, _, pos) @@ -697,6 +705,7 @@ impl Stmt { pub fn is_self_terminated(&self) -> bool { match self { Self::If(_, _, _) + | Self::Switch(_, _, _) | Self::While(_, _, _) | Self::Loop(_, _) | Self::For(_, _, _) @@ -726,10 +735,16 @@ impl Stmt { match self { Self::Noop(_) => true, Self::Expr(expr) => expr.is_pure(), - Self::If(condition, x, _) if x.1.is_some() => { - condition.is_pure() && x.0.is_pure() && x.1.as_ref().unwrap().is_pure() + Self::If(condition, x, _) => { + condition.is_pure() + && x.0.is_pure() + && x.1.as_ref().map(Stmt::is_pure).unwrap_or(true) + } + Self::Switch(expr, x, _) => { + expr.is_pure() + && x.0.values().all(Stmt::is_pure) + && x.1.as_ref().map(Stmt::is_pure).unwrap_or(true) } - Self::If(condition, x, _) => condition.is_pure() && x.0.is_pure(), Self::While(condition, block, _) => condition.is_pure() && block.is_pure(), Self::Loop(block, _) => block.is_pure(), Self::For(iterable, x, _) => iterable.is_pure() && x.1.is_pure(), @@ -872,15 +887,6 @@ pub enum Expr { Dot(Box, Position), /// expr[expr] Index(Box, Position), - /// switch expr { literal or _ => stmt, ... } - Switch( - Box<( - Expr, - HashMap, - Option, - )>, - Position, - ), /// lhs in rhs In(Box, Position), /// lhs && rhs @@ -963,7 +969,6 @@ impl Expr { Self::Stmt(_, pos) => *pos, Self::Variable(x) => (x.3).pos, Self::FnCall(_, pos) => *pos, - Self::Switch(_, pos) => *pos, Self::And(x, _) | Self::Or(x, _) | Self::In(x, _) => x.lhs.position(), @@ -995,7 +1000,6 @@ impl Expr { Self::Property(x) => (x.1).pos = new_pos, Self::Stmt(_, pos) => *pos = new_pos, Self::FnCall(_, pos) => *pos = new_pos, - Self::Switch(_, pos) => *pos = new_pos, Self::And(_, pos) | Self::Or(_, pos) | Self::In(_, pos) => *pos = new_pos, Self::True(pos) | Self::False(pos) | Self::Unit(pos) => *pos = new_pos, Self::Dot(_, pos) | Self::Index(_, pos) => *pos = new_pos, @@ -1089,7 +1093,6 @@ impl Expr { Self::StringConstant(_, _) | Self::Stmt(_, _) | Self::FnCall(_, _) - | Self::Switch(_, _) | Self::Dot(_, _) | Self::Index(_, _) | Self::Array(_, _) diff --git a/src/engine.rs b/src/engine.rs index 40383a12..8da32270 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1803,27 +1803,6 @@ impl Engine { Expr::False(_) => Ok(false.into()), Expr::Unit(_) => Ok(().into()), - Expr::Switch(x, _) => { - let (match_expr, table, def_stmt) = x.as_ref(); - - let hasher = &mut get_hasher(); - self.eval_expr_as_target( - scope, mods, state, lib, this_ptr, match_expr, false, level, - )? - .0 - .as_ref() - .hash(hasher); - let hash = hasher.finish(); - - if let Some(stmt) = table.get(&hash) { - self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level) - } else if let Some(def_stmt) = def_stmt { - self.eval_stmt(scope, mods, state, lib, this_ptr, def_stmt, level) - } else { - Ok(().into()) - } - } - Expr::Custom(custom, _) => { let expressions = custom .keywords() @@ -2063,7 +2042,7 @@ impl Engine { self.eval_statements(scope, mods, state, lib, this_ptr, statements, level) } - // If-else statement + // If statement Stmt::If(expr, x, _) => { let (if_block, else_block) = x.as_ref(); self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)? @@ -2080,6 +2059,28 @@ impl Engine { }) } + // Switch statement + Stmt::Switch(match_expr, x, _) => { + let (table, def_stmt) = x.as_ref(); + + let hasher = &mut get_hasher(); + self.eval_expr_as_target( + scope, mods, state, lib, this_ptr, match_expr, false, level, + )? + .0 + .as_ref() + .hash(hasher); + let hash = hasher.finish(); + + if let Some(stmt) = table.get(&hash) { + self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level) + } else if let Some(def_stmt) = def_stmt { + self.eval_stmt(scope, mods, state, lib, this_ptr, def_stmt, level) + } else { + Ok(().into()) + } + } + // While loop Stmt::While(expr, body, _) => loop { match self diff --git a/src/optimize.rs b/src/optimize.rs index a90dc7fd..501e1132 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -291,6 +291,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { optimize_expr(&mut x.2, state); } }, + // if false { if_block } -> Noop Stmt::If(Expr::False(pos), x, _) if x.1.is_none() => { state.set_dirty(); @@ -348,6 +349,42 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { } } + // switch const { ... } + Stmt::Switch(expr, x, pos) if expr.is_constant() => { + let value = expr.get_constant_value().unwrap(); + let hasher = &mut get_hasher(); + value.hash(hasher); + let hash = hasher.finish(); + + state.set_dirty(); + + let table = &mut x.0; + + if let Some(stmt) = table.get_mut(&hash) { + optimize_stmt(stmt, state, true); + *expr = Expr::Stmt(Box::new(vec![mem::take(stmt)].into()), *pos); + } else if let Some(def_stmt) = x.1.as_mut() { + optimize_stmt(def_stmt, state, true); + *expr = Expr::Stmt(Box::new(vec![mem::take(def_stmt)].into()), *pos); + } else { + *expr = Expr::Unit(*pos); + } + } + // switch + Stmt::Switch(expr, x, _) => { + optimize_expr(expr, state); + x.0.values_mut() + .for_each(|stmt| optimize_stmt(stmt, state, true)); + if let Some(def_stmt) = x.1.as_mut() { + optimize_stmt(def_stmt, state, true); + + match def_stmt { + Stmt::Noop(_) | Stmt::Expr(Expr::Unit(_)) => x.1 = None, + _ => (), + } + } + } + // while false { block } -> Noop Stmt::While(Expr::False(pos), _, _) => { state.set_dirty(); @@ -717,42 +754,6 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) { *expr = result; } - // switch const { ... } - Expr::Switch(x, pos) if x.0.is_constant() => { - let value = x.0.get_constant_value().unwrap(); - let hasher = &mut get_hasher(); - value.hash(hasher); - let hash = hasher.finish(); - - state.set_dirty(); - - let table = &mut x.1; - - if let Some(stmt) = table.get_mut(&hash) { - optimize_stmt(stmt, state, true); - *expr = Expr::Stmt(Box::new(vec![mem::take(stmt)].into()), *pos); - } else if let Some(def_stmt) = x.2.as_mut() { - optimize_stmt(def_stmt, state, true); - *expr = Expr::Stmt(Box::new(vec![mem::take(def_stmt)].into()), *pos); - } else { - *expr = Expr::Unit(*pos); - } - } - - // switch - Expr::Switch(x, _) => { - optimize_expr(&mut x.0, state); - x.1.values_mut().for_each(|stmt| optimize_stmt(stmt, state, true)); - if let Some(def_stmt) = x.2.as_mut() { - optimize_stmt(def_stmt, state, true); - - match def_stmt { - Stmt::Noop(_) | Stmt::Expr(Expr::Unit(_)) => x.2 = None, - _ => () - } - } - } - // Custom syntax Expr::Custom(x, _) => x.keywords.iter_mut().for_each(|expr| optimize_expr(expr, state)), diff --git a/src/parser.rs b/src/parser.rs index d37bf0e1..11e79a17 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -192,6 +192,8 @@ struct ParseSettings { allow_anonymous_fn: bool, /// Is if-expression allowed? allow_if_expr: bool, + /// Is switch expression allowed? + allow_switch_expr: bool, /// Is statement-expression allowed? allow_stmt_expr: bool, /// Current expression nesting level. @@ -793,8 +795,12 @@ fn parse_switch( input: &mut TokenStream, state: &mut ParseState, lib: &mut FunctionsLib, - settings: ParseSettings, -) -> Result { + mut settings: ParseSettings, +) -> Result { + // switch ... + let token_pos = eat_token(input, Token::Switch); + settings.pos = token_pos; + #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -901,8 +907,9 @@ fn parse_switch( } } - Ok(Expr::Switch( - Box::new((item, table, def_stmt)), + Ok(Stmt::Switch( + item, + Box::new((table, def_stmt)), settings.pos, )) } @@ -1004,7 +1011,6 @@ fn parse_primary( Token::LeftBracket => parse_array_literal(input, state, lib, settings.level_up())?, #[cfg(not(feature = "no_object"))] Token::MapStart => parse_map_literal(input, state, lib, settings.level_up())?, - Token::Switch => parse_switch(input, state, lib, settings.level_up())?, Token::True => Expr::True(settings.pos), Token::False => Expr::False(settings.pos), Token::LexError(err) => return Err(err.into_err(settings.pos)), @@ -1136,6 +1142,12 @@ fn parse_unary( block.push(parse_if(input, state, lib, settings.level_up())?); Ok(Expr::Stmt(Box::new(block), settings.pos)) } + // Switch statement is allowed to act as expressions + Token::Switch if settings.allow_switch_expr => { + let mut block: StaticVec<_> = Default::default(); + block.push(parse_switch(input, state, lib, settings.level_up())?); + Ok(Expr::Stmt(Box::new(block), settings.pos)) + } // -expr Token::UnaryMinus => { let pos = eat_token(input, Token::UnaryMinus); @@ -1219,6 +1231,7 @@ fn parse_unary( let settings = ParseSettings { allow_if_expr: true, + allow_switch_expr: true, allow_stmt_expr: true, allow_anonymous_fn: true, is_global: false, @@ -2388,6 +2401,7 @@ fn parse_stmt( let settings = ParseSettings { allow_if_expr: true, + allow_switch_expr: true, allow_stmt_expr: true, allow_anonymous_fn: true, is_global: false, @@ -2827,6 +2841,7 @@ impl Engine { let settings = ParseSettings { allow_if_expr: false, + allow_switch_expr: false, allow_stmt_expr: false, allow_anonymous_fn: false, is_global: true, @@ -2879,6 +2894,7 @@ impl Engine { while !input.peek().unwrap().0.is_eof() { let settings = ParseSettings { allow_if_expr: true, + allow_switch_expr: true, allow_stmt_expr: true, allow_anonymous_fn: true, is_global: true, diff --git a/tests/comments.rs b/tests/comments.rs index d49b7586..d7525960 100644 --- a/tests/comments.rs +++ b/tests/comments.rs @@ -1,14 +1,25 @@ -use rhai::{Engine, INT}; +use rhai::{Engine, EvalAltResult, INT}; #[test] -fn test_comments() { +fn test_comments() -> Result<(), Box> { let engine = Engine::new(); - assert!(engine - .eval::("let x = 5; x // I am a single line comment, yay!") - .is_ok()); + assert_eq!( + engine.eval::("let x = 42; x // I am a single line comment, yay!")?, + 42 + ); - assert!(engine - .eval::("let /* I am a multi-line comment, yay! */ x = 5; x") - .is_ok()); + assert_eq!( + engine.eval::( + r#" + let /* I am a + multi-line + comment, yay! + */ x = 42; x + "# + )?, + 42 + ); + + Ok(()) } diff --git a/tests/switch.rs b/tests/switch.rs index 5699da6d..9f2cca35 100644 --- a/tests/switch.rs +++ b/tests/switch.rs @@ -1,4 +1,4 @@ -use rhai::{Dynamic, Engine, EvalAltResult, Scope, INT}; +use rhai::{Engine, EvalAltResult, Scope, INT}; #[test] fn test_switch() -> Result<(), Box> {