Merge pull request #582 from schungx/master
Allow duplicated switch cases.
This commit is contained in:
commit
4f26c1465c
11
CHANGELOG.md
11
CHANGELOG.md
@ -4,6 +4,8 @@ Rhai Release Notes
|
||||
Version 1.9.0
|
||||
=============
|
||||
|
||||
The minimum Rust version is now `1.60.0` in order to use the `dep:` syntax for dependencies.
|
||||
|
||||
New features
|
||||
------------
|
||||
|
||||
@ -12,8 +14,17 @@ New features
|
||||
Enhancements
|
||||
------------
|
||||
|
||||
### `switch` statement
|
||||
|
||||
* `switch` cases can now include multiple values separated by `|`.
|
||||
* Duplicated `switch` cases are now allowed.
|
||||
* The error `ParseErrorType::DuplicatedSwitchCase` is deprecated.
|
||||
* Ranges in `switch` statements that are small (currently no more than 16 items) are unrolled if possible.
|
||||
|
||||
### Others
|
||||
|
||||
* `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.
|
||||
* A new `range` function variant that takes an exclusive range with a step.
|
||||
|
||||
|
||||
Version 1.8.0
|
||||
|
@ -4,7 +4,7 @@ members = [".", "codegen"]
|
||||
[package]
|
||||
name = "rhai"
|
||||
version = "1.8.0"
|
||||
rust-version = "1.60"
|
||||
rust-version = "1.60.0"
|
||||
edition = "2018"
|
||||
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"]
|
||||
description = "Embedded scripting for Rust"
|
||||
@ -12,7 +12,7 @@ homepage = "https://rhai.rs"
|
||||
repository = "https://github.com/rhaiscript"
|
||||
readme = "README.md"
|
||||
license = "MIT OR Apache-2.0"
|
||||
include = ["**/*.rs", "scripts/*.rhai", "**/*.md", "Cargo.toml"]
|
||||
include = ["/src/**/*.rs", "/Cargo.toml", "/README.md"]
|
||||
keywords = ["scripting", "scripting-engine", "scripting-language", "embedded"]
|
||||
categories = ["no-std", "embedded", "wasm", "parser-implementations"]
|
||||
|
||||
@ -31,7 +31,7 @@ serde = { version = "1.0", default-features = false, features = ["derive", "allo
|
||||
serde_json = { version = "1.0", default-features = false, features = ["alloc"], optional = true }
|
||||
unicode-xid = { version = "0.2", default-features = false, optional = true }
|
||||
rust_decimal = { version = "1.16", default-features = false, features = ["maths"], optional = true }
|
||||
rustyline = { version = "9", optional = true }
|
||||
rustyline = { version = "10", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
serde_bytes = "0.11"
|
||||
@ -96,4 +96,4 @@ features = ["metadata", "serde", "internals", "decimal", "debugging"]
|
||||
[patch.crates-io]
|
||||
# Notice that a custom modified version of `rustyline` is used which supports bracketed paste on Windows.
|
||||
# This can be moved to the official version when bracketed paste is added.
|
||||
rustyline = { git = "https://github.com/schungx/rustyline" }
|
||||
rustyline = { git = "https://github.com/schungx/rustyline", branch = "v10" }
|
||||
|
@ -11,7 +11,6 @@ Rhai - Embedded Scripting for Rust
|
||||
[![VS Code plugin installs](https://img.shields.io/visual-studio-marketplace/i/rhaiscript.vscode-rhai?logo=visual-studio-code&label=vs%20code)](https://marketplace.visualstudio.com/items?itemName=rhaiscript.vscode-rhai)
|
||||
[![Sublime Text package downloads](https://img.shields.io/packagecontrol/dt/Rhai.svg?logo=sublime-text&label=sublime%20text)](https://packagecontrol.io/packages/Rhai)
|
||||
[![Discord Chat](https://img.shields.io/discord/767611025456889857.svg?logo=discord&label=discord)](https://discord.gg/HquqbYFcZ9)
|
||||
[![Forum](https://img.shields.io/discourse/topics?server=https%3A%2F%2Frhai.discourse.group&logo=discourse&label=forum)](https://rhai.discourse.group/)
|
||||
[![Zulip Chat](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg?logo=zulip)](https://rhaiscript.zulipchat.com)
|
||||
[![Reddit Channel](https://img.shields.io/reddit/subreddit-subscribers/Rhai?logo=reddit&label=reddit)](https://www.reddit.com/r/Rhai)
|
||||
|
||||
@ -27,7 +26,7 @@ Targets and builds
|
||||
* All CPU and O/S targets supported by Rust, including:
|
||||
* WebAssembly (WASM)
|
||||
* `no-std`
|
||||
* Minimum Rust version 1.60
|
||||
* Minimum Rust version 1.60.0
|
||||
|
||||
|
||||
Standard features
|
||||
|
@ -22,8 +22,8 @@ pub use script_fn::EncapsulatedEnviron;
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub use script_fn::{ScriptFnDef, ScriptFnMetadata};
|
||||
pub use stmt::{
|
||||
ConditionalStmtBlock, OpAssignment, RangeCase, Stmt, StmtBlock, StmtBlockContainer,
|
||||
SwitchCases, TryCatchBlock,
|
||||
CaseBlocksList, ConditionalStmtBlock, OpAssignment, RangeCase, Stmt, StmtBlock,
|
||||
StmtBlockContainer, SwitchCasesCollection, TryCatchBlock,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
|
@ -175,17 +175,28 @@ impl fmt::Debug for RangeCase {
|
||||
|
||||
impl From<Range<INT>> for RangeCase {
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
fn from(value: Range<INT>) -> Self {
|
||||
Self::ExclusiveInt(value, 0)
|
||||
Self::ExclusiveInt(value, usize::MAX)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RangeInclusive<INT>> for RangeCase {
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
fn from(value: RangeInclusive<INT>) -> Self {
|
||||
Self::InclusiveInt(value, 0)
|
||||
Self::InclusiveInt(value, usize::MAX)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for RangeCase {
|
||||
type Item = INT;
|
||||
type IntoIter = Box<dyn Iterator<Item = Self::Item>>;
|
||||
|
||||
#[inline(always)]
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
match self {
|
||||
Self::ExclusiveInt(r, ..) => Box::new(r.into_iter()),
|
||||
Self::InclusiveInt(r, ..) => Box::new(r.into_iter()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -199,6 +210,17 @@ impl RangeCase {
|
||||
Self::InclusiveInt(r, ..) => r.is_empty(),
|
||||
}
|
||||
}
|
||||
/// Size of the range.
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn len(&self) -> usize {
|
||||
match self {
|
||||
Self::ExclusiveInt(r, ..) if r.is_empty() => 0,
|
||||
Self::ExclusiveInt(r, ..) => (r.end - r.start) as usize,
|
||||
Self::InclusiveInt(r, ..) if r.is_empty() => 0,
|
||||
Self::InclusiveInt(r, ..) => (*r.end() - *r.start()) as usize,
|
||||
}
|
||||
}
|
||||
/// Is the specified number within this range?
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
@ -208,19 +230,6 @@ impl RangeCase {
|
||||
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<INT> {
|
||||
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]
|
||||
@ -247,14 +256,16 @@ impl RangeCase {
|
||||
}
|
||||
}
|
||||
|
||||
pub type CaseBlocksList = smallvec::SmallVec<[usize; 1]>;
|
||||
|
||||
/// _(internals)_ A type containing all cases for a `switch` statement.
|
||||
/// Exported under the `internals` feature only.
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub struct SwitchCases {
|
||||
pub struct SwitchCasesCollection {
|
||||
/// List of [`ConditionalStmtBlock`]'s.
|
||||
pub blocks: StaticVec<ConditionalStmtBlock>,
|
||||
pub case_blocks: StaticVec<ConditionalStmtBlock>,
|
||||
/// Dictionary mapping value hashes to [`ConditionalStmtBlock`]'s.
|
||||
pub cases: BTreeMap<u64, usize>,
|
||||
pub cases: BTreeMap<u64, CaseBlocksList>,
|
||||
/// Statements block for the default case (there can be no condition for the default case).
|
||||
pub def_case: usize,
|
||||
/// List of range cases.
|
||||
@ -503,7 +514,7 @@ pub enum Stmt {
|
||||
/// 0) Hash table for (condition, block)
|
||||
/// 1) Default block
|
||||
/// 2) List of ranges: (start, end, inclusive, condition, statement)
|
||||
Switch(Box<(Expr, SwitchCases)>, Position),
|
||||
Switch(Box<(Expr, SwitchCasesCollection)>, Position),
|
||||
/// `while` expr `{` stmt `}` | `loop` `{` stmt `}`
|
||||
///
|
||||
/// If the guard expression is [`UNIT`][Expr::Unit], then it is a `loop` statement.
|
||||
@ -746,15 +757,18 @@ impl Stmt {
|
||||
Self::Switch(x, ..) => {
|
||||
let (expr, sw) = &**x;
|
||||
expr.is_pure()
|
||||
&& sw.cases.values().all(|&c| {
|
||||
let block = &sw.blocks[c];
|
||||
&& sw.cases.values().flat_map(|cases| cases.iter()).all(|&c| {
|
||||
let block = &sw.case_blocks[c];
|
||||
block.condition.is_pure() && block.statements.iter().all(Stmt::is_pure)
|
||||
})
|
||||
&& sw.ranges.iter().all(|r| {
|
||||
let block = &sw.blocks[r.index()];
|
||||
let block = &sw.case_blocks[r.index()];
|
||||
block.condition.is_pure() && block.statements.iter().all(Stmt::is_pure)
|
||||
})
|
||||
&& sw.blocks[sw.def_case].statements.iter().all(Stmt::is_pure)
|
||||
&& sw.case_blocks[sw.def_case]
|
||||
.statements
|
||||
.iter()
|
||||
.all(Stmt::is_pure)
|
||||
}
|
||||
|
||||
// Loops that exit can be pure because it can never be infinite.
|
||||
@ -895,20 +909,22 @@ impl Stmt {
|
||||
if !expr.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
for (.., &b) in &sw.cases {
|
||||
let block = &sw.blocks[b];
|
||||
for (.., blocks) in &sw.cases {
|
||||
for &b in blocks {
|
||||
let block = &sw.case_blocks[b];
|
||||
|
||||
if !block.condition.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
for s in &block.statements {
|
||||
if !s.walk(path, on_node) {
|
||||
if !block.condition.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
for s in &block.statements {
|
||||
if !s.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for r in &sw.ranges {
|
||||
let block = &sw.blocks[r.index()];
|
||||
let block = &sw.case_blocks[r.index()];
|
||||
|
||||
if !block.condition.walk(path, on_node) {
|
||||
return false;
|
||||
@ -919,7 +935,7 @@ impl Stmt {
|
||||
}
|
||||
}
|
||||
}
|
||||
for s in &sw.blocks[sw.def_case].statements {
|
||||
for s in &sw.case_blocks[sw.def_case].statements {
|
||||
if !s.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
|
@ -194,7 +194,7 @@ fn setup_editor() -> Editor<()> {
|
||||
.indent_size(4)
|
||||
.bracketed_paste(true)
|
||||
.build();
|
||||
let mut rl = Editor::<()>::with_config(config);
|
||||
let mut rl = Editor::<()>::with_config(config).unwrap();
|
||||
|
||||
// Bind more keys
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
use super::{Caches, EvalContext, GlobalRuntimeState, Target};
|
||||
use crate::api::events::VarDefInfo;
|
||||
use crate::ast::{
|
||||
ASTFlags, BinaryExpr, Expr, Ident, OpAssignment, Stmt, SwitchCases, TryCatchBlock,
|
||||
ASTFlags, BinaryExpr, Expr, Ident, OpAssignment, Stmt, SwitchCasesCollection, TryCatchBlock,
|
||||
};
|
||||
use crate::func::get_hasher;
|
||||
use crate::types::dynamic::{AccessMode, Union};
|
||||
@ -393,8 +393,8 @@ impl Engine {
|
||||
Stmt::Switch(x, ..) => {
|
||||
let (
|
||||
expr,
|
||||
SwitchCases {
|
||||
blocks,
|
||||
SwitchCasesCollection {
|
||||
case_blocks,
|
||||
cases,
|
||||
def_case,
|
||||
ranges,
|
||||
@ -411,32 +411,49 @@ impl Engine {
|
||||
let hash = hasher.finish();
|
||||
|
||||
// First check hashes
|
||||
if let Some(&case_block) = cases.get(&hash) {
|
||||
let case_block = &blocks[case_block];
|
||||
if let Some(case_blocks_list) = cases.get(&hash) {
|
||||
assert!(!case_blocks_list.is_empty());
|
||||
|
||||
let cond_result = match case_block.condition {
|
||||
Expr::BoolConstant(b, ..) => Ok(b),
|
||||
ref c => self
|
||||
.eval_expr(scope, global, caches, lib, this_ptr, c, level)
|
||||
.and_then(|v| {
|
||||
v.as_bool().map_err(|typ| {
|
||||
self.make_type_mismatch_err::<bool>(typ, c.position())
|
||||
})
|
||||
}),
|
||||
};
|
||||
let mut result = Ok(None);
|
||||
|
||||
match cond_result {
|
||||
Ok(true) => Ok(Some(&case_block.statements)),
|
||||
Ok(false) => Ok(None),
|
||||
_ => cond_result.map(|_| None),
|
||||
for &index in case_blocks_list {
|
||||
let block = &case_blocks[index];
|
||||
|
||||
let cond_result = match block.condition {
|
||||
Expr::BoolConstant(b, ..) => Ok(b),
|
||||
ref c => self
|
||||
.eval_expr(scope, global, caches, lib, this_ptr, c, level)
|
||||
.and_then(|v| {
|
||||
v.as_bool().map_err(|typ| {
|
||||
self.make_type_mismatch_err::<bool>(
|
||||
typ,
|
||||
c.position(),
|
||||
)
|
||||
})
|
||||
}),
|
||||
};
|
||||
|
||||
match cond_result {
|
||||
Ok(true) => {
|
||||
result = Ok(Some(&block.statements));
|
||||
break;
|
||||
}
|
||||
Ok(false) => (),
|
||||
_ => {
|
||||
result = cond_result.map(|_| None);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
} else if value.is::<INT>() && !ranges.is_empty() {
|
||||
// Then check integer ranges
|
||||
let value = value.as_int().expect("`INT`");
|
||||
let mut result = Ok(None);
|
||||
|
||||
for r in ranges.iter().filter(|r| r.contains(value)) {
|
||||
let block = &blocks[r.index()];
|
||||
let block = &case_blocks[r.index()];
|
||||
|
||||
let cond_result = match block.condition {
|
||||
Expr::BoolConstant(b, ..) => Ok(b),
|
||||
@ -481,7 +498,7 @@ impl Engine {
|
||||
}
|
||||
} else if let Ok(None) = stmt_block_result {
|
||||
// Default match clause
|
||||
let def_case = &blocks[*def_case].statements;
|
||||
let def_case = &case_blocks[*def_case].statements;
|
||||
|
||||
if !def_case.is_empty() {
|
||||
self.eval_stmt_block(
|
||||
|
@ -285,7 +285,7 @@ pub use parser::ParseState;
|
||||
#[cfg(feature = "internals")]
|
||||
pub use ast::{
|
||||
ASTFlags, ASTNode, BinaryExpr, ConditionalStmtBlock, Expr, FnCallExpr, FnCallHashes, Ident,
|
||||
OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlock, SwitchCases, TryCatchBlock,
|
||||
OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlock, SwitchCasesCollection, TryCatchBlock,
|
||||
};
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
|
185
src/optimizer.rs
185
src/optimizer.rs
@ -1,7 +1,9 @@
|
||||
//! Module implementing the [`AST`] optimizer.
|
||||
#![cfg(not(feature = "no_optimize"))]
|
||||
|
||||
use crate::ast::{ASTFlags, Expr, OpAssignment, Stmt, StmtBlock, StmtBlockContainer, SwitchCases};
|
||||
use crate::ast::{
|
||||
ASTFlags, Expr, OpAssignment, Stmt, StmtBlock, StmtBlockContainer, SwitchCasesCollection,
|
||||
};
|
||||
use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF};
|
||||
use crate::eval::{Caches, GlobalRuntimeState};
|
||||
use crate::func::builtin::get_builtin_binary_op_fn;
|
||||
@ -524,8 +526,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
Stmt::Switch(x, pos) if x.0.is_constant() => {
|
||||
let (
|
||||
match_expr,
|
||||
SwitchCases {
|
||||
blocks: blocks_list,
|
||||
SwitchCasesCollection {
|
||||
case_blocks,
|
||||
cases,
|
||||
ranges,
|
||||
def_case,
|
||||
@ -538,39 +540,65 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
let hash = hasher.finish();
|
||||
|
||||
// First check hashes
|
||||
if let Some(block) = cases.remove(&hash) {
|
||||
let mut block = mem::take(&mut blocks_list[block]);
|
||||
cases.clear();
|
||||
if let Some(case_blocks_list) = cases.get(&hash) {
|
||||
match &case_blocks_list[..] {
|
||||
[] => (),
|
||||
[index] => {
|
||||
let mut b = mem::take(&mut case_blocks[*index]);
|
||||
cases.clear();
|
||||
|
||||
match block.condition {
|
||||
Expr::BoolConstant(true, ..) => {
|
||||
// Promote the matched case
|
||||
let statements: StmtBlockContainer = mem::take(&mut block.statements);
|
||||
let statements = optimize_stmt_block(statements, state, true, true, false);
|
||||
*stmt = (statements, block.statements.span()).into();
|
||||
match b.condition {
|
||||
Expr::BoolConstant(true, ..) => {
|
||||
// Promote the matched case
|
||||
let statements: StmtBlockContainer = mem::take(&mut b.statements);
|
||||
let statements =
|
||||
optimize_stmt_block(statements, state, true, true, false);
|
||||
*stmt = (statements, b.statements.span()).into();
|
||||
}
|
||||
ref mut condition => {
|
||||
// switch const { case if condition => stmt, _ => def } => if condition { stmt } else { def }
|
||||
optimize_expr(condition, state, false);
|
||||
|
||||
let def_case = &mut case_blocks[*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(
|
||||
(
|
||||
mem::take(condition),
|
||||
mem::take(&mut b.statements),
|
||||
StmtBlock::new_with_span(def_stmt, def_span),
|
||||
)
|
||||
.into(),
|
||||
match_expr.start_position(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
state.set_dirty();
|
||||
return;
|
||||
}
|
||||
ref mut condition => {
|
||||
// switch const { case if condition => stmt, _ => def } => if condition { stmt } else { def }
|
||||
optimize_expr(condition, state, false);
|
||||
_ => {
|
||||
for &index in case_blocks_list {
|
||||
let mut b = mem::take(&mut case_blocks[index]);
|
||||
|
||||
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(
|
||||
(
|
||||
mem::take(condition),
|
||||
mem::take(&mut block.statements),
|
||||
StmtBlock::new_with_span(def_stmt, def_span),
|
||||
)
|
||||
.into(),
|
||||
match_expr.start_position(),
|
||||
);
|
||||
match b.condition {
|
||||
Expr::BoolConstant(true, ..) => {
|
||||
// Promote the matched case
|
||||
let statements: StmtBlockContainer =
|
||||
mem::take(&mut b.statements);
|
||||
let statements =
|
||||
optimize_stmt_block(statements, state, true, true, false);
|
||||
*stmt = (statements, b.statements.span()).into();
|
||||
state.set_dirty();
|
||||
return;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.set_dirty();
|
||||
return;
|
||||
}
|
||||
|
||||
// Then check ranges
|
||||
@ -581,18 +609,18 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
if ranges.len() == 1
|
||||
|| ranges.iter().all(|r| {
|
||||
matches!(
|
||||
blocks_list[r.index()].condition,
|
||||
case_blocks[r.index()].condition,
|
||||
Expr::BoolConstant(true, ..)
|
||||
)
|
||||
})
|
||||
{
|
||||
for r in ranges.iter().filter(|r| r.contains(value)) {
|
||||
let condition = mem::take(&mut blocks_list[r.index()].condition);
|
||||
let condition = mem::take(&mut case_blocks[r.index()].condition);
|
||||
|
||||
match condition {
|
||||
Expr::BoolConstant(true, ..) => {
|
||||
// Promote the matched case
|
||||
let block = &mut blocks_list[r.index()];
|
||||
let block = &mut case_blocks[r.index()];
|
||||
let statements = mem::take(&mut *block.statements);
|
||||
let statements =
|
||||
optimize_stmt_block(statements, state, true, true, false);
|
||||
@ -602,13 +630,13 @@ 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_case = &mut blocks_list[*def_case].statements;
|
||||
let def_case = &mut case_blocks[*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);
|
||||
let statements = mem::take(&mut case_blocks[r.index()].statements);
|
||||
|
||||
*stmt = Stmt::If(
|
||||
(
|
||||
@ -641,16 +669,16 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
}
|
||||
|
||||
for r in &*ranges {
|
||||
let block = &mut blocks_list[r.index()];
|
||||
let statements = mem::take(&mut *block.statements);
|
||||
*block.statements =
|
||||
let b = &mut case_blocks[r.index()];
|
||||
let statements = mem::take(&mut *b.statements);
|
||||
*b.statements =
|
||||
optimize_stmt_block(statements, state, preserve_result, true, false);
|
||||
|
||||
optimize_expr(&mut block.condition, state, false);
|
||||
optimize_expr(&mut b.condition, state, false);
|
||||
|
||||
match block.condition {
|
||||
match b.condition {
|
||||
Expr::Unit(pos) => {
|
||||
block.condition = Expr::BoolConstant(true, pos);
|
||||
b.condition = Expr::BoolConstant(true, pos);
|
||||
state.set_dirty()
|
||||
}
|
||||
_ => (),
|
||||
@ -662,7 +690,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
|
||||
// Promote the default case
|
||||
state.set_dirty();
|
||||
let def_case = &mut blocks_list[*def_case].statements;
|
||||
let def_case = &mut case_blocks[*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);
|
||||
@ -672,8 +700,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
Stmt::Switch(x, ..) => {
|
||||
let (
|
||||
match_expr,
|
||||
SwitchCases {
|
||||
blocks: blocks_list,
|
||||
SwitchCasesCollection {
|
||||
case_blocks,
|
||||
cases,
|
||||
ranges,
|
||||
def_case,
|
||||
@ -684,21 +712,21 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
optimize_expr(match_expr, state, false);
|
||||
|
||||
// Optimize blocks
|
||||
for block in blocks_list.iter_mut() {
|
||||
let statements = mem::take(&mut *block.statements);
|
||||
*block.statements =
|
||||
for b in case_blocks.iter_mut() {
|
||||
let statements = mem::take(&mut *b.statements);
|
||||
*b.statements =
|
||||
optimize_stmt_block(statements, state, preserve_result, true, false);
|
||||
|
||||
optimize_expr(&mut block.condition, state, false);
|
||||
optimize_expr(&mut b.condition, state, false);
|
||||
|
||||
match block.condition {
|
||||
match b.condition {
|
||||
Expr::Unit(pos) => {
|
||||
block.condition = Expr::BoolConstant(true, pos);
|
||||
b.condition = Expr::BoolConstant(true, pos);
|
||||
state.set_dirty();
|
||||
}
|
||||
Expr::BoolConstant(false, ..) => {
|
||||
if !block.statements.is_empty() {
|
||||
block.statements = StmtBlock::NONE;
|
||||
if !b.statements.is_empty() {
|
||||
b.statements = StmtBlock::NONE;
|
||||
state.set_dirty();
|
||||
}
|
||||
}
|
||||
@ -707,15 +735,31 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
}
|
||||
|
||||
// Remove false cases
|
||||
cases.retain(|_, &mut block| match blocks_list[block].condition {
|
||||
Expr::BoolConstant(false, ..) => {
|
||||
state.set_dirty();
|
||||
false
|
||||
let cases_len = cases.len();
|
||||
cases.retain(|_, list| {
|
||||
// Remove all entries that have false conditions
|
||||
list.retain(|index| match case_blocks[*index].condition {
|
||||
Expr::BoolConstant(false, ..) => false,
|
||||
_ => true,
|
||||
});
|
||||
// Remove all entries after a `true` condition
|
||||
if let Some(n) = list
|
||||
.iter()
|
||||
.find(|&&index| match case_blocks[index].condition {
|
||||
Expr::BoolConstant(true, ..) => true,
|
||||
_ => false,
|
||||
})
|
||||
{
|
||||
list.truncate(n + 1);
|
||||
}
|
||||
_ => true,
|
||||
// Remove if no entry left
|
||||
!list.is_empty()
|
||||
});
|
||||
if cases.len() != cases_len {
|
||||
state.set_dirty();
|
||||
}
|
||||
// Remove false ranges
|
||||
ranges.retain(|r| match blocks_list[r.index()].condition {
|
||||
ranges.retain(|r| match case_blocks[r.index()].condition {
|
||||
Expr::BoolConstant(false, ..) => {
|
||||
state.set_dirty();
|
||||
false
|
||||
@ -723,9 +767,26 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
_ => true,
|
||||
});
|
||||
|
||||
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);
|
||||
let def_stmt_block = &mut case_blocks[*def_case].statements;
|
||||
let def_block = mem::take(&mut **def_stmt_block);
|
||||
**def_stmt_block = optimize_stmt_block(def_block, state, preserve_result, true, false);
|
||||
|
||||
// Remove unused block statements
|
||||
for index in 0..case_blocks.len() {
|
||||
if *def_case == index
|
||||
|| cases.values().flat_map(|c| c.iter()).any(|&n| n == index)
|
||||
|| ranges.iter().any(|r| r.index() == index)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let b = &mut case_blocks[index];
|
||||
|
||||
if !b.statements.is_empty() {
|
||||
b.statements = StmtBlock::NONE;
|
||||
state.set_dirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// while false { block } -> Noop
|
||||
|
@ -287,7 +287,7 @@ macro_rules! reg_range {
|
||||
"/// Return an iterator over the exclusive range of `from..to`, each iteration increasing by `step`.",
|
||||
"/// The value `to` is never included.",
|
||||
"///",
|
||||
"/// If `from` > `to` and `step` < 0, the iteration goes backwards.",
|
||||
"/// If `from` > `to` and `step` < 0, iteration goes backwards.",
|
||||
"///",
|
||||
"/// If `from` > `to` and `step` > 0 or `from` < `to` and `step` < 0, an empty iterator is returned.",
|
||||
"///",
|
||||
@ -305,6 +305,35 @@ macro_rules! reg_range {
|
||||
"/// }",
|
||||
"/// ```"
|
||||
]);
|
||||
|
||||
let _hash = $lib.set_native_fn($x, |range: std::ops::Range<$y>, step: $y| StepRange::new(range.start, range.end, step, $add));
|
||||
|
||||
#[cfg(feature = "metadata")]
|
||||
$lib.update_fn_metadata_with_comments(_hash, [
|
||||
concat!("range: Range<", stringify!($y), ">"),
|
||||
concat!("step: ", stringify!($y)),
|
||||
concat!("Iterator<Item=", stringify!($y), ">")
|
||||
], [
|
||||
"/// Return an iterator over an exclusive range, each iteration increasing by `step`.",
|
||||
"///",
|
||||
"/// If `range` is reversed and `step` < 0, iteration goes backwards.",
|
||||
"///",
|
||||
"/// Otherwise, if `range` is empty, an empty iterator is returned.",
|
||||
"///",
|
||||
"/// # Example",
|
||||
"///",
|
||||
"/// ```rhai",
|
||||
"/// // prints all values from 8 to 17 in steps of 3",
|
||||
"/// for n in range(8..18, 3) {",
|
||||
"/// print(n);",
|
||||
"/// }",
|
||||
"///",
|
||||
"/// // prints all values down from 18 to 9 in steps of -3",
|
||||
"/// for n in range(18..8, -3) {",
|
||||
"/// print(n);",
|
||||
"/// }",
|
||||
"/// ```"
|
||||
]);
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
@ -3,8 +3,9 @@
|
||||
use crate::api::events::VarDefInfo;
|
||||
use crate::api::options::LangOptions;
|
||||
use crate::ast::{
|
||||
ASTFlags, BinaryExpr, ConditionalStmtBlock, Expr, FnCallExpr, FnCallHashes, Ident,
|
||||
OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlockContainer, SwitchCases, TryCatchBlock,
|
||||
ASTFlags, BinaryExpr, CaseBlocksList, ConditionalStmtBlock, Expr, FnCallExpr, FnCallHashes,
|
||||
Ident, OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlockContainer, SwitchCasesCollection,
|
||||
TryCatchBlock,
|
||||
};
|
||||
use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS};
|
||||
use crate::eval::GlobalRuntimeState;
|
||||
@ -39,6 +40,9 @@ const SCOPE_SEARCH_BARRIER_MARKER: &str = "$ BARRIER $";
|
||||
/// The message: `TokenStream` never ends
|
||||
const NEVER_ENDS: &str = "`Token`";
|
||||
|
||||
/// Unroll `switch` ranges no larger than this.
|
||||
const SMALL_SWITCH_RANGE: usize = 16;
|
||||
|
||||
/// _(internals)_ A type that encapsulates the current state of the parser.
|
||||
/// Exported under the `internals` feature only.
|
||||
pub struct ParseState<'e> {
|
||||
@ -1051,11 +1055,11 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
let mut blocks = StaticVec::<ConditionalStmtBlock>::new();
|
||||
let mut cases = BTreeMap::<u64, usize>::new();
|
||||
let mut case_blocks = StaticVec::<ConditionalStmtBlock>::new();
|
||||
let mut cases = BTreeMap::<u64, CaseBlocksList>::new();
|
||||
let mut ranges = StaticVec::<RangeCase>::new();
|
||||
let mut def_pos = Position::NONE;
|
||||
let mut def_stmt = None;
|
||||
let mut def_stmt_pos = Position::NONE;
|
||||
let mut def_stmt_index = None;
|
||||
|
||||
loop {
|
||||
const MISSING_RBRACE: &str = "to end this switch block";
|
||||
@ -1071,8 +1075,8 @@ impl Engine {
|
||||
.into_err(*pos),
|
||||
)
|
||||
}
|
||||
(Token::Underscore, pos) if def_stmt.is_none() => {
|
||||
def_pos = *pos;
|
||||
(Token::Underscore, pos) if def_stmt_index.is_none() => {
|
||||
def_stmt_pos = *pos;
|
||||
eat_token(input, Token::Underscore);
|
||||
|
||||
let (if_clause, if_pos) = match_token(input, Token::If);
|
||||
@ -1083,10 +1087,8 @@ impl Engine {
|
||||
|
||||
(Default::default(), Expr::BoolConstant(true, Position::NONE))
|
||||
}
|
||||
(Token::Underscore, pos) => return Err(PERR::DuplicatedSwitchCase.into_err(*pos)),
|
||||
|
||||
_ if def_stmt.is_some() => {
|
||||
return Err(PERR::WrongSwitchDefaultCase.into_err(def_pos))
|
||||
_ if def_stmt_index.is_some() => {
|
||||
return Err(PERR::WrongSwitchDefaultCase.into_err(def_stmt_pos))
|
||||
}
|
||||
|
||||
_ => {
|
||||
@ -1138,9 +1140,10 @@ impl Engine {
|
||||
|
||||
let stmt = self.parse_stmt(input, state, lib, settings.level_up())?;
|
||||
let need_comma = !stmt.is_self_terminated();
|
||||
let has_condition = !matches!(condition, Expr::BoolConstant(true, ..));
|
||||
|
||||
blocks.push((condition, stmt).into());
|
||||
let index = blocks.len() - 1;
|
||||
case_blocks.push((condition, stmt).into());
|
||||
let index = case_blocks.len() - 1;
|
||||
|
||||
if !case_expr_list.is_empty() {
|
||||
for expr in case_expr_list {
|
||||
@ -1159,14 +1162,18 @@ impl Engine {
|
||||
|
||||
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(index);
|
||||
// Do not unroll ranges if there are previous non-unrolled ranges
|
||||
if !has_condition && ranges.is_empty() && r.len() <= SMALL_SWITCH_RANGE
|
||||
{
|
||||
// Unroll small range
|
||||
for n in r {
|
||||
let hasher = &mut get_hasher();
|
||||
Dynamic::from_int(n).hash(hasher);
|
||||
cases
|
||||
.entry(hasher.finish())
|
||||
.and_modify(|cases| cases.push(index))
|
||||
.or_insert_with(|| [index].into());
|
||||
}
|
||||
} else {
|
||||
// Other range
|
||||
r.set_index(index);
|
||||
@ -1184,13 +1191,13 @@ impl Engine {
|
||||
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);
|
||||
cases
|
||||
.entry(hash)
|
||||
.and_modify(|cases| cases.push(index))
|
||||
.or_insert_with(|| [index].into());
|
||||
}
|
||||
} else {
|
||||
def_stmt = Some(index);
|
||||
def_stmt_index = Some(index);
|
||||
}
|
||||
|
||||
match input.peek().expect(NEVER_ENDS) {
|
||||
@ -1216,13 +1223,13 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
let def_case = def_stmt.unwrap_or_else(|| {
|
||||
blocks.push(Default::default());
|
||||
blocks.len() - 1
|
||||
let def_case = def_stmt_index.unwrap_or_else(|| {
|
||||
case_blocks.push(Default::default());
|
||||
case_blocks.len() - 1
|
||||
});
|
||||
|
||||
let cases = SwitchCases {
|
||||
blocks,
|
||||
let cases = SwitchCasesCollection {
|
||||
case_blocks,
|
||||
cases,
|
||||
def_case,
|
||||
ranges,
|
||||
|
@ -97,6 +97,14 @@ pub enum ParseErrorType {
|
||||
/// A map definition has duplicated property names. Wrapped value is the property name.
|
||||
DuplicatedProperty(String),
|
||||
/// A `switch` case is duplicated.
|
||||
///
|
||||
/// # Deprecated
|
||||
///
|
||||
/// This error variant is deprecated. It never occurs and will be removed in the next major version.
|
||||
#[deprecated(
|
||||
since = "1.9.0",
|
||||
note = "This error variant is deprecated. It never occurs and will be removed in the next major version."
|
||||
)]
|
||||
DuplicatedSwitchCase,
|
||||
/// A variable name is duplicated. Wrapped value is the variable name.
|
||||
DuplicatedVariable(String),
|
||||
@ -211,6 +219,7 @@ impl fmt::Display for ParseErrorType {
|
||||
Self::FnDuplicatedParam(s, arg) => write!(f, "Duplicated parameter {} for function {}", arg, s),
|
||||
|
||||
Self::DuplicatedProperty(s) => write!(f, "Duplicated property for object map literal: {}", s),
|
||||
#[allow(deprecated)]
|
||||
Self::DuplicatedSwitchCase => f.write_str("Duplicated switch case"),
|
||||
Self::DuplicatedVariable(s) => write!(f, "Duplicated variable name: {}", s),
|
||||
|
||||
|
@ -32,7 +32,6 @@ fn test_switch() -> Result<(), Box<EvalAltResult>> {
|
||||
)?,
|
||||
'a'
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval_with_scope::<bool>(&mut scope, "switch x { 1 => (), 2 => 'a', 42 => true }")?,
|
||||
true
|
||||
@ -98,6 +97,16 @@ fn test_switch() -> Result<(), Box<EvalAltResult>> {
|
||||
3
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval_with_scope::<INT>(&mut scope, "switch 42 { 42 => 123, 42 => 999 }")?,
|
||||
123
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval_with_scope::<INT>(&mut scope, "switch x { 42 => 123, 42 => 999 }")?,
|
||||
123
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -105,13 +114,6 @@ fn test_switch() -> Result<(), Box<EvalAltResult>> {
|
||||
fn test_switch_errors() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.compile("switch x { 1 => 123, 1 => 42 }")
|
||||
.expect_err("should error")
|
||||
.0,
|
||||
ParseErrorType::DuplicatedSwitchCase
|
||||
));
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.compile("switch x { _ => 123, 1 => 42 }")
|
||||
@ -159,23 +161,22 @@ fn test_switch_condition() -> Result<(), Box<EvalAltResult>> {
|
||||
9
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.compile(
|
||||
"
|
||||
switch x {
|
||||
21 if x < 40 => 1,
|
||||
21 if x == 10 => 10,
|
||||
0 if x < 100 => 2,
|
||||
1 => 3,
|
||||
_ => 9
|
||||
}
|
||||
"
|
||||
)
|
||||
.expect_err("should error")
|
||||
.0,
|
||||
ParseErrorType::DuplicatedSwitchCase
|
||||
));
|
||||
assert_eq!(
|
||||
engine.eval_with_scope::<INT>(
|
||||
&mut scope,
|
||||
"
|
||||
switch x {
|
||||
42 if x < 40 => 1,
|
||||
42 if x > 40 => 7,
|
||||
0 if x < 100 => 2,
|
||||
1 => 3,
|
||||
42 if x == 10 => 10,
|
||||
_ => 9
|
||||
}
|
||||
"
|
||||
)?,
|
||||
7
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
*engine
|
||||
@ -290,6 +291,49 @@ fn test_switch_ranges() -> Result<(), Box<EvalAltResult>> {
|
||||
)?,
|
||||
'x'
|
||||
);
|
||||
assert_eq!(
|
||||
engine.eval_with_scope::<INT>(
|
||||
&mut scope,
|
||||
"
|
||||
switch 5 {
|
||||
'a' => true,
|
||||
0..10 => 123,
|
||||
2..12 => 'z',
|
||||
_ => 'x'
|
||||
}
|
||||
"
|
||||
)?,
|
||||
123
|
||||
);
|
||||
assert_eq!(
|
||||
engine.eval_with_scope::<INT>(
|
||||
&mut scope,
|
||||
"
|
||||
switch 5 {
|
||||
'a' => true,
|
||||
4 | 5 | 6 => 42,
|
||||
0..10 => 123,
|
||||
2..12 => 'z',
|
||||
_ => 'x'
|
||||
}
|
||||
"
|
||||
)?,
|
||||
42
|
||||
);
|
||||
assert_eq!(
|
||||
engine.eval_with_scope::<char>(
|
||||
&mut scope,
|
||||
"
|
||||
switch 5 {
|
||||
'a' => true,
|
||||
2..12 => 'z',
|
||||
0..10 if x+2==1+2 => print(40+2),
|
||||
_ => 'x'
|
||||
}
|
||||
"
|
||||
)?,
|
||||
'z'
|
||||
);
|
||||
assert_eq!(
|
||||
engine.eval_with_scope::<char>(
|
||||
&mut scope,
|
||||
|
Loading…
Reference in New Issue
Block a user