Add case alternatives for switch.
This commit is contained in:
parent
54db9a2819
commit
dee66a409f
@ -1,6 +1,15 @@
|
|||||||
Rhai Release Notes
|
Rhai Release Notes
|
||||||
==================
|
==================
|
||||||
|
|
||||||
|
Version 1.9.0
|
||||||
|
=============
|
||||||
|
|
||||||
|
Enhancements
|
||||||
|
------------
|
||||||
|
|
||||||
|
* `switch` cases can now include multiple values separated by `|`.
|
||||||
|
|
||||||
|
|
||||||
Version 1.8.0
|
Version 1.8.0
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
@ -20,8 +20,8 @@ pub use script_fn::EncapsulatedEnviron;
|
|||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
pub use script_fn::{ScriptFnDef, ScriptFnMetadata};
|
pub use script_fn::{ScriptFnDef, ScriptFnMetadata};
|
||||||
pub use stmt::{
|
pub use stmt::{
|
||||||
ConditionalStmtBlock, OpAssignment, Stmt, StmtBlock, StmtBlockContainer, SwitchCases,
|
ConditionalStmtBlock, OpAssignment, RangeCase, Stmt, StmtBlock, StmtBlockContainer,
|
||||||
TryCatchBlock,
|
SwitchCases, TryCatchBlock,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
139
src/ast/stmt.rs
139
src/ast/stmt.rs
@ -12,7 +12,7 @@ use std::{
|
|||||||
hash::Hash,
|
hash::Hash,
|
||||||
mem,
|
mem,
|
||||||
num::NonZeroUsize,
|
num::NonZeroUsize,
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut, Range, RangeInclusive},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// _(internals)_ An op-assignment operator.
|
/// _(internals)_ An op-assignment operator.
|
||||||
@ -125,7 +125,7 @@ impl fmt::Debug for OpAssignment {
|
|||||||
/// A statements block with a condition.
|
/// A statements block with a condition.
|
||||||
///
|
///
|
||||||
/// The condition may simply be [`Expr::BoolConstant`] with `true` if there is actually no 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 {
|
pub struct ConditionalStmtBlock {
|
||||||
/// Condition.
|
/// Condition.
|
||||||
pub condition: Expr,
|
pub condition: Expr,
|
||||||
@ -153,16 +153,110 @@ impl<B: Into<StmtBlock>> 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<INT>, usize),
|
||||||
|
/// Inclusive range.
|
||||||
|
InclusiveInt(RangeInclusive<INT>, 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<Range<INT>> for RangeCase {
|
||||||
|
#[inline(always)]
|
||||||
|
fn from(value: Range<INT>) -> Self {
|
||||||
|
Self::ExclusiveInt(value, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RangeInclusive<INT>> for RangeCase {
|
||||||
|
#[inline(always)]
|
||||||
|
fn from(value: RangeInclusive<INT>) -> 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<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]
|
||||||
|
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.
|
/// _(internals)_ A type containing all cases for a `switch` statement.
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub struct SwitchCases {
|
pub struct SwitchCases {
|
||||||
|
/// List of [`ConditionalStmtBlock`]'s.
|
||||||
|
pub blocks: StaticVec<ConditionalStmtBlock>,
|
||||||
/// Dictionary mapping value hashes to [`ConditionalStmtBlock`]'s.
|
/// Dictionary mapping value hashes to [`ConditionalStmtBlock`]'s.
|
||||||
pub cases: BTreeMap<u64, Box<ConditionalStmtBlock>>,
|
pub cases: BTreeMap<u64, usize>,
|
||||||
/// Statements block for the default case (there can be no condition for the default case).
|
/// Statements block for the default case (there can be no condition for the default case).
|
||||||
pub def_case: Box<StmtBlock>,
|
pub def_case: usize,
|
||||||
/// List of range cases.
|
/// List of range cases.
|
||||||
pub ranges: StaticVec<(INT, INT, bool, Box<ConditionalStmtBlock>)>,
|
pub ranges: StaticVec<RangeCase>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// _(internals)_ A `try-catch` block.
|
/// _(internals)_ A `try-catch` block.
|
||||||
@ -195,7 +289,9 @@ pub type StmtBlockContainer = StaticVec<Stmt>;
|
|||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
#[derive(Clone, Hash, Default)]
|
#[derive(Clone, Hash, Default)]
|
||||||
pub struct StmtBlock {
|
pub struct StmtBlock {
|
||||||
|
/// List of [statements][Stmt].
|
||||||
block: StmtBlockContainer,
|
block: StmtBlockContainer,
|
||||||
|
/// [Position] of the statements block.
|
||||||
span: Span,
|
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)
|
x.0.is_pure() && x.1.iter().all(Stmt::is_pure) && x.2.iter().all(Stmt::is_pure)
|
||||||
}
|
}
|
||||||
Self::Switch(x, ..) => {
|
Self::Switch(x, ..) => {
|
||||||
x.0.is_pure()
|
let (expr, sw) = x.as_ref();
|
||||||
&& x.1.cases.values().all(|block| {
|
expr.is_pure()
|
||||||
|
&& sw.cases.values().all(|&c| {
|
||||||
|
let block = &sw.blocks[c];
|
||||||
block.condition.is_pure() && block.statements.iter().all(Stmt::is_pure)
|
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)
|
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.
|
// Loops that exit can be pure because it can never be infinite.
|
||||||
@ -777,30 +876,36 @@ impl Stmt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::Switch(x, ..) => {
|
Self::Switch(x, ..) => {
|
||||||
if !x.0.walk(path, on_node) {
|
let (expr, sw) = x.as_ref();
|
||||||
|
|
||||||
|
if !expr.walk(path, on_node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for b in x.1.cases.values() {
|
for (.., &b) in sw.cases.iter() {
|
||||||
if !b.condition.walk(path, on_node) {
|
let block = &sw.blocks[b];
|
||||||
|
|
||||||
|
if !block.condition.walk(path, on_node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for s in b.statements.iter() {
|
for s in block.statements.iter() {
|
||||||
if !s.walk(path, on_node) {
|
if !s.walk(path, on_node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (.., b) in &x.1.ranges {
|
for r in sw.ranges.iter() {
|
||||||
if !b.condition.walk(path, on_node) {
|
let block = &sw.blocks[r.index()];
|
||||||
|
|
||||||
|
if !block.condition.walk(path, on_node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for s in b.statements.iter() {
|
for s in block.statements.iter() {
|
||||||
if !s.walk(path, on_node) {
|
if !s.walk(path, on_node) {
|
||||||
return false;
|
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) {
|
if !s.walk(path, on_node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -394,6 +394,7 @@ impl Engine {
|
|||||||
let (
|
let (
|
||||||
expr,
|
expr,
|
||||||
SwitchCases {
|
SwitchCases {
|
||||||
|
blocks,
|
||||||
cases,
|
cases,
|
||||||
def_case,
|
def_case,
|
||||||
ranges,
|
ranges,
|
||||||
@ -410,7 +411,9 @@ impl Engine {
|
|||||||
let hash = hasher.finish();
|
let hash = hasher.finish();
|
||||||
|
|
||||||
// First check hashes
|
// 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 {
|
let cond_result = match case_block.condition {
|
||||||
Expr::BoolConstant(b, ..) => Ok(b),
|
Expr::BoolConstant(b, ..) => Ok(b),
|
||||||
ref c => self
|
ref c => self
|
||||||
@ -432,12 +435,9 @@ impl Engine {
|
|||||||
let value = value.as_int().expect("`INT`");
|
let value = value.as_int().expect("`INT`");
|
||||||
let mut result = Ok(None);
|
let mut result = Ok(None);
|
||||||
|
|
||||||
for (.., block) in
|
for r in ranges.iter().filter(|r| r.contains(value)) {
|
||||||
ranges.iter().filter(|&&(start, end, inclusive, ..)| {
|
let block = &blocks[r.index()];
|
||||||
(!inclusive && (start..end).contains(&value))
|
|
||||||
|| (inclusive && (start..=end).contains(&value))
|
|
||||||
})
|
|
||||||
{
|
|
||||||
let cond_result = match block.condition {
|
let cond_result = match block.condition {
|
||||||
Expr::BoolConstant(b, ..) => Ok(b),
|
Expr::BoolConstant(b, ..) => Ok(b),
|
||||||
ref c => self
|
ref c => self
|
||||||
@ -481,6 +481,8 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
} else if let Ok(None) = stmt_block_result {
|
} else if let Ok(None) = stmt_block_result {
|
||||||
// Default match clause
|
// Default match clause
|
||||||
|
let def_case = &blocks[*def_case].statements;
|
||||||
|
|
||||||
if !def_case.is_empty() {
|
if !def_case.is_empty() {
|
||||||
self.eval_stmt_block(
|
self.eval_stmt_block(
|
||||||
scope, global, caches, lib, this_ptr, def_case, true, level,
|
scope, global, caches, lib, this_ptr, def_case, true, level,
|
||||||
|
@ -283,7 +283,8 @@ pub use parser::ParseState;
|
|||||||
#[cfg(feature = "internals")]
|
#[cfg(feature = "internals")]
|
||||||
pub use ast::{
|
pub use ast::{
|
||||||
ASTFlags, ASTNode, BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr,
|
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")]
|
#[cfg(feature = "internals")]
|
||||||
|
131
src/optimizer.rs
131
src/optimizer.rs
@ -525,6 +525,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
let (
|
let (
|
||||||
match_expr,
|
match_expr,
|
||||||
SwitchCases {
|
SwitchCases {
|
||||||
|
blocks: blocks_list,
|
||||||
cases,
|
cases,
|
||||||
ranges,
|
ranges,
|
||||||
def_case,
|
def_case,
|
||||||
@ -537,34 +538,30 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
let hash = hasher.finish();
|
let hash = hasher.finish();
|
||||||
|
|
||||||
// First check hashes
|
// First check hashes
|
||||||
if let Some(block) = cases.get_mut(&hash) {
|
if let Some(block) = cases.remove(&hash) {
|
||||||
match mem::take(&mut block.condition) {
|
let mut block = mem::take(&mut blocks_list[block]);
|
||||||
|
cases.clear();
|
||||||
|
|
||||||
|
match block.condition {
|
||||||
Expr::BoolConstant(true, ..) => {
|
Expr::BoolConstant(true, ..) => {
|
||||||
// Promote the matched case
|
// Promote the matched case
|
||||||
let statements = optimize_stmt_block(
|
let statements: StmtBlockContainer = mem::take(&mut block.statements);
|
||||||
mem::take(&mut block.statements),
|
let statements = optimize_stmt_block(statements, state, true, true, false);
|
||||||
state,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
*stmt = (statements, block.statements.span()).into();
|
*stmt = (statements, block.statements.span()).into();
|
||||||
}
|
}
|
||||||
mut condition => {
|
ref mut condition => {
|
||||||
// switch const { case if condition => stmt, _ => def } => if condition { stmt } else { def }
|
// switch const { case if condition => stmt, _ => def } => if condition { stmt } else { def }
|
||||||
optimize_expr(&mut condition, state, false);
|
optimize_expr(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);
|
||||||
*stmt = Stmt::If(
|
*stmt = Stmt::If(
|
||||||
(
|
(
|
||||||
condition,
|
mem::take(condition),
|
||||||
mem::take(&mut block.statements),
|
mem::take(&mut block.statements),
|
||||||
StmtBlock::new_with_span(
|
StmtBlock::new_with_span(def_stmt, def_span),
|
||||||
def_stmt,
|
|
||||||
def_case.span_or_else(*pos, Position::NONE),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.into(),
|
.into(),
|
||||||
match_expr.start_position(),
|
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
|
// Only one range or all ranges without conditions
|
||||||
if ranges.len() == 1
|
if ranges.len() == 1
|
||||||
|| ranges
|
|| ranges.iter().all(|r| {
|
||||||
.iter()
|
matches!(
|
||||||
.all(|(.., c)| matches!(c.condition, Expr::BoolConstant(true, ..)))
|
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, ..) => {
|
Expr::BoolConstant(true, ..) => {
|
||||||
// Promote the matched case
|
// Promote the matched case
|
||||||
|
let block = &mut blocks_list[r.index()];
|
||||||
let statements = mem::take(&mut *block.statements);
|
let statements = mem::take(&mut *block.statements);
|
||||||
let statements =
|
let statements =
|
||||||
optimize_stmt_block(statements, state, true, true, false);
|
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 }
|
// switch const { range if condition => stmt, _ => def } => if condition { stmt } else { def }
|
||||||
optimize_expr(&mut condition, state, false);
|
optimize_expr(&mut condition, state, false);
|
||||||
|
|
||||||
let def_stmt = optimize_stmt_block(
|
let def_case = &mut blocks_list[*def_case].statements;
|
||||||
mem::take(def_case),
|
let def_span = def_case.span_or_else(*pos, Position::NONE);
|
||||||
state,
|
let def_case: StmtBlockContainer = mem::take(def_case);
|
||||||
true,
|
let def_stmt =
|
||||||
true,
|
optimize_stmt_block(def_case, state, true, true, false);
|
||||||
false,
|
|
||||||
);
|
let statements = mem::take(&mut blocks_list[r.index()].statements);
|
||||||
|
|
||||||
*stmt = Stmt::If(
|
*stmt = Stmt::If(
|
||||||
(
|
(
|
||||||
condition,
|
condition,
|
||||||
mem::take(&mut block.statements),
|
statements,
|
||||||
StmtBlock::new_with_span(
|
StmtBlock::new_with_span(def_stmt, def_span),
|
||||||
def_stmt,
|
|
||||||
def_case.span_or_else(*pos, Position::NONE),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.into(),
|
.into(),
|
||||||
match_expr.start_position(),
|
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();
|
let old_ranges_len = ranges.len();
|
||||||
|
|
||||||
ranges.retain(|&mut (start, end, inclusive, ..)| {
|
ranges.retain(|r| r.contains(value));
|
||||||
(!inclusive && (start..end).contains(&value))
|
|
||||||
|| (inclusive && (start..=end).contains(&value))
|
|
||||||
});
|
|
||||||
|
|
||||||
if ranges.len() != old_ranges_len {
|
if ranges.len() != old_ranges_len {
|
||||||
state.set_dirty();
|
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);
|
let statements = mem::take(&mut *block.statements);
|
||||||
*block.statements =
|
*block.statements =
|
||||||
optimize_stmt_block(statements, state, preserve_result, true, false);
|
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
|
// Promote the default case
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
let def_stmt = optimize_stmt_block(mem::take(def_case), state, true, true, false);
|
let def_case = &mut blocks_list[*def_case].statements;
|
||||||
*stmt = (def_stmt, def_case.span_or_else(*pos, Position::NONE)).into();
|
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
|
// switch
|
||||||
Stmt::Switch(x, ..) => {
|
Stmt::Switch(x, ..) => {
|
||||||
let (
|
let (
|
||||||
match_expr,
|
match_expr,
|
||||||
SwitchCases {
|
SwitchCases {
|
||||||
|
blocks: blocks_list,
|
||||||
cases,
|
cases,
|
||||||
ranges,
|
ranges,
|
||||||
def_case,
|
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_expr(match_expr, state, false);
|
||||||
|
|
||||||
// Optimize cases
|
// Optimize blocks
|
||||||
for block in cases.values_mut() {
|
for block in blocks_list.iter_mut() {
|
||||||
let statements = mem::take(&mut *block.statements);
|
let statements = mem::take(&mut *block.statements);
|
||||||
*block.statements =
|
*block.statements =
|
||||||
optimize_stmt_block(statements, state, preserve_result, true, false);
|
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);
|
block.condition = Expr::BoolConstant(true, pos);
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
}
|
}
|
||||||
|
Expr::BoolConstant(false, ..) => {
|
||||||
|
if !block.statements.is_empty() {
|
||||||
|
block.statements = StmtBlock::NONE;
|
||||||
|
state.set_dirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove false cases
|
// Remove false cases
|
||||||
cases.retain(|_, block| match block.condition {
|
cases.retain(|_, &mut block| match blocks_list[block].condition {
|
||||||
Expr::BoolConstant(false, ..) => {
|
Expr::BoolConstant(false, ..) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
_ => true,
|
_ => 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
|
// Remove false ranges
|
||||||
ranges.retain(|(.., block)| match block.condition {
|
ranges.retain(|r| match blocks_list[r.index()].condition {
|
||||||
Expr::BoolConstant(false, ..) => {
|
Expr::BoolConstant(false, ..) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
false
|
false
|
||||||
@ -739,8 +723,9 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
_ => true,
|
_ => true,
|
||||||
});
|
});
|
||||||
|
|
||||||
let def_block = mem::take(&mut ***def_case);
|
let def_case = &mut blocks_list[*def_case].statements;
|
||||||
***def_case = optimize_stmt_block(def_block, state, preserve_result, true, false);
|
let def_block = mem::take(&mut **def_case);
|
||||||
|
**def_case = optimize_stmt_block(def_block, state, preserve_result, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// while false { block } -> Noop
|
// while false { block } -> Noop
|
||||||
|
179
src/parser.rs
179
src/parser.rs
@ -5,7 +5,7 @@ use crate::api::events::VarDefInfo;
|
|||||||
use crate::api::options::LangOptions;
|
use crate::api::options::LangOptions;
|
||||||
use crate::ast::{
|
use crate::ast::{
|
||||||
ASTFlags, BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident,
|
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::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS};
|
||||||
use crate::eval::GlobalRuntimeState;
|
use crate::eval::GlobalRuntimeState;
|
||||||
@ -25,6 +25,7 @@ use crate::{
|
|||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
use std::{
|
use std::{
|
||||||
collections::BTreeMap,
|
collections::BTreeMap,
|
||||||
|
fmt,
|
||||||
hash::{Hash, Hasher},
|
hash::{Hash, Hasher},
|
||||||
num::{NonZeroU8, NonZeroUsize},
|
num::{NonZeroU8, NonZeroUsize},
|
||||||
};
|
};
|
||||||
@ -41,7 +42,6 @@ const NEVER_ENDS: &str = "`Token`";
|
|||||||
|
|
||||||
/// _(internals)_ A type that encapsulates the current state of the parser.
|
/// _(internals)_ A type that encapsulates the current state of the parser.
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ParseState<'e> {
|
pub struct ParseState<'e> {
|
||||||
/// Input stream buffer containing the next character to read.
|
/// Input stream buffer containing the next character to read.
|
||||||
pub tokenizer_control: TokenizerControl,
|
pub tokenizer_control: TokenizerControl,
|
||||||
@ -55,6 +55,8 @@ pub struct ParseState<'e> {
|
|||||||
pub stack: Scope<'e>,
|
pub stack: Scope<'e>,
|
||||||
/// Size of the local variables stack upon entry of the current block scope.
|
/// Size of the local variables stack upon entry of the current block scope.
|
||||||
pub block_stack_len: usize,
|
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).
|
/// Tracks a list of external variables (variables that are not explicitly declared in the scope).
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
pub external_vars: Vec<crate::ast::Ident>,
|
pub external_vars: Vec<crate::ast::Ident>,
|
||||||
@ -72,6 +74,23 @@ pub struct ParseState<'e> {
|
|||||||
pub max_expr_depth: usize,
|
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> {
|
impl<'e> ParseState<'e> {
|
||||||
/// Create a new [`ParseState`].
|
/// Create a new [`ParseState`].
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -79,6 +98,7 @@ impl<'e> ParseState<'e> {
|
|||||||
pub fn new(engine: &Engine, scope: &'e Scope, tokenizer_control: TokenizerControl) -> Self {
|
pub fn new(engine: &Engine, scope: &'e Scope, tokenizer_control: TokenizerControl) -> Self {
|
||||||
Self {
|
Self {
|
||||||
tokenizer_control,
|
tokenizer_control,
|
||||||
|
expr_filter: |_| true,
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
external_vars: Vec::new(),
|
external_vars: Vec::new(),
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
@ -1027,15 +1047,16 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut cases = BTreeMap::<u64, Box<ConditionalStmtBlock>>::new();
|
let mut blocks = StaticVec::<ConditionalStmtBlock>::new();
|
||||||
let mut ranges = StaticVec::<(INT, INT, bool, Box<ConditionalStmtBlock>)>::new();
|
let mut cases = BTreeMap::<u64, usize>::new();
|
||||||
|
let mut ranges = StaticVec::<RangeCase>::new();
|
||||||
let mut def_pos = Position::NONE;
|
let mut def_pos = Position::NONE;
|
||||||
let mut def_stmt = None;
|
let mut def_stmt = None;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
const MISSING_RBRACE: &str = "to end this switch block";
|
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, ..) => {
|
(Token::RightBrace, ..) => {
|
||||||
eat_token(input, Token::RightBrace);
|
eat_token(input, Token::RightBrace);
|
||||||
break;
|
break;
|
||||||
@ -1056,7 +1077,7 @@ impl Engine {
|
|||||||
return Err(PERR::WrongSwitchCaseCondition.into_err(if_pos));
|
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)),
|
(Token::Underscore, pos) => return Err(PERR::DuplicatedSwitchCase.into_err(*pos)),
|
||||||
|
|
||||||
@ -1065,8 +1086,25 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
let case_expr =
|
let mut case_expr_list = StaticVec::new();
|
||||||
Some(self.parse_expr(input, state, lib, settings.level_up())?);
|
|
||||||
|
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 {
|
let condition = if match_token(input, Token::If).0 {
|
||||||
ensure_not_statement_expr(input, "a boolean")?;
|
ensure_not_statement_expr(input, "a boolean")?;
|
||||||
@ -1078,37 +1116,10 @@ impl Engine {
|
|||||||
} else {
|
} else {
|
||||||
Expr::BoolConstant(true, Position::NONE)
|
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::<ExclusiveRange>();
|
|
||||||
|
|
||||||
if let Some(range) = guard {
|
|
||||||
(None, Some((range.start, range.end, false)))
|
|
||||||
} else if let Some(range) = value.read_lock::<InclusiveRange>() {
|
|
||||||
(None, Some((*range.start(), *range.end(), true)))
|
|
||||||
} else if value.is::<INT>() && !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) {
|
match input.next().expect(NEVER_ENDS) {
|
||||||
(Token::DoubleArrow, ..) => (),
|
(Token::DoubleArrow, ..) => (),
|
||||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
(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 stmt = self.parse_stmt(input, state, lib, settings.level_up())?;
|
||||||
|
|
||||||
let need_comma = !stmt.is_self_terminated();
|
let need_comma = !stmt.is_self_terminated();
|
||||||
|
|
||||||
def_stmt = match (hash, range) {
|
blocks.push((condition, stmt).into());
|
||||||
(None, Some(range)) => {
|
let index = blocks.len() - 1;
|
||||||
let is_empty = if range.2 {
|
|
||||||
(range.0..=range.1).is_empty()
|
|
||||||
} else {
|
|
||||||
(range.0..range.1).is_empty()
|
|
||||||
};
|
|
||||||
|
|
||||||
if !is_empty {
|
if !case_expr_list.is_empty() {
|
||||||
match (range.1.checked_sub(range.0), range.2) {
|
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<RangeCase> = None;
|
||||||
|
|
||||||
|
let guard = value.read_lock::<ExclusiveRange>();
|
||||||
|
if let Some(range) = guard {
|
||||||
|
range_value = Some(range.clone().into());
|
||||||
|
} else if let Some(range) = value.read_lock::<InclusiveRange>() {
|
||||||
|
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
|
// Unroll single range
|
||||||
(Some(1), false) | (Some(0), true) => {
|
let value = Dynamic::from_int(n);
|
||||||
let value = Dynamic::from_int(range.0);
|
|
||||||
let hasher = &mut get_hasher();
|
let hasher = &mut get_hasher();
|
||||||
value.hash(hasher);
|
value.hash(hasher);
|
||||||
let hash = hasher.finish();
|
let hash = hasher.finish();
|
||||||
|
|
||||||
cases.entry(hash).or_insert_with(|| {
|
cases.entry(hash).or_insert(index);
|
||||||
let block: ConditionalStmtBlock = (condition, stmt).into();
|
} else {
|
||||||
block.into()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Other range
|
// Other range
|
||||||
_ => {
|
r.set_index(index);
|
||||||
let block: ConditionalStmtBlock = (condition, stmt).into();
|
ranges.push(r);
|
||||||
ranges.push((range.0, range.1, range.2, block.into()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
None
|
|
||||||
|
if value.is::<INT>() && !ranges.is_empty() {
|
||||||
|
return Err(PERR::WrongSwitchIntegerCase.into_err(expr.start_position()));
|
||||||
}
|
}
|
||||||
(Some(hash), None) => {
|
|
||||||
let block: ConditionalStmtBlock = (condition, stmt).into();
|
let hasher = &mut get_hasher();
|
||||||
cases.insert(hash, block.into());
|
value.hash(hasher);
|
||||||
None
|
let hash = hasher.finish();
|
||||||
|
|
||||||
|
if cases.contains_key(&hash) {
|
||||||
|
return Err(PERR::DuplicatedSwitchCase.into_err(expr.start_position()));
|
||||||
|
}
|
||||||
|
cases.insert(hash, index);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
def_stmt = Some(index);
|
||||||
}
|
}
|
||||||
(None, None) => Some(Box::new(stmt.into())),
|
|
||||||
_ => unreachable!("both hash and range in switch statement case"),
|
|
||||||
};
|
|
||||||
|
|
||||||
match input.peek().expect(NEVER_ENDS) {
|
match input.peek().expect(NEVER_ENDS) {
|
||||||
(Token::Comma, ..) => {
|
(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 {
|
let cases = SwitchCases {
|
||||||
|
blocks,
|
||||||
cases,
|
cases,
|
||||||
def_case: def_stmt.unwrap_or_else(|| StmtBlock::NONE.into()),
|
def_case,
|
||||||
ranges,
|
ranges,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1214,6 +1244,12 @@ impl Engine {
|
|||||||
settings.pos = *token_pos;
|
settings.pos = *token_pos;
|
||||||
|
|
||||||
let root_expr = match token {
|
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::EOF => return Err(PERR::UnexpectedEOF.into_err(settings.pos)),
|
||||||
|
|
||||||
Token::Unit => {
|
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)
|
self.parse_postfix(input, state, lib, root_expr, settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1738,6 +1778,10 @@ impl Engine {
|
|||||||
|
|
||||||
let (token, token_pos) = input.peek().expect(NEVER_ENDS);
|
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;
|
let mut settings = settings;
|
||||||
settings.pos = *token_pos;
|
settings.pos = *token_pos;
|
||||||
|
|
||||||
@ -2130,6 +2174,11 @@ impl Engine {
|
|||||||
|
|
||||||
loop {
|
loop {
|
||||||
let (current_op, current_pos) = input.peek().expect(NEVER_ENDS);
|
let (current_op, current_pos) = input.peek().expect(NEVER_ENDS);
|
||||||
|
|
||||||
|
if !(state.expr_filter)(current_op) {
|
||||||
|
return Ok(root);
|
||||||
|
}
|
||||||
|
|
||||||
let precedence = match current_op {
|
let precedence = match current_op {
|
||||||
Token::Custom(c) => self
|
Token::Custom(c) => self
|
||||||
.custom_keywords
|
.custom_keywords
|
||||||
|
@ -283,7 +283,7 @@ impl AddAssign for Position {
|
|||||||
|
|
||||||
/// _(internals)_ A span consisting of a starting and an ending [positions][Position].
|
/// _(internals)_ A span consisting of a starting and an ending [positions][Position].
|
||||||
/// Exported under the `internals` feature only.
|
/// 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 {
|
pub struct Span {
|
||||||
/// Starting [position][Position].
|
/// Starting [position][Position].
|
||||||
start: Position,
|
start: Position,
|
||||||
@ -291,6 +291,12 @@ pub struct Span {
|
|||||||
end: Position,
|
end: Position,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Span {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::NONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Span {
|
impl Span {
|
||||||
/// Empty [`Span`].
|
/// Empty [`Span`].
|
||||||
pub const NONE: Self = Self::new(Position::NONE, Position::NONE);
|
pub const NONE: Self = Self::new(Position::NONE, Position::NONE);
|
||||||
|
@ -45,6 +45,13 @@ fn test_switch() -> Result<(), Box<EvalAltResult>> {
|
|||||||
engine.eval_with_scope::<()>(&mut scope, "switch x { 1 => 123, 2 => 'a' }")?,
|
engine.eval_with_scope::<()>(&mut scope, "switch x { 1 => 123, 2 => 'a' }")?,
|
||||||
()
|
()
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval_with_scope::<INT>(
|
||||||
|
&mut scope,
|
||||||
|
"switch x { 1 | 2 | 3 | 5..50 | 'x' | true => 123, 'z' => 'a' }"
|
||||||
|
)?,
|
||||||
|
123
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>("let x = timestamp(); switch x { 1 => 123, _ => 42 }")?,
|
engine.eval::<INT>("let x = timestamp(); switch x { 1 => 123, _ => 42 }")?,
|
||||||
42
|
42
|
||||||
|
Loading…
Reference in New Issue
Block a user