Merge pull request #105 from schungx/master

Fixes and cleanup plus an optimizer.
This commit is contained in:
Stephen Chung 2020-03-10 11:32:24 +08:00 committed by GitHub
commit faf78ccdd3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 2478 additions and 1136 deletions

View File

@ -14,6 +14,10 @@ include = [
"Cargo.toml"
]
[dependencies]
num-traits = "*"
[features]
debug_msgs = []
no_stdlib = []
no_stdlib = []
unchecked = []

985
README.md

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
use rhai::{Engine, RegisterFn};
use rhai::{Engine, EvalAltResult, RegisterFn};
#[derive(Clone)]
struct TestStruct {
@ -15,7 +15,7 @@ impl TestStruct {
}
}
fn main() {
fn main() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
engine.register_type::<TestStruct>();
@ -23,7 +23,9 @@ fn main() {
engine.register_fn("update", TestStruct::update);
engine.register_fn("new_ts", TestStruct::new);
if let Ok(result) = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x") {
println!("result: {}", result.x); // prints 1001
}
let result = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?;
println!("result: {}", result.x); // prints 1001
Ok(())
}

View File

@ -1,9 +1,11 @@
use rhai::Engine;
use rhai::{Engine, EvalAltResult};
fn main() {
fn main() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
if let Ok(result) = engine.eval::<i64>("40 + 2") {
println!("Answer: {}", result); // prints 42
}
let result = engine.eval::<i64>("40 + 2")?;
println!("Answer: {}", result); // prints 42
Ok(())
}

View File

@ -1,25 +1,66 @@
use rhai::{Engine, RegisterFn, Scope};
use std::io::{stdin, stdout, Write};
use std::process::exit;
use rhai::{Engine, EvalAltResult, Scope};
use std::{
io::{stdin, stdout, Write},
iter,
};
pub fn main() {
let mut engine = Engine::new();
let mut scope = Scope::new();
fn print_error(input: &str, err: EvalAltResult) {
fn padding(pad: &str, len: usize) -> String {
iter::repeat(pad).take(len).collect::<String>()
}
engine.register_fn("exit", || exit(0));
let lines: Vec<_> = input.split("\n").collect();
loop {
print!("> ");
let mut input = String::new();
stdout().flush().expect("couldn't flush stdout");
if let Err(e) = stdin().read_line(&mut input) {
println!("input error: {}", e);
// Print error
match err.position() {
p if p.is_eof() => {
// EOF
let last = lines[lines.len() - 2];
println!("{}", last);
println!("{}^ {}", padding(" ", last.len() - 1), err);
}
p if p.is_none() => {
// No position
println!("{}", err);
}
p => {
// Specific position
let pos_text = format!(
" (line {}, position {})",
p.line().unwrap(),
p.position().unwrap()
);
if let Err(e) = engine.consume_with_scope(&mut scope, &input) {
println!("error: {}", e);
println!("{}", lines[p.line().unwrap() - 1]);
println!(
"{}^ {}",
padding(" ", p.position().unwrap() - 1),
err.to_string().replace(&pos_text, "")
);
}
}
}
fn main() {
let mut engine = Engine::new();
let mut scope = Scope::new();
let mut input = String::new();
loop {
print!("rhai> ");
stdout().flush().expect("couldn't flush stdout");
input.clear();
if let Err(err) = stdin().read_line(&mut input) {
println!("input error: {}", err);
}
if let Err(err) = engine.consume_with_scope(&mut scope, true, &input) {
println!("");
print_error(&input, err);
println!("");
}
}
}

View File

@ -1,14 +1,14 @@
use rhai::{Engine, Scope};
use rhai::{Engine, EvalAltResult, Scope};
fn main() {
fn main() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
let mut scope = Scope::new();
assert!(engine
.eval_with_scope::<()>(&mut scope, "let x = 4 + 5")
.is_ok());
engine.eval_with_scope::<()>(&mut scope, false, "let x = 4 + 5")?;
if let Ok(result) = engine.eval_with_scope::<i64>(&mut scope, "x") {
println!("result: {}", result);
}
let result = engine.eval_with_scope::<i64>(&mut scope, false, "x")?;
println!("result: {}", result);
Ok(())
}

View File

@ -1,27 +1,79 @@
use rhai::{Engine, RegisterFn};
use std::env;
use std::fmt::Display;
use rhai::{Engine, EvalAltResult};
use std::{env, fs::File, io::Read, iter, process::exit};
fn showit<T: Display>(x: &mut T) -> () {
println!("{}", x)
fn padding(pad: &str, len: usize) -> String {
iter::repeat(pad).take(len).collect::<String>()
}
fn main() {
for fname in env::args().skip(1) {
let mut engine = Engine::new();
fn eprint_error(input: &str, err: EvalAltResult) {
fn eprint_line(lines: &Vec<&str>, line: usize, pos: usize, err: &str) {
let line_no = format!("{}: ", line);
let pos_text = format!(" (line {}, position {})", line, pos);
engine.register_fn("print", showit as fn(x: &mut i32) -> ());
engine.register_fn("print", showit as fn(x: &mut i64) -> ());
engine.register_fn("print", showit as fn(x: &mut u32) -> ());
engine.register_fn("print", showit as fn(x: &mut u64) -> ());
engine.register_fn("print", showit as fn(x: &mut f32) -> ());
engine.register_fn("print", showit as fn(x: &mut f64) -> ());
engine.register_fn("print", showit as fn(x: &mut bool) -> ());
engine.register_fn("print", showit as fn(x: &mut String) -> ());
eprintln!("{}{}", line_no, lines[line - 1]);
eprintln!(
"{}^ {}",
padding(" ", line_no.len() + pos - 1),
err.replace(&pos_text, "")
);
eprintln!("");
}
match engine.eval_file::<()>(&fname) {
Ok(_) => (),
Err(e) => println!("Error: {}", e),
let lines: Vec<_> = input.split("\n").collect();
// Print error
match err.position() {
p if p.is_eof() => {
// EOF
let line = lines.len() - 1;
let pos = lines[line - 1].len();
eprint_line(&lines, line, pos, &err.to_string());
}
p if p.is_none() => {
// No position
eprintln!("{}", err);
}
p => {
// Specific position
eprint_line(
&lines,
p.line().unwrap(),
p.position().unwrap(),
&err.to_string(),
)
}
}
}
fn main() {
for filename in env::args().skip(1) {
let mut engine = Engine::new();
let mut f = match File::open(&filename) {
Err(err) => {
eprintln!("Error reading script file: {}\n{}", filename, err);
exit(1);
}
Ok(f) => f,
};
let mut contents = String::new();
match f.read_to_string(&mut contents) {
Err(err) => {
eprintln!("Error reading script file: {}\n{}", filename, err);
exit(1);
}
_ => (),
}
if let Err(err) = engine.consume(&contents) {
eprintln!("{}", padding("=", filename.len()));
eprintln!("{}", filename);
eprintln!("{}", padding("=", filename.len()));
eprintln!("");
eprint_error(&contents, err);
}
}
}

View File

@ -1,6 +1,6 @@
use rhai::{Engine, RegisterFn};
use rhai::{Engine, EvalAltResult, RegisterFn};
fn main() {
fn main() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
fn add(x: i64, y: i64) -> i64 {
@ -9,7 +9,9 @@ fn main() {
engine.register_fn("add", add);
if let Ok(result) = engine.eval::<i64>("add(40, 2)") {
println!("Answer: {}", result); // prints 42
}
let result = engine.eval::<i64>("add(40, 2)")?;
println!("Answer: {}", result); // prints 42
Ok(())
}

View File

@ -1,5 +1,9 @@
use std::any::{type_name, TypeId};
use std::fmt;
//! Helper module which defines the `Any` trait to to allow dynamic value handling.
use std::{
any::{type_name, TypeId},
fmt,
};
/// An raw value of any type.
pub type Variant = dyn Any;

View File

@ -1,3 +1,5 @@
//! Module that defines the extern API of `Engine`.
use crate::any::{Any, AnyExt, Dynamic};
use crate::call::FuncArgs;
use crate::engine::{Engine, FnAny, FnIntExt, FnSpec};
@ -6,10 +8,14 @@ use crate::fn_register::RegisterFn;
use crate::parser::{lex, parse, Position, AST};
use crate::result::EvalAltResult;
use crate::scope::Scope;
use std::any::TypeId;
use std::sync::Arc;
use std::{
any::{type_name, TypeId},
fs::File,
io::prelude::*,
sync::Arc,
};
impl<'a> Engine<'a> {
impl<'e> Engine<'e> {
pub(crate) fn register_fn_raw(
&mut self,
fn_name: &str,
@ -17,12 +23,16 @@ impl<'a> Engine<'a> {
f: Box<FnAny>,
) {
debug_println!(
"Register function: {} for {} parameter(s)",
"Register function: {} with {}",
fn_name,
if let Some(a) = &args {
format!("{}", a.len())
format!(
"{} parameter{}",
a.len(),
if a.len() > 1 { "s" } else { "" }
)
} else {
"no".to_string()
"no parameter".to_string()
}
);
@ -31,24 +41,21 @@ impl<'a> Engine<'a> {
args,
};
self.external_functions
.insert(spec, Arc::new(FnIntExt::Ext(f)));
self.ext_functions.insert(spec, Arc::new(FnIntExt::Ext(f)));
}
/// Register a custom type for use with the `Engine`.
/// The type must be `Clone`.
pub fn register_type<T: Any + Clone>(&mut self) {
self.register_type_with_name::<T>(std::any::type_name::<T>());
self.register_type_with_name::<T>(type_name::<T>());
}
/// Register a custom type for use with the `Engine` with a name for the `type_of` function.
/// The type must be `Clone`.
pub fn register_type_with_name<T: Any + Clone>(&mut self, type_name: &str) {
pub fn register_type_with_name<T: Any + Clone>(&mut self, name: &str) {
// Add the pretty-print type name into the map
self.type_names.insert(
std::any::type_name::<T>().to_string(),
type_name.to_string(),
);
self.type_names
.insert(type_name::<T>().to_string(), name.to_string());
}
/// Register an iterator adapter for a type with the `Engine`.
@ -91,17 +98,14 @@ impl<'a> Engine<'a> {
self.register_set(name, set_fn);
}
/// Compile a string into an AST
pub fn compile(input: &str) -> Result<AST, ParseError> {
/// Compile a string into an AST.
pub fn compile(&self, input: &str) -> Result<AST, ParseError> {
let tokens = lex(input);
parse(&mut tokens.peekable())
parse(&mut tokens.peekable(), self.optimize)
}
/// Compile a file into an AST
pub fn compile_file(filename: &str) -> Result<AST, EvalAltResult> {
use std::fs::File;
use std::io::prelude::*;
/// Compile a file into an AST.
pub fn compile_file(&self, filename: &str) -> Result<AST, EvalAltResult> {
let mut f = File::open(filename)
.map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err))?;
@ -109,14 +113,11 @@ impl<'a> Engine<'a> {
f.read_to_string(&mut contents)
.map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err))
.and_then(|_| Self::compile(&contents).map_err(EvalAltResult::ErrorParsing))
.and_then(|_| self.compile(&contents).map_err(EvalAltResult::ErrorParsing))
}
/// Evaluate a file
/// Evaluate a file.
pub fn eval_file<T: Any + Clone>(&mut self, filename: &str) -> Result<T, EvalAltResult> {
use std::fs::File;
use std::io::prelude::*;
let mut f = File::open(filename)
.map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err))?;
@ -127,32 +128,40 @@ impl<'a> Engine<'a> {
.and_then(|_| self.eval::<T>(&contents))
}
/// Evaluate a string
/// Evaluate a string.
pub fn eval<T: Any + Clone>(&mut self, input: &str) -> Result<T, EvalAltResult> {
let mut scope = Scope::new();
self.eval_with_scope(&mut scope, input)
self.eval_with_scope(&mut scope, false, input)
}
/// Evaluate a string with own scope
/// Evaluate a string with own scope.
///
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
/// and not cleared from run to run.
pub fn eval_with_scope<T: Any + Clone>(
&mut self,
scope: &mut Scope,
retain_functions: bool,
input: &str,
) -> Result<T, EvalAltResult> {
let ast = Self::compile(input).map_err(EvalAltResult::ErrorParsing)?;
self.eval_ast_with_scope(scope, &ast)
let ast = self.compile(input).map_err(EvalAltResult::ErrorParsing)?;
self.eval_ast_with_scope(scope, retain_functions, &ast)
}
/// Evaluate an AST
/// Evaluate an AST.
pub fn eval_ast<T: Any + Clone>(&mut self, ast: &AST) -> Result<T, EvalAltResult> {
let mut scope = Scope::new();
self.eval_ast_with_scope(&mut scope, ast)
self.eval_ast_with_scope(&mut scope, false, ast)
}
/// Evaluate an AST with own scope
/// Evaluate an AST with own scope.
///
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
/// and not cleared from run to run.
pub fn eval_ast_with_scope<T: Any + Clone>(
&mut self,
scope: &mut Scope,
retain_functions: bool,
ast: &AST,
) -> Result<T, EvalAltResult> {
let AST(statements, functions) = ast;
@ -169,9 +178,11 @@ impl<'a> Engine<'a> {
let result = statements
.iter()
.try_fold(().into_dynamic(), |_, o| self.eval_stmt(scope, o));
.try_fold(().into_dynamic(), |_, stmt| self.eval_stmt(scope, stmt));
self.script_functions.clear(); // Clean up engine
if !retain_functions {
self.clear_functions();
}
match result {
Err(EvalAltResult::Return(out, pos)) => out.downcast::<T>().map(|v| *v).map_err(|a| {
@ -193,12 +204,8 @@ impl<'a> Engine<'a> {
}
/// Evaluate a file, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need
/// to keep track of possible errors
/// Useful for when you don't need the result, but still need to keep track of possible errors.
pub fn consume_file(&mut self, filename: &str) -> Result<(), EvalAltResult> {
use std::fs::File;
use std::io::prelude::*;
let mut f = File::open(filename)
.map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err))?;
@ -210,23 +217,25 @@ impl<'a> Engine<'a> {
}
/// Evaluate a string, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need
/// to keep track of possible errors
/// Useful for when you don't need the result, but still need to keep track of possible errors.
pub fn consume(&mut self, input: &str) -> Result<(), EvalAltResult> {
self.consume_with_scope(&mut Scope::new(), input)
self.consume_with_scope(&mut Scope::new(), false, input)
}
/// Evaluate a string with own scope, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need
/// to keep track of possible errors
/// Evaluate a string, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors.
///
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
/// and not cleared from run to run.
pub fn consume_with_scope(
&mut self,
scope: &mut Scope,
retain_functions: bool,
input: &str,
) -> Result<(), EvalAltResult> {
let tokens = lex(input);
parse(&mut tokens.peekable())
parse(&mut tokens.peekable(), self.optimize)
.map_err(|err| EvalAltResult::ErrorParsing(err))
.and_then(|AST(ref statements, ref functions)| {
for f in functions {
@ -244,7 +253,9 @@ impl<'a> Engine<'a> {
.try_fold(().into_dynamic(), |_, o| self.eval_stmt(scope, o))
.map(|_| ());
self.script_functions.clear(); // Clean up engine
if !retain_functions {
self.clear_functions();
}
val
})
@ -255,13 +266,14 @@ impl<'a> Engine<'a> {
/// # Example
///
/// ```rust
/// # use rhai::{Engine, EvalAltResult};
/// # fn main() -> Result<(), EvalAltResult> {
/// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::Engine;
///
/// let mut engine = Engine::new();
///
/// let ast = Engine::compile("fn add(x, y) { x.len() + y }")?;
/// let ast = engine.compile("fn add(x, y) { x.len() + y }")?;
///
/// let result: i64 = engine.call_fn("add", ast, (&mut String::from("abc"), &mut 123_i64))?;
/// let result: i64 = engine.call_fn("add", &ast, (&mut String::from("abc"), &mut 123_i64))?;
///
/// assert_eq!(result, 126);
/// # Ok(())
@ -270,7 +282,7 @@ impl<'a> Engine<'a> {
pub fn call_fn<'f, A: FuncArgs<'f>, T: Any + Clone>(
&mut self,
name: &str,
ast: AST,
ast: &AST,
args: A,
) -> Result<T, EvalAltResult> {
let pos = Default::default();
@ -296,7 +308,7 @@ impl<'a> Engine<'a> {
})
});
self.script_functions.clear(); // Clean up engine
self.clear_functions();
result
}
@ -306,18 +318,22 @@ impl<'a> Engine<'a> {
/// # Example
///
/// ```rust
/// # use rhai::Engine;
/// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::Engine;
///
/// let mut result = String::from("");
/// {
/// let mut engine = Engine::new();
///
/// // Override action of 'print' function
/// engine.on_print(|s| result.push_str(s));
/// engine.consume("print(40 + 2);").unwrap();
/// engine.consume("print(40 + 2);")?;
/// }
/// assert_eq!(result, "42");
/// # Ok(())
/// # }
/// ```
pub fn on_print(&mut self, callback: impl FnMut(&str) + 'a) {
pub fn on_print(&mut self, callback: impl FnMut(&str) + 'e) {
self.on_print = Box::new(callback);
}
@ -326,18 +342,22 @@ impl<'a> Engine<'a> {
/// # Example
///
/// ```rust
/// # use rhai::Engine;
/// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::Engine;
///
/// let mut result = String::from("");
/// {
/// let mut engine = Engine::new();
///
/// // Override action of 'debug' function
/// engine.on_debug(|s| result.push_str(s));
/// engine.consume(r#"debug("hello");"#).unwrap();
/// engine.consume(r#"debug("hello");"#)?;
/// }
/// assert_eq!(result, "\"hello\"");
/// # Ok(())
/// # }
/// ```
pub fn on_debug(&mut self, callback: impl FnMut(&str) + 'a) {
pub fn on_debug(&mut self, callback: impl FnMut(&str) + 'e) {
self.on_debug = Box::new(callback);
}
}

View File

@ -1,8 +1,29 @@
//! Helper module that allows registration of the _core library_ and
//! _standard library_ of utility functions.
use crate::any::Any;
use crate::engine::{Array, Engine};
use crate::fn_register::RegisterFn;
use std::fmt::{Debug, Display};
use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub};
use std::{
fmt::{Debug, Display},
i32, i64,
ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Sub},
u32,
};
#[cfg(feature = "unchecked")]
use std::ops::{Shl, Shr};
#[cfg(not(feature = "unchecked"))]
use crate::{parser::Position, result::EvalAltResult, RegisterResultFn};
#[cfg(not(feature = "unchecked"))]
use std::convert::TryFrom;
#[cfg(not(feature = "unchecked"))]
use num_traits::{
CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, CheckedShr, CheckedSub,
};
macro_rules! reg_op {
($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => (
@ -12,6 +33,24 @@ macro_rules! reg_op {
)
}
#[cfg(not(feature = "unchecked"))]
macro_rules! reg_op_result {
($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => (
$(
$self.register_result_fn($x, $op as fn(x: $y, y: $y)->Result<$y,EvalAltResult>);
)*
)
}
#[cfg(not(feature = "unchecked"))]
macro_rules! reg_op_result1 {
($self:expr, $x:expr, $op:expr, $v:ty, $( $y:ty ),*) => (
$(
$self.register_result_fn($x, $op as fn(x: $y, y: $v)->Result<$y,EvalAltResult>);
)*
)
}
macro_rules! reg_un {
($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => (
$(
@ -20,6 +59,14 @@ macro_rules! reg_un {
)
}
#[cfg(not(feature = "unchecked"))]
macro_rules! reg_un_result {
($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => (
$(
$self.register_result_fn($x, $op as fn(x: $y)->Result<$y,EvalAltResult>);
)*
)
}
macro_rules! reg_cmp {
($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => (
$(
@ -66,21 +113,102 @@ macro_rules! reg_func3 {
impl Engine<'_> {
/// Register the core built-in library.
pub(crate) fn register_core_lib(&mut self) {
fn add<T: Add>(x: T, y: T) -> <T as Add>::Output {
#[cfg(not(feature = "unchecked"))]
fn add<T: Display + CheckedAdd>(x: T, y: T) -> Result<T, EvalAltResult> {
x.checked_add(&y).ok_or_else(|| {
EvalAltResult::ErrorArithmetic(
format!("Addition overflow: {} + {}", x, y),
Position::none(),
)
})
}
#[cfg(not(feature = "unchecked"))]
fn sub<T: Display + CheckedSub>(x: T, y: T) -> Result<T, EvalAltResult> {
x.checked_sub(&y).ok_or_else(|| {
EvalAltResult::ErrorArithmetic(
format!("Subtraction underflow: {} - {}", x, y),
Position::none(),
)
})
}
#[cfg(not(feature = "unchecked"))]
fn mul<T: Display + CheckedMul>(x: T, y: T) -> Result<T, EvalAltResult> {
x.checked_mul(&y).ok_or_else(|| {
EvalAltResult::ErrorArithmetic(
format!("Multiplication overflow: {} * {}", x, y),
Position::none(),
)
})
}
#[cfg(not(feature = "unchecked"))]
fn div<T>(x: T, y: T) -> Result<T, EvalAltResult>
where
T: Display + CheckedDiv + PartialEq + TryFrom<i8>,
{
if y == <T as TryFrom<i8>>::try_from(0)
.map_err(|_| ())
.expect("zero should always succeed")
{
return Err(EvalAltResult::ErrorArithmetic(
format!("Division by zero: {} / {}", x, y),
Position::none(),
));
}
x.checked_div(&y).ok_or_else(|| {
EvalAltResult::ErrorArithmetic(
format!("Division overflow: {} / {}", x, y),
Position::none(),
)
})
}
#[cfg(not(feature = "unchecked"))]
fn neg<T: Display + CheckedNeg>(x: T) -> Result<T, EvalAltResult> {
x.checked_neg().ok_or_else(|| {
EvalAltResult::ErrorArithmetic(
format!("Negation overflow: -{}", x),
Position::none(),
)
})
}
#[cfg(not(feature = "unchecked"))]
fn abs<T: Display + CheckedNeg + PartialOrd + From<i8>>(x: T) -> Result<T, EvalAltResult> {
if x >= 0.into() {
Ok(x)
} else {
x.checked_neg().ok_or_else(|| {
EvalAltResult::ErrorArithmetic(
format!("Negation overflow: -{}", x),
Position::none(),
)
})
}
}
fn add_u<T: Add>(x: T, y: T) -> <T as Add>::Output {
x + y
}
fn sub<T: Sub>(x: T, y: T) -> <T as Sub>::Output {
fn sub_u<T: Sub>(x: T, y: T) -> <T as Sub>::Output {
x - y
}
fn mul<T: Mul>(x: T, y: T) -> <T as Mul>::Output {
fn mul_u<T: Mul>(x: T, y: T) -> <T as Mul>::Output {
x * y
}
fn div<T: Div>(x: T, y: T) -> <T as Div>::Output {
fn div_u<T: Div>(x: T, y: T) -> <T as Div>::Output {
x / y
}
fn neg<T: Neg>(x: T) -> <T as Neg>::Output {
fn neg_u<T: Neg>(x: T) -> <T as Neg>::Output {
-x
}
fn abs_u<T: Neg + PartialOrd + From<i8>>(x: T) -> T
where
<T as Neg>::Output: Into<T>,
{
if x < 0.into() {
(-x).into()
} else {
x
}
}
fn lt<T: PartialOrd>(x: T, y: T) -> bool {
x < y
}
@ -117,29 +245,117 @@ impl Engine<'_> {
fn binary_xor<T: BitXor>(x: T, y: T) -> <T as BitXor>::Output {
x ^ y
}
fn left_shift<T: Shl<T>>(x: T, y: T) -> <T as Shl<T>>::Output {
#[cfg(not(feature = "unchecked"))]
fn shl<T: Display + CheckedShl>(x: T, y: i64) -> Result<T, EvalAltResult> {
if y < 0 {
return Err(EvalAltResult::ErrorArithmetic(
format!("Left-shift by a negative number: {} << {}", x, y),
Position::none(),
));
}
CheckedShl::checked_shl(&x, y as u32).ok_or_else(|| {
EvalAltResult::ErrorArithmetic(
format!("Left-shift overflow: {} << {}", x, y),
Position::none(),
)
})
}
#[cfg(not(feature = "unchecked"))]
fn shr<T: Display + CheckedShr>(x: T, y: i64) -> Result<T, EvalAltResult> {
if y < 0 {
return Err(EvalAltResult::ErrorArithmetic(
format!("Right-shift by a negative number: {} >> {}", x, y),
Position::none(),
));
}
CheckedShr::checked_shr(&x, y as u32).ok_or_else(|| {
EvalAltResult::ErrorArithmetic(
format!("Right-shift overflow: {} % {}", x, y),
Position::none(),
)
})
}
#[cfg(feature = "unchecked")]
fn shl_u<T: Shl<T>>(x: T, y: T) -> <T as Shl<T>>::Output {
x.shl(y)
}
fn right_shift<T: Shr<T>>(x: T, y: T) -> <T as Shr<T>>::Output {
#[cfg(feature = "unchecked")]
fn shr_u<T: Shr<T>>(x: T, y: T) -> <T as Shr<T>>::Output {
x.shr(y)
}
fn modulo<T: Rem<T>>(x: T, y: T) -> <T as Rem<T>>::Output {
#[cfg(not(feature = "unchecked"))]
fn modulo<T: Display + CheckedRem>(x: T, y: T) -> Result<T, EvalAltResult> {
x.checked_rem(&y).ok_or_else(|| {
EvalAltResult::ErrorArithmetic(
format!("Modulo division overflow: {} % {}", x, y),
Position::none(),
)
})
}
fn modulo_u<T: Rem>(x: T, y: T) -> <T as Rem>::Output {
x % y
}
#[cfg(not(feature = "unchecked"))]
fn pow_i64_i64_u(x: i64, y: i64) -> Result<i64, EvalAltResult> {
if y > (u32::MAX as i64) {
return Err(EvalAltResult::ErrorArithmetic(
format!("Power overflow: {} ~ {}", x, y),
Position::none(),
));
}
x.checked_pow(y as u32).ok_or_else(|| {
EvalAltResult::ErrorArithmetic(
format!("Power overflow: {} ~ {}", x, y),
Position::none(),
)
})
}
#[cfg(feature = "unchecked")]
fn pow_i64_i64(x: i64, y: i64) -> i64 {
x.pow(y as u32)
}
fn pow_f64_f64(x: f64, y: f64) -> f64 {
x.powf(y)
}
#[cfg(not(feature = "unchecked"))]
fn pow_f64_i64_u(x: f64, y: i64) -> Result<f64, EvalAltResult> {
if y > (i32::MAX as i64) {
return Err(EvalAltResult::ErrorArithmetic(
format!("Power overflow: {} ~ {}", x, y),
Position::none(),
));
}
Ok(x.powi(y as i32))
}
#[cfg(feature = "unchecked")]
fn pow_f64_i64(x: f64, y: i64) -> f64 {
x.powi(y as i32)
}
reg_op!(self, "+", add, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64);
reg_op!(self, "-", sub, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64);
reg_op!(self, "*", mul, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64);
reg_op!(self, "/", div, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64);
#[cfg(not(feature = "unchecked"))]
{
reg_op_result!(self, "+", add, i8, u8, i16, u16, i32, i64, u32, u64);
reg_op_result!(self, "-", sub, i8, u8, i16, u16, i32, i64, u32, u64);
reg_op_result!(self, "*", mul, i8, u8, i16, u16, i32, i64, u32, u64);
reg_op_result!(self, "/", div, i8, u8, i16, u16, i32, i64, u32, u64);
}
#[cfg(feature = "unchecked")]
{
reg_op!(self, "+", add_u, i8, u8, i16, u16, i32, i64, u32, u64);
reg_op!(self, "-", sub_u, i8, u8, i16, u16, i32, i64, u32, u64);
reg_op!(self, "*", mul_u, i8, u8, i16, u16, i32, i64, u32, u64);
reg_op!(self, "/", div_u, i8, u8, i16, u16, i32, i64, u32, u64);
}
reg_op!(self, "+", add_u, f32, f64);
reg_op!(self, "-", sub_u, f32, f64);
reg_op!(self, "*", mul_u, f32, f64);
reg_op!(self, "/", div_u, f32, f64);
reg_cmp!(self, "<", lt, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64, String, char);
reg_cmp!(self, "<=", lte, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64, String, char);
@ -159,15 +375,53 @@ impl Engine<'_> {
reg_op!(self, "&", binary_and, i8, u8, i16, u16, i32, i64, u32, u64);
reg_op!(self, "&", and, bool);
reg_op!(self, "^", binary_xor, i8, u8, i16, u16, i32, i64, u32, u64);
reg_op!(self, "<<", left_shift, i8, u8, i16, u16, i32, i64, u32, u64);
reg_op!(self, ">>", right_shift, i8, u8, i16, u16);
reg_op!(self, ">>", right_shift, i32, i64, u32, u64);
reg_op!(self, "%", modulo, i8, u8, i16, u16, i32, i64, u32, u64);
self.register_fn("~", pow_i64_i64);
self.register_fn("~", pow_f64_f64);
self.register_fn("~", pow_f64_i64);
reg_un!(self, "-", neg, i8, i16, i32, i64, f32, f64);
#[cfg(not(feature = "unchecked"))]
{
reg_op_result1!(self, "<<", shl, i64, i8, u8, i16, u16, i32, i64, u32, u64);
reg_op_result1!(self, ">>", shr, i64, i8, u8, i16, u16);
reg_op_result1!(self, ">>", shr, i64, i32, i64, u32, u64);
reg_op_result!(self, "%", modulo, i8, u8, i16, u16, i32, i64, u32, u64);
}
#[cfg(feature = "unchecked")]
{
reg_op!(self, "<<", shl_u, i64, i8, u8, i16, u16, i32, i64, u32, u64);
reg_op!(self, ">>", shr_u, i64, i8, u8, i16, u16);
reg_op!(self, ">>", shr_u, i64, i32, i64, u32, u64);
reg_op!(self, "%", modulo_u, i8, u8, i16, u16, i32, i64, u32, u64);
}
reg_op!(self, "%", modulo_u, f32, f64);
self.register_fn("~", pow_f64_f64);
#[cfg(not(feature = "unchecked"))]
{
self.register_result_fn("~", pow_i64_i64_u);
self.register_result_fn("~", pow_f64_i64_u);
}
#[cfg(feature = "unchecked")]
{
self.register_fn("~", pow_i64_i64);
self.register_fn("~", pow_f64_i64);
}
#[cfg(not(feature = "unchecked"))]
{
reg_un_result!(self, "-", neg, i8, i16, i32, i64);
reg_un_result!(self, "abs", abs, i8, i16, i32, i64);
}
#[cfg(feature = "unchecked")]
{
reg_un!(self, "-", neg_u, i8, i16, i32, i64);
reg_un!(self, "abs", abs_u, i8, i16, i32, i64);
}
reg_un!(self, "-", neg_u, f32, f64);
reg_un!(self, "abs", abs_u, f32, f64);
reg_un!(self, "!", not, bool);
self.register_fn("+", |x: String, y: String| x + &y); // String + String
@ -216,6 +470,33 @@ impl Engine<'_> {
pub(crate) fn register_stdlib(&mut self) {
use crate::fn_register::RegisterDynamicFn;
// Advanced math functions
self.register_fn("sin", |x: f64| x.to_radians().sin());
self.register_fn("cos", |x: f64| x.to_radians().cos());
self.register_fn("tan", |x: f64| x.to_radians().tan());
self.register_fn("sinh", |x: f64| x.to_radians().sinh());
self.register_fn("cosh", |x: f64| x.to_radians().cosh());
self.register_fn("tanh", |x: f64| x.to_radians().tanh());
self.register_fn("asin", |x: f64| x.asin().to_degrees());
self.register_fn("acos", |x: f64| x.acos().to_degrees());
self.register_fn("atan", |x: f64| x.atan().to_degrees());
self.register_fn("asinh", |x: f64| x.asinh().to_degrees());
self.register_fn("acosh", |x: f64| x.acosh().to_degrees());
self.register_fn("atanh", |x: f64| x.atanh().to_degrees());
self.register_fn("sqrt", |x: f64| x.sqrt());
self.register_fn("exp", |x: f64| x.exp());
self.register_fn("ln", |x: f64| x.ln());
self.register_fn("log", |x: f64, base: f64| x.log(base));
self.register_fn("log10", |x: f64| x.log10());
self.register_fn("floor", |x: f64| x.floor());
self.register_fn("ceiling", |x: f64| x.ceil());
self.register_fn("round", |x: f64| x.ceil());
self.register_fn("int", |x: f64| x.trunc());
self.register_fn("fraction", |x: f64| x.fract());
self.register_fn("is_nan", |x: f64| x.is_nan());
self.register_fn("is_finite", |x: f64| x.is_finite());
self.register_fn("is_infinite", |x: f64| x.is_infinite());
// Register conversion functions
self.register_fn("to_float", |x: i8| x as f64);
self.register_fn("to_float", |x: u8| x as f64);
@ -234,11 +515,38 @@ impl Engine<'_> {
self.register_fn("to_int", |x: i32| x as i64);
self.register_fn("to_int", |x: u32| x as i64);
self.register_fn("to_int", |x: u64| x as i64);
self.register_fn("to_int", |x: f32| x as i64);
self.register_fn("to_int", |x: f64| x as i64);
self.register_fn("to_int", |ch: char| ch as i64);
#[cfg(not(feature = "unchecked"))]
{
self.register_result_fn("to_int", |x: f32| {
if x > (i64::MAX as f32) {
return Err(EvalAltResult::ErrorArithmetic(
format!("Integer overflow: to_int({})", x),
Position::none(),
));
}
Ok(x.trunc() as i64)
});
self.register_result_fn("to_int", |x: f64| {
if x > (i64::MAX as f64) {
return Err(EvalAltResult::ErrorArithmetic(
format!("Integer overflow: to_int({})", x),
Position::none(),
));
}
Ok(x.trunc() as i64)
});
}
#[cfg(feature = "unchecked")]
{
self.register_fn("to_int", |x: f32| x as i64);
self.register_fn("to_int", |x: f64| x as i64);
}
// Register array utility functions
fn push<T: Any>(list: &mut Array, item: T) {
list.push(Box::new(item));
@ -303,6 +611,8 @@ impl Engine<'_> {
self.register_fn("contains", |s: &mut String, ch: char| s.contains(ch));
self.register_fn("contains", |s: &mut String, find: String| s.contains(&find));
self.register_fn("clear", |s: &mut String| s.clear());
self.register_fn("append", |s: &mut String, ch: char| s.push(ch));
self.register_fn("append", |s: &mut String, add: String| s.push_str(&add));
self.register_fn("truncate", |s: &mut String, len: i64| {
if len >= 0 {
let chars: Vec<_> = s.chars().take(len as usize).collect();

View File

@ -1,5 +1,4 @@
//! Helper module which defines `FnArgs`
//! to make function calling easier.
//! Helper module which defines `FnArgs` to make function calling easier.
use crate::any::{Any, Variant};

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
//! Module containing error definitions for the parsing process.
use crate::parser::Position;
use std::char;
use std::error::Error;
use std::fmt;
use std::{char, error::Error, fmt};
/// Error when tokenizing the script text.
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
@ -64,9 +64,9 @@ pub enum ParseErrorType {
/// An open `[` is missing the corresponding closing `]`.
MissingRightBracket(String),
/// An expression in function call arguments `()` has syntax error.
MalformedCallExpr,
MalformedCallExpr(String),
/// An expression in indexing brackets `[]` has syntax error.
MalformedIndexExpr,
MalformedIndexExpr(String),
/// Missing a variable name after the `let` keyword.
VarExpectsIdentifier,
/// Defining a function `fn` in an appropriate place (e.g. inside another function).
@ -75,11 +75,13 @@ pub enum ParseErrorType {
FnMissingName,
/// A function definition is missing the parameters list. Wrapped value is the function name.
FnMissingParams(String),
/// Assignment to an inappropriate LHS (left-hand-side) expression.
AssignmentToInvalidLHS,
}
/// Error when parsing a script.
#[derive(Debug, PartialEq, Clone)]
pub struct ParseError(ParseErrorType, Position);
pub struct ParseError(pub(crate) ParseErrorType, pub(crate) Position);
impl ParseError {
/// Create a new `ParseError`.
@ -108,12 +110,13 @@ impl Error for ParseError {
ParseErrorType::MissingLeftBrace => "Expecting '{'",
ParseErrorType::MissingRightBrace(_) => "Expecting '}'",
ParseErrorType::MissingRightBracket(_) => "Expecting ']'",
ParseErrorType::MalformedCallExpr => "Invalid expression in function call arguments",
ParseErrorType::MalformedIndexExpr => "Invalid index in indexing expression",
ParseErrorType::MalformedCallExpr(_) => "Invalid expression in function call arguments",
ParseErrorType::MalformedIndexExpr(_) => "Invalid index in indexing expression",
ParseErrorType::VarExpectsIdentifier => "Expecting name of a variable",
ParseErrorType::FnMissingName => "Expecting name 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::AssignmentToInvalidLHS => "Cannot assign to this expression because it will only be changing a copy of the value"
}
}
@ -125,7 +128,11 @@ impl Error for ParseError {
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {
ParseErrorType::BadInput(ref s) => write!(f, "{}", s)?,
ParseErrorType::BadInput(ref s)
| ParseErrorType::MalformedIndexExpr(ref s)
| ParseErrorType::MalformedCallExpr(ref s) => {
write!(f, "{}", if s.is_empty() { self.description() } else { s })?
}
ParseErrorType::UnknownOperator(ref s) => write!(f, "{}: '{}'", self.description(), s)?,
ParseErrorType::FnMissingParams(ref s) => {
write!(f, "Expecting parameters for function '{}'", s)?
@ -140,6 +147,9 @@ impl fmt::Display for ParseError {
if !self.1.is_eof() {
write!(f, " ({})", self.1)
} else if !self.1.is_none() {
// Do not write any position if None
Ok(())
} else {
write!(f, " at the end of the script but there is no more input")
}

View File

@ -1,15 +1,17 @@
use std::any::TypeId;
//! Module which defines the function registration mechanism.
use crate::any::{Any, Dynamic};
use crate::engine::{Engine, FnCallArgs};
use crate::parser::Position;
use crate::result::EvalAltResult;
use std::any::TypeId;
/// A trait to register custom functions with the `Engine`.
///
/// # Example
///
/// ```rust
/// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::{Engine, RegisterFn};
///
/// // Normal function
@ -22,9 +24,11 @@ use crate::result::EvalAltResult;
/// // You must use the trait rhai::RegisterFn to get this method.
/// engine.register_fn("add", add);
///
/// if let Ok(result) = engine.eval::<i64>("add(40, 2)") {
/// println!("Answer: {}", result); // prints 42
/// }
/// let result = engine.eval::<i64>("add(40, 2)")?;
///
/// println!("Answer: {}", result); // prints 42
/// # Ok(())
/// # }
/// ```
pub trait RegisterFn<FN, ARGS, RET> {
/// Register a custom function with the `Engine`.
@ -36,7 +40,8 @@ pub trait RegisterFn<FN, ARGS, RET> {
/// # Example
///
/// ```rust
/// use rhai::{Engine, RegisterDynamicFn, Dynamic};
/// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::{Engine, Dynamic, RegisterDynamicFn};
///
/// // Function that returns a Dynamic value
/// fn get_an_any(x: i64) -> Dynamic {
@ -48,15 +53,46 @@ pub trait RegisterFn<FN, ARGS, RET> {
/// // You must use the trait rhai::RegisterDynamicFn to get this method.
/// engine.register_dynamic_fn("get_an_any", get_an_any);
///
/// if let Ok(result) = engine.eval::<i64>("get_an_any(42)") {
/// println!("Answer: {}", result); // prints 42
/// }
/// let result = engine.eval::<i64>("get_an_any(42)")?;
///
/// println!("Answer: {}", result); // prints 42
/// # Ok(())
/// # }
/// ```
pub trait RegisterDynamicFn<FN, ARGS> {
/// Register a custom function returning `Dynamic` values with the `Engine`.
fn register_dynamic_fn(&mut self, name: &str, f: FN);
}
/// A trait to register fallible custom functions returning Result<_, EvalAltResult> with the `Engine`.
///
/// # Example
///
/// ```rust
/// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::{Engine, RegisterFn};
///
/// // Normal function
/// fn add(x: i64, y: i64) -> i64 {
/// x + y
/// }
///
/// let mut engine = Engine::new();
///
/// // You must use the trait rhai::RegisterFn to get this method.
/// engine.register_fn("add", add);
///
/// let result = engine.eval::<i64>("add(40, 2)")?;
///
/// println!("Answer: {}", result); // prints 42
/// # Ok(())
/// # }
/// ```
pub trait RegisterResultFn<FN, ARGS, RET> {
/// Register a custom function with the `Engine`.
fn register_result_fn(&mut self, name: &str, f: FN);
}
pub struct Ref<A>(A);
pub struct Mut<A>(A);
@ -90,7 +126,7 @@ macro_rules! def_register {
let mut drain = args.drain(..);
$(
// Downcast every element, return in case of a type mismatch
let $par = (drain.next().unwrap().downcast_mut() as Option<&mut $par>).unwrap();
let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap();
)*
// Call the user-supplied function using ($clone) to
@ -122,7 +158,7 @@ macro_rules! def_register {
let mut drain = args.drain(..);
$(
// Downcast every element, return in case of a type mismatch
let $par = (drain.next().unwrap().downcast_mut() as Option<&mut $par>).unwrap();
let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap();
)*
// Call the user-supplied function using ($clone) to
@ -134,6 +170,44 @@ macro_rules! def_register {
}
}
impl<
$($par: Any + Clone,)*
FN: Fn($($param),*) -> Result<RET, EvalAltResult> + 'static,
RET: Any
> RegisterResultFn<FN, ($($mark,)*), RET> for Engine<'_>
{
fn register_result_fn(&mut self, name: &str, f: FN) {
let fn_name = name.to_string();
let fun = move |mut args: FnCallArgs, pos: Position| {
// Check for length at the beginning to avoid per-element bound checks.
const NUM_ARGS: usize = count_args!($($par)*);
if args.len() != NUM_ARGS {
Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos))
} else {
#[allow(unused_variables, unused_mut)]
let mut drain = args.drain(..);
$(
// Downcast every element, return in case of a type mismatch
let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap();
)*
// Call the user-supplied function using ($clone) to
// potentially clone the value, otherwise pass the reference.
match f($(($clone)($par)),*) {
Ok(r) => Ok(Box::new(r) as Dynamic),
Err(mut err) => {
err.set_position(pos);
Err(err)
}
}
}
};
self.register_fn_raw(name, Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun));
}
}
//def_register!(imp_pop $($par => $mark => $param),*);
};
($p0:ident $(, $p:ident)*) => {

View File

@ -17,15 +17,22 @@
//! And the Rust part:
//!
//! ```rust,no_run
//! use rhai::{Engine, RegisterFn};
//! use rhai::{Engine, EvalAltResult, RegisterFn};
//!
//! fn compute_something(x: i64) -> bool {
//! (x % 40) == 0
//! fn main() -> Result<(), EvalAltResult>
//! {
//! fn compute_something(x: i64) -> bool {
//! (x % 40) == 0
//! }
//!
//! let mut engine = Engine::new();
//!
//! engine.register_fn("compute_something", compute_something);
//!
//! assert_eq!(engine.eval_file::<bool>("my_script.rhai")?, true);
//!
//! Ok(())
//! }
//!
//! let mut engine = Engine::new();
//! engine.register_fn("compute_something", compute_something);
//! assert_eq!(engine.eval_file::<bool>("my_script.rhai").unwrap(), true);
//! ```
//!
//! [Check out the README on GitHub for more information!](https://github.com/jonathandturner/rhai)
@ -61,6 +68,7 @@ mod call;
mod engine;
mod error;
mod fn_register;
mod optimize;
mod parser;
mod result;
mod scope;
@ -69,7 +77,7 @@ pub use any::{Any, AnyExt, Dynamic, Variant};
pub use call::FuncArgs;
pub use engine::{Array, Engine};
pub use error::{ParseError, ParseErrorType};
pub use fn_register::{RegisterDynamicFn, RegisterFn};
pub use fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn};
pub use parser::{Position, AST};
pub use result::EvalAltResult;
pub use scope::Scope;

228
src/optimize.rs Normal file
View File

@ -0,0 +1,228 @@
use crate::parser::{Expr, Stmt};
fn optimize_stmt(stmt: Stmt, changed: &mut bool) -> Stmt {
match stmt {
Stmt::IfElse(expr, stmt1, None) => match *expr {
Expr::False(pos) => {
*changed = true;
Stmt::Noop(pos)
}
Expr::True(_) => optimize_stmt(*stmt1, changed),
expr => Stmt::IfElse(
Box::new(optimize_expr(expr, changed)),
Box::new(optimize_stmt(*stmt1, changed)),
None,
),
},
Stmt::IfElse(expr, stmt1, Some(stmt2)) => match *expr {
Expr::False(_) => optimize_stmt(*stmt2, changed),
Expr::True(_) => optimize_stmt(*stmt1, changed),
expr => Stmt::IfElse(
Box::new(optimize_expr(expr, changed)),
Box::new(optimize_stmt(*stmt1, changed)),
Some(Box::new(optimize_stmt(*stmt2, changed))),
),
},
Stmt::While(expr, stmt) => match *expr {
Expr::False(pos) => {
*changed = true;
Stmt::Noop(pos)
}
Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(*stmt, changed))),
expr => Stmt::While(
Box::new(optimize_expr(expr, changed)),
Box::new(optimize_stmt(*stmt, changed)),
),
},
Stmt::Loop(stmt) => Stmt::Loop(Box::new(optimize_stmt(*stmt, changed))),
Stmt::For(id, expr, stmt) => Stmt::For(
id,
Box::new(optimize_expr(*expr, changed)),
Box::new(optimize_stmt(*stmt, changed)),
),
Stmt::Let(id, Some(expr), pos) => {
Stmt::Let(id, Some(Box::new(optimize_expr(*expr, changed))), pos)
}
Stmt::Let(_, None, _) => stmt,
Stmt::Block(statements, pos) => {
let original_len = statements.len();
let mut result: Vec<_> = statements
.into_iter() // For each statement
.map(|s| optimize_stmt(s, changed)) // Optimize the statement
.filter(Stmt::is_op) // Remove no-op's
.collect();
if let Some(last_stmt) = result.pop() {
// Remove all raw expression statements that evaluate to constants
// except for the very last statement
result.retain(|stmt| match stmt {
Stmt::Expr(expr) if expr.is_constant() => false,
_ => true,
});
result.push(last_stmt);
}
*changed = *changed || original_len != result.len();
match result[..] {
[] => {
// No statements in block - change to No-op
*changed = true;
Stmt::Noop(pos)
}
[Stmt::Let(_, None, _)] => {
// Only one empty variable declaration - change to No-op
*changed = true;
Stmt::Noop(pos)
}
[Stmt::Let(_, Some(_), _)] => {
// Only one let statement, but cannot promote
// (otherwise the variable gets declared in the scope above)
// and still need to run just in case there are side effects
Stmt::Block(result, pos)
}
[_] => {
// Only one statement - promote
*changed = true;
result.remove(0)
}
_ => Stmt::Block(result, pos),
}
}
Stmt::Expr(expr) => Stmt::Expr(Box::new(optimize_expr(*expr, changed))),
Stmt::ReturnWithVal(Some(expr), is_return, pos) => Stmt::ReturnWithVal(
Some(Box::new(optimize_expr(*expr, changed))),
is_return,
pos,
),
stmt @ Stmt::ReturnWithVal(None, _, _) => stmt,
stmt @ Stmt::Noop(_) | stmt @ Stmt::Break(_) => stmt,
}
}
fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
match expr {
Expr::IntegerConstant(_, _)
| Expr::FloatConstant(_, _)
| Expr::Identifier(_, _)
| Expr::CharConstant(_, _)
| Expr::StringConstant(_, _)
| Expr::True(_)
| Expr::False(_)
| Expr::Unit(_) => expr,
Expr::Stmt(stmt, pos) => match optimize_stmt(*stmt, changed) {
Stmt::Noop(_) => {
*changed = true;
Expr::Unit(pos)
}
Stmt::Expr(expr) => {
*changed = true;
*expr
}
stmt => Expr::Stmt(Box::new(stmt), pos),
},
Expr::Assignment(id, expr, pos) => {
Expr::Assignment(id, Box::new(optimize_expr(*expr, changed)), pos)
}
Expr::Dot(lhs, rhs, pos) => Expr::Dot(
Box::new(optimize_expr(*lhs, changed)),
Box::new(optimize_expr(*rhs, changed)),
pos,
),
Expr::Index(lhs, rhs, pos) => Expr::Index(
Box::new(optimize_expr(*lhs, changed)),
Box::new(optimize_expr(*rhs, changed)),
pos,
),
Expr::Array(items, pos) => {
let original_len = items.len();
let items: Vec<_> = items
.into_iter()
.map(|expr| optimize_expr(expr, changed))
.collect();
*changed = *changed || original_len != items.len();
Expr::Array(items, pos)
}
Expr::And(lhs, rhs) => match (*lhs, *rhs) {
(Expr::True(_), rhs) => {
*changed = true;
rhs
}
(Expr::False(pos), _) => {
*changed = true;
Expr::False(pos)
}
(lhs, Expr::True(_)) => {
*changed = true;
lhs
}
(lhs, rhs) => Expr::And(
Box::new(optimize_expr(lhs, changed)),
Box::new(optimize_expr(rhs, changed)),
),
},
Expr::Or(lhs, rhs) => match (*lhs, *rhs) {
(Expr::False(_), rhs) => {
*changed = true;
rhs
}
(Expr::True(pos), _) => {
*changed = true;
Expr::True(pos)
}
(lhs, Expr::False(_)) => {
*changed = true;
lhs
}
(lhs, rhs) => Expr::Or(
Box::new(optimize_expr(lhs, changed)),
Box::new(optimize_expr(rhs, changed)),
),
},
Expr::FunctionCall(id, args, def_value, pos) => {
let original_len = args.len();
let args: Vec<_> = args
.into_iter()
.map(|a| optimize_expr(a, changed))
.collect();
*changed = *changed || original_len != args.len();
Expr::FunctionCall(id, args, def_value, pos)
}
}
}
pub(crate) fn optimize(mut statements: Vec<Stmt>) -> Vec<Stmt> {
loop {
let mut changed = false;
statements = statements
.into_iter()
.map(|stmt| optimize_stmt(stmt, &mut changed))
.filter(Stmt::is_op)
.collect();
if !changed {
break;
}
}
statements
}

View File

@ -1,8 +1,9 @@
//! Main module defining the lexer and parser.
use crate::any::Dynamic;
use crate::error::{LexError, ParseError, ParseErrorType};
use std::char;
use std::iter::Peekable;
use std::str::Chars;
use crate::optimize::optimize;
use std::{borrow::Cow, char, fmt, iter::Peekable, str::Chars, str::FromStr, usize};
type LERR = LexError;
type PERR = ParseErrorType;
@ -17,25 +18,33 @@ pub struct Position {
impl Position {
/// Create a new `Position`.
pub fn new(line: usize, position: usize) -> Self {
if line == 0 || (line == usize::MAX && position == usize::MAX) {
panic!("invalid position: ({}, {})", line, position);
}
Self {
line,
pos: position,
}
}
/// Get the line number (1-based), or `None` if EOF.
/// Get the line number (1-based), or `None` if no position or EOF.
pub fn line(&self) -> Option<usize> {
match self.line {
0 => None,
x => Some(x),
if self.is_none() || self.is_eof() {
None
} else {
Some(self.line)
}
}
/// Get the character position (1-based), or `None` if at beginning of a line.
pub fn position(&self) -> Option<usize> {
match self.pos {
0 => None,
x => Some(x),
if self.is_none() || self.is_eof() {
None
} else if self.pos == 0 {
None
} else {
Some(self.pos)
}
}
@ -61,14 +70,27 @@ impl Position {
self.pos = 0;
}
/// Create a `Position` representing no position.
pub(crate) fn none() -> Self {
Self { line: 0, pos: 0 }
}
/// Create a `Position` at EOF.
pub(crate) fn eof() -> Self {
Self { line: 0, pos: 0 }
Self {
line: usize::MAX,
pos: usize::MAX,
}
}
/// Is there no `Position`?
pub fn is_none(&self) -> bool {
self.line == 0 && self.pos == 0
}
/// Is the `Position` at EOF?
pub fn is_eof(&self) -> bool {
self.line == 0
self.line == usize::MAX && self.pos == usize::MAX
}
}
@ -78,18 +100,20 @@ impl Default for Position {
}
}
impl std::fmt::Display for Position {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl fmt::Display for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_eof() {
write!(f, "EOF")
} else if self.is_none() {
write!(f, "none")
} else {
write!(f, "line {}, position {}", self.line, self.pos)
}
}
}
impl std::fmt::Debug for Position {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl fmt::Debug for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_eof() {
write!(f, "(EOF)")
} else {
@ -99,29 +123,39 @@ impl std::fmt::Debug for Position {
}
/// Compiled AST (abstract syntax tree) of a Rhai script.
pub struct AST(pub(crate) Vec<Stmt>, pub(crate) Vec<FnDef>);
pub struct AST(pub(crate) Vec<Stmt>, pub(crate) Vec<FnDef<'static>>);
#[derive(Debug, Clone)]
pub struct FnDef {
pub name: String,
pub params: Vec<String>,
pub body: Box<Stmt>,
pub struct FnDef<'a> {
pub name: Cow<'a, str>,
pub params: Vec<Cow<'a, str>>,
pub body: Stmt,
pub pos: Position,
}
#[derive(Debug, Clone)]
pub enum Stmt {
Noop(Position),
IfElse(Box<Expr>, Box<Stmt>, Option<Box<Stmt>>),
While(Box<Expr>, Box<Stmt>),
Loop(Box<Stmt>),
For(String, Box<Expr>, Box<Stmt>),
Let(String, Option<Box<Expr>>, Position),
Block(Vec<Stmt>),
Block(Vec<Stmt>, Position),
Expr(Box<Expr>),
Break(Position),
ReturnWithVal(Option<Box<Expr>>, bool, Position),
}
impl Stmt {
pub fn is_op(&self) -> bool {
match self {
Stmt::Noop(_) => false,
_ => true,
}
}
}
#[derive(Debug, Clone)]
pub enum Expr {
IntegerConstant(i64, Position),
@ -129,10 +163,11 @@ pub enum Expr {
Identifier(String, Position),
CharConstant(char, Position),
StringConstant(String, Position),
Stmt(Box<Stmt>, Position),
FunctionCall(String, Vec<Expr>, Option<Dynamic>, Position),
Assignment(Box<Expr>, Box<Expr>),
Dot(Box<Expr>, Box<Expr>),
Index(Box<Expr>, Box<Expr>),
Assignment(Box<Expr>, Box<Expr>, Position),
Dot(Box<Expr>, Box<Expr>, Position),
Index(Box<Expr>, Box<Expr>, Position),
Array(Vec<Expr>, Position),
And(Box<Expr>, Box<Expr>),
Or(Box<Expr>, Box<Expr>),
@ -150,18 +185,34 @@ impl Expr {
| Expr::CharConstant(_, pos)
| Expr::StringConstant(_, pos)
| Expr::FunctionCall(_, _, _, pos)
| Expr::Stmt(_, pos)
| Expr::Array(_, pos)
| Expr::True(pos)
| Expr::False(pos)
| Expr::Unit(pos) => *pos,
Expr::Index(e, _)
| Expr::Assignment(e, _)
| Expr::Dot(e, _)
Expr::Index(e, _, _)
| Expr::Assignment(e, _, _)
| Expr::Dot(e, _, _)
| Expr::And(e, _)
| Expr::Or(e, _) => e.position(),
}
}
pub fn is_constant(&self) -> bool {
match self {
Expr::IntegerConstant(_, _)
| Expr::FloatConstant(_, _)
| Expr::Identifier(_, _)
| Expr::CharConstant(_, _)
| Expr::StringConstant(_, _)
| Expr::True(_)
| Expr::False(_)
| Expr::Unit(_) => true,
_ => false,
}
}
}
#[derive(Debug, PartialEq, Clone)]
@ -232,14 +283,14 @@ pub enum Token {
}
impl Token {
pub fn syntax(&self) -> std::borrow::Cow<'static, str> {
pub fn syntax<'a>(&'a self) -> Cow<'a, str> {
use self::Token::*;
match *self {
IntegerConstant(ref s) => s.to_string().into(),
FloatConstant(ref s) => s.to_string().into(),
Identifier(ref s) => s.to_string().into(),
CharConstant(ref s) => s.to_string().into(),
IntegerConstant(ref i) => i.to_string().into(),
FloatConstant(ref f) => f.to_string().into(),
Identifier(ref s) => s.into(),
CharConstant(ref c) => c.to_string().into(),
LexError(ref err) => err.to_string().into(),
ref token => (match token {
@ -301,7 +352,7 @@ impl Token {
PowerOfAssign => "~=",
For => "for",
In => "in",
_ => panic!(),
_ => panic!("operator should be match in outer scope"),
})
.into(),
}
@ -313,6 +364,7 @@ impl Token {
use self::Token::*;
match *self {
LexError(_) |
LeftBrace | // (+expr) - is unary
// RightBrace | {expr} - expr not unary & is closing
LeftParen | // {-expr} - is unary
@ -361,6 +413,7 @@ impl Token {
PowerOf |
In |
PowerOfAssign => true,
_ => false,
}
}
@ -373,6 +426,7 @@ impl Token {
RightBrace | RightParen | RightBracket | Plus | Minus | Multiply | Divide | Comma
| Equals | LessThan | GreaterThan | LessThanEqualsTo | GreaterThanEqualsTo
| EqualsTo | NotEqualsTo | Pipe | Or | Ampersand | And | PowerOf => true,
_ => false,
}
}
@ -538,11 +592,12 @@ impl<'a> TokenIterator<'a> {
}
}
let out: String = result.iter().collect();
Ok(out)
Ok(result.iter().collect())
}
fn inner_next(&mut self) -> Option<(Token, Position)> {
let mut negated = false;
while let Some(c) = self.char_stream.next() {
self.advance();
@ -629,31 +684,34 @@ impl<'a> TokenIterator<'a> {
}
}
if negated {
result.insert(0, '-');
}
if let Some(radix) = radix_base {
let out: String = result.iter().skip(2).filter(|&&c| c != '_').collect();
return Some((
if let Ok(val) = i64::from_str_radix(&out, radix) {
Token::IntegerConstant(val)
} else {
Token::LexError(LERR::MalformedNumber(result.iter().collect()))
},
i64::from_str_radix(&out, radix)
.map(Token::IntegerConstant)
.unwrap_or_else(|_| {
Token::LexError(LERR::MalformedNumber(result.iter().collect()))
}),
pos,
));
} else {
let out: String = result.iter().filter(|&&c| c != '_').collect();
return Some((
i64::from_str(&out)
.map(Token::IntegerConstant)
.or_else(|_| f64::from_str(&out).map(Token::FloatConstant))
.unwrap_or_else(|_| {
Token::LexError(LERR::MalformedNumber(result.iter().collect()))
}),
pos,
));
}
let out: String = result.iter().filter(|&&c| c != '_').collect();
return Some((
if let Ok(val) = out.parse::<i64>() {
Token::IntegerConstant(val)
} else if let Ok(val) = out.parse::<f64>() {
Token::FloatConstant(val)
} else {
Token::LexError(LERR::MalformedNumber(result.iter().collect()))
},
pos,
));
}
'A'..='Z' | 'a'..='z' | '_' => {
let mut result = Vec::new();
@ -687,7 +745,7 @@ impl<'a> TokenIterator<'a> {
"fn" => Token::Fn,
"for" => Token::For,
"in" => Token::In,
x => Token::Identifier(x.into()),
_ => Token::Identifier(out),
},
pos,
));
@ -737,20 +795,17 @@ impl<'a> TokenIterator<'a> {
pos,
))
}
'-' => {
return Some((
match self.char_stream.peek() {
Some(&'=') => {
self.char_stream.next();
self.advance();
Token::MinusAssign
}
_ if self.last.is_next_unary() => Token::UnaryMinus,
_ => Token::Minus,
},
pos,
))
}
'-' => match self.char_stream.peek() {
// Negative number?
Some('0'..='9') => negated = true,
Some('=') => {
self.char_stream.next();
self.advance();
return Some((Token::MinusAssign, pos));
}
_ if self.last.is_next_unary() => return Some((Token::UnaryMinus, pos)),
_ => return Some((Token::Minus, pos)),
},
'*' => {
return Some((
match self.char_stream.peek() {
@ -1010,19 +1065,28 @@ fn get_precedence(token: &Token) -> i8 {
| Token::XOrAssign
| Token::ModuloAssign
| Token::PowerOfAssign => 10,
Token::Or | Token::XOr | Token::Pipe => 11,
Token::And | Token::Ampersand => 12,
Token::LessThan
| Token::LessThanEqualsTo
| Token::GreaterThan
| Token::GreaterThanEqualsTo
| Token::EqualsTo
| Token::NotEqualsTo => 15,
Token::Plus | Token::Minus => 20,
Token::Divide | Token::Multiply | Token::PowerOf => 40,
Token::LeftShift | Token::RightShift => 50,
Token::Modulo => 60,
Token::Period => 100,
_ => -1,
}
}
@ -1032,6 +1096,7 @@ fn parse_paren_expr<'a>(
begin: Position,
) -> Result<Expr, ParseError> {
match input.peek() {
// ()
Some((Token::RightParen, _)) => {
input.next();
return Ok(Expr::Unit(begin));
@ -1104,11 +1169,71 @@ fn parse_call_expr<'a>(
fn parse_index_expr<'a>(
lhs: Box<Expr>,
input: &mut Peekable<TokenIterator<'a>>,
pos: Position,
) -> Result<Expr, ParseError> {
parse_expr(input).and_then(|idx_expr| match input.peek() {
let idx_expr = parse_expr(input)?;
// Check type of indexing - must be integer
match &idx_expr {
Expr::IntegerConstant(i, pos) if *i < 0 => {
return Err(ParseError::new(
PERR::MalformedIndexExpr(format!(
"Array access expects non-negative index: {} < 0",
i
)),
*pos,
))
}
Expr::FloatConstant(_, pos) => {
return Err(ParseError::new(
PERR::MalformedIndexExpr("Array access expects integer index, not a float".into()),
*pos,
))
}
Expr::CharConstant(_, pos) => {
return Err(ParseError::new(
PERR::MalformedIndexExpr(
"Array access expects integer index, not a character".into(),
),
*pos,
))
}
Expr::StringConstant(_, pos) => {
return Err(ParseError::new(
PERR::MalformedIndexExpr("Array access expects integer index, not a string".into()),
*pos,
))
}
Expr::Assignment(_, _, pos) | Expr::Unit(pos) => {
return Err(ParseError::new(
PERR::MalformedIndexExpr("Array access expects integer index, not ()".into()),
*pos,
))
}
Expr::And(lhs, _) | Expr::Or(lhs, _) => {
return Err(ParseError::new(
PERR::MalformedIndexExpr(
"Array access expects integer index, not a boolean".into(),
),
lhs.position(),
))
}
Expr::True(pos) | Expr::False(pos) => {
return Err(ParseError::new(
PERR::MalformedIndexExpr(
"Array access expects integer index, not a boolean".into(),
),
*pos,
))
}
_ => (),
}
// Check if there is a closing bracket
match input.peek() {
Some(&(Token::RightBracket, _)) => {
input.next();
return Ok(Expr::Index(lhs, Box::new(idx_expr)));
return Ok(Expr::Index(lhs, Box::new(idx_expr), pos));
}
Some(&(_, pos)) => {
return Err(ParseError::new(
@ -1122,7 +1247,7 @@ fn parse_index_expr<'a>(
Position::eof(),
))
}
})
}
}
fn parse_ident_expr<'a>(
@ -1135,9 +1260,9 @@ fn parse_ident_expr<'a>(
input.next();
parse_call_expr(id, input, begin)
}
Some(&(Token::LeftBracket, _)) => {
Some(&(Token::LeftBracket, pos)) => {
input.next();
parse_index_expr(Box::new(Expr::Identifier(id, begin)), input)
parse_index_expr(Box::new(Expr::Identifier(id, begin)), input, pos)
}
Some(_) => Ok(Expr::Identifier(id, begin)),
None => Ok(Expr::Identifier(id, Position::eof())),
@ -1185,6 +1310,14 @@ fn parse_array_expr<'a>(
}
fn parse_primary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, ParseError> {
// Block statement as expression
match input.peek() {
Some(&(Token::LeftBrace, pos)) => {
return parse_block(input).map(|block| Expr::Stmt(Box::new(block), pos))
}
_ => (),
}
let token = input.next();
let mut follow_on = false;
@ -1226,9 +1359,9 @@ fn parse_primary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pa
}
// Tail processing all possible indexing
while let Some(&(Token::LeftBracket, _)) = input.peek() {
while let Some(&(Token::LeftBracket, pos)) = input.peek() {
input.next();
root_expr = parse_index_expr(Box::new(root_expr), input)?;
root_expr = parse_index_expr(Box::new(root_expr), input, pos)?;
}
Ok(root_expr)
@ -1239,16 +1372,22 @@ fn parse_unary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pars
Some(&(Token::UnaryMinus, pos)) => {
input.next();
Ok(Expr::FunctionCall(
"-".into(),
vec![parse_primary(input)?],
None,
pos,
))
match parse_unary(input) {
// Negative integer
Ok(Expr::IntegerConstant(i, pos)) => Ok(i
.checked_neg()
.map(|x| Expr::IntegerConstant(x, pos))
.unwrap_or_else(|| Expr::FloatConstant(-(i as f64), pos))),
// Negative float
Ok(Expr::FloatConstant(f, pos)) => Ok(Expr::FloatConstant(-f, pos)),
// Call negative function
Ok(expr) => Ok(Expr::FunctionCall("-".into(), vec![expr], None, pos)),
err @ Err(_) => err,
}
}
Some(&(Token::UnaryPlus, _)) => {
input.next();
parse_primary(input)
parse_unary(input)
}
Some(&(Token::Bang, pos)) => {
input.next();
@ -1264,6 +1403,75 @@ 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 valid_assignment_chain(expr: &Expr) -> (bool, Position) {
match expr {
Expr::Identifier(_, pos) => (true, *pos),
Expr::Index(idx_lhs, _, _) => match idx_lhs.as_ref() {
Expr::Identifier(_, _) => (true, idx_lhs.position()),
_ => (false, idx_lhs.position()),
},
Expr::Dot(dot_lhs, dot_rhs, _) => match dot_lhs.as_ref() {
Expr::Identifier(_, _) => valid_assignment_chain(dot_rhs),
Expr::Index(idx_lhs, _, _) => match idx_lhs.as_ref() {
Expr::Identifier(_, _) => valid_assignment_chain(dot_rhs),
_ => (false, idx_lhs.position()),
},
_ => (false, dot_lhs.position()),
},
_ => (false, expr.position()),
}
}
//println!("{:?} = {:?}", lhs, rhs);
match valid_assignment_chain(&lhs) {
(true, _) => Ok(Expr::Assignment(Box::new(lhs), Box::new(rhs), pos)),
(false, pos) => Err(ParseError::new(PERR::AssignmentToInvalidLHS, pos)),
}
}
fn parse_op_assignment(
function: &str,
lhs: Expr,
rhs: Expr,
pos: Position,
) -> Result<Expr, ParseError> {
let lhs_copy = lhs.clone();
parse_assignment(
lhs,
Expr::FunctionCall(function.into(), vec![lhs_copy, rhs], None, pos),
pos,
)
/*
const LHS_VALUE: &'static str = "@LHS_VALUE@";
let lhs_pos = lhs.position();
Ok(Expr::Block(
Box::new(Stmt::Block(vec![
Stmt::Let(LHS_VALUE.to_string(), Some(Box::new(lhs)), lhs_pos),
Stmt::Expr(Box::new(parse_assignment(
lhs,
Expr::FunctionCall(
function.into(),
vec![Expr::Identifier(LHS_VALUE.to_string(), lhs_pos), rhs],
None,
pos,
),
pos,
)?)),
])),
pos,
))
*/
}
fn parse_binary_op<'a>(
input: &mut Peekable<TokenIterator<'a>>,
precedence: i8,
@ -1308,32 +1516,10 @@ fn parse_binary_op<'a>(
}
Token::Divide => Expr::FunctionCall("/".into(), vec![current_lhs, rhs], None, pos),
Token::Equals => Expr::Assignment(Box::new(current_lhs), Box::new(rhs)),
Token::PlusAssign => {
let lhs_copy = current_lhs.clone();
Expr::Assignment(
Box::new(current_lhs),
Box::new(Expr::FunctionCall(
"+".into(),
vec![lhs_copy, rhs],
None,
pos,
)),
)
}
Token::MinusAssign => {
let lhs_copy = current_lhs.clone();
Expr::Assignment(
Box::new(current_lhs),
Box::new(Expr::FunctionCall(
"-".into(),
vec![lhs_copy, rhs],
None,
pos,
)),
)
}
Token::Period => Expr::Dot(Box::new(current_lhs), Box::new(rhs)),
Token::Equals => parse_assignment(current_lhs, rhs, pos)?,
Token::PlusAssign => 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),
// Comparison operators default to false when passed invalid operands
Token::EqualsTo => Expr::FunctionCall(
@ -1376,66 +1562,11 @@ fn parse_binary_op<'a>(
Token::Or => Expr::Or(Box::new(current_lhs), Box::new(rhs)),
Token::And => Expr::And(Box::new(current_lhs), Box::new(rhs)),
Token::XOr => Expr::FunctionCall("^".into(), vec![current_lhs, rhs], None, pos),
Token::OrAssign => {
let lhs_copy = current_lhs.clone();
Expr::Assignment(
Box::new(current_lhs),
Box::new(Expr::FunctionCall(
"|".into(),
vec![lhs_copy, rhs],
None,
pos,
)),
)
}
Token::AndAssign => {
let lhs_copy = current_lhs.clone();
Expr::Assignment(
Box::new(current_lhs),
Box::new(Expr::FunctionCall(
"&".into(),
vec![lhs_copy, rhs],
None,
pos,
)),
)
}
Token::XOrAssign => {
let lhs_copy = current_lhs.clone();
Expr::Assignment(
Box::new(current_lhs),
Box::new(Expr::FunctionCall(
"^".into(),
vec![lhs_copy, rhs],
None,
pos,
)),
)
}
Token::MultiplyAssign => {
let lhs_copy = current_lhs.clone();
Expr::Assignment(
Box::new(current_lhs),
Box::new(Expr::FunctionCall(
"*".into(),
vec![lhs_copy, rhs],
None,
pos,
)),
)
}
Token::DivideAssign => {
let lhs_copy = current_lhs.clone();
Expr::Assignment(
Box::new(current_lhs),
Box::new(Expr::FunctionCall(
"/".into(),
vec![lhs_copy, rhs],
None,
pos,
)),
)
}
Token::OrAssign => parse_op_assignment("|", current_lhs, rhs, pos)?,
Token::AndAssign => parse_op_assignment("&", current_lhs, rhs, pos)?,
Token::XOrAssign => parse_op_assignment("^", current_lhs, rhs, pos)?,
Token::MultiplyAssign => parse_op_assignment("*", current_lhs, rhs, pos)?,
Token::DivideAssign => parse_op_assignment("/", current_lhs, rhs, pos)?,
Token::Pipe => Expr::FunctionCall("|".into(), vec![current_lhs, rhs], None, pos),
Token::LeftShift => {
Expr::FunctionCall("<<".into(), vec![current_lhs, rhs], None, pos)
@ -1443,59 +1574,15 @@ fn parse_binary_op<'a>(
Token::RightShift => {
Expr::FunctionCall(">>".into(), vec![current_lhs, rhs], None, pos)
}
Token::LeftShiftAssign => {
let lhs_copy = current_lhs.clone();
Expr::Assignment(
Box::new(current_lhs),
Box::new(Expr::FunctionCall(
"<<".into(),
vec![lhs_copy, rhs],
None,
pos,
)),
)
}
Token::RightShiftAssign => {
let lhs_copy = current_lhs.clone();
Expr::Assignment(
Box::new(current_lhs),
Box::new(Expr::FunctionCall(
">>".into(),
vec![lhs_copy, rhs],
None,
pos,
)),
)
}
Token::LeftShiftAssign => parse_op_assignment("<<", current_lhs, rhs, pos)?,
Token::RightShiftAssign => parse_op_assignment(">>", current_lhs, rhs, pos)?,
Token::Ampersand => {
Expr::FunctionCall("&".into(), vec![current_lhs, rhs], None, pos)
}
Token::Modulo => Expr::FunctionCall("%".into(), vec![current_lhs, rhs], None, pos),
Token::ModuloAssign => {
let lhs_copy = current_lhs.clone();
Expr::Assignment(
Box::new(current_lhs),
Box::new(Expr::FunctionCall(
"%".into(),
vec![lhs_copy, rhs],
None,
pos,
)),
)
}
Token::ModuloAssign => parse_op_assignment("%", current_lhs, rhs, pos)?,
Token::PowerOf => Expr::FunctionCall("~".into(), vec![current_lhs, rhs], None, pos),
Token::PowerOfAssign => {
let lhs_copy = current_lhs.clone();
Expr::Assignment(
Box::new(current_lhs),
Box::new(Expr::FunctionCall(
"~".into(),
vec![lhs_copy, rhs],
None,
pos,
)),
)
}
Token::PowerOfAssign => parse_op_assignment("~", current_lhs, rhs, pos)?,
token => {
return Err(ParseError::new(
PERR::UnknownOperator(token.syntax().into()),
@ -1591,8 +1678,8 @@ fn parse_var<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseE
match input.peek() {
Some(&(Token::Equals, _)) => {
input.next();
let initializer = parse_expr(input)?;
Ok(Stmt::Let(name, Some(Box::new(initializer)), pos))
let init_value = parse_expr(input)?;
Ok(Stmt::Let(name, Some(Box::new(init_value)), pos))
}
_ => Ok(Stmt::Let(name, None, pos)),
}
@ -1605,7 +1692,7 @@ fn parse_block<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Pars
None => return Err(ParseError::new(PERR::MissingLeftBrace, Position::eof())),
}
input.next();
let pos = input.next().unwrap().1;
let mut statements = Vec::new();
@ -1633,7 +1720,7 @@ fn parse_block<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Pars
match input.peek() {
Some(&(Token::RightBrace, _)) => {
input.next();
Ok(Stmt::Block(statements))
Ok(Stmt::Block(statements, pos))
}
Some(&(_, pos)) => Err(ParseError::new(
PERR::MissingRightBrace("end of block".into()),
@ -1687,7 +1774,7 @@ fn parse_stmt<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Parse
}
}
fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseError> {
fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef<'static>, ParseError> {
let pos = match input.next() {
Some((_, tok_pos)) => tok_pos,
_ => return Err(ParseError::new(PERR::InputPastEndOfFile, Position::eof())),
@ -1723,10 +1810,24 @@ fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseE
Some((Token::RightParen, _)) => break,
Some((Token::Comma, _)) => (),
Some((Token::Identifier(s), _)) => {
params.push(s);
params.push(s.into());
}
Some((_, pos)) => {
return Err(ParseError::new(
PERR::MalformedCallExpr(
"Function call arguments missing either a ',' or a ')'".into(),
),
pos,
))
}
None => {
return Err(ParseError::new(
PERR::MalformedCallExpr(
"Function call arguments missing a closing ')'".into(),
),
Position::eof(),
))
}
Some((_, pos)) => return Err(ParseError::new(PERR::MalformedCallExpr, pos)),
None => return Err(ParseError::new(PERR::MalformedCallExpr, Position::eof())),
}
},
}
@ -1734,14 +1835,17 @@ fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseE
let body = parse_block(input)?;
Ok(FnDef {
name: name,
params: params,
body: Box::new(body),
pos: pos,
name: name.into(),
params,
body,
pos,
})
}
fn parse_top_level<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<AST, ParseError> {
fn parse_top_level<'a>(
input: &mut Peekable<TokenIterator<'a>>,
optimize_ast: bool,
) -> Result<AST, ParseError> {
let mut statements = Vec::new();
let mut functions = Vec::new();
@ -1757,9 +1861,26 @@ fn parse_top_level<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<AST, P
}
}
Ok(AST(statements, functions))
return Ok(if optimize_ast {
AST(
optimize(statements),
functions
.into_iter()
.map(|mut fn_def| {
let mut body = optimize(vec![fn_def.body]);
fn_def.body = body.pop().unwrap();
fn_def
})
.collect(),
)
} else {
AST(statements, functions)
});
}
pub fn parse<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<AST, ParseError> {
parse_top_level(input)
pub fn parse<'a>(
input: &mut Peekable<TokenIterator<'a>>,
optimize_ast: bool,
) -> Result<AST, ParseError> {
parse_top_level(input, optimize_ast)
}

View File

@ -1,8 +1,9 @@
use std::error::Error;
//! Module containing error definitions for the evaluation process.
use crate::any::Dynamic;
use crate::error::ParseError;
use crate::parser::Position;
use std::{error::Error, fmt};
/// Evaluation result.
///
@ -19,6 +20,8 @@ pub enum EvalAltResult {
ErrorFunctionArgsMismatch(String, usize, usize, Position),
/// Non-boolean operand encountered for boolean operator. Wrapped value is the operator.
ErrorBooleanArgMismatch(String, Position),
/// Non-character value encountered where a character is required.
ErrorCharMismatch(Position),
/// Array access out-of-bounds.
/// Wrapped values are the current number of elements in the array and the index number.
ErrorArrayBounds(usize, i64, Position),
@ -26,7 +29,7 @@ pub enum EvalAltResult {
/// Wrapped values are the current number of characters in the string and the index number.
ErrorStringBounds(usize, i64, Position),
/// Trying to index into a type that is not an array and not a string.
ErrorIndexingType(Position),
ErrorIndexingType(String, Position),
/// Trying to index into an array or string with an index that is not `i64`.
ErrorIndexExpr(Position),
/// The guard expression in an `if` statement does not return a boolean value.
@ -43,7 +46,7 @@ pub enum EvalAltResult {
/// Error reading from a script file. Wrapped value is the path of the script file.
ErrorReadingScriptFile(String, std::io::Error),
/// Inappropriate member access.
ErrorDotExpr(Position),
ErrorDotExpr(String, Position),
/// Arithmetic error encountered. Wrapped value is the error message.
ErrorArithmetic(String, Position),
/// Run-time error encountered. Wrapped value is the error message.
@ -64,8 +67,11 @@ impl Error for EvalAltResult {
"Function call with wrong number of arguments"
}
Self::ErrorBooleanArgMismatch(_, _) => "Boolean operator expects boolean operands",
Self::ErrorCharMismatch(_) => "Character expected",
Self::ErrorIndexExpr(_) => "Indexing into an array or string expects an integer index",
Self::ErrorIndexingType(_) => "Indexing can only be performed on an array or a string",
Self::ErrorIndexingType(_, _) => {
"Indexing can only be performed on an array or a string"
}
Self::ErrorArrayBounds(_, index, _) if *index < 0 => {
"Array access expects non-negative index"
}
@ -84,7 +90,7 @@ impl Error for EvalAltResult {
}
Self::ErrorMismatchOutputType(_, _) => "Output type is incorrect",
Self::ErrorReadingScriptFile(_, _) => "Cannot read from script file",
Self::ErrorDotExpr(_) => "Malformed dot expression",
Self::ErrorDotExpr(_, _) => "Malformed dot expression",
Self::ErrorArithmetic(_, _) => "Arithmetic error",
Self::ErrorRuntime(_, _) => "Runtime error",
Self::LoopBreak => "[Not Error] Breaks out of loop",
@ -97,23 +103,25 @@ impl Error for EvalAltResult {
}
}
impl std::fmt::Display for EvalAltResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl fmt::Display for EvalAltResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let desc = self.description();
match self {
Self::ErrorFunctionNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos),
Self::ErrorVariableNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos),
Self::ErrorIndexingType(pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorIndexingType(_, pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorIndexExpr(pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorIfGuard(pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorFor(pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorAssignmentToUnknownLHS(pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorMismatchOutputType(s, pos) => write!(f, "{}: {} ({})", desc, s, pos),
Self::ErrorDotExpr(pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorArithmetic(s, pos) => write!(f, "{}: {} ({})", desc, s, pos),
Self::ErrorRuntime(s, pos) if s.is_empty() => write!(f, "{} ({})", desc, pos),
Self::ErrorRuntime(s, pos) => 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::ErrorArithmetic(s, pos) => write!(f, "{} ({})", s, pos),
Self::ErrorRuntime(s, pos) => {
write!(f, "{} ({})", if s.is_empty() { desc } else { s }, pos)
}
Self::LoopBreak => write!(f, "{}", desc),
Self::Return(_, pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorReadingScriptFile(filename, err) => {
@ -128,6 +136,9 @@ impl std::fmt::Display for EvalAltResult {
Self::ErrorBooleanArgMismatch(op, pos) => {
write!(f, "{} operator expects boolean operands ({})", op, pos)
}
Self::ErrorCharMismatch(pos) => {
write!(f, "string indexing expects a character value ({})", pos)
}
Self::ErrorArrayBounds(_, index, pos) if *index < 0 => {
write!(f, "{}: {} < 0 ({})", desc, index, pos)
}
@ -161,3 +172,62 @@ impl From<ParseError> for EvalAltResult {
Self::ErrorParsing(err)
}
}
impl EvalAltResult {
pub fn position(&self) -> Position {
match self {
Self::ErrorReadingScriptFile(_, _) | Self::LoopBreak => Position::none(),
Self::ErrorParsing(err) => err.position(),
Self::ErrorFunctionNotFound(_, pos)
| Self::ErrorFunctionArgsMismatch(_, _, _, pos)
| Self::ErrorBooleanArgMismatch(_, pos)
| Self::ErrorCharMismatch(pos)
| Self::ErrorArrayBounds(_, _, pos)
| Self::ErrorStringBounds(_, _, pos)
| Self::ErrorIndexingType(_, pos)
| Self::ErrorIndexExpr(pos)
| Self::ErrorIfGuard(pos)
| Self::ErrorFor(pos)
| Self::ErrorVariableNotFound(_, pos)
| Self::ErrorAssignmentToUnknownLHS(pos)
| Self::ErrorMismatchOutputType(_, pos)
| Self::ErrorDotExpr(_, pos)
| Self::ErrorArithmetic(_, pos)
| Self::ErrorRuntime(_, pos)
| Self::Return(_, pos) => *pos,
}
}
pub(crate) fn set_position(&mut self, new_position: Position) {
match self {
Self::ErrorReadingScriptFile(_, _) | Self::LoopBreak => (),
Self::ErrorParsing(ParseError(_, ref mut pos))
| Self::ErrorFunctionNotFound(_, ref mut pos)
| Self::ErrorFunctionArgsMismatch(_, _, _, ref mut pos)
| Self::ErrorBooleanArgMismatch(_, ref mut pos)
| Self::ErrorCharMismatch(ref mut pos)
| Self::ErrorArrayBounds(_, _, ref mut pos)
| Self::ErrorStringBounds(_, _, ref mut pos)
| Self::ErrorIndexingType(_, ref mut pos)
| Self::ErrorIndexExpr(ref mut pos)
| Self::ErrorIfGuard(ref mut pos)
| Self::ErrorFor(ref mut pos)
| Self::ErrorVariableNotFound(_, ref mut pos)
| Self::ErrorAssignmentToUnknownLHS(ref mut pos)
| Self::ErrorMismatchOutputType(_, ref mut pos)
| Self::ErrorDotExpr(_, ref mut pos)
| Self::ErrorArithmetic(_, ref mut pos)
| Self::ErrorRuntime(_, ref mut pos)
| Self::Return(_, ref mut pos) => *pos = new_position,
}
}
}
impl<T: AsRef<str>> From<T> for EvalAltResult {
fn from(err: T) -> Self {
Self::ErrorRuntime(err.as_ref().to_string(), Position::none())
}
}

View File

@ -1,25 +1,32 @@
//! Module that defines the `Scope` type representing a function call-stack scope.
use crate::any::{Any, Dynamic};
use std::borrow::Cow;
/// A type containing information about current scope.
/// Useful for keeping state between `Engine` runs
/// Useful for keeping state between `Engine` runs.
///
/// # Example
///
/// ```rust
/// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::{Engine, Scope};
///
/// let mut engine = Engine::new();
/// let mut my_scope = Scope::new();
///
/// assert!(engine.eval_with_scope::<()>(&mut my_scope, "let x = 5;").is_ok());
/// assert_eq!(engine.eval_with_scope::<i64>(&mut my_scope, "x + 1").unwrap(), 6);
/// engine.eval_with_scope::<()>(&mut my_scope, false, "let x = 5;")?;
///
/// assert_eq!(engine.eval_with_scope::<i64>(&mut my_scope, false, "x + 1")?, 6);
/// # Ok(())
/// # }
/// ```
///
/// When searching for variables, newly-added variables are found before similarly-named but older variables,
/// allowing for automatic _shadowing_ of variables.
pub struct Scope(Vec<(String, Dynamic)>);
pub struct Scope<'a>(Vec<(Cow<'a, str>, Dynamic)>);
impl Scope {
impl<'a> Scope<'a> {
/// Create a new Scope.
pub fn new() -> Self {
Self(Vec::new())
@ -36,18 +43,18 @@ impl Scope {
}
/// Add (push) a new variable to the Scope.
pub fn push<T: Any>(&mut self, key: String, value: T) {
self.0.push((key, Box::new(value)));
pub fn push<K: Into<Cow<'a, str>>, T: Any>(&mut self, key: K, value: T) {
self.0.push((key.into(), Box::new(value)));
}
/// Add (push) a new variable to the Scope.
pub(crate) fn push_dynamic(&mut self, key: String, value: Dynamic) {
self.0.push((key, value));
pub(crate) fn push_dynamic<K: Into<Cow<'a, str>>>(&mut self, key: K, value: Dynamic) {
self.0.push((key.into(), value));
}
/// Remove (pop) the last variable from the Scope.
pub fn pop(&mut self) -> Option<(String, Dynamic)> {
self.0.pop()
self.0.pop().map(|(key, value)| (key.to_string(), value))
}
/// Truncate (rewind) the Scope to a previous size.
@ -56,13 +63,13 @@ impl Scope {
}
/// Find a variable in the Scope, starting from the last.
pub fn get(&self, key: &str) -> Option<(usize, String, Dynamic)> {
pub fn get(&self, key: &str) -> Option<(usize, &str, Dynamic)> {
self.0
.iter()
.enumerate()
.rev() // Always search a Scope in reverse order
.find(|(_, (name, _))| name == key)
.map(|(i, (name, value))| (i, name.clone(), value.clone()))
.map(|(i, (name, value))| (i, name.as_ref(), value.clone()))
}
/// Get the value of a variable in the Scope, starting from the last.
@ -97,20 +104,26 @@ impl Scope {
self.0
.iter()
.rev() // Always search a Scope in reverse order
.map(|(key, value)| (key.as_str(), value))
.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_str(), value))
.map(|(key, value)| (key.as_ref(), value))
}
*/
}
impl std::iter::Extend<(String, Dynamic)> for Scope {
fn extend<T: IntoIterator<Item = (String, Dynamic)>>(&mut self, iter: T) {
self.0.extend(iter);
impl<'a, K> std::iter::Extend<(K, Dynamic)> for Scope<'a>
where
K: Into<Cow<'a, str>>,
{
fn extend<T: IntoIterator<Item = (K, Dynamic)>>(&mut self, iter: T) {
self.0
.extend(iter.into_iter().map(|(key, value)| (key.into(), value)));
}
}

View File

@ -4,9 +4,9 @@ use rhai::{Engine, EvalAltResult};
fn test_engine_call_fn() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
let ast = Engine::compile("fn hello(x, y) { x.len() + y }")?;
let ast = engine.compile("fn hello(x, y) { x.len() + y }")?;
let result: i64 = engine.call_fn("hello", ast, (&mut String::from("abc"), &mut 123_i64))?;
let result: i64 = engine.call_fn("hello", &ast, (&mut String::from("abc"), &mut 123_i64))?;
assert_eq!(result, 126);

43
tests/math.rs Normal file
View File

@ -0,0 +1,43 @@
use rhai::{Engine, EvalAltResult};
#[test]
fn test_math() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
assert_eq!(engine.eval::<i64>("1 + 2")?, 3);
assert_eq!(engine.eval::<i64>("1 - 2")?, -1);
assert_eq!(engine.eval::<i64>("2 * 3")?, 6);
assert_eq!(engine.eval::<i64>("1 / 2")?, 0);
assert_eq!(engine.eval::<i64>("3 % 2")?, 1);
assert_eq!(
engine.eval::<i64>("(-9223372036854775807).abs()")?,
9223372036854775807
);
// Overflow/underflow/division-by-zero errors
#[cfg(not(feature = "unchecked"))]
{
match engine.eval::<i64>("9223372036854775807 + 1") {
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
r => panic!("should return overflow error: {:?}", r),
}
match engine.eval::<i64>("-9223372036854775808 - 1") {
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
r => panic!("should return underflow error: {:?}", r),
}
match engine.eval::<i64>("9223372036854775807 * 9223372036854775807") {
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
r => panic!("should return overflow error: {:?}", r),
}
match engine.eval::<i64>("9223372036854775807 / 0") {
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
r => panic!("should return division by zero error: {:?}", r),
}
match engine.eval::<i64>("9223372036854775807 % 0") {
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
r => panic!("should return division by zero error: {:?}", r),
}
}
Ok(())
}

View File

@ -5,8 +5,8 @@ fn test_unary_minus() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
assert_eq!(engine.eval::<i64>("let x = -5; x")?, -5);
assert_eq!(engine.eval::<i64>("fn n(x) { -x } n(5)")?, -5);
assert_eq!(engine.eval::<i64>("5 - -(-5)")?, 0);
assert_eq!(engine.eval::<i64>("fn neg(x) { -x } neg(5)")?, -5);
assert_eq!(engine.eval::<i64>("5 - -+++--+-5")?, 0);
Ok(())
}

View File

@ -5,12 +5,15 @@ fn test_var_scope() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
let mut scope = Scope::new();
engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5")?;
assert_eq!(engine.eval_with_scope::<i64>(&mut scope, "x")?, 9);
engine.eval_with_scope::<()>(&mut scope, "x = x + 1; x = x + 2;")?;
assert_eq!(engine.eval_with_scope::<i64>(&mut scope, "x")?, 12);
assert_eq!(engine.eval_with_scope::<()>(&mut scope, "{let x = 3}")?, ());
assert_eq!(engine.eval_with_scope::<i64>(&mut scope, "x")?, 12);
engine.eval_with_scope::<()>(&mut scope, false, "let x = 4 + 5")?;
assert_eq!(engine.eval_with_scope::<i64>(&mut scope, false, "x")?, 9);
engine.eval_with_scope::<()>(&mut scope, false, "x = x + 1; x = x + 2;")?;
assert_eq!(engine.eval_with_scope::<i64>(&mut scope, false, "x")?, 12);
assert_eq!(
engine.eval_with_scope::<()>(&mut scope, false, "{let x = 3}")?,
()
);
assert_eq!(engine.eval_with_scope::<i64>(&mut scope, false, "x")?, 12);
Ok(())
}
@ -25,24 +28,18 @@ fn test_scope_eval() -> Result<(), EvalAltResult> {
// Then push some initialized variables into the state
// NOTE: Remember the default numbers used by Rhai are i64 and f64.
// Better stick to them or it gets hard to work with other variables in the script.
scope.push("y".into(), 42_i64);
scope.push("z".into(), 999_i64);
scope.push("y", 42_i64);
scope.push("z", 999_i64);
// First invocation
engine
.eval_with_scope::<()>(
&mut scope,
r"
let x = 4 + 5 - y + z;
y = 1;
",
)
.eval_with_scope::<()>(&mut scope, false, " let x = 4 + 5 - y + z; y = 1;")
.expect("y and z not found?");
// Second invocation using the same state
if let Ok(result) = engine.eval_with_scope::<i64>(&mut scope, "x") {
println!("result: {}", result); // should print 966
}
let result = engine.eval_with_scope::<i64>(&mut scope, false, "x")?;
println!("result: {}", result); // should print 966
// Variable y is changed in the script
assert_eq!(scope.get_value::<i64>("y").unwrap(), 1);