Expr::Switch -> Stmt::Switch.

This commit is contained in:
Stephen Chung 2020-11-14 23:43:36 +08:00
parent 0182117759
commit a63f14b59c
6 changed files with 119 additions and 87 deletions

View File

@ -583,6 +583,12 @@ pub enum Stmt {
Noop(Position),
/// if expr { stmt } else { stmt }
If(Expr, Box<(Stmt, Option<Stmt>)>, Position),
/// switch expr { literal or _ => stmt, ... }
Switch(
Expr,
Box<(HashMap<u64, Stmt, StraightHasherBuilder>, Option<Stmt>)>,
Position,
),
/// while expr { stmt }
While(Expr, Box<Stmt>, 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<BinaryExpr>, Position),
/// expr[expr]
Index(Box<BinaryExpr>, Position),
/// switch expr { literal or _ => stmt, ... }
Switch(
Box<(
Expr,
HashMap<u64, Stmt, StraightHasherBuilder>,
Option<Stmt>,
)>,
Position,
),
/// lhs in rhs
In(Box<BinaryExpr>, 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(_, _)

View File

@ -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

View File

@ -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)),

View File

@ -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<Expr, ParseError> {
mut settings: ParseSettings,
) -> Result<Stmt, ParseError> {
// 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,

View File

@ -1,14 +1,25 @@
use rhai::{Engine, INT};
use rhai::{Engine, EvalAltResult, INT};
#[test]
fn test_comments() {
fn test_comments() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
assert!(engine
.eval::<INT>("let x = 5; x // I am a single line comment, yay!")
.is_ok());
assert_eq!(
engine.eval::<INT>("let x = 42; x // I am a single line comment, yay!")?,
42
);
assert!(engine
.eval::<INT>("let /* I am a multi-line comment, yay! */ x = 5; x")
.is_ok());
assert_eq!(
engine.eval::<INT>(
r#"
let /* I am a
multi-line
comment, yay!
*/ x = 42; x
"#
)?,
42
);
Ok(())
}

View File

@ -1,4 +1,4 @@
use rhai::{Dynamic, Engine, EvalAltResult, Scope, INT};
use rhai::{Engine, EvalAltResult, Scope, INT};
#[test]
fn test_switch() -> Result<(), Box<EvalAltResult>> {