Merge branch 'constants'
This commit is contained in:
commit
cb6171ebcd
25
README.md
25
README.md
@ -57,8 +57,8 @@ Related
|
|||||||
|
|
||||||
Other cool projects to check out:
|
Other cool projects to check out:
|
||||||
|
|
||||||
* [ChaiScript] - A strong inspiration for Rhai. An embedded scripting language for C++ that I helped created many moons ago, now being lead by my cousin.
|
* [ChaiScript](http://chaiscript.com/) - A strong inspiration for Rhai. An embedded scripting language for C++ that I helped created many moons ago, now being lead by my cousin.
|
||||||
* You can also check out the list of [scripting languages for Rust] on [awesome-rust].
|
* You can also check out the list of [scripting languages for Rust](https://github.com/rust-unofficial/awesome-rust#scripting) on [awesome-rust](https://github.com/rust-unofficial/awesome-rust)
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
--------
|
--------
|
||||||
@ -597,6 +597,17 @@ Variables in Rhai follow normal naming rules (i.e. must contain only ASCII lette
|
|||||||
let x = 3;
|
let x = 3;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Constants
|
||||||
|
---------
|
||||||
|
|
||||||
|
Constants can be defined and are immutable. Constants follow the same naming rules as [variables](#variables).
|
||||||
|
|
||||||
|
```rust
|
||||||
|
const x = 42;
|
||||||
|
print(x * 2); // prints 84
|
||||||
|
x = 123; // <- syntax error - cannot assign to constant
|
||||||
|
```
|
||||||
|
|
||||||
Numbers
|
Numbers
|
||||||
-------
|
-------
|
||||||
|
|
||||||
@ -1108,10 +1119,12 @@ The above script optimizes to:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Constant propagation is used to remove dead code:
|
Constants propagation is used to remove dead code:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
if true || some_work() { print("done!"); } // since '||' short-circuits, 'some_work' is never called
|
const abc = true;
|
||||||
|
if abc || some_work() { print("done!"); } // 'abc' is constant so it is replaced by 'true'...
|
||||||
|
if true || some_work() { print("done!"); } // since '||' short-circuits, 'some_work' is never called because the LHS is 'true'
|
||||||
if true { print("done!"); } // <-- the line above is equivalent to this
|
if true { print("done!"); } // <-- the line above is equivalent to this
|
||||||
print("done!"); // <-- the line above is further simplified to this
|
print("done!"); // <-- the line above is further simplified to this
|
||||||
// because the condition is always true
|
// because the condition is always true
|
||||||
@ -1169,10 +1182,6 @@ engine.set_optimization(false); // turn off the optimizer
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
[ChaiScript]: http://chaiscript.com/
|
|
||||||
[scripting languages for Rust]: https://github.com/rust-unofficial/awesome-rust#scripting
|
|
||||||
[awesome-rust]: https://github.com/rust-unofficial/awesome-rust
|
|
||||||
|
|
||||||
[`num-traits`]: https://crates.io/crates/num-traits/
|
[`num-traits`]: https://crates.io/crates/num-traits/
|
||||||
[`debug_msgs`]: #optional-features
|
[`debug_msgs`]: #optional-features
|
||||||
[`unchecked`]: #optional-features
|
[`unchecked`]: #optional-features
|
||||||
|
@ -73,7 +73,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Err(err) = engine
|
if let Err(err) = engine
|
||||||
.compile(&input)
|
.compile_with_scope(&scope, &input)
|
||||||
.map_err(EvalAltResult::ErrorParsing)
|
.map_err(EvalAltResult::ErrorParsing)
|
||||||
.and_then(|r| {
|
.and_then(|r| {
|
||||||
ast = Some(r);
|
ast = Some(r);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// This is a script to calculate prime numbers.
|
// This is a script to calculate prime numbers.
|
||||||
|
|
||||||
let MAX_NUMBER_TO_CHECK = 10000; // 1229 primes
|
const MAX_NUMBER_TO_CHECK = 10000; // 1229 primes
|
||||||
|
|
||||||
let prime_mask = [];
|
let prime_mask = [];
|
||||||
prime_mask.pad(MAX_NUMBER_TO_CHECK, true);
|
prime_mask.pad(MAX_NUMBER_TO_CHECK, true);
|
||||||
|
26
src/api.rs
26
src/api.rs
@ -101,8 +101,14 @@ impl<'e> Engine<'e> {
|
|||||||
|
|
||||||
/// Compile a string into an AST.
|
/// Compile a string into an AST.
|
||||||
pub fn compile(&self, input: &str) -> Result<AST, ParseError> {
|
pub fn compile(&self, input: &str) -> Result<AST, ParseError> {
|
||||||
|
self.compile_with_scope(&Scope::new(), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compile a string into an AST using own scope.
|
||||||
|
/// The scope is useful for passing constants into the script for optimization.
|
||||||
|
pub fn compile_with_scope(&self, scope: &Scope, input: &str) -> Result<AST, ParseError> {
|
||||||
let tokens_stream = lex(input);
|
let tokens_stream = lex(input);
|
||||||
parse(&mut tokens_stream.peekable(), self.optimize)
|
parse(&mut tokens_stream.peekable(), scope, self.optimize)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_file(path: PathBuf) -> Result<String, EvalAltResult> {
|
fn read_file(path: PathBuf) -> Result<String, EvalAltResult> {
|
||||||
@ -118,8 +124,20 @@ impl<'e> Engine<'e> {
|
|||||||
|
|
||||||
/// Compile a file into an AST.
|
/// Compile a file into an AST.
|
||||||
pub fn compile_file(&self, path: PathBuf) -> Result<AST, EvalAltResult> {
|
pub fn compile_file(&self, path: PathBuf) -> Result<AST, EvalAltResult> {
|
||||||
Self::read_file(path)
|
self.compile_file_with_scope(&Scope::new(), path)
|
||||||
.and_then(|contents| self.compile(&contents).map_err(|err| err.into()))
|
}
|
||||||
|
|
||||||
|
/// Compile a file into an AST using own scope.
|
||||||
|
/// The scope is useful for passing constants into the script for optimization.
|
||||||
|
pub fn compile_file_with_scope(
|
||||||
|
&self,
|
||||||
|
scope: &Scope,
|
||||||
|
path: PathBuf,
|
||||||
|
) -> Result<AST, EvalAltResult> {
|
||||||
|
Self::read_file(path).and_then(|contents| {
|
||||||
|
self.compile_with_scope(scope, &contents)
|
||||||
|
.map_err(|err| err.into())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate a file.
|
/// Evaluate a file.
|
||||||
@ -235,7 +253,7 @@ impl<'e> Engine<'e> {
|
|||||||
) -> Result<(), EvalAltResult> {
|
) -> Result<(), EvalAltResult> {
|
||||||
let tokens_stream = lex(input);
|
let tokens_stream = lex(input);
|
||||||
|
|
||||||
let ast = parse(&mut tokens_stream.peekable(), self.optimize)
|
let ast = parse(&mut tokens_stream.peekable(), scope, self.optimize)
|
||||||
.map_err(EvalAltResult::ErrorParsing)?;
|
.map_err(EvalAltResult::ErrorParsing)?;
|
||||||
|
|
||||||
self.consume_ast_with_scope(scope, retain_functions, &ast)
|
self.consume_ast_with_scope(scope, retain_functions, &ast)
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
use crate::any::{Any, AnyExt, Dynamic, Variant};
|
use crate::any::{Any, AnyExt, Dynamic, Variant};
|
||||||
use crate::parser::{Expr, FnDef, Position, ReturnType, Stmt};
|
use crate::parser::{Expr, FnDef, Position, ReturnType, Stmt};
|
||||||
use crate::result::EvalAltResult;
|
use crate::result::EvalAltResult;
|
||||||
use crate::scope::Scope;
|
use crate::scope::{Scope, VariableType};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
use crate::INT;
|
use crate::INT;
|
||||||
@ -148,7 +148,8 @@ impl Engine<'_> {
|
|||||||
fn_def
|
fn_def
|
||||||
.params
|
.params
|
||||||
.iter()
|
.iter()
|
||||||
.zip(args.iter().map(|x| (*x).into_dynamic())),
|
.zip(args.iter().map(|x| (*x).into_dynamic()))
|
||||||
|
.map(|(name, value)| (name, VariableType::Normal, value)),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Evaluate
|
// Evaluate
|
||||||
@ -255,7 +256,7 @@ impl Engine<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// xxx.id
|
// xxx.id
|
||||||
Expr::Identifier(id, pos) => {
|
Expr::Property(id, pos) => {
|
||||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||||
|
|
||||||
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
||||||
@ -266,7 +267,7 @@ impl Engine<'_> {
|
|||||||
Expr::Index(idx_lhs, idx_expr, idx_pos) => {
|
Expr::Index(idx_lhs, idx_expr, idx_pos) => {
|
||||||
let (val, _) = match idx_lhs.as_ref() {
|
let (val, _) = match idx_lhs.as_ref() {
|
||||||
// xxx.id[idx_expr]
|
// xxx.id[idx_expr]
|
||||||
Expr::Identifier(id, pos) => {
|
Expr::Property(id, pos) => {
|
||||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||||
(
|
(
|
||||||
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?,
|
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?,
|
||||||
@ -294,7 +295,7 @@ impl Engine<'_> {
|
|||||||
// xxx.dot_lhs.rhs
|
// xxx.dot_lhs.rhs
|
||||||
Expr::Dot(dot_lhs, rhs, _) => match dot_lhs.as_ref() {
|
Expr::Dot(dot_lhs, rhs, _) => match dot_lhs.as_ref() {
|
||||||
// xxx.id.rhs
|
// xxx.id.rhs
|
||||||
Expr::Identifier(id, pos) => {
|
Expr::Property(id, pos) => {
|
||||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||||
|
|
||||||
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
||||||
@ -305,7 +306,7 @@ impl Engine<'_> {
|
|||||||
Expr::Index(idx_lhs, idx_expr, idx_pos) => {
|
Expr::Index(idx_lhs, idx_expr, idx_pos) => {
|
||||||
let (val, _) = match idx_lhs.as_ref() {
|
let (val, _) = match idx_lhs.as_ref() {
|
||||||
// xxx.id[idx_expr].rhs
|
// xxx.id[idx_expr].rhs
|
||||||
Expr::Identifier(id, pos) => {
|
Expr::Property(id, pos) => {
|
||||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||||
(
|
(
|
||||||
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?,
|
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?,
|
||||||
@ -353,8 +354,8 @@ impl Engine<'_> {
|
|||||||
) -> Result<Dynamic, EvalAltResult> {
|
) -> Result<Dynamic, EvalAltResult> {
|
||||||
match dot_lhs {
|
match dot_lhs {
|
||||||
// id.???
|
// id.???
|
||||||
Expr::Identifier(id, pos) => {
|
Expr::Variable(id, pos) => {
|
||||||
let (src_idx, mut target) = Self::search_scope(scope, id, Ok, *pos)?;
|
let (src_idx, _, mut target) = Self::search_scope(scope, id, Ok, *pos)?;
|
||||||
let val = self.get_dot_val_helper(scope, target.as_mut(), dot_rhs);
|
let val = self.get_dot_val_helper(scope, target.as_mut(), dot_rhs);
|
||||||
|
|
||||||
// In case the expression mutated `target`, we need to update it back into the scope because it is cloned.
|
// In case the expression mutated `target`, we need to update it back into the scope because it is cloned.
|
||||||
@ -400,11 +401,11 @@ impl Engine<'_> {
|
|||||||
id: &str,
|
id: &str,
|
||||||
map: impl FnOnce(Dynamic) -> Result<T, EvalAltResult>,
|
map: impl FnOnce(Dynamic) -> Result<T, EvalAltResult>,
|
||||||
begin: Position,
|
begin: Position,
|
||||||
) -> Result<(usize, T), EvalAltResult> {
|
) -> Result<(usize, VariableType, T), EvalAltResult> {
|
||||||
scope
|
scope
|
||||||
.get(id)
|
.get(id)
|
||||||
.ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.into(), begin))
|
.ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.into(), begin))
|
||||||
.and_then(move |(idx, _, val)| map(val).map(|v| (idx, v)))
|
.and_then(move |(idx, _, var_type, val)| map(val).map(|v| (idx, var_type, v)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate the value of an index (must evaluate to INT)
|
/// Evaluate the value of an index (must evaluate to INT)
|
||||||
@ -481,13 +482,13 @@ impl Engine<'_> {
|
|||||||
|
|
||||||
match lhs {
|
match lhs {
|
||||||
// id[idx_expr]
|
// id[idx_expr]
|
||||||
Expr::Identifier(id, _) => Self::search_scope(
|
Expr::Variable(id, _) => Self::search_scope(
|
||||||
scope,
|
scope,
|
||||||
&id,
|
&id,
|
||||||
|val| self.get_indexed_value(&val, idx, idx_expr.position(), idx_pos),
|
|val| self.get_indexed_value(&val, idx, idx_expr.position(), idx_pos),
|
||||||
lhs.position(),
|
lhs.position(),
|
||||||
)
|
)
|
||||||
.map(|(src_idx, (val, src_type))| {
|
.map(|(src_idx, _, (val, src_type))| {
|
||||||
(src_type, Some((id.as_str(), src_idx)), idx as usize, val)
|
(src_type, Some((id.as_str(), src_idx)), idx as usize, val)
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@ -585,7 +586,7 @@ impl Engine<'_> {
|
|||||||
) -> Result<Dynamic, EvalAltResult> {
|
) -> Result<Dynamic, EvalAltResult> {
|
||||||
match dot_rhs {
|
match dot_rhs {
|
||||||
// xxx.id
|
// xxx.id
|
||||||
Expr::Identifier(id, pos) => {
|
Expr::Property(id, pos) => {
|
||||||
let set_fn_name = format!("{}{}", FUNC_SETTER, id);
|
let set_fn_name = format!("{}{}", FUNC_SETTER, id);
|
||||||
|
|
||||||
self.call_fn_raw(&set_fn_name, vec![this_ptr, new_val.as_mut()], None, *pos)
|
self.call_fn_raw(&set_fn_name, vec![this_ptr, new_val.as_mut()], None, *pos)
|
||||||
@ -596,7 +597,7 @@ impl Engine<'_> {
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(lhs, idx_expr, idx_pos) => match lhs.as_ref() {
|
Expr::Index(lhs, idx_expr, idx_pos) => match lhs.as_ref() {
|
||||||
// xxx.id[idx_expr]
|
// xxx.id[idx_expr]
|
||||||
Expr::Identifier(id, pos) => {
|
Expr::Property(id, pos) => {
|
||||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||||
|
|
||||||
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
||||||
@ -620,7 +621,7 @@ impl Engine<'_> {
|
|||||||
// xxx.lhs.{...}
|
// xxx.lhs.{...}
|
||||||
Expr::Dot(lhs, rhs, _) => match lhs.as_ref() {
|
Expr::Dot(lhs, rhs, _) => match lhs.as_ref() {
|
||||||
// xxx.id.rhs
|
// xxx.id.rhs
|
||||||
Expr::Identifier(id, pos) => {
|
Expr::Property(id, pos) => {
|
||||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||||
|
|
||||||
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
||||||
@ -640,7 +641,7 @@ impl Engine<'_> {
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(lhs, idx_expr, idx_pos) => match lhs.as_ref() {
|
Expr::Index(lhs, idx_expr, idx_pos) => match lhs.as_ref() {
|
||||||
// xxx.id[idx_expr].rhs
|
// xxx.id[idx_expr].rhs
|
||||||
Expr::Identifier(id, pos) => {
|
Expr::Property(id, pos) => {
|
||||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||||
|
|
||||||
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
||||||
@ -702,11 +703,23 @@ impl Engine<'_> {
|
|||||||
dot_rhs: &Expr,
|
dot_rhs: &Expr,
|
||||||
new_val: Dynamic,
|
new_val: Dynamic,
|
||||||
val_pos: Position,
|
val_pos: Position,
|
||||||
|
op_pos: Position,
|
||||||
) -> Result<Dynamic, EvalAltResult> {
|
) -> Result<Dynamic, EvalAltResult> {
|
||||||
match dot_lhs {
|
match dot_lhs {
|
||||||
// id.???
|
// id.???
|
||||||
Expr::Identifier(id, pos) => {
|
Expr::Variable(id, pos) => {
|
||||||
let (src_idx, mut target) = Self::search_scope(scope, id, Ok, *pos)?;
|
let (src_idx, var_type, mut target) = Self::search_scope(scope, id, Ok, *pos)?;
|
||||||
|
|
||||||
|
match var_type {
|
||||||
|
VariableType::Constant => {
|
||||||
|
return Err(EvalAltResult::ErrorAssignmentToConstant(
|
||||||
|
id.to_string(),
|
||||||
|
op_pos,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
let val =
|
let val =
|
||||||
self.set_dot_val_helper(scope, target.as_mut(), dot_rhs, new_val, val_pos);
|
self.set_dot_val_helper(scope, target.as_mut(), dot_rhs, new_val, val_pos);
|
||||||
|
|
||||||
@ -758,9 +771,10 @@ impl Engine<'_> {
|
|||||||
Expr::IntegerConstant(i, _) => Ok(i.into_dynamic()),
|
Expr::IntegerConstant(i, _) => Ok(i.into_dynamic()),
|
||||||
Expr::StringConstant(s, _) => Ok(s.into_dynamic()),
|
Expr::StringConstant(s, _) => Ok(s.into_dynamic()),
|
||||||
Expr::CharConstant(c, _) => Ok(c.into_dynamic()),
|
Expr::CharConstant(c, _) => Ok(c.into_dynamic()),
|
||||||
Expr::Identifier(id, pos) => {
|
Expr::Variable(id, pos) => {
|
||||||
Self::search_scope(scope, id, Ok, *pos).map(|(_, val)| val)
|
Self::search_scope(scope, id, Ok, *pos).map(|(_, _, val)| val)
|
||||||
}
|
}
|
||||||
|
Expr::Property(_, _) => panic!("unexpected property."),
|
||||||
|
|
||||||
// lhs[idx_expr]
|
// lhs[idx_expr]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
@ -775,19 +789,21 @@ impl Engine<'_> {
|
|||||||
Expr::Stmt(stmt, _) => self.eval_stmt(scope, stmt),
|
Expr::Stmt(stmt, _) => self.eval_stmt(scope, stmt),
|
||||||
|
|
||||||
// lhs = rhs
|
// lhs = rhs
|
||||||
Expr::Assignment(lhs, rhs, _) => {
|
Expr::Assignment(lhs, rhs, op_pos) => {
|
||||||
let rhs_val = self.eval_expr(scope, rhs)?;
|
let rhs_val = self.eval_expr(scope, rhs)?;
|
||||||
|
|
||||||
match lhs.as_ref() {
|
match lhs.as_ref() {
|
||||||
// name = rhs
|
// name = rhs
|
||||||
Expr::Identifier(name, pos) => {
|
Expr::Variable(name, pos) => match scope.get(name) {
|
||||||
if let Some((idx, _, _)) = scope.get(name) {
|
Some((idx, _, VariableType::Normal, _)) => {
|
||||||
*scope.get_mut(name, idx) = rhs_val;
|
*scope.get_mut(name, idx) = rhs_val;
|
||||||
Ok(().into_dynamic())
|
Ok(().into_dynamic())
|
||||||
} else {
|
|
||||||
Err(EvalAltResult::ErrorVariableNotFound(name.clone(), *pos))
|
|
||||||
}
|
}
|
||||||
}
|
Some((_, _, VariableType::Constant, _)) => Err(
|
||||||
|
EvalAltResult::ErrorAssignmentToConstant(name.to_string(), *op_pos),
|
||||||
|
),
|
||||||
|
_ => Err(EvalAltResult::ErrorVariableNotFound(name.clone(), *pos)),
|
||||||
|
},
|
||||||
|
|
||||||
// idx_lhs[idx_expr] = rhs
|
// idx_lhs[idx_expr] = rhs
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
@ -814,9 +830,15 @@ impl Engine<'_> {
|
|||||||
|
|
||||||
// dot_lhs.dot_rhs = rhs
|
// dot_lhs.dot_rhs = rhs
|
||||||
Expr::Dot(dot_lhs, dot_rhs, _) => {
|
Expr::Dot(dot_lhs, dot_rhs, _) => {
|
||||||
self.set_dot_val(scope, dot_lhs, dot_rhs, rhs_val, rhs.position())
|
self.set_dot_val(scope, dot_lhs, dot_rhs, rhs_val, rhs.position(), *op_pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Error assignment to constant
|
||||||
|
expr if expr.is_constant() => Err(EvalAltResult::ErrorAssignmentToConstant(
|
||||||
|
expr.get_value_str(),
|
||||||
|
lhs.position(),
|
||||||
|
)),
|
||||||
|
|
||||||
// Syntax error
|
// Syntax error
|
||||||
_ => Err(EvalAltResult::ErrorAssignmentToUnknownLHS(lhs.position())),
|
_ => Err(EvalAltResult::ErrorAssignmentToUnknownLHS(lhs.position())),
|
||||||
}
|
}
|
||||||
@ -1045,7 +1067,7 @@ impl Engine<'_> {
|
|||||||
// Let statement
|
// Let statement
|
||||||
Stmt::Let(name, Some(expr), _) => {
|
Stmt::Let(name, Some(expr), _) => {
|
||||||
let val = self.eval_expr(scope, expr)?;
|
let val = self.eval_expr(scope, expr)?;
|
||||||
scope.push_dynamic(name.clone(), val);
|
scope.push_dynamic(name.clone(), VariableType::Normal, val);
|
||||||
Ok(().into_dynamic())
|
Ok(().into_dynamic())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1053,6 +1075,15 @@ impl Engine<'_> {
|
|||||||
scope.push(name.clone(), ());
|
scope.push(name.clone(), ());
|
||||||
Ok(().into_dynamic())
|
Ok(().into_dynamic())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Const statement
|
||||||
|
Stmt::Const(name, expr, _) if expr.is_constant() => {
|
||||||
|
let val = self.eval_expr(scope, expr)?;
|
||||||
|
scope.push_dynamic(name.clone(), VariableType::Constant, val);
|
||||||
|
Ok(().into_dynamic())
|
||||||
|
}
|
||||||
|
|
||||||
|
Stmt::Const(_, _, _) => panic!("constant expression not constant!"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
20
src/error.rs
20
src/error.rs
@ -67,6 +67,8 @@ pub enum ParseErrorType {
|
|||||||
MalformedCallExpr(String),
|
MalformedCallExpr(String),
|
||||||
/// An expression in indexing brackets `[]` has syntax error.
|
/// An expression in indexing brackets `[]` has syntax error.
|
||||||
MalformedIndexExpr(String),
|
MalformedIndexExpr(String),
|
||||||
|
/// Invalid expression assigned to constant.
|
||||||
|
ForbiddenConstantExpr(String),
|
||||||
/// Missing a variable name after the `let` keyword.
|
/// Missing a variable name after the `let` keyword.
|
||||||
VarExpectsIdentifier,
|
VarExpectsIdentifier,
|
||||||
/// Defining a function `fn` in an appropriate place (e.g. inside another function).
|
/// Defining a function `fn` in an appropriate place (e.g. inside another function).
|
||||||
@ -77,6 +79,10 @@ pub enum ParseErrorType {
|
|||||||
FnMissingParams(String),
|
FnMissingParams(String),
|
||||||
/// Assignment to an inappropriate LHS (left-hand-side) expression.
|
/// Assignment to an inappropriate LHS (left-hand-side) expression.
|
||||||
AssignmentToInvalidLHS,
|
AssignmentToInvalidLHS,
|
||||||
|
/// Assignment to a copy of a value.
|
||||||
|
AssignmentToCopy,
|
||||||
|
/// Assignment to an a constant variable.
|
||||||
|
AssignmentToConstant(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Error when parsing a script.
|
/// Error when parsing a script.
|
||||||
@ -112,11 +118,14 @@ impl Error for ParseError {
|
|||||||
ParseErrorType::MissingRightBracket(_) => "Expecting ']'",
|
ParseErrorType::MissingRightBracket(_) => "Expecting ']'",
|
||||||
ParseErrorType::MalformedCallExpr(_) => "Invalid expression in function call arguments",
|
ParseErrorType::MalformedCallExpr(_) => "Invalid expression in function call arguments",
|
||||||
ParseErrorType::MalformedIndexExpr(_) => "Invalid index in indexing expression",
|
ParseErrorType::MalformedIndexExpr(_) => "Invalid index in indexing expression",
|
||||||
|
ParseErrorType::ForbiddenConstantExpr(_) => "Expecting a constant",
|
||||||
ParseErrorType::VarExpectsIdentifier => "Expecting name of a variable",
|
ParseErrorType::VarExpectsIdentifier => "Expecting name of a variable",
|
||||||
ParseErrorType::FnMissingName => "Expecting name in function declaration",
|
ParseErrorType::FnMissingName => "Expecting name in function declaration",
|
||||||
ParseErrorType::FnMissingParams(_) => "Expecting parameters in function declaration",
|
ParseErrorType::FnMissingParams(_) => "Expecting parameters in function declaration",
|
||||||
ParseErrorType::WrongFnDefinition => "Function definitions must be at top level and cannot be inside a block or another function",
|
ParseErrorType::WrongFnDefinition => "Function definitions must be at top level and cannot be inside a block or another function",
|
||||||
ParseErrorType::AssignmentToInvalidLHS => "Cannot assign to this expression because it will only be changing a copy of the value"
|
ParseErrorType::AssignmentToInvalidLHS => "Cannot assign to this expression",
|
||||||
|
ParseErrorType::AssignmentToCopy => "Cannot assign to this expression because it will only be changing a copy of the value",
|
||||||
|
ParseErrorType::AssignmentToConstant(_) => "Cannot assign to a constant variable."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,6 +142,9 @@ impl fmt::Display for ParseError {
|
|||||||
| ParseErrorType::MalformedCallExpr(ref s) => {
|
| ParseErrorType::MalformedCallExpr(ref s) => {
|
||||||
write!(f, "{}", if s.is_empty() { self.description() } else { s })?
|
write!(f, "{}", if s.is_empty() { self.description() } else { s })?
|
||||||
}
|
}
|
||||||
|
ParseErrorType::ForbiddenConstantExpr(ref s) => {
|
||||||
|
write!(f, "Expecting a constant to assign to '{}'", s)?
|
||||||
|
}
|
||||||
ParseErrorType::UnknownOperator(ref s) => write!(f, "{}: '{}'", self.description(), s)?,
|
ParseErrorType::UnknownOperator(ref s) => write!(f, "{}: '{}'", self.description(), s)?,
|
||||||
ParseErrorType::FnMissingParams(ref s) => {
|
ParseErrorType::FnMissingParams(ref s) => {
|
||||||
write!(f, "Expecting parameters for function '{}'", s)?
|
write!(f, "Expecting parameters for function '{}'", s)?
|
||||||
@ -142,6 +154,12 @@ impl fmt::Display for ParseError {
|
|||||||
| ParseErrorType::MissingRightBracket(ref s) => {
|
| ParseErrorType::MissingRightBracket(ref s) => {
|
||||||
write!(f, "{} for {}", self.description(), s)?
|
write!(f, "{} for {}", self.description(), s)?
|
||||||
}
|
}
|
||||||
|
ParseErrorType::AssignmentToConstant(ref s) if s.is_empty() => {
|
||||||
|
write!(f, "{}", self.description())?
|
||||||
|
}
|
||||||
|
ParseErrorType::AssignmentToConstant(ref s) => {
|
||||||
|
write!(f, "Cannot assign to constant '{}'", s)?
|
||||||
|
}
|
||||||
_ => write!(f, "{}", self.description())?,
|
_ => write!(f, "{}", self.description())?,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
220
src/optimize.rs
220
src/optimize.rs
@ -1,13 +1,52 @@
|
|||||||
use crate::engine::KEYWORD_DUMP_AST;
|
use crate::engine::KEYWORD_DUMP_AST;
|
||||||
use crate::parser::{Expr, Stmt};
|
use crate::parser::{Expr, Stmt};
|
||||||
|
use crate::scope::{Scope, ScopeEntry, VariableType};
|
||||||
|
|
||||||
fn optimize_stmt(stmt: Stmt, changed: &mut bool, preserve_result: bool) -> Stmt {
|
struct State {
|
||||||
|
changed: bool,
|
||||||
|
constants: Vec<(String, Expr)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
State {
|
||||||
|
changed: false,
|
||||||
|
constants: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn set_dirty(&mut self) {
|
||||||
|
self.changed = true;
|
||||||
|
}
|
||||||
|
pub fn is_dirty(&self) -> bool {
|
||||||
|
self.changed
|
||||||
|
}
|
||||||
|
pub fn contains_constant(&self, name: &str) -> bool {
|
||||||
|
self.constants.iter().any(|(n, _)| n == name)
|
||||||
|
}
|
||||||
|
pub fn restore_constants(&mut self, len: usize) {
|
||||||
|
self.constants.truncate(len)
|
||||||
|
}
|
||||||
|
pub fn push_constant(&mut self, name: &str, value: Expr) {
|
||||||
|
self.constants.push((name.to_string(), value))
|
||||||
|
}
|
||||||
|
pub fn find_constant(&self, name: &str) -> Option<&Expr> {
|
||||||
|
for (n, expr) in self.constants.iter().rev() {
|
||||||
|
if n == name {
|
||||||
|
return Some(expr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
|
||||||
match stmt {
|
match stmt {
|
||||||
Stmt::IfElse(expr, stmt1, None) if stmt1.is_noop() => {
|
Stmt::IfElse(expr, stmt1, None) if stmt1.is_noop() => {
|
||||||
*changed = true;
|
state.set_dirty();
|
||||||
|
|
||||||
let pos = expr.position();
|
let pos = expr.position();
|
||||||
let expr = optimize_expr(*expr, changed);
|
let expr = optimize_expr(*expr, state);
|
||||||
|
|
||||||
match expr {
|
match expr {
|
||||||
Expr::False(_) | Expr::True(_) => Stmt::Noop(stmt1.position()),
|
Expr::False(_) | Expr::True(_) => Stmt::Noop(stmt1.position()),
|
||||||
@ -25,24 +64,24 @@ fn optimize_stmt(stmt: Stmt, changed: &mut bool, preserve_result: bool) -> Stmt
|
|||||||
|
|
||||||
Stmt::IfElse(expr, stmt1, None) => match *expr {
|
Stmt::IfElse(expr, stmt1, None) => match *expr {
|
||||||
Expr::False(pos) => {
|
Expr::False(pos) => {
|
||||||
*changed = true;
|
state.set_dirty();
|
||||||
Stmt::Noop(pos)
|
Stmt::Noop(pos)
|
||||||
}
|
}
|
||||||
Expr::True(_) => optimize_stmt(*stmt1, changed, true),
|
Expr::True(_) => optimize_stmt(*stmt1, state, true),
|
||||||
expr => Stmt::IfElse(
|
expr => Stmt::IfElse(
|
||||||
Box::new(optimize_expr(expr, changed)),
|
Box::new(optimize_expr(expr, state)),
|
||||||
Box::new(optimize_stmt(*stmt1, changed, true)),
|
Box::new(optimize_stmt(*stmt1, state, true)),
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
Stmt::IfElse(expr, stmt1, Some(stmt2)) => match *expr {
|
Stmt::IfElse(expr, stmt1, Some(stmt2)) => match *expr {
|
||||||
Expr::False(_) => optimize_stmt(*stmt2, changed, true),
|
Expr::False(_) => optimize_stmt(*stmt2, state, true),
|
||||||
Expr::True(_) => optimize_stmt(*stmt1, changed, true),
|
Expr::True(_) => optimize_stmt(*stmt1, state, true),
|
||||||
expr => Stmt::IfElse(
|
expr => Stmt::IfElse(
|
||||||
Box::new(optimize_expr(expr, changed)),
|
Box::new(optimize_expr(expr, state)),
|
||||||
Box::new(optimize_stmt(*stmt1, changed, true)),
|
Box::new(optimize_stmt(*stmt1, state, true)),
|
||||||
match optimize_stmt(*stmt2, changed, true) {
|
match optimize_stmt(*stmt2, state, true) {
|
||||||
stmt if stmt.is_noop() => None,
|
stmt if stmt.is_noop() => None,
|
||||||
stmt => Some(Box::new(stmt)),
|
stmt => Some(Box::new(stmt)),
|
||||||
},
|
},
|
||||||
@ -51,38 +90,45 @@ fn optimize_stmt(stmt: Stmt, changed: &mut bool, preserve_result: bool) -> Stmt
|
|||||||
|
|
||||||
Stmt::While(expr, stmt) => match *expr {
|
Stmt::While(expr, stmt) => match *expr {
|
||||||
Expr::False(pos) => {
|
Expr::False(pos) => {
|
||||||
*changed = true;
|
state.set_dirty();
|
||||||
Stmt::Noop(pos)
|
Stmt::Noop(pos)
|
||||||
}
|
}
|
||||||
Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(*stmt, changed, false))),
|
Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(*stmt, state, false))),
|
||||||
expr => Stmt::While(
|
expr => Stmt::While(
|
||||||
Box::new(optimize_expr(expr, changed)),
|
Box::new(optimize_expr(expr, state)),
|
||||||
Box::new(optimize_stmt(*stmt, changed, false)),
|
Box::new(optimize_stmt(*stmt, state, false)),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
Stmt::Loop(stmt) => Stmt::Loop(Box::new(optimize_stmt(*stmt, changed, false))),
|
Stmt::Loop(stmt) => Stmt::Loop(Box::new(optimize_stmt(*stmt, state, false))),
|
||||||
Stmt::For(id, expr, stmt) => Stmt::For(
|
Stmt::For(id, expr, stmt) => Stmt::For(
|
||||||
id,
|
id,
|
||||||
Box::new(optimize_expr(*expr, changed)),
|
Box::new(optimize_expr(*expr, state)),
|
||||||
Box::new(optimize_stmt(*stmt, changed, false)),
|
Box::new(optimize_stmt(*stmt, state, false)),
|
||||||
),
|
),
|
||||||
Stmt::Let(id, Some(expr), pos) => {
|
Stmt::Let(id, Some(expr), pos) => {
|
||||||
Stmt::Let(id, Some(Box::new(optimize_expr(*expr, changed))), pos)
|
Stmt::Let(id, Some(Box::new(optimize_expr(*expr, state))), pos)
|
||||||
}
|
}
|
||||||
Stmt::Let(_, None, _) => stmt,
|
Stmt::Let(_, None, _) => stmt,
|
||||||
|
|
||||||
Stmt::Block(statements, pos) => {
|
Stmt::Block(statements, pos) => {
|
||||||
let orig_len = statements.len();
|
let orig_len = statements.len();
|
||||||
|
let orig_constants = state.constants.len();
|
||||||
|
|
||||||
let mut result: Vec<_> = statements
|
let mut result: Vec<_> = statements
|
||||||
.into_iter() // For each statement
|
.into_iter() // For each statement
|
||||||
.rev() // Scan in reverse
|
.map(|stmt| {
|
||||||
.map(|s| optimize_stmt(s, changed, preserve_result)) // Optimize the statement
|
if let Stmt::Const(name, value, pos) = stmt {
|
||||||
|
state.push_constant(&name, *value);
|
||||||
|
state.set_dirty();
|
||||||
|
Stmt::Noop(pos) // No need to keep constants
|
||||||
|
} else {
|
||||||
|
optimize_stmt(stmt, state, preserve_result) // Optimize the statement
|
||||||
|
}
|
||||||
|
})
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter(|(i, s)| s.is_op() || (preserve_result && *i == 0)) // Remove no-op's but leave the last one if we need the result
|
.filter(|(i, stmt)| stmt.is_op() || (preserve_result && *i == orig_len - 1)) // Remove no-op's but leave the last one if we need the result
|
||||||
.map(|(_, s)| s)
|
.map(|(_, stmt)| stmt)
|
||||||
.rev()
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Remove all raw expression statements that are pure except for the very last statement
|
// Remove all raw expression statements that are pure except for the very last statement
|
||||||
@ -123,59 +169,61 @@ fn optimize_stmt(stmt: Stmt, changed: &mut bool, preserve_result: bool) -> Stmt
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.rev()
|
.rev()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, s)| optimize_stmt(s, changed, i == 0)) // Optimize all other statements again
|
.map(|(i, s)| optimize_stmt(s, state, i == 0)) // Optimize all other statements again
|
||||||
.rev()
|
.rev()
|
||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
*changed = *changed || orig_len != result.len();
|
if orig_len != result.len() {
|
||||||
|
state.set_dirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
state.restore_constants(orig_constants);
|
||||||
|
|
||||||
match result[..] {
|
match result[..] {
|
||||||
// No statements in block - change to No-op
|
// No statements in block - change to No-op
|
||||||
[] => {
|
[] => {
|
||||||
*changed = true;
|
state.set_dirty();
|
||||||
Stmt::Noop(pos)
|
Stmt::Noop(pos)
|
||||||
}
|
}
|
||||||
// Only one statement - promote
|
// Only one statement - promote
|
||||||
[_] => {
|
[_] => {
|
||||||
*changed = true;
|
state.set_dirty();
|
||||||
result.remove(0)
|
result.remove(0)
|
||||||
}
|
}
|
||||||
_ => Stmt::Block(result, pos),
|
_ => Stmt::Block(result, pos),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Stmt::Expr(expr) => Stmt::Expr(Box::new(optimize_expr(*expr, changed))),
|
Stmt::Expr(expr) => Stmt::Expr(Box::new(optimize_expr(*expr, state))),
|
||||||
|
|
||||||
Stmt::ReturnWithVal(Some(expr), is_return, pos) => Stmt::ReturnWithVal(
|
Stmt::ReturnWithVal(Some(expr), is_return, pos) => {
|
||||||
Some(Box::new(optimize_expr(*expr, changed))),
|
Stmt::ReturnWithVal(Some(Box::new(optimize_expr(*expr, state))), is_return, pos)
|
||||||
is_return,
|
}
|
||||||
pos,
|
|
||||||
),
|
|
||||||
|
|
||||||
stmt => stmt,
|
stmt => stmt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
|
fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
||||||
match expr {
|
match expr {
|
||||||
Expr::Stmt(stmt, pos) => match optimize_stmt(*stmt, changed, true) {
|
Expr::Stmt(stmt, pos) => match optimize_stmt(*stmt, state, true) {
|
||||||
Stmt::Noop(_) => {
|
Stmt::Noop(_) => {
|
||||||
*changed = true;
|
state.set_dirty();
|
||||||
Expr::Unit(pos)
|
Expr::Unit(pos)
|
||||||
}
|
}
|
||||||
Stmt::Expr(expr) => {
|
Stmt::Expr(expr) => {
|
||||||
*changed = true;
|
state.set_dirty();
|
||||||
*expr
|
*expr
|
||||||
}
|
}
|
||||||
stmt => Expr::Stmt(Box::new(stmt), pos),
|
stmt => Expr::Stmt(Box::new(stmt), pos),
|
||||||
},
|
},
|
||||||
Expr::Assignment(id, expr, pos) => {
|
Expr::Assignment(id, expr, pos) => {
|
||||||
Expr::Assignment(id, Box::new(optimize_expr(*expr, changed)), pos)
|
Expr::Assignment(id, Box::new(optimize_expr(*expr, state)), pos)
|
||||||
}
|
}
|
||||||
Expr::Dot(lhs, rhs, pos) => Expr::Dot(
|
Expr::Dot(lhs, rhs, pos) => Expr::Dot(
|
||||||
Box::new(optimize_expr(*lhs, changed)),
|
Box::new(optimize_expr(*lhs, state)),
|
||||||
Box::new(optimize_expr(*rhs, changed)),
|
Box::new(optimize_expr(*rhs, state)),
|
||||||
pos,
|
pos,
|
||||||
),
|
),
|
||||||
|
|
||||||
@ -186,12 +234,12 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
|
|||||||
{
|
{
|
||||||
// Array where everything is a pure - promote the indexed item.
|
// Array where everything is a pure - promote the indexed item.
|
||||||
// All other items can be thrown away.
|
// All other items can be thrown away.
|
||||||
*changed = true;
|
state.set_dirty();
|
||||||
items.remove(i as usize)
|
items.remove(i as usize)
|
||||||
}
|
}
|
||||||
(lhs, rhs) => Expr::Index(
|
(lhs, rhs) => Expr::Index(
|
||||||
Box::new(optimize_expr(lhs, changed)),
|
Box::new(optimize_expr(lhs, state)),
|
||||||
Box::new(optimize_expr(rhs, changed)),
|
Box::new(optimize_expr(rhs, state)),
|
||||||
pos,
|
pos,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -204,10 +252,12 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
|
|||||||
|
|
||||||
let items: Vec<_> = items
|
let items: Vec<_> = items
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|expr| optimize_expr(expr, changed))
|
.map(|expr| optimize_expr(expr, state))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
*changed = *changed || orig_len != items.len();
|
if orig_len != items.len() {
|
||||||
|
state.set_dirty();
|
||||||
|
}
|
||||||
|
|
||||||
Expr::Array(items, pos)
|
Expr::Array(items, pos)
|
||||||
}
|
}
|
||||||
@ -217,38 +267,38 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
|
|||||||
|
|
||||||
Expr::And(lhs, rhs) => match (*lhs, *rhs) {
|
Expr::And(lhs, rhs) => match (*lhs, *rhs) {
|
||||||
(Expr::True(_), rhs) => {
|
(Expr::True(_), rhs) => {
|
||||||
*changed = true;
|
state.set_dirty();
|
||||||
rhs
|
rhs
|
||||||
}
|
}
|
||||||
(Expr::False(pos), _) => {
|
(Expr::False(pos), _) => {
|
||||||
*changed = true;
|
state.set_dirty();
|
||||||
Expr::False(pos)
|
Expr::False(pos)
|
||||||
}
|
}
|
||||||
(lhs, Expr::True(_)) => {
|
(lhs, Expr::True(_)) => {
|
||||||
*changed = true;
|
state.set_dirty();
|
||||||
lhs
|
lhs
|
||||||
}
|
}
|
||||||
(lhs, rhs) => Expr::And(
|
(lhs, rhs) => Expr::And(
|
||||||
Box::new(optimize_expr(lhs, changed)),
|
Box::new(optimize_expr(lhs, state)),
|
||||||
Box::new(optimize_expr(rhs, changed)),
|
Box::new(optimize_expr(rhs, state)),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
Expr::Or(lhs, rhs) => match (*lhs, *rhs) {
|
Expr::Or(lhs, rhs) => match (*lhs, *rhs) {
|
||||||
(Expr::False(_), rhs) => {
|
(Expr::False(_), rhs) => {
|
||||||
*changed = true;
|
state.set_dirty();
|
||||||
rhs
|
rhs
|
||||||
}
|
}
|
||||||
(Expr::True(pos), _) => {
|
(Expr::True(pos), _) => {
|
||||||
*changed = true;
|
state.set_dirty();
|
||||||
Expr::True(pos)
|
Expr::True(pos)
|
||||||
}
|
}
|
||||||
(lhs, Expr::False(_)) => {
|
(lhs, Expr::False(_)) => {
|
||||||
*changed = true;
|
state.set_dirty();
|
||||||
lhs
|
lhs
|
||||||
}
|
}
|
||||||
(lhs, rhs) => Expr::Or(
|
(lhs, rhs) => Expr::Or(
|
||||||
Box::new(optimize_expr(lhs, changed)),
|
Box::new(optimize_expr(lhs, state)),
|
||||||
Box::new(optimize_expr(rhs, changed)),
|
Box::new(optimize_expr(rhs, state)),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -258,41 +308,69 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
|
|||||||
Expr::FunctionCall(id, args, def_value, pos) => {
|
Expr::FunctionCall(id, args, def_value, pos) => {
|
||||||
let orig_len = args.len();
|
let orig_len = args.len();
|
||||||
|
|
||||||
let args: Vec<_> = args
|
let args: Vec<_> = args.into_iter().map(|a| optimize_expr(a, state)).collect();
|
||||||
.into_iter()
|
|
||||||
.map(|a| optimize_expr(a, changed))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
*changed = *changed || orig_len != args.len();
|
if orig_len != args.len() {
|
||||||
|
state.set_dirty();
|
||||||
|
}
|
||||||
|
|
||||||
Expr::FunctionCall(id, args, def_value, pos)
|
Expr::FunctionCall(id, args, def_value, pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Expr::Variable(ref name, _) if state.contains_constant(name) => {
|
||||||
|
state.set_dirty();
|
||||||
|
|
||||||
|
// Replace constant with value
|
||||||
|
state
|
||||||
|
.find_constant(name)
|
||||||
|
.expect("can't find constant in scope!")
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
|
||||||
expr => expr,
|
expr => expr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn optimize(statements: Vec<Stmt>) -> Vec<Stmt> {
|
pub(crate) fn optimize(statements: Vec<Stmt>, scope: &Scope) -> Vec<Stmt> {
|
||||||
let mut result = statements;
|
let mut result = statements;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let mut changed = false;
|
let mut state = State::new();
|
||||||
|
let num_statements = result.len();
|
||||||
|
|
||||||
|
scope
|
||||||
|
.iter()
|
||||||
|
.filter(|ScopeEntry { var_type, expr, .. }| {
|
||||||
|
// Get all the constants with definite constant expressions
|
||||||
|
*var_type == VariableType::Constant
|
||||||
|
&& expr.as_ref().map(|e| e.is_constant()).unwrap_or(false)
|
||||||
|
})
|
||||||
|
.for_each(|ScopeEntry { name, expr, .. }| {
|
||||||
|
state.push_constant(
|
||||||
|
name.as_ref(),
|
||||||
|
expr.as_ref().expect("should be Some(expr)").clone(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
result = result
|
result = result
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.rev() // Scan in reverse
|
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, stmt)| {
|
.map(|(i, stmt)| {
|
||||||
// Keep all variable declarations at this level
|
if let Stmt::Const(name, value, _) = &stmt {
|
||||||
let keep = stmt.is_var();
|
// Load constants
|
||||||
|
state.push_constant(name, value.as_ref().clone());
|
||||||
|
stmt // Keep it in the top scope
|
||||||
|
} else {
|
||||||
|
// Keep all variable declarations at this level
|
||||||
|
// and always keep the last return value
|
||||||
|
let keep = stmt.is_var() || i == num_statements - 1;
|
||||||
|
|
||||||
// Always keep the last return value
|
optimize_stmt(stmt, &mut state, keep)
|
||||||
optimize_stmt(stmt, &mut changed, keep || i == 0)
|
}
|
||||||
})
|
})
|
||||||
.rev()
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if !changed {
|
if !state.is_dirty() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
195
src/parser.rs
195
src/parser.rs
@ -3,6 +3,7 @@
|
|||||||
use crate::any::Dynamic;
|
use crate::any::Dynamic;
|
||||||
use crate::error::{LexError, ParseError, ParseErrorType};
|
use crate::error::{LexError, ParseError, ParseErrorType};
|
||||||
use crate::optimize::optimize;
|
use crate::optimize::optimize;
|
||||||
|
use crate::scope::{Scope, VariableType};
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow, char, cmp::Ordering, fmt, iter::Peekable, str::Chars, str::FromStr, sync::Arc,
|
borrow::Cow, char, cmp::Ordering, fmt, iter::Peekable, str::Chars, str::FromStr, sync::Arc,
|
||||||
@ -148,6 +149,42 @@ pub struct AST(
|
|||||||
#[cfg(not(feature = "no_function"))] pub(crate) Vec<Arc<FnDef>>,
|
#[cfg(not(feature = "no_function"))] pub(crate) Vec<Arc<FnDef>>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
impl AST {
|
||||||
|
/// Optimize the AST with constants defined in an external Scope.
|
||||||
|
///
|
||||||
|
/// Although optimization is performed by default during compilation, sometimes it is necessary to
|
||||||
|
/// "re"-optimize an AST. For example, when working with constants that are passed in via an
|
||||||
|
/// external scope, it will be more efficient to optimize the AST once again to take advantage
|
||||||
|
/// of the new constants.
|
||||||
|
///
|
||||||
|
/// With this method, it is no longer necessary to regenerate a large script with hard-coded
|
||||||
|
/// constant values. The script AST can be compiled once and stored. During actually evaluation,
|
||||||
|
/// constants are passed into the Engine via an external scope (i.e. with `scope.push_constant(...)`).
|
||||||
|
/// Then, the AST is cloned and the copy re-optimized before running.
|
||||||
|
pub fn optimize(self, scope: &Scope) -> Self {
|
||||||
|
AST(
|
||||||
|
crate::optimize::optimize(self.0, scope),
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
self.1
|
||||||
|
.into_iter()
|
||||||
|
.map(|fn_def| {
|
||||||
|
let pos = fn_def.body.position();
|
||||||
|
let body = optimize(vec![fn_def.body.clone()], scope)
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.unwrap_or_else(|| Stmt::Noop(pos));
|
||||||
|
Arc::new(FnDef {
|
||||||
|
name: fn_def.name.clone(),
|
||||||
|
params: fn_def.params.clone(),
|
||||||
|
body,
|
||||||
|
pos: fn_def.pos,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)] // Do not derive Clone because it is expensive
|
#[derive(Debug)] // Do not derive Clone because it is expensive
|
||||||
pub struct FnDef {
|
pub struct FnDef {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@ -179,6 +216,7 @@ pub enum Stmt {
|
|||||||
Loop(Box<Stmt>),
|
Loop(Box<Stmt>),
|
||||||
For(String, Box<Expr>, Box<Stmt>),
|
For(String, Box<Expr>, Box<Stmt>),
|
||||||
Let(String, Option<Box<Expr>>, Position),
|
Let(String, Option<Box<Expr>>, Position),
|
||||||
|
Const(String, Box<Expr>, Position),
|
||||||
Block(Vec<Stmt>, Position),
|
Block(Vec<Stmt>, Position),
|
||||||
Expr(Box<Expr>),
|
Expr(Box<Expr>),
|
||||||
Break(Position),
|
Break(Position),
|
||||||
@ -211,6 +249,7 @@ impl Stmt {
|
|||||||
match self {
|
match self {
|
||||||
Stmt::Noop(pos)
|
Stmt::Noop(pos)
|
||||||
| Stmt::Let(_, _, pos)
|
| Stmt::Let(_, _, pos)
|
||||||
|
| Stmt::Const(_, _, pos)
|
||||||
| Stmt::Block(_, pos)
|
| Stmt::Block(_, pos)
|
||||||
| Stmt::Break(pos)
|
| Stmt::Break(pos)
|
||||||
| Stmt::ReturnWithVal(_, _, pos) => *pos,
|
| Stmt::ReturnWithVal(_, _, pos) => *pos,
|
||||||
@ -225,7 +264,8 @@ pub enum Expr {
|
|||||||
IntegerConstant(INT, Position),
|
IntegerConstant(INT, Position),
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
FloatConstant(FLOAT, Position),
|
FloatConstant(FLOAT, Position),
|
||||||
Identifier(String, Position),
|
Variable(String, Position),
|
||||||
|
Property(String, Position),
|
||||||
CharConstant(char, Position),
|
CharConstant(char, Position),
|
||||||
StringConstant(String, Position),
|
StringConstant(String, Position),
|
||||||
Stmt(Box<Stmt>, Position),
|
Stmt(Box<Stmt>, Position),
|
||||||
@ -242,12 +282,29 @@ pub enum Expr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Expr {
|
impl Expr {
|
||||||
|
pub fn get_value_str(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Expr::IntegerConstant(i, _) => i.to_string(),
|
||||||
|
Expr::CharConstant(c, _) => c.to_string(),
|
||||||
|
Expr::StringConstant(_, _) => "string".to_string(),
|
||||||
|
Expr::True(_) => "true".to_string(),
|
||||||
|
Expr::False(_) => "false".to_string(),
|
||||||
|
Expr::Unit(_) => "()".to_string(),
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
Expr::FloatConstant(f, _) => f.to_string(),
|
||||||
|
|
||||||
|
_ => "".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn position(&self) -> Position {
|
pub fn position(&self) -> Position {
|
||||||
match self {
|
match self {
|
||||||
Expr::IntegerConstant(_, pos)
|
Expr::IntegerConstant(_, pos)
|
||||||
| Expr::Identifier(_, pos)
|
|
||||||
| Expr::CharConstant(_, pos)
|
| Expr::CharConstant(_, pos)
|
||||||
| Expr::StringConstant(_, pos)
|
| Expr::StringConstant(_, pos)
|
||||||
|
| Expr::Variable(_, pos)
|
||||||
|
| Expr::Property(_, pos)
|
||||||
| Expr::Stmt(_, pos)
|
| Expr::Stmt(_, pos)
|
||||||
| Expr::FunctionCall(_, _, _, pos)
|
| Expr::FunctionCall(_, _, _, pos)
|
||||||
| Expr::Array(_, pos)
|
| Expr::Array(_, pos)
|
||||||
@ -273,7 +330,7 @@ impl Expr {
|
|||||||
match self {
|
match self {
|
||||||
Expr::Array(expressions, _) => expressions.iter().all(Expr::is_pure),
|
Expr::Array(expressions, _) => expressions.iter().all(Expr::is_pure),
|
||||||
Expr::And(x, y) | Expr::Or(x, y) | Expr::Index(x, y, _) => x.is_pure() && y.is_pure(),
|
Expr::And(x, y) | Expr::Or(x, y) | Expr::Index(x, y, _) => x.is_pure() && y.is_pure(),
|
||||||
expr => expr.is_constant() || expr.is_identifier(),
|
expr => expr.is_constant() || expr.is_variable(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,9 +349,17 @@ impl Expr {
|
|||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn is_identifier(&self) -> bool {
|
|
||||||
|
pub fn is_variable(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Expr::Identifier(_, _) => true,
|
Expr::Variable(_, _) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_property(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Expr::Property(_, _) => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -328,6 +393,7 @@ pub enum Token {
|
|||||||
True,
|
True,
|
||||||
False,
|
False,
|
||||||
Let,
|
Let,
|
||||||
|
Const,
|
||||||
If,
|
If,
|
||||||
Else,
|
Else,
|
||||||
While,
|
While,
|
||||||
@ -402,6 +468,7 @@ impl Token {
|
|||||||
True => "true",
|
True => "true",
|
||||||
False => "false",
|
False => "false",
|
||||||
Let => "let",
|
Let => "let",
|
||||||
|
Const => "const",
|
||||||
If => "if",
|
If => "if",
|
||||||
Else => "else",
|
Else => "else",
|
||||||
While => "while",
|
While => "while",
|
||||||
@ -829,6 +896,7 @@ impl<'a> TokenIterator<'a> {
|
|||||||
"true" => Token::True,
|
"true" => Token::True,
|
||||||
"false" => Token::False,
|
"false" => Token::False,
|
||||||
"let" => Token::Let,
|
"let" => Token::Let,
|
||||||
|
"const" => Token::Const,
|
||||||
"if" => Token::If,
|
"if" => Token::If,
|
||||||
"else" => Token::Else,
|
"else" => Token::Else,
|
||||||
"while" => Token::While,
|
"while" => Token::While,
|
||||||
@ -1360,10 +1428,10 @@ fn parse_ident_expr<'a>(
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Some(&(Token::LeftBracket, pos)) => {
|
Some(&(Token::LeftBracket, pos)) => {
|
||||||
input.next();
|
input.next();
|
||||||
parse_index_expr(Box::new(Expr::Identifier(id, begin)), input, pos)
|
parse_index_expr(Box::new(Expr::Variable(id, begin)), input, pos)
|
||||||
}
|
}
|
||||||
Some(_) => Ok(Expr::Identifier(id, begin)),
|
Some(_) => Ok(Expr::Variable(id, begin)),
|
||||||
None => Ok(Expr::Identifier(id, Position::eof())),
|
None => Ok(Expr::Variable(id, Position::eof())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1522,37 +1590,69 @@ fn parse_unary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pars
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result<Expr, ParseError> {
|
fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result<Expr, ParseError> {
|
||||||
fn valid_assignment_chain(expr: &Expr) -> (bool, Position) {
|
fn valid_assignment_chain(expr: &Expr, is_top: bool) -> Option<ParseError> {
|
||||||
match expr {
|
match expr {
|
||||||
Expr::Identifier(_, pos) => (true, *pos),
|
Expr::Variable(_, _) => {
|
||||||
|
assert!(is_top, "property expected but gets variable");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Expr::Property(_, _) => {
|
||||||
|
assert!(!is_top, "variable expected but gets property");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(idx_lhs, _, _) if idx_lhs.is_identifier() => (true, idx_lhs.position()),
|
Expr::Index(idx_lhs, _, _) if idx_lhs.is_variable() => {
|
||||||
|
assert!(is_top, "property expected but gets variable");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(idx_lhs, _, _) => (false, idx_lhs.position()),
|
Expr::Index(idx_lhs, _, _) if idx_lhs.is_property() => {
|
||||||
|
assert!(!is_top, "variable expected but gets property");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
Expr::Index(idx_lhs, _, _) if is_top => valid_assignment_chain(idx_lhs, true),
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
Expr::Index(idx_lhs, _, _) if !is_top => Some(ParseError::new(
|
||||||
|
ParseErrorType::AssignmentToInvalidLHS,
|
||||||
|
idx_lhs.position(),
|
||||||
|
)),
|
||||||
|
|
||||||
Expr::Dot(dot_lhs, dot_rhs, _) => match dot_lhs.as_ref() {
|
Expr::Dot(dot_lhs, dot_rhs, _) => match dot_lhs.as_ref() {
|
||||||
Expr::Identifier(_, _) => valid_assignment_chain(dot_rhs),
|
Expr::Variable(_, _) if is_top => valid_assignment_chain(dot_rhs, false),
|
||||||
|
Expr::Property(_, _) if !is_top => valid_assignment_chain(dot_rhs, false),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(idx_lhs, _, _) if idx_lhs.is_identifier() => {
|
Expr::Index(idx_lhs, _, _)
|
||||||
valid_assignment_chain(dot_rhs)
|
if (idx_lhs.is_variable() && is_top) || (idx_lhs.is_property() && !is_top) =>
|
||||||
|
{
|
||||||
|
valid_assignment_chain(dot_rhs, false)
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(idx_lhs, _, _) => (false, idx_lhs.position()),
|
Expr::Index(idx_lhs, _, _) => Some(ParseError::new(
|
||||||
|
ParseErrorType::AssignmentToCopy,
|
||||||
|
idx_lhs.position(),
|
||||||
|
)),
|
||||||
|
|
||||||
_ => (false, dot_lhs.position()),
|
expr => panic!("unexpected dot expression {:#?}", expr),
|
||||||
},
|
},
|
||||||
|
|
||||||
_ => (false, expr.position()),
|
_ => Some(ParseError::new(
|
||||||
|
ParseErrorType::AssignmentToInvalidLHS,
|
||||||
|
expr.position(),
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//println!("{:#?} = {:#?}", lhs, rhs);
|
//println!("{:#?} = {:#?}", lhs, rhs);
|
||||||
|
|
||||||
match valid_assignment_chain(&lhs) {
|
match valid_assignment_chain(&lhs, true) {
|
||||||
(true, _) => Ok(Expr::Assignment(Box::new(lhs), Box::new(rhs), pos)),
|
None => Ok(Expr::Assignment(Box::new(lhs), Box::new(rhs), pos)),
|
||||||
(false, pos) => Err(ParseError::new(PERR::AssignmentToInvalidLHS, pos)),
|
Some(err) => Err(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1619,7 +1719,28 @@ fn parse_binary_op<'a>(
|
|||||||
Token::PlusAssign => parse_op_assignment("+", current_lhs, rhs, pos)?,
|
Token::PlusAssign => parse_op_assignment("+", current_lhs, rhs, pos)?,
|
||||||
Token::MinusAssign => parse_op_assignment("-", current_lhs, rhs, pos)?,
|
Token::MinusAssign => parse_op_assignment("-", current_lhs, rhs, pos)?,
|
||||||
|
|
||||||
Token::Period => Expr::Dot(Box::new(current_lhs), Box::new(rhs), pos),
|
Token::Period => {
|
||||||
|
fn change_var_to_property(expr: Expr) -> Expr {
|
||||||
|
match expr {
|
||||||
|
Expr::Dot(lhs, rhs, pos) => Expr::Dot(
|
||||||
|
Box::new(change_var_to_property(*lhs)),
|
||||||
|
Box::new(change_var_to_property(*rhs)),
|
||||||
|
pos,
|
||||||
|
),
|
||||||
|
Expr::Index(lhs, idx, pos) => {
|
||||||
|
Expr::Index(Box::new(change_var_to_property(*lhs)), idx, pos)
|
||||||
|
}
|
||||||
|
Expr::Variable(s, pos) => Expr::Property(s, pos),
|
||||||
|
expr => expr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr::Dot(
|
||||||
|
Box::new(current_lhs),
|
||||||
|
Box::new(change_var_to_property(rhs)),
|
||||||
|
pos,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Comparison operators default to false when passed invalid operands
|
// Comparison operators default to false when passed invalid operands
|
||||||
Token::EqualsTo => Expr::FunctionCall(
|
Token::EqualsTo => Expr::FunctionCall(
|
||||||
@ -1763,7 +1884,10 @@ fn parse_for<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseE
|
|||||||
Ok(Stmt::For(name, Box::new(expr), Box::new(body)))
|
Ok(Stmt::For(name, Box::new(expr), Box::new(body)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_var<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
|
fn parse_var<'a>(
|
||||||
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
|
var_type: VariableType,
|
||||||
|
) -> Result<Stmt, ParseError> {
|
||||||
let pos = match input.next() {
|
let pos = match input.next() {
|
||||||
Some((_, tok_pos)) => tok_pos,
|
Some((_, tok_pos)) => tok_pos,
|
||||||
_ => return Err(ParseError::new(PERR::InputPastEndOfFile, Position::eof())),
|
_ => return Err(ParseError::new(PERR::InputPastEndOfFile, Position::eof())),
|
||||||
@ -1779,7 +1903,19 @@ fn parse_var<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseE
|
|||||||
Some(&(Token::Equals, _)) => {
|
Some(&(Token::Equals, _)) => {
|
||||||
input.next();
|
input.next();
|
||||||
let init_value = parse_expr(input)?;
|
let init_value = parse_expr(input)?;
|
||||||
Ok(Stmt::Let(name, Some(Box::new(init_value)), pos))
|
|
||||||
|
match var_type {
|
||||||
|
VariableType::Normal => Ok(Stmt::Let(name, Some(Box::new(init_value)), pos)),
|
||||||
|
|
||||||
|
VariableType::Constant if init_value.is_constant() => {
|
||||||
|
Ok(Stmt::Const(name, Box::new(init_value), pos))
|
||||||
|
}
|
||||||
|
// Constants require a constant expression
|
||||||
|
VariableType::Constant => Err(ParseError(
|
||||||
|
PERR::ForbiddenConstantExpr(name.to_string()),
|
||||||
|
init_value.position(),
|
||||||
|
)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => Ok(Stmt::Let(name, None, pos)),
|
_ => Ok(Stmt::Let(name, None, pos)),
|
||||||
}
|
}
|
||||||
@ -1867,7 +2003,8 @@ fn parse_stmt<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Parse
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(&(Token::LeftBrace, _)) => parse_block(input),
|
Some(&(Token::LeftBrace, _)) => parse_block(input),
|
||||||
Some(&(Token::Let, _)) => parse_var(input),
|
Some(&(Token::Let, _)) => parse_var(input, VariableType::Normal),
|
||||||
|
Some(&(Token::Const, _)) => parse_var(input, VariableType::Constant),
|
||||||
_ => parse_expr_stmt(input),
|
_ => parse_expr_stmt(input),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1943,6 +2080,7 @@ fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseE
|
|||||||
|
|
||||||
fn parse_top_level<'a>(
|
fn parse_top_level<'a>(
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
|
scope: &Scope,
|
||||||
optimize_ast: bool,
|
optimize_ast: bool,
|
||||||
) -> Result<AST, ParseError> {
|
) -> Result<AST, ParseError> {
|
||||||
let mut statements = Vec::<Stmt>::new();
|
let mut statements = Vec::<Stmt>::new();
|
||||||
@ -1973,7 +2111,7 @@ fn parse_top_level<'a>(
|
|||||||
|
|
||||||
return Ok(AST(
|
return Ok(AST(
|
||||||
if optimize_ast {
|
if optimize_ast {
|
||||||
optimize(statements)
|
optimize(statements, &scope)
|
||||||
} else {
|
} else {
|
||||||
statements
|
statements
|
||||||
},
|
},
|
||||||
@ -1983,7 +2121,7 @@ fn parse_top_level<'a>(
|
|||||||
.map(|mut fn_def| {
|
.map(|mut fn_def| {
|
||||||
if optimize_ast {
|
if optimize_ast {
|
||||||
let pos = fn_def.body.position();
|
let pos = fn_def.body.position();
|
||||||
let mut body = optimize(vec![fn_def.body]);
|
let mut body = optimize(vec![fn_def.body], &scope);
|
||||||
fn_def.body = body.pop().unwrap_or_else(|| Stmt::Noop(pos));
|
fn_def.body = body.pop().unwrap_or_else(|| Stmt::Noop(pos));
|
||||||
}
|
}
|
||||||
Arc::new(fn_def)
|
Arc::new(fn_def)
|
||||||
@ -1994,7 +2132,8 @@ fn parse_top_level<'a>(
|
|||||||
|
|
||||||
pub fn parse<'a>(
|
pub fn parse<'a>(
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
|
scope: &Scope,
|
||||||
optimize_ast: bool,
|
optimize_ast: bool,
|
||||||
) -> Result<AST, ParseError> {
|
) -> Result<AST, ParseError> {
|
||||||
parse_top_level(input, optimize_ast)
|
parse_top_level(input, scope, optimize_ast)
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,8 @@ pub enum EvalAltResult {
|
|||||||
ErrorVariableNotFound(String, Position),
|
ErrorVariableNotFound(String, Position),
|
||||||
/// Assignment to an inappropriate LHS (left-hand-side) expression.
|
/// Assignment to an inappropriate LHS (left-hand-side) expression.
|
||||||
ErrorAssignmentToUnknownLHS(Position),
|
ErrorAssignmentToUnknownLHS(Position),
|
||||||
|
/// Assignment to a constant variable.
|
||||||
|
ErrorAssignmentToConstant(String, Position),
|
||||||
/// Returned type is not the same as the required output type.
|
/// Returned type is not the same as the required output type.
|
||||||
/// Wrapped value is the type of the actual result.
|
/// Wrapped value is the type of the actual result.
|
||||||
ErrorMismatchOutputType(String, Position),
|
ErrorMismatchOutputType(String, Position),
|
||||||
@ -89,6 +91,7 @@ impl Error for EvalAltResult {
|
|||||||
Self::ErrorAssignmentToUnknownLHS(_) => {
|
Self::ErrorAssignmentToUnknownLHS(_) => {
|
||||||
"Assignment to an unsupported left-hand side expression"
|
"Assignment to an unsupported left-hand side expression"
|
||||||
}
|
}
|
||||||
|
Self::ErrorAssignmentToConstant(_, _) => "Assignment to a constant variable",
|
||||||
Self::ErrorMismatchOutputType(_, _) => "Output type is incorrect",
|
Self::ErrorMismatchOutputType(_, _) => "Output type is incorrect",
|
||||||
Self::ErrorReadingScriptFile(_, _) => "Cannot read from script file",
|
Self::ErrorReadingScriptFile(_, _) => "Cannot read from script file",
|
||||||
Self::ErrorDotExpr(_, _) => "Malformed dot expression",
|
Self::ErrorDotExpr(_, _) => "Malformed dot expression",
|
||||||
@ -116,6 +119,7 @@ impl fmt::Display for EvalAltResult {
|
|||||||
Self::ErrorIfGuard(pos) => write!(f, "{} ({})", desc, pos),
|
Self::ErrorIfGuard(pos) => write!(f, "{} ({})", desc, pos),
|
||||||
Self::ErrorFor(pos) => write!(f, "{} ({})", desc, pos),
|
Self::ErrorFor(pos) => write!(f, "{} ({})", desc, pos),
|
||||||
Self::ErrorAssignmentToUnknownLHS(pos) => write!(f, "{} ({})", desc, pos),
|
Self::ErrorAssignmentToUnknownLHS(pos) => write!(f, "{} ({})", desc, pos),
|
||||||
|
Self::ErrorAssignmentToConstant(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos),
|
||||||
Self::ErrorMismatchOutputType(s, pos) => write!(f, "{}: {} ({})", desc, s, pos),
|
Self::ErrorMismatchOutputType(s, pos) => write!(f, "{}: {} ({})", desc, s, pos),
|
||||||
Self::ErrorDotExpr(s, pos) if !s.is_empty() => write!(f, "{} {} ({})", desc, s, pos),
|
Self::ErrorDotExpr(s, pos) if !s.is_empty() => write!(f, "{} {} ({})", desc, s, pos),
|
||||||
Self::ErrorDotExpr(_, pos) => write!(f, "{} ({})", desc, pos),
|
Self::ErrorDotExpr(_, pos) => write!(f, "{} ({})", desc, pos),
|
||||||
@ -213,6 +217,7 @@ impl EvalAltResult {
|
|||||||
| Self::ErrorFor(pos)
|
| Self::ErrorFor(pos)
|
||||||
| Self::ErrorVariableNotFound(_, pos)
|
| Self::ErrorVariableNotFound(_, pos)
|
||||||
| Self::ErrorAssignmentToUnknownLHS(pos)
|
| Self::ErrorAssignmentToUnknownLHS(pos)
|
||||||
|
| Self::ErrorAssignmentToConstant(_, pos)
|
||||||
| Self::ErrorMismatchOutputType(_, pos)
|
| Self::ErrorMismatchOutputType(_, pos)
|
||||||
| Self::ErrorDotExpr(_, pos)
|
| Self::ErrorDotExpr(_, pos)
|
||||||
| Self::ErrorArithmetic(_, pos)
|
| Self::ErrorArithmetic(_, pos)
|
||||||
@ -238,6 +243,7 @@ impl EvalAltResult {
|
|||||||
| Self::ErrorFor(ref mut pos)
|
| Self::ErrorFor(ref mut pos)
|
||||||
| Self::ErrorVariableNotFound(_, ref mut pos)
|
| Self::ErrorVariableNotFound(_, ref mut pos)
|
||||||
| Self::ErrorAssignmentToUnknownLHS(ref mut pos)
|
| Self::ErrorAssignmentToUnknownLHS(ref mut pos)
|
||||||
|
| Self::ErrorAssignmentToConstant(_, ref mut pos)
|
||||||
| Self::ErrorMismatchOutputType(_, ref mut pos)
|
| Self::ErrorMismatchOutputType(_, ref mut pos)
|
||||||
| Self::ErrorDotExpr(_, ref mut pos)
|
| Self::ErrorDotExpr(_, ref mut pos)
|
||||||
| Self::ErrorArithmetic(_, ref mut pos)
|
| Self::ErrorArithmetic(_, ref mut pos)
|
||||||
|
198
src/scope.rs
198
src/scope.rs
@ -1,9 +1,26 @@
|
|||||||
//! Module that defines the `Scope` type representing a function call-stack scope.
|
//! Module that defines the `Scope` type representing a function call-stack scope.
|
||||||
|
|
||||||
use crate::any::{Any, Dynamic};
|
use crate::any::{Any, AnyExt, Dynamic};
|
||||||
|
use crate::parser::{Expr, Position, INT};
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
use crate::parser::FLOAT;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)]
|
||||||
|
pub enum VariableType {
|
||||||
|
Normal,
|
||||||
|
Constant,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ScopeEntry<'a> {
|
||||||
|
pub name: Cow<'a, str>,
|
||||||
|
pub var_type: VariableType,
|
||||||
|
pub value: Dynamic,
|
||||||
|
pub expr: Option<Expr>,
|
||||||
|
}
|
||||||
|
|
||||||
/// A type containing information about current scope.
|
/// A type containing information about current scope.
|
||||||
/// Useful for keeping state between `Engine` runs.
|
/// Useful for keeping state between `Engine` runs.
|
||||||
///
|
///
|
||||||
@ -25,7 +42,7 @@ use std::borrow::Cow;
|
|||||||
///
|
///
|
||||||
/// When searching for variables, newly-added variables are found before similarly-named but older variables,
|
/// When searching for variables, newly-added variables are found before similarly-named but older variables,
|
||||||
/// allowing for automatic _shadowing_ of variables.
|
/// allowing for automatic _shadowing_ of variables.
|
||||||
pub struct Scope<'a>(Vec<(Cow<'a, str>, Dynamic)>);
|
pub struct Scope<'a>(Vec<ScopeEntry<'a>>);
|
||||||
|
|
||||||
impl<'a> Scope<'a> {
|
impl<'a> Scope<'a> {
|
||||||
/// Create a new Scope.
|
/// Create a new Scope.
|
||||||
@ -44,18 +61,62 @@ impl<'a> Scope<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Add (push) a new variable to the Scope.
|
/// Add (push) a new variable to the Scope.
|
||||||
pub fn push<K: Into<Cow<'a, str>>, T: Any>(&mut self, key: K, value: T) {
|
pub fn push<K: Into<Cow<'a, str>>, T: Any>(&mut self, name: K, value: T) {
|
||||||
self.0.push((key.into(), Box::new(value)));
|
let value = value.into_dynamic();
|
||||||
|
|
||||||
|
// Map into constant expressions
|
||||||
|
let (expr, value) = map_dynamic_to_expr(value);
|
||||||
|
|
||||||
|
self.0.push(ScopeEntry {
|
||||||
|
name: name.into(),
|
||||||
|
var_type: VariableType::Normal,
|
||||||
|
value,
|
||||||
|
expr,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add (push) a new variable to the Scope.
|
/// Add (push) a new constant to the Scope.
|
||||||
pub(crate) fn push_dynamic<K: Into<Cow<'a, str>>>(&mut self, key: K, value: Dynamic) {
|
pub fn push_constant<K: Into<Cow<'a, str>>, T: Any>(&mut self, name: K, value: T) {
|
||||||
self.0.push((key.into(), value));
|
let value = value.into_dynamic();
|
||||||
|
|
||||||
|
// Map into constant expressions
|
||||||
|
let (expr, value) = map_dynamic_to_expr(value);
|
||||||
|
|
||||||
|
self.0.push(ScopeEntry {
|
||||||
|
name: name.into(),
|
||||||
|
var_type: VariableType::Constant,
|
||||||
|
value,
|
||||||
|
expr,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add (push) a new variable with a `Dynamic` value to the Scope.
|
||||||
|
pub(crate) fn push_dynamic<K: Into<Cow<'a, str>>>(
|
||||||
|
&mut self,
|
||||||
|
name: K,
|
||||||
|
var_type: VariableType,
|
||||||
|
value: Dynamic,
|
||||||
|
) {
|
||||||
|
let (expr, value) = map_dynamic_to_expr(value);
|
||||||
|
|
||||||
|
self.0.push(ScopeEntry {
|
||||||
|
name: name.into(),
|
||||||
|
var_type,
|
||||||
|
value,
|
||||||
|
expr,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove (pop) the last variable from the Scope.
|
/// Remove (pop) the last variable from the Scope.
|
||||||
pub fn pop(&mut self) -> Option<(String, Dynamic)> {
|
pub fn pop(&mut self) -> Option<(String, VariableType, Dynamic)> {
|
||||||
self.0.pop().map(|(key, value)| (key.to_string(), value))
|
self.0.pop().map(
|
||||||
|
|ScopeEntry {
|
||||||
|
name,
|
||||||
|
var_type,
|
||||||
|
value,
|
||||||
|
..
|
||||||
|
}| (name.to_string(), var_type, value),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Truncate (rewind) the Scope to a previous size.
|
/// Truncate (rewind) the Scope to a previous size.
|
||||||
@ -64,13 +125,23 @@ impl<'a> Scope<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Find a variable in the Scope, starting from the last.
|
/// Find a variable in the Scope, starting from the last.
|
||||||
pub fn get(&self, key: &str) -> Option<(usize, &str, Dynamic)> {
|
pub fn get(&self, key: &str) -> Option<(usize, &str, VariableType, Dynamic)> {
|
||||||
self.0
|
self.0
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.rev() // Always search a Scope in reverse order
|
.rev() // Always search a Scope in reverse order
|
||||||
.find(|(_, (name, _))| name == key)
|
.find(|(_, ScopeEntry { name, .. })| name == key)
|
||||||
.map(|(i, (name, value))| (i, name.as_ref(), value.clone()))
|
.map(
|
||||||
|
|(
|
||||||
|
i,
|
||||||
|
ScopeEntry {
|
||||||
|
name,
|
||||||
|
var_type,
|
||||||
|
value,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
)| (i, name.as_ref(), *var_type, value.clone()),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the value of a variable in the Scope, starting from the last.
|
/// Get the value of a variable in the Scope, starting from the last.
|
||||||
@ -79,53 +150,104 @@ impl<'a> Scope<'a> {
|
|||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.rev() // Always search a Scope in reverse order
|
.rev() // Always search a Scope in reverse order
|
||||||
.find(|(_, (name, _))| name == key)
|
.find(|(_, ScopeEntry { name, .. })| name == key)
|
||||||
.and_then(|(_, (_, value))| value.downcast_ref::<T>())
|
.and_then(|(_, ScopeEntry { value, .. })| value.downcast_ref::<T>())
|
||||||
.map(|value| value.clone())
|
.map(T::clone)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a mutable reference to a variable in the Scope.
|
/// Get a mutable reference to a variable in the Scope.
|
||||||
pub(crate) fn get_mut(&mut self, key: &str, index: usize) -> &mut Dynamic {
|
pub(crate) fn get_mut(&mut self, name: &str, index: usize) -> &mut Dynamic {
|
||||||
let entry = self.0.get_mut(index).expect("invalid index in Scope");
|
let entry = self.0.get_mut(index).expect("invalid index in Scope");
|
||||||
|
|
||||||
assert_eq!(entry.0, key, "incorrect key at Scope entry");
|
assert_ne!(
|
||||||
|
entry.var_type,
|
||||||
|
VariableType::Constant,
|
||||||
|
"get mut of constant variable"
|
||||||
|
);
|
||||||
|
assert_eq!(entry.name, name, "incorrect key at Scope entry");
|
||||||
|
|
||||||
&mut entry.1
|
&mut entry.value
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a mutable reference to a variable in the Scope and downcast it to a specific type
|
/// Get a mutable reference to a variable in the Scope and downcast it to a specific type
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
pub(crate) fn get_mut_by_type<T: Any + Clone>(&mut self, key: &str, index: usize) -> &mut T {
|
pub(crate) fn get_mut_by_type<T: Any + Clone>(&mut self, name: &str, index: usize) -> &mut T {
|
||||||
self.get_mut(key, index)
|
self.get_mut(name, index)
|
||||||
.downcast_mut::<T>()
|
.downcast_mut::<T>()
|
||||||
.expect("wrong type cast")
|
.expect("wrong type cast")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get an iterator to variables in the Scope.
|
/// Get an iterator to variables in the Scope.
|
||||||
pub fn iter(&self) -> impl Iterator<Item = (&str, &Dynamic)> {
|
pub fn iter(&self) -> impl Iterator<Item = &ScopeEntry> {
|
||||||
self.0
|
self.0.iter().rev() // Always search a Scope in reverse order
|
||||||
.iter()
|
|
||||||
.rev() // Always search a Scope in reverse order
|
|
||||||
.map(|(key, value)| (key.as_ref(), value))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
/// Get a mutable iterator to variables in the Scope.
|
|
||||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &mut Dynamic)> {
|
|
||||||
self.0
|
|
||||||
.iter_mut()
|
|
||||||
.rev() // Always search a Scope in reverse order
|
|
||||||
.map(|(key, value)| (key.as_ref(), value))
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, K> std::iter::Extend<(K, Dynamic)> for Scope<'a>
|
impl<'a, K> std::iter::Extend<(K, VariableType, Dynamic)> for Scope<'a>
|
||||||
where
|
where
|
||||||
K: Into<Cow<'a, str>>,
|
K: Into<Cow<'a, str>>,
|
||||||
{
|
{
|
||||||
fn extend<T: IntoIterator<Item = (K, Dynamic)>>(&mut self, iter: T) {
|
fn extend<T: IntoIterator<Item = (K, VariableType, Dynamic)>>(&mut self, iter: T) {
|
||||||
self.0
|
self.0
|
||||||
.extend(iter.into_iter().map(|(key, value)| (key.into(), value)));
|
.extend(iter.into_iter().map(|(name, var_type, value)| ScopeEntry {
|
||||||
|
name: name.into(),
|
||||||
|
var_type,
|
||||||
|
value,
|
||||||
|
expr: None,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_dynamic_to_expr(value: Dynamic) -> (Option<Expr>, Dynamic) {
|
||||||
|
if value.is::<INT>() {
|
||||||
|
let value2 = value.clone();
|
||||||
|
(
|
||||||
|
Some(Expr::IntegerConstant(
|
||||||
|
*value.downcast::<INT>().expect("value should be INT"),
|
||||||
|
Position::none(),
|
||||||
|
)),
|
||||||
|
value2,
|
||||||
|
)
|
||||||
|
} else if value.is::<FLOAT>() {
|
||||||
|
let value2 = value.clone();
|
||||||
|
(
|
||||||
|
Some(Expr::FloatConstant(
|
||||||
|
*value.downcast::<FLOAT>().expect("value should be FLOAT"),
|
||||||
|
Position::none(),
|
||||||
|
)),
|
||||||
|
value2,
|
||||||
|
)
|
||||||
|
} else if value.is::<char>() {
|
||||||
|
let value2 = value.clone();
|
||||||
|
(
|
||||||
|
Some(Expr::CharConstant(
|
||||||
|
*value.downcast::<char>().expect("value should be char"),
|
||||||
|
Position::none(),
|
||||||
|
)),
|
||||||
|
value2,
|
||||||
|
)
|
||||||
|
} else if value.is::<String>() {
|
||||||
|
let value2 = value.clone();
|
||||||
|
(
|
||||||
|
Some(Expr::StringConstant(
|
||||||
|
*value.downcast::<String>().expect("value should be String"),
|
||||||
|
Position::none(),
|
||||||
|
)),
|
||||||
|
value2,
|
||||||
|
)
|
||||||
|
} else if value.is::<bool>() {
|
||||||
|
let value2 = value.clone();
|
||||||
|
(
|
||||||
|
Some(
|
||||||
|
if *value.downcast::<bool>().expect("value should be bool") {
|
||||||
|
Expr::True(Position::none())
|
||||||
|
} else {
|
||||||
|
Expr::False(Position::none())
|
||||||
|
},
|
||||||
|
),
|
||||||
|
value2,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(None, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,12 +11,12 @@ fn test_chars() -> Result<(), EvalAltResult> {
|
|||||||
{
|
{
|
||||||
assert_eq!(engine.eval::<char>(r#"let x="hello"; x[2]"#)?, 'l');
|
assert_eq!(engine.eval::<char>(r#"let x="hello"; x[2]"#)?, 'l');
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<String>(r#"let x="hello"; x[2]='$'; x"#)?,
|
engine.eval::<String>(r#"let y="hello"; y[2]='$'; y"#)?,
|
||||||
"he$lo".to_string()
|
"he$lo".to_string()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert!(engine.eval::<char>("'\\uhello'").is_err());
|
assert!(engine.eval::<char>(r"'\uhello'").is_err());
|
||||||
assert!(engine.eval::<char>("''").is_err());
|
assert!(engine.eval::<char>("''").is_err());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
16
tests/constants.rs
Normal file
16
tests/constants.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
use rhai::{Engine, EvalAltResult};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_constant() -> Result<(), EvalAltResult> {
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
assert_eq!(engine.eval::<i64>("const x = 123; x")?, 123);
|
||||||
|
|
||||||
|
match engine.eval::<i64>("const x = 123; x = 42; x") {
|
||||||
|
Err(EvalAltResult::ErrorAssignmentToConstant(var, _)) if var == "x" => (),
|
||||||
|
Err(err) => return Err(err),
|
||||||
|
Ok(_) => panic!("expecting compilation error"),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user