commit
7d1ec1b4b7
@ -18,6 +18,7 @@ Breaking changes
|
||||
----------------
|
||||
|
||||
* Negative index to an array or string yields the appropriate element/character counting from the _end_.
|
||||
* The default `_` case of a `switch` statement now must be the last case, together with two new error variants: `EvalAltResult::WrongSwitchDefaultCase` and `EvalAltResult::WrongSwitchCaseCondition`.
|
||||
* `ModuleResolver` trait methods take an additional parameter `source_path` that contains the path of the current environment. This is to facilitate loading other script files always from the current directory.
|
||||
* `FileModuleResolver` now resolves relative paths under the source path if there is no base path set.
|
||||
* `FileModuleResolver::base_path` now returns `Option<&str>` which is `None` if there is no base path set.
|
||||
@ -34,6 +35,7 @@ New features
|
||||
* String interpolation support is added via the `` `... ${`` ... ``} ...` `` syntax.
|
||||
* `FileModuleResolver` resolves relative paths under the parent path (i.e. the path holding the script that does the loading). This allows seamless cross-loading of scripts from a directory hierarchy instead of having all relative paths load from the current working directory.
|
||||
* Negative index to an array or string yields the appropriate element/character counting from the _end_.
|
||||
* `switch` statement cases can now have an optional `if` clause.
|
||||
|
||||
|
||||
Version 0.19.15
|
||||
|
@ -7,14 +7,8 @@ Testing scripts written in Rhai.
|
||||
How to Run
|
||||
----------
|
||||
|
||||
Compile the `rhai-run` example:
|
||||
Run scripts using the `rhai-run` tool:
|
||||
|
||||
```bash
|
||||
cargo build --example rhai-run
|
||||
```
|
||||
|
||||
Run it:
|
||||
|
||||
```bash
|
||||
./target/debug/examples/rhai-run ./scripts/test_script_to_run.rhai
|
||||
```sh
|
||||
cargo run --bin rhai-run ./scripts/test_script_to_run.rhai
|
||||
```
|
||||
|
@ -8,4 +8,4 @@ let /* I am a spy in a variable declaration! */ x = 5;
|
||||
|
||||
/* look /* at /* that, /* multi-line */ comments */ can be */ nested */
|
||||
|
||||
/* surrounded by */ this_is_not_a_comment = true // comments
|
||||
/* surrounded by */ let this_is_not_a_comment = true // comments
|
||||
|
@ -1,7 +1,8 @@
|
||||
// This script calculates the n-th Fibonacci number using a really dumb algorithm
|
||||
// to test the speed of the scripting engine.
|
||||
|
||||
const target = 28;
|
||||
const TARGET = 28;
|
||||
const REPEAT = 5;
|
||||
|
||||
fn fib(n) {
|
||||
if n < 2 {
|
||||
@ -11,19 +12,19 @@ fn fib(n) {
|
||||
}
|
||||
}
|
||||
|
||||
print("Running Fibonacci(28) x 5 times...");
|
||||
print(`Running Fibonacci(28) x ${REPEAT} times...`);
|
||||
print("Ready... Go!");
|
||||
|
||||
let result;
|
||||
let now = timestamp();
|
||||
|
||||
for n in range(0, 5) {
|
||||
result = fib(target);
|
||||
for n in range(0, REPEAT) {
|
||||
result = fib(TARGET);
|
||||
}
|
||||
|
||||
print(`Finished. Run time = ${now.elapsed} seconds.`);
|
||||
|
||||
print(`Fibonacci number #${target} = ${result}`);
|
||||
print(`Fibonacci number #${TARGET} = ${result}`);
|
||||
|
||||
if result != 317_811 {
|
||||
print("The answer is WRONG! Should be 317,811!");
|
||||
|
@ -9,6 +9,6 @@ fn addme(a, b) {
|
||||
|
||||
let result = addme(a, 4);
|
||||
|
||||
print(!addme(a, 4) should be 46: ${result}``);
|
||||
print(`addme(a, 4) should be 46: ${result}`);
|
||||
|
||||
print(`a should still be 3: ${a}`); // should print 3 - 'a' is never changed
|
||||
|
180
src/ast.rs
180
src/ast.rs
@ -10,7 +10,7 @@ use crate::stdlib::{
|
||||
hash::Hash,
|
||||
iter::empty,
|
||||
num::{NonZeroU8, NonZeroUsize},
|
||||
ops::{Add, AddAssign},
|
||||
ops::{Add, AddAssign, Deref, DerefMut},
|
||||
vec,
|
||||
vec::Vec,
|
||||
};
|
||||
@ -187,10 +187,7 @@ impl AST {
|
||||
) -> Self {
|
||||
Self {
|
||||
source: None,
|
||||
body: StmtBlock {
|
||||
statements: statements.into_iter().collect(),
|
||||
pos: Position::NONE,
|
||||
},
|
||||
body: StmtBlock(statements.into_iter().collect(), Position::NONE),
|
||||
functions: functions.into(),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
resolver: None,
|
||||
@ -205,10 +202,7 @@ impl AST {
|
||||
) -> Self {
|
||||
Self {
|
||||
source: Some(source.into()),
|
||||
body: StmtBlock {
|
||||
statements: statements.into_iter().collect(),
|
||||
pos: Position::NONE,
|
||||
},
|
||||
body: StmtBlock(statements.into_iter().collect(), Position::NONE),
|
||||
functions: functions.into(),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
resolver: None,
|
||||
@ -245,7 +239,7 @@ impl AST {
|
||||
#[cfg(not(feature = "internals"))]
|
||||
#[inline(always)]
|
||||
pub(crate) fn statements(&self) -> &[Stmt] {
|
||||
&self.body.statements
|
||||
&self.body.0
|
||||
}
|
||||
/// _(INTERNALS)_ Get the statements.
|
||||
/// Exported under the `internals` feature only.
|
||||
@ -253,13 +247,13 @@ impl AST {
|
||||
#[deprecated = "this method is volatile and may change"]
|
||||
#[inline(always)]
|
||||
pub fn statements(&self) -> &[Stmt] {
|
||||
&self.body.statements
|
||||
&self.body.0
|
||||
}
|
||||
/// Get a mutable reference to the statements.
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
#[inline(always)]
|
||||
pub(crate) fn statements_mut(&mut self) -> &mut StaticVec<Stmt> {
|
||||
&mut self.body.statements
|
||||
&mut self.body.0
|
||||
}
|
||||
/// Get the internal shared [`Module`] containing all script-defined functions.
|
||||
#[cfg(not(feature = "internals"))]
|
||||
@ -535,8 +529,7 @@ impl AST {
|
||||
let merged = match (body.is_empty(), other.body.is_empty()) {
|
||||
(false, false) => {
|
||||
let mut body = body.clone();
|
||||
body.statements
|
||||
.extend(other.body.statements.iter().cloned());
|
||||
body.0.extend(other.body.0.iter().cloned());
|
||||
body
|
||||
}
|
||||
(false, true) => body.clone(),
|
||||
@ -550,9 +543,9 @@ impl AST {
|
||||
functions.merge_filtered(&other.functions, &filter);
|
||||
|
||||
if let Some(source) = source {
|
||||
Self::new_with_source(merged.statements, functions, source)
|
||||
Self::new_with_source(merged.0, functions, source)
|
||||
} else {
|
||||
Self::new(merged.statements, functions)
|
||||
Self::new(merged.0, functions)
|
||||
}
|
||||
}
|
||||
/// Combine one [`AST`] with another. The second [`AST`] is consumed.
|
||||
@ -612,9 +605,7 @@ impl AST {
|
||||
other: Self,
|
||||
filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool,
|
||||
) -> &mut Self {
|
||||
self.body
|
||||
.statements
|
||||
.extend(other.body.statements.into_iter());
|
||||
self.body.0.extend(other.body.0.into_iter());
|
||||
|
||||
if !other.functions.is_empty() {
|
||||
shared_make_mut(&mut self.functions).merge_filtered(&other.functions, &filter);
|
||||
@ -705,7 +696,7 @@ impl AST {
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
for stmt in self.iter_fn_def().flat_map(|f| f.body.statements.iter()) {
|
||||
for stmt in self.iter_fn_def().flat_map(|f| f.body.0.iter()) {
|
||||
if !stmt.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
@ -727,7 +718,7 @@ impl AST {
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
for stmt in self.iter_fn_def().flat_map(|f| f.body.statements.iter()) {
|
||||
for stmt in self.iter_fn_def().flat_map(|f| f.body.0.iter()) {
|
||||
if !stmt.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
@ -833,35 +824,65 @@ impl<'a> From<&'a Expr> for ASTNode<'a> {
|
||||
///
|
||||
/// This type is volatile and may change.
|
||||
#[derive(Clone, Hash, Default)]
|
||||
pub struct StmtBlock {
|
||||
pub statements: StaticVec<Stmt>,
|
||||
pub pos: Position,
|
||||
}
|
||||
pub struct StmtBlock(StaticVec<Stmt>, Position);
|
||||
|
||||
impl StmtBlock {
|
||||
/// Create a new [`StmtBlock`].
|
||||
pub fn new(statements: impl Into<StaticVec<Stmt>>, pos: Position) -> Self {
|
||||
Self(statements.into(), pos)
|
||||
}
|
||||
/// Is this statements block empty?
|
||||
#[inline(always)]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.statements.is_empty()
|
||||
self.0.is_empty()
|
||||
}
|
||||
/// Number of statements in this statements block.
|
||||
#[inline(always)]
|
||||
pub fn len(&self) -> usize {
|
||||
self.statements.len()
|
||||
self.0.len()
|
||||
}
|
||||
/// Get the position of this statements block.
|
||||
pub fn position(&self) -> Position {
|
||||
self.1
|
||||
}
|
||||
/// Get the statements of this statements block.
|
||||
pub fn statements(&mut self) -> &mut StaticVec<Stmt> {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for StmtBlock {
|
||||
type Target = StaticVec<Stmt>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for StmtBlock {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for StmtBlock {
|
||||
#[inline(always)]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(&self.statements, f)?;
|
||||
if !self.pos.is_none() {
|
||||
write!(f, " @ {:?}", self.pos)?;
|
||||
fmt::Debug::fmt(&self.0, f)?;
|
||||
if !self.1.is_none() {
|
||||
write!(f, " @ {:?}", self.1)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StmtBlock> for Stmt {
|
||||
fn from(block: StmtBlock) -> Self {
|
||||
let block_pos = block.position();
|
||||
Self::Block(block.0.into_vec(), block_pos)
|
||||
}
|
||||
}
|
||||
|
||||
/// _(INTERNALS)_ A statement.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
@ -874,10 +895,10 @@ pub enum Stmt {
|
||||
Noop(Position),
|
||||
/// `if` expr `{` stmt `}` `else` `{` stmt `}`
|
||||
If(Expr, Box<(StmtBlock, StmtBlock)>, Position),
|
||||
/// `switch` expr `{` literal or _ `=>` stmt `,` ... `}`
|
||||
/// `switch` expr `if` condition `{` literal or _ `=>` stmt `,` ... `}`
|
||||
Switch(
|
||||
Expr,
|
||||
Box<(BTreeMap<u64, Box<StmtBlock>>, StmtBlock)>,
|
||||
Box<(BTreeMap<u64, Box<(Option<Expr>, StmtBlock)>>, StmtBlock)>,
|
||||
Position,
|
||||
),
|
||||
/// `while` expr `{` stmt `}`
|
||||
@ -930,18 +951,11 @@ impl From<Stmt> for StmtBlock {
|
||||
#[inline(always)]
|
||||
fn from(stmt: Stmt) -> Self {
|
||||
match stmt {
|
||||
Stmt::Block(block, pos) => Self {
|
||||
statements: block.into(),
|
||||
pos,
|
||||
},
|
||||
Stmt::Noop(pos) => Self {
|
||||
statements: Default::default(),
|
||||
pos,
|
||||
},
|
||||
Stmt::Block(block, pos) => Self(block.into(), pos),
|
||||
Stmt::Noop(pos) => Self(Default::default(), pos),
|
||||
_ => {
|
||||
let pos = stmt.position();
|
||||
let statements = vec![stmt].into();
|
||||
Self { statements, pos }
|
||||
Self(vec![stmt].into(), pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1081,28 +1095,26 @@ impl Stmt {
|
||||
Self::Expr(expr) => expr.is_pure(),
|
||||
Self::If(condition, x, _) => {
|
||||
condition.is_pure()
|
||||
&& x.0.statements.iter().all(Stmt::is_pure)
|
||||
&& x.1.statements.iter().all(Stmt::is_pure)
|
||||
&& (x.0).0.iter().all(Stmt::is_pure)
|
||||
&& (x.1).0.iter().all(Stmt::is_pure)
|
||||
}
|
||||
Self::Switch(expr, x, _) => {
|
||||
expr.is_pure()
|
||||
&& x.0
|
||||
.values()
|
||||
.flat_map(|block| block.statements.iter())
|
||||
.all(Stmt::is_pure)
|
||||
&& x.1.statements.iter().all(Stmt::is_pure)
|
||||
&& x.0.values().all(|block| {
|
||||
block.0.as_ref().map(Expr::is_pure).unwrap_or(true)
|
||||
&& (block.1).0.iter().all(Stmt::is_pure)
|
||||
})
|
||||
&& (x.1).0.iter().all(Stmt::is_pure)
|
||||
}
|
||||
Self::While(condition, block, _) | Self::Do(block, condition, _, _) => {
|
||||
condition.is_pure() && block.statements.iter().all(Stmt::is_pure)
|
||||
}
|
||||
Self::For(iterable, x, _) => {
|
||||
iterable.is_pure() && x.1.statements.iter().all(Stmt::is_pure)
|
||||
condition.is_pure() && block.0.iter().all(Stmt::is_pure)
|
||||
}
|
||||
Self::For(iterable, x, _) => iterable.is_pure() && (x.1).0.iter().all(Stmt::is_pure),
|
||||
Self::Let(_, _, _, _) | Self::Const(_, _, _, _) | Self::Assignment(_, _) => false,
|
||||
Self::Block(block, _) => block.iter().all(|stmt| stmt.is_pure()),
|
||||
Self::Continue(_) | Self::Break(_) | Self::Return(_, _, _) => false,
|
||||
Self::TryCatch(x, _, _) => {
|
||||
x.0.statements.iter().all(Stmt::is_pure) && x.2.statements.iter().all(Stmt::is_pure)
|
||||
(x.0).0.iter().all(Stmt::is_pure) && (x.2).0.iter().all(Stmt::is_pure)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
@ -1168,12 +1180,12 @@ impl Stmt {
|
||||
if !e.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
for s in &x.0.statements {
|
||||
for s in &(x.0).0 {
|
||||
if !s.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for s in &x.1.statements {
|
||||
for s in &(x.1).0 {
|
||||
if !s.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
@ -1183,12 +1195,17 @@ impl Stmt {
|
||||
if !e.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
for s in x.0.values().flat_map(|block| block.statements.iter()) {
|
||||
for b in x.0.values() {
|
||||
if !b.0.as_ref().map(|e| e.walk(path, on_node)).unwrap_or(true) {
|
||||
return false;
|
||||
}
|
||||
for s in &(b.1).0 {
|
||||
if !s.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for s in &x.1.statements {
|
||||
}
|
||||
for s in &(x.1).0 {
|
||||
if !s.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
@ -1198,7 +1215,7 @@ impl Stmt {
|
||||
if !e.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
for s in &s.statements {
|
||||
for s in &s.0 {
|
||||
if !s.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
@ -1208,7 +1225,7 @@ impl Stmt {
|
||||
if !e.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
for s in &x.1.statements {
|
||||
for s in &(x.1).0 {
|
||||
if !s.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
@ -1230,12 +1247,12 @@ impl Stmt {
|
||||
}
|
||||
}
|
||||
Self::TryCatch(x, _, _) => {
|
||||
for s in &x.0.statements {
|
||||
for s in &(x.0).0 {
|
||||
if !s.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for s in &x.2.statements {
|
||||
for s in &(x.2).0 {
|
||||
if !s.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
@ -1283,7 +1300,7 @@ pub struct CustomExpr {
|
||||
/// # Volatile Data Structure
|
||||
///
|
||||
/// This type is volatile and may change.
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
#[derive(Clone, Hash)]
|
||||
pub struct BinaryExpr {
|
||||
/// LHS expression.
|
||||
pub lhs: Expr,
|
||||
@ -1419,7 +1436,7 @@ impl FnCallHash {
|
||||
/// # Volatile Data Structure
|
||||
///
|
||||
/// This type is volatile and may change.
|
||||
#[derive(Debug, Clone, Default, Hash)]
|
||||
#[derive(Clone, Default, Hash)]
|
||||
pub struct FnCallExpr {
|
||||
/// Pre-calculated hash.
|
||||
pub hash: FnCallHash,
|
||||
@ -1668,12 +1685,12 @@ impl fmt::Debug for Expr {
|
||||
Self::Variable(i, pos, x) => {
|
||||
f.write_str("Variable(")?;
|
||||
match x.1 {
|
||||
Some((_, ref namespace)) => write!(f, "{}::", namespace)?,
|
||||
Some((_, ref namespace)) => write!(f, "{}", namespace)?,
|
||||
_ => (),
|
||||
}
|
||||
write!(f, "{}", x.2)?;
|
||||
match i.map_or_else(|| x.0, |n| NonZeroUsize::new(n.get() as usize)) {
|
||||
Some(n) => write!(f, " [{}]", n)?,
|
||||
Some(n) => write!(f, ", {}", n)?,
|
||||
_ => (),
|
||||
}
|
||||
write!(f, ") @ {:?}", pos)
|
||||
@ -1681,15 +1698,28 @@ impl fmt::Debug for Expr {
|
||||
Self::Property(x) => write!(f, "Property({:?} @ {:?})", x.2.name, x.2.pos),
|
||||
Self::Stmt(x) => {
|
||||
f.write_str("Stmt")?;
|
||||
f.debug_list().entries(x.statements.iter()).finish()?;
|
||||
write!(f, " @ {:?}", x.pos)
|
||||
f.debug_list().entries(x.0.iter()).finish()?;
|
||||
write!(f, " @ {:?}", x.1)
|
||||
}
|
||||
Self::FnCall(x, pos) => {
|
||||
f.debug_tuple("FnCall").field(x).finish()?;
|
||||
let mut ff = f.debug_struct("FnCall");
|
||||
if let Some(ref ns) = x.namespace {
|
||||
ff.field("namespace", ns);
|
||||
}
|
||||
ff.field("name", &x.name)
|
||||
.field("hash", &x.hash)
|
||||
.field("args", &x.args);
|
||||
if !x.constant_args.is_empty() {
|
||||
ff.field("constant_args", &x.constant_args);
|
||||
}
|
||||
if x.capture {
|
||||
ff.field("capture", &x.capture);
|
||||
}
|
||||
ff.finish()?;
|
||||
write!(f, " @ {:?}", pos)
|
||||
}
|
||||
Self::Dot(x, pos) | Self::Index(x, pos) | Self::And(x, pos) | Self::Or(x, pos) => {
|
||||
let op = match self {
|
||||
let op_name = match self {
|
||||
Self::Dot(_, _) => "Dot",
|
||||
Self::Index(_, _) => "Index",
|
||||
Self::And(_, _) => "And",
|
||||
@ -1697,7 +1727,7 @@ impl fmt::Debug for Expr {
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
f.debug_struct(op)
|
||||
f.debug_struct(op_name)
|
||||
.field("lhs", &x.lhs)
|
||||
.field("rhs", &x.rhs)
|
||||
.finish()?;
|
||||
@ -1783,7 +1813,7 @@ impl Expr {
|
||||
Self::Array(_, pos) => *pos,
|
||||
Self::Map(_, pos) => *pos,
|
||||
Self::Property(x) => (x.2).pos,
|
||||
Self::Stmt(x) => x.pos,
|
||||
Self::Stmt(x) => x.1,
|
||||
Self::Variable(_, pos, _) => *pos,
|
||||
Self::FnCall(_, pos) => *pos,
|
||||
|
||||
@ -1816,7 +1846,7 @@ impl Expr {
|
||||
Self::Map(_, pos) => *pos = new_pos,
|
||||
Self::Variable(_, pos, _) => *pos = new_pos,
|
||||
Self::Property(x) => (x.2).pos = new_pos,
|
||||
Self::Stmt(x) => x.pos = new_pos,
|
||||
Self::Stmt(x) => x.1 = new_pos,
|
||||
Self::FnCall(_, pos) => *pos = new_pos,
|
||||
Self::And(_, pos) | Self::Or(_, pos) => *pos = new_pos,
|
||||
Self::Unit(pos) => *pos = new_pos,
|
||||
@ -1840,7 +1870,7 @@ impl Expr {
|
||||
x.lhs.is_pure() && x.rhs.is_pure()
|
||||
}
|
||||
|
||||
Self::Stmt(x) => x.statements.iter().all(Stmt::is_pure),
|
||||
Self::Stmt(x) => x.0.iter().all(Stmt::is_pure),
|
||||
|
||||
Self::Variable(_, _, _) => true,
|
||||
|
||||
@ -1946,7 +1976,7 @@ impl Expr {
|
||||
|
||||
match self {
|
||||
Self::Stmt(x) => {
|
||||
for s in &x.statements {
|
||||
for s in &x.0 {
|
||||
if !s.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
|
102
src/engine.rs
102
src/engine.rs
@ -1,6 +1,6 @@
|
||||
//! Main module defining the script evaluation [`Engine`].
|
||||
|
||||
use crate::ast::{Expr, FnCallExpr, Ident, OpAssignment, ReturnType, Stmt, StmtBlock};
|
||||
use crate::ast::{Expr, FnCallExpr, Ident, OpAssignment, ReturnType, Stmt};
|
||||
use crate::dynamic::{map_std_type_name, AccessMode, Union, Variant};
|
||||
use crate::fn_native::{
|
||||
CallableFunction, IteratorFn, OnDebugCallback, OnPrintCallback, OnProgressCallback,
|
||||
@ -1733,8 +1733,7 @@ impl Engine {
|
||||
// Statement block
|
||||
Expr::Stmt(x) if x.is_empty() => Ok(Dynamic::UNIT),
|
||||
Expr::Stmt(x) => {
|
||||
let statements = &x.statements;
|
||||
self.eval_stmt_block(scope, mods, state, lib, this_ptr, statements, true, level)
|
||||
self.eval_stmt_block(scope, mods, state, lib, this_ptr, x, true, level)
|
||||
}
|
||||
|
||||
// lhs[idx_expr]
|
||||
@ -2134,40 +2133,29 @@ impl Engine {
|
||||
}
|
||||
|
||||
// If statement
|
||||
Stmt::If(expr, x, _) => {
|
||||
let (
|
||||
StmtBlock {
|
||||
statements: if_stmt,
|
||||
..
|
||||
},
|
||||
StmtBlock {
|
||||
statements: else_stmt,
|
||||
..
|
||||
},
|
||||
) = x.as_ref();
|
||||
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
||||
Stmt::If(expr, x, _) => self
|
||||
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
||||
.as_bool()
|
||||
.map_err(|err| self.make_type_mismatch_err::<bool>(err, expr.position()))
|
||||
.and_then(|guard_val| {
|
||||
if guard_val {
|
||||
if !if_stmt.is_empty() {
|
||||
if !x.0.is_empty() {
|
||||
self.eval_stmt_block(
|
||||
scope, mods, state, lib, this_ptr, if_stmt, true, level,
|
||||
scope, mods, state, lib, this_ptr, &x.0, true, level,
|
||||
)
|
||||
} else {
|
||||
Ok(Dynamic::UNIT)
|
||||
}
|
||||
} else {
|
||||
if !else_stmt.is_empty() {
|
||||
if !x.1.is_empty() {
|
||||
self.eval_stmt_block(
|
||||
scope, mods, state, lib, this_ptr, else_stmt, true, level,
|
||||
scope, mods, state, lib, this_ptr, &x.1, true, level,
|
||||
)
|
||||
} else {
|
||||
Ok(Dynamic::UNIT)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}),
|
||||
|
||||
// Switch statement
|
||||
Stmt::Switch(match_expr, x, _) => {
|
||||
@ -2180,16 +2168,33 @@ impl Engine {
|
||||
value.hash(hasher);
|
||||
let hash = hasher.finish();
|
||||
|
||||
table.get(&hash).map(|t| {
|
||||
let statements = &t.statements;
|
||||
table.get(&hash).and_then(|t| {
|
||||
if let Some(condition) = &t.0 {
|
||||
match self
|
||||
.eval_expr(scope, mods, state, lib, this_ptr, &condition, level)
|
||||
.and_then(|v| {
|
||||
v.as_bool().map_err(|err| {
|
||||
self.make_type_mismatch_err::<bool>(
|
||||
err,
|
||||
condition.position(),
|
||||
)
|
||||
})
|
||||
}) {
|
||||
Ok(true) => (),
|
||||
Ok(false) => return None,
|
||||
Err(err) => return Some(Err(err)),
|
||||
}
|
||||
}
|
||||
|
||||
if !statements.is_empty() {
|
||||
let statements = &t.1;
|
||||
|
||||
Some(if !statements.is_empty() {
|
||||
self.eval_stmt_block(
|
||||
scope, mods, state, lib, this_ptr, statements, true, level,
|
||||
)
|
||||
} else {
|
||||
Ok(Dynamic::UNIT)
|
||||
}
|
||||
})
|
||||
})
|
||||
} else {
|
||||
// Non-hashable values never match any specific clause
|
||||
@ -2197,7 +2202,6 @@ impl Engine {
|
||||
}
|
||||
.unwrap_or_else(|| {
|
||||
// Default match clause
|
||||
let def_stmt = &def_stmt.statements;
|
||||
if !def_stmt.is_empty() {
|
||||
self.eval_stmt_block(
|
||||
scope, mods, state, lib, this_ptr, def_stmt, true, level,
|
||||
@ -2209,15 +2213,11 @@ impl Engine {
|
||||
}
|
||||
|
||||
// While loop
|
||||
Stmt::While(expr, body, _) => {
|
||||
let body = &body.statements;
|
||||
loop {
|
||||
Stmt::While(expr, body, _) => loop {
|
||||
let condition = if !expr.is_unit() {
|
||||
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
||||
.as_bool()
|
||||
.map_err(|err| {
|
||||
self.make_type_mismatch_err::<bool>(err, expr.position())
|
||||
})?
|
||||
.map_err(|err| self.make_type_mismatch_err::<bool>(err, expr.position()))?
|
||||
} else {
|
||||
true
|
||||
};
|
||||
@ -2229,8 +2229,7 @@ impl Engine {
|
||||
continue;
|
||||
}
|
||||
|
||||
match self.eval_stmt_block(scope, mods, state, lib, this_ptr, body, true, level)
|
||||
{
|
||||
match self.eval_stmt_block(scope, mods, state, lib, this_ptr, body, true, level) {
|
||||
Ok(_) => (),
|
||||
Err(err) => match *err {
|
||||
EvalAltResult::LoopBreak(false, _) => (),
|
||||
@ -2238,17 +2237,12 @@ impl Engine {
|
||||
_ => return Err(err),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Do loop
|
||||
Stmt::Do(body, expr, is_while, _) => {
|
||||
let body = &body.statements;
|
||||
|
||||
loop {
|
||||
Stmt::Do(body, expr, is_while, _) => loop {
|
||||
if !body.is_empty() {
|
||||
match self
|
||||
.eval_stmt_block(scope, mods, state, lib, this_ptr, body, true, level)
|
||||
match self.eval_stmt_block(scope, mods, state, lib, this_ptr, body, true, level)
|
||||
{
|
||||
Ok(_) => (),
|
||||
Err(err) => match *err {
|
||||
@ -2272,12 +2266,12 @@ impl Engine {
|
||||
return Ok(Dynamic::UNIT);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// For loop
|
||||
Stmt::For(expr, x, _) => {
|
||||
let (Ident { name, .. }, StmtBlock { statements, pos }) = x.as_ref();
|
||||
let (Ident { name, .. }, statements) = x.as_ref();
|
||||
let pos = statements.position();
|
||||
let iter_obj = self
|
||||
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
||||
.flatten();
|
||||
@ -2326,7 +2320,7 @@ impl Engine {
|
||||
*loop_var = value;
|
||||
}
|
||||
|
||||
self.inc_operations(state, *pos)?;
|
||||
self.inc_operations(state, pos)?;
|
||||
|
||||
if statements.is_empty() {
|
||||
continue;
|
||||
@ -2360,20 +2354,10 @@ impl Engine {
|
||||
|
||||
// Try/Catch statement
|
||||
Stmt::TryCatch(x, _, _) => {
|
||||
let (
|
||||
StmtBlock {
|
||||
statements: try_body,
|
||||
..
|
||||
},
|
||||
err_var,
|
||||
StmtBlock {
|
||||
statements: catch_body,
|
||||
..
|
||||
},
|
||||
) = x.as_ref();
|
||||
let (try_stmt, err_var, catch_stmt) = x.as_ref();
|
||||
|
||||
let result = self
|
||||
.eval_stmt_block(scope, mods, state, lib, this_ptr, try_body, true, level)
|
||||
.eval_stmt_block(scope, mods, state, lib, this_ptr, try_stmt, true, level)
|
||||
.map(|_| Dynamic::UNIT);
|
||||
|
||||
match result {
|
||||
@ -2433,7 +2417,7 @@ impl Engine {
|
||||
}
|
||||
|
||||
let result = self.eval_stmt_block(
|
||||
scope, mods, state, lib, this_ptr, catch_body, true, level,
|
||||
scope, mods, state, lib, this_ptr, catch_stmt, true, level,
|
||||
);
|
||||
|
||||
state.scope_level -= 1;
|
||||
|
@ -540,7 +540,7 @@ impl Engine {
|
||||
}
|
||||
|
||||
// Evaluate the function
|
||||
let body = &fn_def.body.statements;
|
||||
let body = &fn_def.body;
|
||||
|
||||
let result = self
|
||||
.eval_stmt_block(scope, mods, state, unified_lib, this_ptr, body, true, level)
|
||||
|
183
src/optimize.rs
183
src/optimize.rs
@ -1,6 +1,6 @@
|
||||
//! Module implementing the [`AST`] optimizer.
|
||||
|
||||
use crate::ast::{Expr, Stmt, StmtBlock};
|
||||
use crate::ast::{Expr, Stmt};
|
||||
use crate::dynamic::AccessMode;
|
||||
use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF};
|
||||
use crate::fn_builtin::get_builtin_binary_op_fn;
|
||||
@ -416,73 +416,123 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
|
||||
// if false { if_block } else { else_block } -> else_block
|
||||
Stmt::If(Expr::BoolConstant(false, _), x, _) => {
|
||||
state.set_dirty();
|
||||
let else_block = mem::take(&mut x.1.statements).into_vec();
|
||||
let else_block = mem::take(&mut *x.1).into_vec();
|
||||
*stmt = match optimize_stmt_block(else_block, state, preserve_result, true, false) {
|
||||
statements if statements.is_empty() => Stmt::Noop(x.1.pos),
|
||||
statements => Stmt::Block(statements, x.1.pos),
|
||||
statements if statements.is_empty() => Stmt::Noop(x.1.position()),
|
||||
statements => Stmt::Block(statements, x.1.position()),
|
||||
}
|
||||
}
|
||||
// if true { if_block } else { else_block } -> if_block
|
||||
Stmt::If(Expr::BoolConstant(true, _), x, _) => {
|
||||
state.set_dirty();
|
||||
let if_block = mem::take(&mut x.0.statements).into_vec();
|
||||
let if_block = mem::take(&mut *x.0).into_vec();
|
||||
*stmt = match optimize_stmt_block(if_block, state, preserve_result, true, false) {
|
||||
statements if statements.is_empty() => Stmt::Noop(x.0.pos),
|
||||
statements => Stmt::Block(statements, x.0.pos),
|
||||
statements if statements.is_empty() => Stmt::Noop(x.0.position()),
|
||||
statements => Stmt::Block(statements, x.0.position()),
|
||||
}
|
||||
}
|
||||
// if expr { if_block } else { else_block }
|
||||
Stmt::If(condition, x, _) => {
|
||||
optimize_expr(condition, state);
|
||||
let if_block = mem::take(&mut x.0.statements).into_vec();
|
||||
x.0.statements =
|
||||
let if_block = mem::take(x.0.statements()).into_vec();
|
||||
*x.0.statements() =
|
||||
optimize_stmt_block(if_block, state, preserve_result, true, false).into();
|
||||
let else_block = mem::take(&mut x.1.statements).into_vec();
|
||||
x.1.statements =
|
||||
let else_block = mem::take(x.1.statements()).into_vec();
|
||||
*x.1.statements() =
|
||||
optimize_stmt_block(else_block, state, preserve_result, true, false).into();
|
||||
}
|
||||
|
||||
// switch const { ... }
|
||||
Stmt::Switch(expr, x, pos) if expr.is_constant() => {
|
||||
let value = expr.get_constant_value().unwrap();
|
||||
Stmt::Switch(match_expr, x, pos) if match_expr.is_constant() => {
|
||||
let value = match_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;
|
||||
|
||||
let (statements, new_pos) = if let Some(block) = table.get_mut(&hash) {
|
||||
let match_block = mem::take(&mut block.statements).into_vec();
|
||||
(
|
||||
optimize_stmt_block(match_block, state, true, true, false).into(),
|
||||
block.pos,
|
||||
)
|
||||
if let Some(block) = table.get_mut(&hash) {
|
||||
if let Some(mut condition) = mem::take(&mut block.0) {
|
||||
// switch const { case if condition => stmt, _ => def } => if condition { stmt } else { def }
|
||||
optimize_expr(&mut condition, state);
|
||||
|
||||
let def_block = mem::take(&mut *x.1).into_vec();
|
||||
let def_stmt = optimize_stmt_block(def_block, state, true, true, false);
|
||||
let def_pos = if x.1.position().is_none() {
|
||||
*pos
|
||||
} else {
|
||||
let def_block = mem::take(&mut x.1.statements).into_vec();
|
||||
(
|
||||
optimize_stmt_block(def_block, state, true, true, false).into(),
|
||||
if x.1.pos.is_none() { *pos } else { x.1.pos },
|
||||
)
|
||||
x.1.position()
|
||||
};
|
||||
|
||||
*expr = Expr::Stmt(Box::new(StmtBlock {
|
||||
statements,
|
||||
pos: new_pos,
|
||||
}));
|
||||
*stmt = Stmt::If(
|
||||
condition,
|
||||
Box::new((
|
||||
mem::take(&mut block.1),
|
||||
Stmt::Block(def_stmt, def_pos).into(),
|
||||
)),
|
||||
match_expr.position(),
|
||||
);
|
||||
} else {
|
||||
// Promote the matched case
|
||||
let new_pos = block.1.position();
|
||||
let statements = mem::take(&mut *block.1);
|
||||
let statements =
|
||||
optimize_stmt_block(statements.into_vec(), state, true, true, false);
|
||||
*stmt = Stmt::Block(statements, new_pos);
|
||||
}
|
||||
} else {
|
||||
// Promote the default case
|
||||
let def_block = mem::take(&mut *x.1).into_vec();
|
||||
let def_stmt = optimize_stmt_block(def_block, state, true, true, false);
|
||||
let def_pos = if x.1.position().is_none() {
|
||||
*pos
|
||||
} else {
|
||||
x.1.position()
|
||||
};
|
||||
*stmt = Stmt::Block(def_stmt, def_pos);
|
||||
}
|
||||
}
|
||||
// switch
|
||||
Stmt::Switch(expr, x, _) => {
|
||||
optimize_expr(expr, state);
|
||||
Stmt::Switch(match_expr, x, _) => {
|
||||
optimize_expr(match_expr, state);
|
||||
x.0.values_mut().for_each(|block| {
|
||||
let match_block = mem::take(&mut block.statements).into_vec();
|
||||
block.statements =
|
||||
optimize_stmt_block(match_block, state, preserve_result, true, false).into()
|
||||
let condition = if let Some(mut condition) = mem::take(&mut block.0) {
|
||||
optimize_expr(&mut condition, state);
|
||||
condition
|
||||
} else {
|
||||
Expr::Unit(Position::NONE)
|
||||
};
|
||||
|
||||
match condition {
|
||||
Expr::Unit(_) | Expr::BoolConstant(true, _) => (),
|
||||
_ => {
|
||||
block.0 = Some(condition);
|
||||
|
||||
*block.1.statements() = optimize_stmt_block(
|
||||
mem::take(block.1.statements()).into_vec(),
|
||||
state,
|
||||
preserve_result,
|
||||
true,
|
||||
false,
|
||||
)
|
||||
.into();
|
||||
}
|
||||
}
|
||||
});
|
||||
let def_block = mem::take(&mut x.1.statements).into_vec();
|
||||
x.1.statements =
|
||||
optimize_stmt_block(def_block, state, preserve_result, true, false).into()
|
||||
|
||||
// Remove false cases
|
||||
while let Some((&key, _)) = x.0.iter().find(|(_, block)| match block.0 {
|
||||
Some(Expr::BoolConstant(false, _)) => true,
|
||||
_ => false,
|
||||
}) {
|
||||
state.set_dirty();
|
||||
x.0.remove(&key);
|
||||
}
|
||||
|
||||
let def_block = mem::take(x.1.statements()).into_vec();
|
||||
*x.1.statements() =
|
||||
optimize_stmt_block(def_block, state, preserve_result, true, false).into();
|
||||
}
|
||||
|
||||
// while false { block } -> Noop
|
||||
@ -493,12 +543,11 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
|
||||
// while expr { block }
|
||||
Stmt::While(condition, body, _) => {
|
||||
optimize_expr(condition, state);
|
||||
|
||||
let block = mem::take(&mut body.statements).into_vec();
|
||||
body.statements = optimize_stmt_block(block, state, false, true, false).into();
|
||||
let block = mem::take(body.statements()).into_vec();
|
||||
*body.statements() = optimize_stmt_block(block, state, false, true, false).into();
|
||||
|
||||
if body.len() == 1 {
|
||||
match body.statements[0] {
|
||||
match body[0] {
|
||||
// while expr { break; } -> { expr; }
|
||||
Stmt::Break(pos) => {
|
||||
// Only a single break statement - turn into running the guard expression once
|
||||
@ -521,23 +570,24 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
|
||||
Stmt::Do(body, Expr::BoolConstant(true, _), false, _)
|
||||
| Stmt::Do(body, Expr::BoolConstant(false, _), true, _) => {
|
||||
state.set_dirty();
|
||||
let block = mem::take(&mut body.statements).into_vec();
|
||||
let block_pos = body.position();
|
||||
let block = mem::take(body.statements()).into_vec();
|
||||
*stmt = Stmt::Block(
|
||||
optimize_stmt_block(block, state, false, true, false),
|
||||
body.pos,
|
||||
block_pos,
|
||||
);
|
||||
}
|
||||
// do { block } while|until expr
|
||||
Stmt::Do(body, condition, _, _) => {
|
||||
optimize_expr(condition, state);
|
||||
let block = mem::take(&mut body.statements).into_vec();
|
||||
body.statements = optimize_stmt_block(block, state, false, true, false).into();
|
||||
let block = mem::take(body.statements()).into_vec();
|
||||
*body.statements() = optimize_stmt_block(block, state, false, true, false).into();
|
||||
}
|
||||
// for id in expr { block }
|
||||
Stmt::For(iterable, x, _) => {
|
||||
optimize_expr(iterable, state);
|
||||
let body = mem::take(&mut x.1.statements).into_vec();
|
||||
x.1.statements = optimize_stmt_block(body, state, false, true, false).into();
|
||||
let body = mem::take(x.1.statements()).into_vec();
|
||||
*x.1.statements() = optimize_stmt_block(body, state, false, true, false).into();
|
||||
}
|
||||
// let id = expr;
|
||||
Stmt::Let(expr, _, _, _) => optimize_expr(expr, state),
|
||||
@ -563,31 +613,32 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
|
||||
}
|
||||
}
|
||||
// try { pure try_block } catch ( var ) { catch_block } -> try_block
|
||||
Stmt::TryCatch(x, _, _) if x.0.statements.iter().all(Stmt::is_pure) => {
|
||||
Stmt::TryCatch(x, _, _) if x.0.iter().all(Stmt::is_pure) => {
|
||||
// If try block is pure, there will never be any exceptions
|
||||
state.set_dirty();
|
||||
let try_block = mem::take(&mut x.0.statements).into_vec();
|
||||
let try_pos = x.0.position();
|
||||
let try_block = mem::take(&mut *x.0).into_vec();
|
||||
*stmt = Stmt::Block(
|
||||
optimize_stmt_block(try_block, state, false, true, false),
|
||||
x.0.pos,
|
||||
try_pos,
|
||||
);
|
||||
}
|
||||
// try { try_block } catch ( var ) { catch_block }
|
||||
Stmt::TryCatch(x, _, _) => {
|
||||
let try_block = mem::take(&mut x.0.statements).into_vec();
|
||||
x.0.statements = optimize_stmt_block(try_block, state, false, true, false).into();
|
||||
let catch_block = mem::take(&mut x.2.statements).into_vec();
|
||||
x.2.statements = optimize_stmt_block(catch_block, state, false, true, false).into();
|
||||
let try_block = mem::take(x.0.statements()).into_vec();
|
||||
*x.0.statements() = optimize_stmt_block(try_block, state, false, true, false).into();
|
||||
let catch_block = mem::take(x.2.statements()).into_vec();
|
||||
*x.2.statements() = optimize_stmt_block(catch_block, state, false, true, false).into();
|
||||
}
|
||||
// {}
|
||||
Stmt::Expr(Expr::Stmt(x)) if x.statements.is_empty() => {
|
||||
Stmt::Expr(Expr::Stmt(x)) if x.is_empty() => {
|
||||
state.set_dirty();
|
||||
*stmt = Stmt::Noop(x.pos);
|
||||
*stmt = Stmt::Noop(x.position());
|
||||
}
|
||||
// {...};
|
||||
Stmt::Expr(Expr::Stmt(x)) => {
|
||||
state.set_dirty();
|
||||
*stmt = Stmt::Block(mem::take(&mut x.statements).into_vec(), x.pos);
|
||||
*stmt = mem::take(x.as_mut()).into();
|
||||
}
|
||||
// expr;
|
||||
Stmt::Expr(expr) => optimize_expr(expr, state),
|
||||
@ -610,13 +661,13 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
|
||||
|
||||
match expr {
|
||||
// {}
|
||||
Expr::Stmt(x) if x.statements.is_empty() => { state.set_dirty(); *expr = Expr::Unit(x.pos) }
|
||||
Expr::Stmt(x) if x.is_empty() => { state.set_dirty(); *expr = Expr::Unit(x.position()) }
|
||||
// { stmt; ... } - do not count promotion as dirty because it gets turned back into an array
|
||||
Expr::Stmt(x) => {
|
||||
x.statements = optimize_stmt_block(mem::take(&mut x.statements).into_vec(), state, true, true, false).into();
|
||||
*x.statements() = optimize_stmt_block(mem::take(x.statements()).into_vec(), state, true, true, false).into();
|
||||
|
||||
// { Stmt(Expr) } - promote
|
||||
match x.statements.as_mut() {
|
||||
match x.as_mut().as_mut() {
|
||||
[ Stmt::Expr(e) ] => { state.set_dirty(); *expr = mem::take(e); }
|
||||
_ => ()
|
||||
}
|
||||
@ -1034,19 +1085,13 @@ pub fn optimize_into_ast(
|
||||
.map(|fn_def| {
|
||||
let mut fn_def = crate::fn_native::shared_take_or_clone(fn_def);
|
||||
|
||||
let pos = fn_def.body.pos;
|
||||
|
||||
let mut body = fn_def.body.statements.into_vec();
|
||||
|
||||
// Optimize the function body
|
||||
let state = &mut State::new(engine, lib2, level);
|
||||
|
||||
body = optimize_stmt_block(body, state, true, true, true);
|
||||
let body = mem::take(fn_def.body.statements()).into_vec();
|
||||
|
||||
fn_def.body = StmtBlock {
|
||||
statements: body.into(),
|
||||
pos,
|
||||
};
|
||||
*fn_def.body.statements() =
|
||||
optimize_stmt_block(body, state, true, true, true).into();
|
||||
|
||||
fn_def
|
||||
})
|
||||
|
@ -113,8 +113,12 @@ pub enum ParseErrorType {
|
||||
///
|
||||
/// Never appears under the `no_object` feature.
|
||||
DuplicatedProperty(String),
|
||||
/// A switch case is duplicated.
|
||||
/// A `switch` case is duplicated.
|
||||
DuplicatedSwitchCase,
|
||||
/// The default case of a `switch` statement is not the last.
|
||||
WrongSwitchDefaultCase,
|
||||
/// The case condition of a `switch` statement is not appropriate.
|
||||
WrongSwitchCaseCondition,
|
||||
/// Missing a property name for custom types and maps.
|
||||
///
|
||||
/// Never appears under the `no_object` feature.
|
||||
@ -195,6 +199,8 @@ impl ParseErrorType {
|
||||
Self::MalformedCapture(_) => "Invalid capturing",
|
||||
Self::DuplicatedProperty(_) => "Duplicated property in object map literal",
|
||||
Self::DuplicatedSwitchCase => "Duplicated switch case",
|
||||
Self::WrongSwitchDefaultCase => "Default switch case is not the last",
|
||||
Self::WrongSwitchCaseCondition => "Default switch case cannot have condition",
|
||||
Self::PropertyExpected => "Expecting name of a property",
|
||||
Self::VariableExpected => "Expecting name of a variable",
|
||||
Self::Reserved(_) => "Invalid use of reserved keyword",
|
||||
|
@ -808,13 +808,14 @@ fn parse_switch(
|
||||
}
|
||||
}
|
||||
|
||||
let mut table = BTreeMap::<u64, Box<StmtBlock>>::new();
|
||||
let mut table = BTreeMap::<u64, Box<(Option<Expr>, StmtBlock)>>::new();
|
||||
let mut def_pos = Position::NONE;
|
||||
let mut def_stmt = None;
|
||||
|
||||
loop {
|
||||
const MISSING_RBRACE: &str = "to end this switch block";
|
||||
|
||||
let expr = match input.peek().unwrap() {
|
||||
let (expr, condition) = match input.peek().unwrap() {
|
||||
(Token::RightBrace, _) => {
|
||||
eat_token(input, Token::RightBrace);
|
||||
break;
|
||||
@ -825,12 +826,31 @@ fn parse_switch(
|
||||
.into_err(*pos),
|
||||
)
|
||||
}
|
||||
(Token::Underscore, _) if def_stmt.is_none() => {
|
||||
(Token::Underscore, pos) if def_stmt.is_none() => {
|
||||
def_pos = *pos;
|
||||
eat_token(input, Token::Underscore);
|
||||
None
|
||||
|
||||
let (if_clause, if_pos) = match_token(input, Token::If);
|
||||
|
||||
if if_clause {
|
||||
return Err(PERR::WrongSwitchCaseCondition.into_err(if_pos));
|
||||
}
|
||||
|
||||
(None, None)
|
||||
}
|
||||
(Token::Underscore, pos) => return Err(PERR::DuplicatedSwitchCase.into_err(*pos)),
|
||||
_ => Some(parse_expr(input, state, lib, settings.level_up())?),
|
||||
_ if def_stmt.is_some() => return Err(PERR::WrongSwitchDefaultCase.into_err(def_pos)),
|
||||
|
||||
_ => {
|
||||
let case_expr = Some(parse_expr(input, state, lib, settings.level_up())?);
|
||||
|
||||
let condition = if match_token(input, Token::If).0 {
|
||||
Some(parse_expr(input, state, lib, settings.level_up())?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
(case_expr, condition)
|
||||
}
|
||||
};
|
||||
|
||||
let hash = if let Some(expr) = expr {
|
||||
@ -868,7 +888,7 @@ fn parse_switch(
|
||||
let need_comma = !stmt.is_self_terminated();
|
||||
|
||||
def_stmt = if let Some(hash) = hash {
|
||||
table.insert(hash, Box::new(stmt.into()));
|
||||
table.insert(hash, Box::new((condition, stmt.into())));
|
||||
None
|
||||
} else {
|
||||
Some(stmt.into())
|
||||
@ -2875,7 +2895,7 @@ fn make_curry_from_externals(
|
||||
let mut statements: StaticVec<_> = Default::default();
|
||||
statements.extend(externals.into_iter().map(Stmt::Share));
|
||||
statements.push(Stmt::Expr(expr));
|
||||
Expr::Stmt(Box::new(StmtBlock { statements, pos }))
|
||||
Expr::Stmt(Box::new(StmtBlock::new(statements, pos)))
|
||||
}
|
||||
|
||||
/// Parse an anonymous function definition.
|
||||
|
28
src/token.rs
28
src/token.rs
@ -866,14 +866,10 @@ pub trait InputStream {
|
||||
/// Returns the parsed string and a boolean indicating whether the string is
|
||||
/// terminated by an interpolation `${`.
|
||||
///
|
||||
/// # Volatile API
|
||||
///
|
||||
/// This function is volatile and may change.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// |Type |Return Value |`state.is_within_text_terminated_by`|
|
||||
/// |---------------------------------|----------------------------|------------------------------------|
|
||||
/// |---------------------------------|:--------------------------:|:----------------------------------:|
|
||||
/// |`"hello"` |`StringConstant("hello")` |`None` |
|
||||
/// |`"hello`_{LF}_ or _{EOF}_ |`LexError` |`None` |
|
||||
/// |`"hello\`_{EOF}_ or _{LF}{EOF}_ |`StringConstant("hello")` |`Some('"')` |
|
||||
@ -882,6 +878,22 @@ pub trait InputStream {
|
||||
/// |`` `hello ${`` |`InterpolatedString("hello ")`<br/>next token is `{`|`None` |
|
||||
/// |`` } hello` `` |`StringConstant(" hello")` |`None` |
|
||||
/// |`} hello`_{EOF}_ |`StringConstant(" hello")` |``Some('`')`` |
|
||||
///
|
||||
/// This function does not throw a `LexError` for the following conditions:
|
||||
///
|
||||
/// * Unterminated literal string at _{EOF}_
|
||||
///
|
||||
/// * Unterminated normal string with continuation at _{EOF}_
|
||||
///
|
||||
/// This is to facilitate using this function to parse a script line-by-line, where the end of the
|
||||
/// line (i.e. _{EOF}_) is not necessarily the end of the script.
|
||||
///
|
||||
/// Any time a [`StringConstant`][`Token::StringConstant`] is returned with
|
||||
/// `state.is_within_text_terminated_by` set to `Some(_)` is one of the above conditions.
|
||||
///
|
||||
/// # Volatile API
|
||||
///
|
||||
/// This function is volatile and may change.
|
||||
pub fn parse_string_literal(
|
||||
stream: &mut impl InputStream,
|
||||
state: &mut TokenizeState,
|
||||
@ -1947,7 +1959,11 @@ impl<'a> Iterator for TokenIterator<'a> {
|
||||
let (token, pos) = match get_next_token(&mut self.stream, &mut self.state, &mut self.pos) {
|
||||
// {EOF}
|
||||
None => return None,
|
||||
// {EOF} after unterminated string
|
||||
// {EOF} after unterminated string.
|
||||
// The only case where `TokenizeState.is_within_text_terminated_by` is set is when
|
||||
// a verbatim string or a string with continuation encounters {EOF}.
|
||||
// This is necessary to handle such cases for line-by-line parsing, but for an entire
|
||||
// script it is a syntax error.
|
||||
Some((Token::StringConstant(_), pos)) if self.state.is_within_text_terminated_by.is_some() => {
|
||||
self.state.is_within_text_terminated_by = None;
|
||||
return Some((Token::LexError(LERR::UnterminatedString), pos));
|
||||
|
@ -1,4 +1,4 @@
|
||||
use rhai::{Engine, EvalAltResult, Scope, INT};
|
||||
use rhai::{Engine, EvalAltResult, ParseErrorType, Scope, INT};
|
||||
|
||||
#[test]
|
||||
fn test_switch() -> Result<(), Box<EvalAltResult>> {
|
||||
@ -67,6 +67,93 @@ fn test_switch() -> Result<(), Box<EvalAltResult>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
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 }")
|
||||
.expect_err("should error")
|
||||
.0,
|
||||
ParseErrorType::WrongSwitchDefaultCase
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_switch_condition() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
let mut scope = Scope::new();
|
||||
scope.push("x", 42 as INT);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval_with_scope::<INT>(
|
||||
&mut scope,
|
||||
r"
|
||||
switch x / 2 {
|
||||
21 if x > 40 => 1,
|
||||
0 if x < 100 => 2,
|
||||
1 => 3,
|
||||
_ => 9
|
||||
}
|
||||
"
|
||||
)?,
|
||||
1
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval_with_scope::<INT>(
|
||||
&mut scope,
|
||||
r"
|
||||
switch x / 2 {
|
||||
21 if x < 40 => 1,
|
||||
0 if x < 100 => 2,
|
||||
1 => 3,
|
||||
_ => 9
|
||||
}
|
||||
"
|
||||
)?,
|
||||
9
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.compile(
|
||||
r"
|
||||
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!(matches!(
|
||||
*engine
|
||||
.compile("switch x { 1 => 123, _ if true => 42 }")
|
||||
.expect_err("should error")
|
||||
.0,
|
||||
ParseErrorType::WrongSwitchCaseCondition
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
mod test_switch_enum {
|
||||
|
Loading…
Reference in New Issue
Block a user