Merge pull request #105 from schungx/master
Fixes and cleanup plus an optimizer.
This commit is contained in:
commit
faf78ccdd3
@ -14,6 +14,10 @@ include = [
|
|||||||
"Cargo.toml"
|
"Cargo.toml"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
num-traits = "*"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
debug_msgs = []
|
debug_msgs = []
|
||||||
no_stdlib = []
|
no_stdlib = []
|
||||||
|
unchecked = []
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use rhai::{Engine, RegisterFn};
|
use rhai::{Engine, EvalAltResult, RegisterFn};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct TestStruct {
|
struct TestStruct {
|
||||||
@ -15,7 +15,7 @@ impl TestStruct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
engine.register_type::<TestStruct>();
|
engine.register_type::<TestStruct>();
|
||||||
@ -23,7 +23,9 @@ fn main() {
|
|||||||
engine.register_fn("update", TestStruct::update);
|
engine.register_fn("update", TestStruct::update);
|
||||||
engine.register_fn("new_ts", TestStruct::new);
|
engine.register_fn("new_ts", TestStruct::new);
|
||||||
|
|
||||||
if let Ok(result) = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x") {
|
let result = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?;
|
||||||
println!("result: {}", result.x); // prints 1001
|
|
||||||
}
|
println!("result: {}", result.x); // prints 1001
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
use rhai::Engine;
|
use rhai::{Engine, EvalAltResult};
|
||||||
|
|
||||||
fn main() {
|
fn main() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
if let Ok(result) = engine.eval::<i64>("40 + 2") {
|
let result = engine.eval::<i64>("40 + 2")?;
|
||||||
println!("Answer: {}", result); // prints 42
|
|
||||||
}
|
println!("Answer: {}", result); // prints 42
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,66 @@
|
|||||||
use rhai::{Engine, RegisterFn, Scope};
|
use rhai::{Engine, EvalAltResult, Scope};
|
||||||
use std::io::{stdin, stdout, Write};
|
use std::{
|
||||||
use std::process::exit;
|
io::{stdin, stdout, Write},
|
||||||
|
iter,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn main() {
|
fn print_error(input: &str, err: EvalAltResult) {
|
||||||
let mut engine = Engine::new();
|
fn padding(pad: &str, len: usize) -> String {
|
||||||
let mut scope = Scope::new();
|
iter::repeat(pad).take(len).collect::<String>()
|
||||||
|
}
|
||||||
|
|
||||||
engine.register_fn("exit", || exit(0));
|
let lines: Vec<_> = input.split("\n").collect();
|
||||||
|
|
||||||
loop {
|
// Print error
|
||||||
print!("> ");
|
match err.position() {
|
||||||
|
p if p.is_eof() => {
|
||||||
let mut input = String::new();
|
// EOF
|
||||||
stdout().flush().expect("couldn't flush stdout");
|
let last = lines[lines.len() - 2];
|
||||||
|
println!("{}", last);
|
||||||
if let Err(e) = stdin().read_line(&mut input) {
|
println!("{}^ {}", padding(" ", last.len() - 1), err);
|
||||||
println!("input error: {}", e);
|
|
||||||
}
|
}
|
||||||
|
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!("{}", lines[p.line().unwrap() - 1]);
|
||||||
println!("error: {}", e);
|
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!("");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 engine = Engine::new();
|
||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
|
|
||||||
assert!(engine
|
engine.eval_with_scope::<()>(&mut scope, false, "let x = 4 + 5")?;
|
||||||
.eval_with_scope::<()>(&mut scope, "let x = 4 + 5")
|
|
||||||
.is_ok());
|
|
||||||
|
|
||||||
if let Ok(result) = engine.eval_with_scope::<i64>(&mut scope, "x") {
|
let result = engine.eval_with_scope::<i64>(&mut scope, false, "x")?;
|
||||||
println!("result: {}", result);
|
|
||||||
}
|
println!("result: {}", result);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,79 @@
|
|||||||
use rhai::{Engine, RegisterFn};
|
use rhai::{Engine, EvalAltResult};
|
||||||
use std::env;
|
use std::{env, fs::File, io::Read, iter, process::exit};
|
||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
fn showit<T: Display>(x: &mut T) -> () {
|
fn padding(pad: &str, len: usize) -> String {
|
||||||
println!("{}", x)
|
iter::repeat(pad).take(len).collect::<String>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn eprint_error(input: &str, err: EvalAltResult) {
|
||||||
for fname in env::args().skip(1) {
|
fn eprint_line(lines: &Vec<&str>, line: usize, pos: usize, err: &str) {
|
||||||
let mut engine = Engine::new();
|
let line_no = format!("{}: ", line);
|
||||||
|
let pos_text = format!(" (line {}, position {})", line, pos);
|
||||||
|
|
||||||
engine.register_fn("print", showit as fn(x: &mut i32) -> ());
|
eprintln!("{}{}", line_no, lines[line - 1]);
|
||||||
engine.register_fn("print", showit as fn(x: &mut i64) -> ());
|
eprintln!(
|
||||||
engine.register_fn("print", showit as fn(x: &mut u32) -> ());
|
"{}^ {}",
|
||||||
engine.register_fn("print", showit as fn(x: &mut u64) -> ());
|
padding(" ", line_no.len() + pos - 1),
|
||||||
engine.register_fn("print", showit as fn(x: &mut f32) -> ());
|
err.replace(&pos_text, "")
|
||||||
engine.register_fn("print", showit as fn(x: &mut f64) -> ());
|
);
|
||||||
engine.register_fn("print", showit as fn(x: &mut bool) -> ());
|
eprintln!("");
|
||||||
engine.register_fn("print", showit as fn(x: &mut String) -> ());
|
}
|
||||||
|
|
||||||
match engine.eval_file::<()>(&fname) {
|
let lines: Vec<_> = input.split("\n").collect();
|
||||||
Ok(_) => (),
|
|
||||||
Err(e) => println!("Error: {}", e),
|
// 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use rhai::{Engine, RegisterFn};
|
use rhai::{Engine, EvalAltResult, RegisterFn};
|
||||||
|
|
||||||
fn main() {
|
fn main() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
fn add(x: i64, y: i64) -> i64 {
|
fn add(x: i64, y: i64) -> i64 {
|
||||||
@ -9,7 +9,9 @@ fn main() {
|
|||||||
|
|
||||||
engine.register_fn("add", add);
|
engine.register_fn("add", add);
|
||||||
|
|
||||||
if let Ok(result) = engine.eval::<i64>("add(40, 2)") {
|
let result = engine.eval::<i64>("add(40, 2)")?;
|
||||||
println!("Answer: {}", result); // prints 42
|
|
||||||
}
|
println!("Answer: {}", result); // prints 42
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
use std::any::{type_name, TypeId};
|
//! Helper module which defines the `Any` trait to to allow dynamic value handling.
|
||||||
use std::fmt;
|
|
||||||
|
use std::{
|
||||||
|
any::{type_name, TypeId},
|
||||||
|
fmt,
|
||||||
|
};
|
||||||
|
|
||||||
/// An raw value of any type.
|
/// An raw value of any type.
|
||||||
pub type Variant = dyn Any;
|
pub type Variant = dyn Any;
|
||||||
|
144
src/api.rs
144
src/api.rs
@ -1,3 +1,5 @@
|
|||||||
|
//! Module that defines the extern API of `Engine`.
|
||||||
|
|
||||||
use crate::any::{Any, AnyExt, Dynamic};
|
use crate::any::{Any, AnyExt, Dynamic};
|
||||||
use crate::call::FuncArgs;
|
use crate::call::FuncArgs;
|
||||||
use crate::engine::{Engine, FnAny, FnIntExt, FnSpec};
|
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::parser::{lex, parse, Position, AST};
|
||||||
use crate::result::EvalAltResult;
|
use crate::result::EvalAltResult;
|
||||||
use crate::scope::Scope;
|
use crate::scope::Scope;
|
||||||
use std::any::TypeId;
|
use std::{
|
||||||
use std::sync::Arc;
|
any::{type_name, TypeId},
|
||||||
|
fs::File,
|
||||||
|
io::prelude::*,
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
impl<'a> Engine<'a> {
|
impl<'e> Engine<'e> {
|
||||||
pub(crate) fn register_fn_raw(
|
pub(crate) fn register_fn_raw(
|
||||||
&mut self,
|
&mut self,
|
||||||
fn_name: &str,
|
fn_name: &str,
|
||||||
@ -17,12 +23,16 @@ impl<'a> Engine<'a> {
|
|||||||
f: Box<FnAny>,
|
f: Box<FnAny>,
|
||||||
) {
|
) {
|
||||||
debug_println!(
|
debug_println!(
|
||||||
"Register function: {} for {} parameter(s)",
|
"Register function: {} with {}",
|
||||||
fn_name,
|
fn_name,
|
||||||
if let Some(a) = &args {
|
if let Some(a) = &args {
|
||||||
format!("{}", a.len())
|
format!(
|
||||||
|
"{} parameter{}",
|
||||||
|
a.len(),
|
||||||
|
if a.len() > 1 { "s" } else { "" }
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
"no".to_string()
|
"no parameter".to_string()
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -31,24 +41,21 @@ impl<'a> Engine<'a> {
|
|||||||
args,
|
args,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.external_functions
|
self.ext_functions.insert(spec, Arc::new(FnIntExt::Ext(f)));
|
||||||
.insert(spec, Arc::new(FnIntExt::Ext(f)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register a custom type for use with the `Engine`.
|
/// Register a custom type for use with the `Engine`.
|
||||||
/// The type must be `Clone`.
|
/// The type must be `Clone`.
|
||||||
pub fn register_type<T: Any + Clone>(&mut self) {
|
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.
|
/// Register a custom type for use with the `Engine` with a name for the `type_of` function.
|
||||||
/// The type must be `Clone`.
|
/// 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
|
// Add the pretty-print type name into the map
|
||||||
self.type_names.insert(
|
self.type_names
|
||||||
std::any::type_name::<T>().to_string(),
|
.insert(type_name::<T>().to_string(), name.to_string());
|
||||||
type_name.to_string(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register an iterator adapter for a type with the `Engine`.
|
/// Register an iterator adapter for a type with the `Engine`.
|
||||||
@ -91,17 +98,14 @@ impl<'a> Engine<'a> {
|
|||||||
self.register_set(name, set_fn);
|
self.register_set(name, set_fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compile a string into an AST
|
/// Compile a string into an AST.
|
||||||
pub fn compile(input: &str) -> Result<AST, ParseError> {
|
pub fn compile(&self, input: &str) -> Result<AST, ParseError> {
|
||||||
let tokens = lex(input);
|
let tokens = lex(input);
|
||||||
parse(&mut tokens.peekable())
|
parse(&mut tokens.peekable(), self.optimize)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compile a file into an AST
|
/// Compile a file into an AST.
|
||||||
pub fn compile_file(filename: &str) -> Result<AST, EvalAltResult> {
|
pub fn compile_file(&self, filename: &str) -> Result<AST, EvalAltResult> {
|
||||||
use std::fs::File;
|
|
||||||
use std::io::prelude::*;
|
|
||||||
|
|
||||||
let mut f = File::open(filename)
|
let mut f = File::open(filename)
|
||||||
.map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err))?;
|
.map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err))?;
|
||||||
|
|
||||||
@ -109,14 +113,11 @@ impl<'a> Engine<'a> {
|
|||||||
|
|
||||||
f.read_to_string(&mut contents)
|
f.read_to_string(&mut contents)
|
||||||
.map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err))
|
.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> {
|
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)
|
let mut f = File::open(filename)
|
||||||
.map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err))?;
|
.map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err))?;
|
||||||
|
|
||||||
@ -127,32 +128,40 @@ impl<'a> Engine<'a> {
|
|||||||
.and_then(|_| self.eval::<T>(&contents))
|
.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> {
|
pub fn eval<T: Any + Clone>(&mut self, input: &str) -> Result<T, EvalAltResult> {
|
||||||
let mut scope = Scope::new();
|
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>(
|
pub fn eval_with_scope<T: Any + Clone>(
|
||||||
&mut self,
|
&mut self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
|
retain_functions: bool,
|
||||||
input: &str,
|
input: &str,
|
||||||
) -> Result<T, EvalAltResult> {
|
) -> Result<T, EvalAltResult> {
|
||||||
let ast = Self::compile(input).map_err(EvalAltResult::ErrorParsing)?;
|
let ast = self.compile(input).map_err(EvalAltResult::ErrorParsing)?;
|
||||||
self.eval_ast_with_scope(scope, &ast)
|
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> {
|
pub fn eval_ast<T: Any + Clone>(&mut self, ast: &AST) -> Result<T, EvalAltResult> {
|
||||||
let mut scope = Scope::new();
|
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>(
|
pub fn eval_ast_with_scope<T: Any + Clone>(
|
||||||
&mut self,
|
&mut self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
|
retain_functions: bool,
|
||||||
ast: &AST,
|
ast: &AST,
|
||||||
) -> Result<T, EvalAltResult> {
|
) -> Result<T, EvalAltResult> {
|
||||||
let AST(statements, functions) = ast;
|
let AST(statements, functions) = ast;
|
||||||
@ -169,9 +178,11 @@ impl<'a> Engine<'a> {
|
|||||||
|
|
||||||
let result = statements
|
let result = statements
|
||||||
.iter()
|
.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 {
|
match result {
|
||||||
Err(EvalAltResult::Return(out, pos)) => out.downcast::<T>().map(|v| *v).map_err(|a| {
|
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).
|
/// 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
|
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||||
/// to keep track of possible errors
|
|
||||||
pub fn consume_file(&mut self, filename: &str) -> Result<(), EvalAltResult> {
|
pub fn consume_file(&mut self, filename: &str) -> Result<(), EvalAltResult> {
|
||||||
use std::fs::File;
|
|
||||||
use std::io::prelude::*;
|
|
||||||
|
|
||||||
let mut f = File::open(filename)
|
let mut f = File::open(filename)
|
||||||
.map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err))?;
|
.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).
|
/// 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
|
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||||
/// to keep track of possible errors
|
|
||||||
pub fn consume(&mut self, input: &str) -> Result<(), EvalAltResult> {
|
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).
|
/// 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
|
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||||
/// 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(
|
pub fn consume_with_scope(
|
||||||
&mut self,
|
&mut self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
|
retain_functions: bool,
|
||||||
input: &str,
|
input: &str,
|
||||||
) -> Result<(), EvalAltResult> {
|
) -> Result<(), EvalAltResult> {
|
||||||
let tokens = lex(input);
|
let tokens = lex(input);
|
||||||
|
|
||||||
parse(&mut tokens.peekable())
|
parse(&mut tokens.peekable(), self.optimize)
|
||||||
.map_err(|err| EvalAltResult::ErrorParsing(err))
|
.map_err(|err| EvalAltResult::ErrorParsing(err))
|
||||||
.and_then(|AST(ref statements, ref functions)| {
|
.and_then(|AST(ref statements, ref functions)| {
|
||||||
for f in functions {
|
for f in functions {
|
||||||
@ -244,7 +253,9 @@ impl<'a> Engine<'a> {
|
|||||||
.try_fold(().into_dynamic(), |_, o| self.eval_stmt(scope, o))
|
.try_fold(().into_dynamic(), |_, o| self.eval_stmt(scope, o))
|
||||||
.map(|_| ());
|
.map(|_| ());
|
||||||
|
|
||||||
self.script_functions.clear(); // Clean up engine
|
if !retain_functions {
|
||||||
|
self.clear_functions();
|
||||||
|
}
|
||||||
|
|
||||||
val
|
val
|
||||||
})
|
})
|
||||||
@ -255,13 +266,14 @@ impl<'a> Engine<'a> {
|
|||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use rhai::{Engine, EvalAltResult};
|
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||||
/// # fn main() -> Result<(), EvalAltResult> {
|
/// use rhai::Engine;
|
||||||
|
///
|
||||||
/// let mut engine = Engine::new();
|
/// 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);
|
/// assert_eq!(result, 126);
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
@ -270,7 +282,7 @@ impl<'a> Engine<'a> {
|
|||||||
pub fn call_fn<'f, A: FuncArgs<'f>, T: Any + Clone>(
|
pub fn call_fn<'f, A: FuncArgs<'f>, T: Any + Clone>(
|
||||||
&mut self,
|
&mut self,
|
||||||
name: &str,
|
name: &str,
|
||||||
ast: AST,
|
ast: &AST,
|
||||||
args: A,
|
args: A,
|
||||||
) -> Result<T, EvalAltResult> {
|
) -> Result<T, EvalAltResult> {
|
||||||
let pos = Default::default();
|
let pos = Default::default();
|
||||||
@ -296,7 +308,7 @@ impl<'a> Engine<'a> {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
self.script_functions.clear(); // Clean up engine
|
self.clear_functions();
|
||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
@ -306,18 +318,22 @@ impl<'a> Engine<'a> {
|
|||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use rhai::Engine;
|
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||||
|
/// use rhai::Engine;
|
||||||
|
///
|
||||||
/// let mut result = String::from("");
|
/// let mut result = String::from("");
|
||||||
/// {
|
/// {
|
||||||
/// let mut engine = Engine::new();
|
/// let mut engine = Engine::new();
|
||||||
///
|
///
|
||||||
/// // Override action of 'print' function
|
/// // Override action of 'print' function
|
||||||
/// engine.on_print(|s| result.push_str(s));
|
/// engine.on_print(|s| result.push_str(s));
|
||||||
/// engine.consume("print(40 + 2);").unwrap();
|
/// engine.consume("print(40 + 2);")?;
|
||||||
/// }
|
/// }
|
||||||
/// assert_eq!(result, "42");
|
/// 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);
|
self.on_print = Box::new(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -326,18 +342,22 @@ impl<'a> Engine<'a> {
|
|||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use rhai::Engine;
|
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||||
|
/// use rhai::Engine;
|
||||||
|
///
|
||||||
/// let mut result = String::from("");
|
/// let mut result = String::from("");
|
||||||
/// {
|
/// {
|
||||||
/// let mut engine = Engine::new();
|
/// let mut engine = Engine::new();
|
||||||
///
|
///
|
||||||
/// // Override action of 'debug' function
|
/// // Override action of 'debug' function
|
||||||
/// engine.on_debug(|s| result.push_str(s));
|
/// engine.on_debug(|s| result.push_str(s));
|
||||||
/// engine.consume(r#"debug("hello");"#).unwrap();
|
/// engine.consume(r#"debug("hello");"#)?;
|
||||||
/// }
|
/// }
|
||||||
/// assert_eq!(result, "\"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);
|
self.on_debug = Box::new(callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
360
src/builtin.rs
360
src/builtin.rs
@ -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::any::Any;
|
||||||
use crate::engine::{Array, Engine};
|
use crate::engine::{Array, Engine};
|
||||||
use crate::fn_register::RegisterFn;
|
use crate::fn_register::RegisterFn;
|
||||||
use std::fmt::{Debug, Display};
|
use std::{
|
||||||
use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub};
|
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 {
|
macro_rules! reg_op {
|
||||||
($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => (
|
($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 {
|
macro_rules! reg_un {
|
||||||
($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => (
|
($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 {
|
macro_rules! reg_cmp {
|
||||||
($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => (
|
($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => (
|
||||||
$(
|
$(
|
||||||
@ -66,21 +113,102 @@ macro_rules! reg_func3 {
|
|||||||
impl Engine<'_> {
|
impl Engine<'_> {
|
||||||
/// Register the core built-in library.
|
/// Register the core built-in library.
|
||||||
pub(crate) fn register_core_lib(&mut self) {
|
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
|
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
|
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
|
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
|
x / y
|
||||||
}
|
}
|
||||||
fn neg<T: Neg>(x: T) -> <T as Neg>::Output {
|
fn neg_u<T: Neg>(x: T) -> <T as Neg>::Output {
|
||||||
-x
|
-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 {
|
fn lt<T: PartialOrd>(x: T, y: T) -> bool {
|
||||||
x < y
|
x < y
|
||||||
}
|
}
|
||||||
@ -117,29 +245,117 @@ impl Engine<'_> {
|
|||||||
fn binary_xor<T: BitXor>(x: T, y: T) -> <T as BitXor>::Output {
|
fn binary_xor<T: BitXor>(x: T, y: T) -> <T as BitXor>::Output {
|
||||||
x ^ y
|
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)
|
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)
|
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
|
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 {
|
fn pow_i64_i64(x: i64, y: i64) -> i64 {
|
||||||
x.pow(y as u32)
|
x.pow(y as u32)
|
||||||
}
|
}
|
||||||
fn pow_f64_f64(x: f64, y: f64) -> f64 {
|
fn pow_f64_f64(x: f64, y: f64) -> f64 {
|
||||||
x.powf(y)
|
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 {
|
fn pow_f64_i64(x: f64, y: i64) -> f64 {
|
||||||
x.powi(y as i32)
|
x.powi(y as i32)
|
||||||
}
|
}
|
||||||
|
|
||||||
reg_op!(self, "+", add, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64);
|
#[cfg(not(feature = "unchecked"))]
|
||||||
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_result!(self, "+", add, i8, u8, i16, u16, i32, i64, u32, u64);
|
||||||
reg_op!(self, "/", div, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64);
|
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, "<", 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);
|
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, "&", binary_and, i8, u8, i16, u16, i32, i64, u32, u64);
|
||||||
reg_op!(self, "&", and, bool);
|
reg_op!(self, "&", and, bool);
|
||||||
reg_op!(self, "^", binary_xor, i8, u8, i16, u16, i32, i64, u32, u64);
|
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);
|
reg_un!(self, "!", not, bool);
|
||||||
|
|
||||||
self.register_fn("+", |x: String, y: String| x + &y); // String + String
|
self.register_fn("+", |x: String, y: String| x + &y); // String + String
|
||||||
@ -216,6 +470,33 @@ impl Engine<'_> {
|
|||||||
pub(crate) fn register_stdlib(&mut self) {
|
pub(crate) fn register_stdlib(&mut self) {
|
||||||
use crate::fn_register::RegisterDynamicFn;
|
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
|
// Register conversion functions
|
||||||
self.register_fn("to_float", |x: i8| x as f64);
|
self.register_fn("to_float", |x: i8| x as f64);
|
||||||
self.register_fn("to_float", |x: u8| 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: i32| x as i64);
|
||||||
self.register_fn("to_int", |x: u32| 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: 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);
|
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
|
// Register array utility functions
|
||||||
fn push<T: Any>(list: &mut Array, item: T) {
|
fn push<T: Any>(list: &mut Array, item: T) {
|
||||||
list.push(Box::new(item));
|
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, ch: char| s.contains(ch));
|
||||||
self.register_fn("contains", |s: &mut String, find: String| s.contains(&find));
|
self.register_fn("contains", |s: &mut String, find: String| s.contains(&find));
|
||||||
self.register_fn("clear", |s: &mut String| s.clear());
|
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| {
|
self.register_fn("truncate", |s: &mut String, len: i64| {
|
||||||
if len >= 0 {
|
if len >= 0 {
|
||||||
let chars: Vec<_> = s.chars().take(len as usize).collect();
|
let chars: Vec<_> = s.chars().take(len as usize).collect();
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
//! Helper module which defines `FnArgs`
|
//! Helper module which defines `FnArgs` to make function calling easier.
|
||||||
//! to make function calling easier.
|
|
||||||
|
|
||||||
use crate::any::{Any, Variant};
|
use crate::any::{Any, Variant};
|
||||||
|
|
||||||
|
705
src/engine.rs
705
src/engine.rs
File diff suppressed because it is too large
Load Diff
28
src/error.rs
28
src/error.rs
@ -1,7 +1,7 @@
|
|||||||
|
//! Module containing error definitions for the parsing process.
|
||||||
|
|
||||||
use crate::parser::Position;
|
use crate::parser::Position;
|
||||||
use std::char;
|
use std::{char, error::Error, fmt};
|
||||||
use std::error::Error;
|
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
/// Error when tokenizing the script text.
|
/// Error when tokenizing the script text.
|
||||||
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
|
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
|
||||||
@ -64,9 +64,9 @@ pub enum ParseErrorType {
|
|||||||
/// An open `[` is missing the corresponding closing `]`.
|
/// An open `[` is missing the corresponding closing `]`.
|
||||||
MissingRightBracket(String),
|
MissingRightBracket(String),
|
||||||
/// An expression in function call arguments `()` has syntax error.
|
/// An expression in function call arguments `()` has syntax error.
|
||||||
MalformedCallExpr,
|
MalformedCallExpr(String),
|
||||||
/// An expression in indexing brackets `[]` has syntax error.
|
/// An expression in indexing brackets `[]` has syntax error.
|
||||||
MalformedIndexExpr,
|
MalformedIndexExpr(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).
|
||||||
@ -75,11 +75,13 @@ pub enum ParseErrorType {
|
|||||||
FnMissingName,
|
FnMissingName,
|
||||||
/// A function definition is missing the parameters list. Wrapped value is the function name.
|
/// A function definition is missing the parameters list. Wrapped value is the function name.
|
||||||
FnMissingParams(String),
|
FnMissingParams(String),
|
||||||
|
/// Assignment to an inappropriate LHS (left-hand-side) expression.
|
||||||
|
AssignmentToInvalidLHS,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Error when parsing a script.
|
/// Error when parsing a script.
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub struct ParseError(ParseErrorType, Position);
|
pub struct ParseError(pub(crate) ParseErrorType, pub(crate) Position);
|
||||||
|
|
||||||
impl ParseError {
|
impl ParseError {
|
||||||
/// Create a new `ParseError`.
|
/// Create a new `ParseError`.
|
||||||
@ -108,12 +110,13 @@ impl Error for ParseError {
|
|||||||
ParseErrorType::MissingLeftBrace => "Expecting '{'",
|
ParseErrorType::MissingLeftBrace => "Expecting '{'",
|
||||||
ParseErrorType::MissingRightBrace(_) => "Expecting '}'",
|
ParseErrorType::MissingRightBrace(_) => "Expecting '}'",
|
||||||
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::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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,7 +128,11 @@ impl Error for ParseError {
|
|||||||
impl fmt::Display for ParseError {
|
impl fmt::Display for ParseError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self.0 {
|
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::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)?
|
||||||
@ -140,6 +147,9 @@ impl fmt::Display for ParseError {
|
|||||||
|
|
||||||
if !self.1.is_eof() {
|
if !self.1.is_eof() {
|
||||||
write!(f, " ({})", self.1)
|
write!(f, " ({})", self.1)
|
||||||
|
} else if !self.1.is_none() {
|
||||||
|
// Do not write any position if None
|
||||||
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
write!(f, " at the end of the script but there is no more input")
|
write!(f, " at the end of the script but there is no more input")
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
use std::any::TypeId;
|
//! Module which defines the function registration mechanism.
|
||||||
|
|
||||||
use crate::any::{Any, Dynamic};
|
use crate::any::{Any, Dynamic};
|
||||||
use crate::engine::{Engine, FnCallArgs};
|
use crate::engine::{Engine, FnCallArgs};
|
||||||
use crate::parser::Position;
|
use crate::parser::Position;
|
||||||
use crate::result::EvalAltResult;
|
use crate::result::EvalAltResult;
|
||||||
|
use std::any::TypeId;
|
||||||
|
|
||||||
/// A trait to register custom functions with the `Engine`.
|
/// A trait to register custom functions with the `Engine`.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
|
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||||
/// use rhai::{Engine, RegisterFn};
|
/// use rhai::{Engine, RegisterFn};
|
||||||
///
|
///
|
||||||
/// // Normal function
|
/// // Normal function
|
||||||
@ -22,9 +24,11 @@ use crate::result::EvalAltResult;
|
|||||||
/// // You must use the trait rhai::RegisterFn to get this method.
|
/// // You must use the trait rhai::RegisterFn to get this method.
|
||||||
/// engine.register_fn("add", add);
|
/// engine.register_fn("add", add);
|
||||||
///
|
///
|
||||||
/// if let Ok(result) = engine.eval::<i64>("add(40, 2)") {
|
/// let result = engine.eval::<i64>("add(40, 2)")?;
|
||||||
/// println!("Answer: {}", result); // prints 42
|
///
|
||||||
/// }
|
/// println!("Answer: {}", result); // prints 42
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub trait RegisterFn<FN, ARGS, RET> {
|
pub trait RegisterFn<FN, ARGS, RET> {
|
||||||
/// Register a custom function with the `Engine`.
|
/// Register a custom function with the `Engine`.
|
||||||
@ -36,7 +40,8 @@ pub trait RegisterFn<FN, ARGS, RET> {
|
|||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use rhai::{Engine, RegisterDynamicFn, Dynamic};
|
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||||
|
/// use rhai::{Engine, Dynamic, RegisterDynamicFn};
|
||||||
///
|
///
|
||||||
/// // Function that returns a Dynamic value
|
/// // Function that returns a Dynamic value
|
||||||
/// fn get_an_any(x: i64) -> Dynamic {
|
/// 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.
|
/// // You must use the trait rhai::RegisterDynamicFn to get this method.
|
||||||
/// engine.register_dynamic_fn("get_an_any", get_an_any);
|
/// engine.register_dynamic_fn("get_an_any", get_an_any);
|
||||||
///
|
///
|
||||||
/// if let Ok(result) = engine.eval::<i64>("get_an_any(42)") {
|
/// let result = engine.eval::<i64>("get_an_any(42)")?;
|
||||||
/// println!("Answer: {}", result); // prints 42
|
///
|
||||||
/// }
|
/// println!("Answer: {}", result); // prints 42
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub trait RegisterDynamicFn<FN, ARGS> {
|
pub trait RegisterDynamicFn<FN, ARGS> {
|
||||||
/// Register a custom function returning `Dynamic` values with the `Engine`.
|
/// Register a custom function returning `Dynamic` values with the `Engine`.
|
||||||
fn register_dynamic_fn(&mut self, name: &str, f: FN);
|
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 Ref<A>(A);
|
||||||
pub struct Mut<A>(A);
|
pub struct Mut<A>(A);
|
||||||
|
|
||||||
@ -90,7 +126,7 @@ macro_rules! def_register {
|
|||||||
let mut drain = args.drain(..);
|
let mut drain = args.drain(..);
|
||||||
$(
|
$(
|
||||||
// Downcast every element, return in case of a type mismatch
|
// 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
|
// Call the user-supplied function using ($clone) to
|
||||||
@ -122,7 +158,7 @@ macro_rules! def_register {
|
|||||||
let mut drain = args.drain(..);
|
let mut drain = args.drain(..);
|
||||||
$(
|
$(
|
||||||
// Downcast every element, return in case of a type mismatch
|
// 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
|
// 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),*);
|
//def_register!(imp_pop $($par => $mark => $param),*);
|
||||||
};
|
};
|
||||||
($p0:ident $(, $p:ident)*) => {
|
($p0:ident $(, $p:ident)*) => {
|
||||||
|
24
src/lib.rs
24
src/lib.rs
@ -17,15 +17,22 @@
|
|||||||
//! And the Rust part:
|
//! And the Rust part:
|
||||||
//!
|
//!
|
||||||
//! ```rust,no_run
|
//! ```rust,no_run
|
||||||
//! use rhai::{Engine, RegisterFn};
|
//! use rhai::{Engine, EvalAltResult, RegisterFn};
|
||||||
//!
|
//!
|
||||||
//! fn compute_something(x: i64) -> bool {
|
//! fn main() -> Result<(), EvalAltResult>
|
||||||
//! (x % 40) == 0
|
//! {
|
||||||
|
//! 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)
|
//! [Check out the README on GitHub for more information!](https://github.com/jonathandturner/rhai)
|
||||||
@ -61,6 +68,7 @@ mod call;
|
|||||||
mod engine;
|
mod engine;
|
||||||
mod error;
|
mod error;
|
||||||
mod fn_register;
|
mod fn_register;
|
||||||
|
mod optimize;
|
||||||
mod parser;
|
mod parser;
|
||||||
mod result;
|
mod result;
|
||||||
mod scope;
|
mod scope;
|
||||||
@ -69,7 +77,7 @@ pub use any::{Any, AnyExt, Dynamic, Variant};
|
|||||||
pub use call::FuncArgs;
|
pub use call::FuncArgs;
|
||||||
pub use engine::{Array, Engine};
|
pub use engine::{Array, Engine};
|
||||||
pub use error::{ParseError, ParseErrorType};
|
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 parser::{Position, AST};
|
||||||
pub use result::EvalAltResult;
|
pub use result::EvalAltResult;
|
||||||
pub use scope::Scope;
|
pub use scope::Scope;
|
||||||
|
228
src/optimize.rs
Normal file
228
src/optimize.rs
Normal 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
|
||||||
|
}
|
587
src/parser.rs
587
src/parser.rs
@ -1,8 +1,9 @@
|
|||||||
|
//! Main module defining the lexer and parser.
|
||||||
|
|
||||||
use crate::any::Dynamic;
|
use crate::any::Dynamic;
|
||||||
use crate::error::{LexError, ParseError, ParseErrorType};
|
use crate::error::{LexError, ParseError, ParseErrorType};
|
||||||
use std::char;
|
use crate::optimize::optimize;
|
||||||
use std::iter::Peekable;
|
use std::{borrow::Cow, char, fmt, iter::Peekable, str::Chars, str::FromStr, usize};
|
||||||
use std::str::Chars;
|
|
||||||
|
|
||||||
type LERR = LexError;
|
type LERR = LexError;
|
||||||
type PERR = ParseErrorType;
|
type PERR = ParseErrorType;
|
||||||
@ -17,25 +18,33 @@ pub struct Position {
|
|||||||
impl Position {
|
impl Position {
|
||||||
/// Create a new `Position`.
|
/// Create a new `Position`.
|
||||||
pub fn new(line: usize, position: usize) -> Self {
|
pub fn new(line: usize, position: usize) -> Self {
|
||||||
|
if line == 0 || (line == usize::MAX && position == usize::MAX) {
|
||||||
|
panic!("invalid position: ({}, {})", line, position);
|
||||||
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
line,
|
line,
|
||||||
pos: position,
|
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> {
|
pub fn line(&self) -> Option<usize> {
|
||||||
match self.line {
|
if self.is_none() || self.is_eof() {
|
||||||
0 => None,
|
None
|
||||||
x => Some(x),
|
} else {
|
||||||
|
Some(self.line)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the character position (1-based), or `None` if at beginning of a line.
|
/// Get the character position (1-based), or `None` if at beginning of a line.
|
||||||
pub fn position(&self) -> Option<usize> {
|
pub fn position(&self) -> Option<usize> {
|
||||||
match self.pos {
|
if self.is_none() || self.is_eof() {
|
||||||
0 => None,
|
None
|
||||||
x => Some(x),
|
} else if self.pos == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(self.pos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,14 +70,27 @@ impl Position {
|
|||||||
self.pos = 0;
|
self.pos = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a `Position` representing no position.
|
||||||
|
pub(crate) fn none() -> Self {
|
||||||
|
Self { line: 0, pos: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a `Position` at EOF.
|
/// Create a `Position` at EOF.
|
||||||
pub(crate) fn eof() -> Self {
|
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?
|
/// Is the `Position` at EOF?
|
||||||
pub fn is_eof(&self) -> bool {
|
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 {
|
impl fmt::Display for Position {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
if self.is_eof() {
|
if self.is_eof() {
|
||||||
write!(f, "EOF")
|
write!(f, "EOF")
|
||||||
|
} else if self.is_none() {
|
||||||
|
write!(f, "none")
|
||||||
} else {
|
} else {
|
||||||
write!(f, "line {}, position {}", self.line, self.pos)
|
write!(f, "line {}, position {}", self.line, self.pos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for Position {
|
impl fmt::Debug for Position {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
if self.is_eof() {
|
if self.is_eof() {
|
||||||
write!(f, "(EOF)")
|
write!(f, "(EOF)")
|
||||||
} else {
|
} else {
|
||||||
@ -99,29 +123,39 @@ impl std::fmt::Debug for Position {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Compiled AST (abstract syntax tree) of a Rhai script.
|
/// 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)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct FnDef {
|
pub struct FnDef<'a> {
|
||||||
pub name: String,
|
pub name: Cow<'a, str>,
|
||||||
pub params: Vec<String>,
|
pub params: Vec<Cow<'a, str>>,
|
||||||
pub body: Box<Stmt>,
|
pub body: Stmt,
|
||||||
pub pos: Position,
|
pub pos: Position,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Stmt {
|
pub enum Stmt {
|
||||||
|
Noop(Position),
|
||||||
IfElse(Box<Expr>, Box<Stmt>, Option<Box<Stmt>>),
|
IfElse(Box<Expr>, Box<Stmt>, Option<Box<Stmt>>),
|
||||||
While(Box<Expr>, Box<Stmt>),
|
While(Box<Expr>, Box<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),
|
||||||
Block(Vec<Stmt>),
|
Block(Vec<Stmt>, Position),
|
||||||
Expr(Box<Expr>),
|
Expr(Box<Expr>),
|
||||||
Break(Position),
|
Break(Position),
|
||||||
ReturnWithVal(Option<Box<Expr>>, bool, Position),
|
ReturnWithVal(Option<Box<Expr>>, bool, Position),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Stmt {
|
||||||
|
pub fn is_op(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Stmt::Noop(_) => false,
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Expr {
|
pub enum Expr {
|
||||||
IntegerConstant(i64, Position),
|
IntegerConstant(i64, Position),
|
||||||
@ -129,10 +163,11 @@ pub enum Expr {
|
|||||||
Identifier(String, Position),
|
Identifier(String, Position),
|
||||||
CharConstant(char, Position),
|
CharConstant(char, Position),
|
||||||
StringConstant(String, Position),
|
StringConstant(String, Position),
|
||||||
|
Stmt(Box<Stmt>, Position),
|
||||||
FunctionCall(String, Vec<Expr>, Option<Dynamic>, Position),
|
FunctionCall(String, Vec<Expr>, Option<Dynamic>, Position),
|
||||||
Assignment(Box<Expr>, Box<Expr>),
|
Assignment(Box<Expr>, Box<Expr>, Position),
|
||||||
Dot(Box<Expr>, Box<Expr>),
|
Dot(Box<Expr>, Box<Expr>, Position),
|
||||||
Index(Box<Expr>, Box<Expr>),
|
Index(Box<Expr>, Box<Expr>, Position),
|
||||||
Array(Vec<Expr>, Position),
|
Array(Vec<Expr>, Position),
|
||||||
And(Box<Expr>, Box<Expr>),
|
And(Box<Expr>, Box<Expr>),
|
||||||
Or(Box<Expr>, Box<Expr>),
|
Or(Box<Expr>, Box<Expr>),
|
||||||
@ -150,18 +185,34 @@ impl Expr {
|
|||||||
| Expr::CharConstant(_, pos)
|
| Expr::CharConstant(_, pos)
|
||||||
| Expr::StringConstant(_, pos)
|
| Expr::StringConstant(_, pos)
|
||||||
| Expr::FunctionCall(_, _, _, pos)
|
| Expr::FunctionCall(_, _, _, pos)
|
||||||
|
| Expr::Stmt(_, pos)
|
||||||
| Expr::Array(_, pos)
|
| Expr::Array(_, pos)
|
||||||
| Expr::True(pos)
|
| Expr::True(pos)
|
||||||
| Expr::False(pos)
|
| Expr::False(pos)
|
||||||
| Expr::Unit(pos) => *pos,
|
| Expr::Unit(pos) => *pos,
|
||||||
|
|
||||||
Expr::Index(e, _)
|
Expr::Index(e, _, _)
|
||||||
| Expr::Assignment(e, _)
|
| Expr::Assignment(e, _, _)
|
||||||
| Expr::Dot(e, _)
|
| Expr::Dot(e, _, _)
|
||||||
| Expr::And(e, _)
|
| Expr::And(e, _)
|
||||||
| Expr::Or(e, _) => e.position(),
|
| 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)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
@ -232,14 +283,14 @@ pub enum Token {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Token {
|
impl Token {
|
||||||
pub fn syntax(&self) -> std::borrow::Cow<'static, str> {
|
pub fn syntax<'a>(&'a self) -> Cow<'a, str> {
|
||||||
use self::Token::*;
|
use self::Token::*;
|
||||||
|
|
||||||
match *self {
|
match *self {
|
||||||
IntegerConstant(ref s) => s.to_string().into(),
|
IntegerConstant(ref i) => i.to_string().into(),
|
||||||
FloatConstant(ref s) => s.to_string().into(),
|
FloatConstant(ref f) => f.to_string().into(),
|
||||||
Identifier(ref s) => s.to_string().into(),
|
Identifier(ref s) => s.into(),
|
||||||
CharConstant(ref s) => s.to_string().into(),
|
CharConstant(ref c) => c.to_string().into(),
|
||||||
LexError(ref err) => err.to_string().into(),
|
LexError(ref err) => err.to_string().into(),
|
||||||
|
|
||||||
ref token => (match token {
|
ref token => (match token {
|
||||||
@ -301,7 +352,7 @@ impl Token {
|
|||||||
PowerOfAssign => "~=",
|
PowerOfAssign => "~=",
|
||||||
For => "for",
|
For => "for",
|
||||||
In => "in",
|
In => "in",
|
||||||
_ => panic!(),
|
_ => panic!("operator should be match in outer scope"),
|
||||||
})
|
})
|
||||||
.into(),
|
.into(),
|
||||||
}
|
}
|
||||||
@ -313,6 +364,7 @@ impl Token {
|
|||||||
use self::Token::*;
|
use self::Token::*;
|
||||||
|
|
||||||
match *self {
|
match *self {
|
||||||
|
LexError(_) |
|
||||||
LeftBrace | // (+expr) - is unary
|
LeftBrace | // (+expr) - is unary
|
||||||
// RightBrace | {expr} - expr not unary & is closing
|
// RightBrace | {expr} - expr not unary & is closing
|
||||||
LeftParen | // {-expr} - is unary
|
LeftParen | // {-expr} - is unary
|
||||||
@ -361,6 +413,7 @@ impl Token {
|
|||||||
PowerOf |
|
PowerOf |
|
||||||
In |
|
In |
|
||||||
PowerOfAssign => true,
|
PowerOfAssign => true,
|
||||||
|
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -373,6 +426,7 @@ impl Token {
|
|||||||
RightBrace | RightParen | RightBracket | Plus | Minus | Multiply | Divide | Comma
|
RightBrace | RightParen | RightBracket | Plus | Minus | Multiply | Divide | Comma
|
||||||
| Equals | LessThan | GreaterThan | LessThanEqualsTo | GreaterThanEqualsTo
|
| Equals | LessThan | GreaterThan | LessThanEqualsTo | GreaterThanEqualsTo
|
||||||
| EqualsTo | NotEqualsTo | Pipe | Or | Ampersand | And | PowerOf => true,
|
| EqualsTo | NotEqualsTo | Pipe | Or | Ampersand | And | PowerOf => true,
|
||||||
|
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -538,11 +592,12 @@ impl<'a> TokenIterator<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let out: String = result.iter().collect();
|
Ok(result.iter().collect())
|
||||||
Ok(out)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn inner_next(&mut self) -> Option<(Token, Position)> {
|
fn inner_next(&mut self) -> Option<(Token, Position)> {
|
||||||
|
let mut negated = false;
|
||||||
|
|
||||||
while let Some(c) = self.char_stream.next() {
|
while let Some(c) = self.char_stream.next() {
|
||||||
self.advance();
|
self.advance();
|
||||||
|
|
||||||
@ -629,31 +684,34 @@ impl<'a> TokenIterator<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if negated {
|
||||||
|
result.insert(0, '-');
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(radix) = radix_base {
|
if let Some(radix) = radix_base {
|
||||||
let out: String = result.iter().skip(2).filter(|&&c| c != '_').collect();
|
let out: String = result.iter().skip(2).filter(|&&c| c != '_').collect();
|
||||||
|
|
||||||
return Some((
|
return Some((
|
||||||
if let Ok(val) = i64::from_str_radix(&out, radix) {
|
i64::from_str_radix(&out, radix)
|
||||||
Token::IntegerConstant(val)
|
.map(Token::IntegerConstant)
|
||||||
} else {
|
.unwrap_or_else(|_| {
|
||||||
Token::LexError(LERR::MalformedNumber(result.iter().collect()))
|
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,
|
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' | '_' => {
|
'A'..='Z' | 'a'..='z' | '_' => {
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
@ -687,7 +745,7 @@ impl<'a> TokenIterator<'a> {
|
|||||||
"fn" => Token::Fn,
|
"fn" => Token::Fn,
|
||||||
"for" => Token::For,
|
"for" => Token::For,
|
||||||
"in" => Token::In,
|
"in" => Token::In,
|
||||||
x => Token::Identifier(x.into()),
|
_ => Token::Identifier(out),
|
||||||
},
|
},
|
||||||
pos,
|
pos,
|
||||||
));
|
));
|
||||||
@ -737,20 +795,17 @@ impl<'a> TokenIterator<'a> {
|
|||||||
pos,
|
pos,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
'-' => {
|
'-' => match self.char_stream.peek() {
|
||||||
return Some((
|
// Negative number?
|
||||||
match self.char_stream.peek() {
|
Some('0'..='9') => negated = true,
|
||||||
Some(&'=') => {
|
Some('=') => {
|
||||||
self.char_stream.next();
|
self.char_stream.next();
|
||||||
self.advance();
|
self.advance();
|
||||||
Token::MinusAssign
|
return Some((Token::MinusAssign, pos));
|
||||||
}
|
}
|
||||||
_ if self.last.is_next_unary() => Token::UnaryMinus,
|
_ if self.last.is_next_unary() => return Some((Token::UnaryMinus, pos)),
|
||||||
_ => Token::Minus,
|
_ => return Some((Token::Minus, pos)),
|
||||||
},
|
},
|
||||||
pos,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
'*' => {
|
'*' => {
|
||||||
return Some((
|
return Some((
|
||||||
match self.char_stream.peek() {
|
match self.char_stream.peek() {
|
||||||
@ -1010,19 +1065,28 @@ fn get_precedence(token: &Token) -> i8 {
|
|||||||
| Token::XOrAssign
|
| Token::XOrAssign
|
||||||
| Token::ModuloAssign
|
| Token::ModuloAssign
|
||||||
| Token::PowerOfAssign => 10,
|
| Token::PowerOfAssign => 10,
|
||||||
|
|
||||||
Token::Or | Token::XOr | Token::Pipe => 11,
|
Token::Or | Token::XOr | Token::Pipe => 11,
|
||||||
|
|
||||||
Token::And | Token::Ampersand => 12,
|
Token::And | Token::Ampersand => 12,
|
||||||
|
|
||||||
Token::LessThan
|
Token::LessThan
|
||||||
| Token::LessThanEqualsTo
|
| Token::LessThanEqualsTo
|
||||||
| Token::GreaterThan
|
| Token::GreaterThan
|
||||||
| Token::GreaterThanEqualsTo
|
| Token::GreaterThanEqualsTo
|
||||||
| Token::EqualsTo
|
| Token::EqualsTo
|
||||||
| Token::NotEqualsTo => 15,
|
| Token::NotEqualsTo => 15,
|
||||||
|
|
||||||
Token::Plus | Token::Minus => 20,
|
Token::Plus | Token::Minus => 20,
|
||||||
|
|
||||||
Token::Divide | Token::Multiply | Token::PowerOf => 40,
|
Token::Divide | Token::Multiply | Token::PowerOf => 40,
|
||||||
|
|
||||||
Token::LeftShift | Token::RightShift => 50,
|
Token::LeftShift | Token::RightShift => 50,
|
||||||
|
|
||||||
Token::Modulo => 60,
|
Token::Modulo => 60,
|
||||||
|
|
||||||
Token::Period => 100,
|
Token::Period => 100,
|
||||||
|
|
||||||
_ => -1,
|
_ => -1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1032,6 +1096,7 @@ fn parse_paren_expr<'a>(
|
|||||||
begin: Position,
|
begin: Position,
|
||||||
) -> Result<Expr, ParseError> {
|
) -> Result<Expr, ParseError> {
|
||||||
match input.peek() {
|
match input.peek() {
|
||||||
|
// ()
|
||||||
Some((Token::RightParen, _)) => {
|
Some((Token::RightParen, _)) => {
|
||||||
input.next();
|
input.next();
|
||||||
return Ok(Expr::Unit(begin));
|
return Ok(Expr::Unit(begin));
|
||||||
@ -1104,11 +1169,71 @@ fn parse_call_expr<'a>(
|
|||||||
fn parse_index_expr<'a>(
|
fn parse_index_expr<'a>(
|
||||||
lhs: Box<Expr>,
|
lhs: Box<Expr>,
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
|
pos: Position,
|
||||||
) -> Result<Expr, ParseError> {
|
) -> 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, _)) => {
|
Some(&(Token::RightBracket, _)) => {
|
||||||
input.next();
|
input.next();
|
||||||
return Ok(Expr::Index(lhs, Box::new(idx_expr)));
|
return Ok(Expr::Index(lhs, Box::new(idx_expr), pos));
|
||||||
}
|
}
|
||||||
Some(&(_, pos)) => {
|
Some(&(_, pos)) => {
|
||||||
return Err(ParseError::new(
|
return Err(ParseError::new(
|
||||||
@ -1122,7 +1247,7 @@ fn parse_index_expr<'a>(
|
|||||||
Position::eof(),
|
Position::eof(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_ident_expr<'a>(
|
fn parse_ident_expr<'a>(
|
||||||
@ -1135,9 +1260,9 @@ fn parse_ident_expr<'a>(
|
|||||||
input.next();
|
input.next();
|
||||||
parse_call_expr(id, input, begin)
|
parse_call_expr(id, input, begin)
|
||||||
}
|
}
|
||||||
Some(&(Token::LeftBracket, _)) => {
|
Some(&(Token::LeftBracket, pos)) => {
|
||||||
input.next();
|
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)),
|
Some(_) => Ok(Expr::Identifier(id, begin)),
|
||||||
None => Ok(Expr::Identifier(id, Position::eof())),
|
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> {
|
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 token = input.next();
|
||||||
|
|
||||||
let mut follow_on = false;
|
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
|
// Tail processing all possible indexing
|
||||||
while let Some(&(Token::LeftBracket, _)) = input.peek() {
|
while let Some(&(Token::LeftBracket, pos)) = input.peek() {
|
||||||
input.next();
|
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)
|
Ok(root_expr)
|
||||||
@ -1239,16 +1372,22 @@ fn parse_unary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pars
|
|||||||
Some(&(Token::UnaryMinus, pos)) => {
|
Some(&(Token::UnaryMinus, pos)) => {
|
||||||
input.next();
|
input.next();
|
||||||
|
|
||||||
Ok(Expr::FunctionCall(
|
match parse_unary(input) {
|
||||||
"-".into(),
|
// Negative integer
|
||||||
vec![parse_primary(input)?],
|
Ok(Expr::IntegerConstant(i, pos)) => Ok(i
|
||||||
None,
|
.checked_neg()
|
||||||
pos,
|
.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, _)) => {
|
Some(&(Token::UnaryPlus, _)) => {
|
||||||
input.next();
|
input.next();
|
||||||
parse_primary(input)
|
parse_unary(input)
|
||||||
}
|
}
|
||||||
Some(&(Token::Bang, pos)) => {
|
Some(&(Token::Bang, pos)) => {
|
||||||
input.next();
|
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>(
|
fn parse_binary_op<'a>(
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
precedence: i8,
|
precedence: i8,
|
||||||
@ -1308,32 +1516,10 @@ fn parse_binary_op<'a>(
|
|||||||
}
|
}
|
||||||
Token::Divide => Expr::FunctionCall("/".into(), vec![current_lhs, rhs], None, pos),
|
Token::Divide => Expr::FunctionCall("/".into(), vec![current_lhs, rhs], None, pos),
|
||||||
|
|
||||||
Token::Equals => Expr::Assignment(Box::new(current_lhs), Box::new(rhs)),
|
Token::Equals => parse_assignment(current_lhs, rhs, pos)?,
|
||||||
Token::PlusAssign => {
|
Token::PlusAssign => parse_op_assignment("+", current_lhs, rhs, pos)?,
|
||||||
let lhs_copy = current_lhs.clone();
|
Token::MinusAssign => parse_op_assignment("-", current_lhs, rhs, pos)?,
|
||||||
Expr::Assignment(
|
Token::Period => Expr::Dot(Box::new(current_lhs), Box::new(rhs), pos),
|
||||||
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)),
|
|
||||||
|
|
||||||
// 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(
|
||||||
@ -1376,66 +1562,11 @@ fn parse_binary_op<'a>(
|
|||||||
Token::Or => Expr::Or(Box::new(current_lhs), Box::new(rhs)),
|
Token::Or => Expr::Or(Box::new(current_lhs), Box::new(rhs)),
|
||||||
Token::And => Expr::And(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::XOr => Expr::FunctionCall("^".into(), vec![current_lhs, rhs], None, pos),
|
||||||
Token::OrAssign => {
|
Token::OrAssign => parse_op_assignment("|", current_lhs, rhs, pos)?,
|
||||||
let lhs_copy = current_lhs.clone();
|
Token::AndAssign => parse_op_assignment("&", current_lhs, rhs, pos)?,
|
||||||
Expr::Assignment(
|
Token::XOrAssign => parse_op_assignment("^", current_lhs, rhs, pos)?,
|
||||||
Box::new(current_lhs),
|
Token::MultiplyAssign => parse_op_assignment("*", current_lhs, rhs, pos)?,
|
||||||
Box::new(Expr::FunctionCall(
|
Token::DivideAssign => parse_op_assignment("/", current_lhs, rhs, pos)?,
|
||||||
"|".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::Pipe => Expr::FunctionCall("|".into(), vec![current_lhs, rhs], None, pos),
|
Token::Pipe => Expr::FunctionCall("|".into(), vec![current_lhs, rhs], None, pos),
|
||||||
Token::LeftShift => {
|
Token::LeftShift => {
|
||||||
Expr::FunctionCall("<<".into(), vec![current_lhs, rhs], None, pos)
|
Expr::FunctionCall("<<".into(), vec![current_lhs, rhs], None, pos)
|
||||||
@ -1443,59 +1574,15 @@ fn parse_binary_op<'a>(
|
|||||||
Token::RightShift => {
|
Token::RightShift => {
|
||||||
Expr::FunctionCall(">>".into(), vec![current_lhs, rhs], None, pos)
|
Expr::FunctionCall(">>".into(), vec![current_lhs, rhs], None, pos)
|
||||||
}
|
}
|
||||||
Token::LeftShiftAssign => {
|
Token::LeftShiftAssign => parse_op_assignment("<<", current_lhs, rhs, pos)?,
|
||||||
let lhs_copy = current_lhs.clone();
|
Token::RightShiftAssign => parse_op_assignment(">>", current_lhs, rhs, pos)?,
|
||||||
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::Ampersand => {
|
Token::Ampersand => {
|
||||||
Expr::FunctionCall("&".into(), vec![current_lhs, rhs], None, pos)
|
Expr::FunctionCall("&".into(), vec![current_lhs, rhs], None, pos)
|
||||||
}
|
}
|
||||||
Token::Modulo => Expr::FunctionCall("%".into(), vec![current_lhs, rhs], None, pos),
|
Token::Modulo => Expr::FunctionCall("%".into(), vec![current_lhs, rhs], None, pos),
|
||||||
Token::ModuloAssign => {
|
Token::ModuloAssign => parse_op_assignment("%", current_lhs, rhs, pos)?,
|
||||||
let lhs_copy = current_lhs.clone();
|
|
||||||
Expr::Assignment(
|
|
||||||
Box::new(current_lhs),
|
|
||||||
Box::new(Expr::FunctionCall(
|
|
||||||
"%".into(),
|
|
||||||
vec![lhs_copy, rhs],
|
|
||||||
None,
|
|
||||||
pos,
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Token::PowerOf => Expr::FunctionCall("~".into(), vec![current_lhs, rhs], None, pos),
|
Token::PowerOf => Expr::FunctionCall("~".into(), vec![current_lhs, rhs], None, pos),
|
||||||
Token::PowerOfAssign => {
|
Token::PowerOfAssign => parse_op_assignment("~", current_lhs, rhs, pos)?,
|
||||||
let lhs_copy = current_lhs.clone();
|
|
||||||
Expr::Assignment(
|
|
||||||
Box::new(current_lhs),
|
|
||||||
Box::new(Expr::FunctionCall(
|
|
||||||
"~".into(),
|
|
||||||
vec![lhs_copy, rhs],
|
|
||||||
None,
|
|
||||||
pos,
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
token => {
|
token => {
|
||||||
return Err(ParseError::new(
|
return Err(ParseError::new(
|
||||||
PERR::UnknownOperator(token.syntax().into()),
|
PERR::UnknownOperator(token.syntax().into()),
|
||||||
@ -1591,8 +1678,8 @@ fn parse_var<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseE
|
|||||||
match input.peek() {
|
match input.peek() {
|
||||||
Some(&(Token::Equals, _)) => {
|
Some(&(Token::Equals, _)) => {
|
||||||
input.next();
|
input.next();
|
||||||
let initializer = parse_expr(input)?;
|
let init_value = parse_expr(input)?;
|
||||||
Ok(Stmt::Let(name, Some(Box::new(initializer)), pos))
|
Ok(Stmt::Let(name, Some(Box::new(init_value)), pos))
|
||||||
}
|
}
|
||||||
_ => Ok(Stmt::Let(name, None, 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())),
|
None => return Err(ParseError::new(PERR::MissingLeftBrace, Position::eof())),
|
||||||
}
|
}
|
||||||
|
|
||||||
input.next();
|
let pos = input.next().unwrap().1;
|
||||||
|
|
||||||
let mut statements = Vec::new();
|
let mut statements = Vec::new();
|
||||||
|
|
||||||
@ -1633,7 +1720,7 @@ fn parse_block<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Pars
|
|||||||
match input.peek() {
|
match input.peek() {
|
||||||
Some(&(Token::RightBrace, _)) => {
|
Some(&(Token::RightBrace, _)) => {
|
||||||
input.next();
|
input.next();
|
||||||
Ok(Stmt::Block(statements))
|
Ok(Stmt::Block(statements, pos))
|
||||||
}
|
}
|
||||||
Some(&(_, pos)) => Err(ParseError::new(
|
Some(&(_, pos)) => Err(ParseError::new(
|
||||||
PERR::MissingRightBrace("end of block".into()),
|
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() {
|
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())),
|
||||||
@ -1723,10 +1810,24 @@ fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseE
|
|||||||
Some((Token::RightParen, _)) => break,
|
Some((Token::RightParen, _)) => break,
|
||||||
Some((Token::Comma, _)) => (),
|
Some((Token::Comma, _)) => (),
|
||||||
Some((Token::Identifier(s), _)) => {
|
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)?;
|
let body = parse_block(input)?;
|
||||||
|
|
||||||
Ok(FnDef {
|
Ok(FnDef {
|
||||||
name: name,
|
name: name.into(),
|
||||||
params: params,
|
params,
|
||||||
body: Box::new(body),
|
body,
|
||||||
pos: pos,
|
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 statements = Vec::new();
|
||||||
let mut functions = 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> {
|
pub fn parse<'a>(
|
||||||
parse_top_level(input)
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
|
optimize_ast: bool,
|
||||||
|
) -> Result<AST, ParseError> {
|
||||||
|
parse_top_level(input, optimize_ast)
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
use std::error::Error;
|
//! Module containing error definitions for the evaluation process.
|
||||||
|
|
||||||
use crate::any::Dynamic;
|
use crate::any::Dynamic;
|
||||||
use crate::error::ParseError;
|
use crate::error::ParseError;
|
||||||
use crate::parser::Position;
|
use crate::parser::Position;
|
||||||
|
use std::{error::Error, fmt};
|
||||||
|
|
||||||
/// Evaluation result.
|
/// Evaluation result.
|
||||||
///
|
///
|
||||||
@ -19,6 +20,8 @@ pub enum EvalAltResult {
|
|||||||
ErrorFunctionArgsMismatch(String, usize, usize, Position),
|
ErrorFunctionArgsMismatch(String, usize, usize, Position),
|
||||||
/// Non-boolean operand encountered for boolean operator. Wrapped value is the operator.
|
/// Non-boolean operand encountered for boolean operator. Wrapped value is the operator.
|
||||||
ErrorBooleanArgMismatch(String, Position),
|
ErrorBooleanArgMismatch(String, Position),
|
||||||
|
/// Non-character value encountered where a character is required.
|
||||||
|
ErrorCharMismatch(Position),
|
||||||
/// Array access out-of-bounds.
|
/// Array access out-of-bounds.
|
||||||
/// Wrapped values are the current number of elements in the array and the index number.
|
/// Wrapped values are the current number of elements in the array and the index number.
|
||||||
ErrorArrayBounds(usize, i64, Position),
|
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.
|
/// Wrapped values are the current number of characters in the string and the index number.
|
||||||
ErrorStringBounds(usize, i64, Position),
|
ErrorStringBounds(usize, i64, Position),
|
||||||
/// Trying to index into a type that is not an array and not a string.
|
/// 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`.
|
/// Trying to index into an array or string with an index that is not `i64`.
|
||||||
ErrorIndexExpr(Position),
|
ErrorIndexExpr(Position),
|
||||||
/// The guard expression in an `if` statement does not return a boolean value.
|
/// 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.
|
/// Error reading from a script file. Wrapped value is the path of the script file.
|
||||||
ErrorReadingScriptFile(String, std::io::Error),
|
ErrorReadingScriptFile(String, std::io::Error),
|
||||||
/// Inappropriate member access.
|
/// Inappropriate member access.
|
||||||
ErrorDotExpr(Position),
|
ErrorDotExpr(String, Position),
|
||||||
/// Arithmetic error encountered. Wrapped value is the error message.
|
/// Arithmetic error encountered. Wrapped value is the error message.
|
||||||
ErrorArithmetic(String, Position),
|
ErrorArithmetic(String, Position),
|
||||||
/// Run-time error encountered. Wrapped value is the error message.
|
/// 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"
|
"Function call with wrong number of arguments"
|
||||||
}
|
}
|
||||||
Self::ErrorBooleanArgMismatch(_, _) => "Boolean operator expects boolean operands",
|
Self::ErrorBooleanArgMismatch(_, _) => "Boolean operator expects boolean operands",
|
||||||
|
Self::ErrorCharMismatch(_) => "Character expected",
|
||||||
Self::ErrorIndexExpr(_) => "Indexing into an array or string expects an integer index",
|
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 => {
|
Self::ErrorArrayBounds(_, index, _) if *index < 0 => {
|
||||||
"Array access expects non-negative index"
|
"Array access expects non-negative index"
|
||||||
}
|
}
|
||||||
@ -84,7 +90,7 @@ impl Error for EvalAltResult {
|
|||||||
}
|
}
|
||||||
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",
|
||||||
Self::ErrorArithmetic(_, _) => "Arithmetic error",
|
Self::ErrorArithmetic(_, _) => "Arithmetic error",
|
||||||
Self::ErrorRuntime(_, _) => "Runtime error",
|
Self::ErrorRuntime(_, _) => "Runtime error",
|
||||||
Self::LoopBreak => "[Not Error] Breaks out of loop",
|
Self::LoopBreak => "[Not Error] Breaks out of loop",
|
||||||
@ -97,23 +103,25 @@ impl Error for EvalAltResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for EvalAltResult {
|
impl fmt::Display for EvalAltResult {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let desc = self.description();
|
let desc = self.description();
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Self::ErrorFunctionNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos),
|
Self::ErrorFunctionNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos),
|
||||||
Self::ErrorVariableNotFound(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::ErrorIndexExpr(pos) => write!(f, "{} ({})", desc, pos),
|
||||||
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::ErrorMismatchOutputType(s, pos) => write!(f, "{}: {} ({})", desc, s, pos),
|
Self::ErrorMismatchOutputType(s, pos) => write!(f, "{}: {} ({})", desc, s, pos),
|
||||||
Self::ErrorDotExpr(pos) => write!(f, "{} ({})", desc, pos),
|
Self::ErrorDotExpr(s, pos) if !s.is_empty() => write!(f, "{} {} ({})", desc, s, pos),
|
||||||
Self::ErrorArithmetic(s, pos) => write!(f, "{}: {} ({})", desc, s, pos),
|
Self::ErrorDotExpr(_, pos) => write!(f, "{} ({})", desc, pos),
|
||||||
Self::ErrorRuntime(s, pos) if s.is_empty() => write!(f, "{} ({})", desc, pos),
|
Self::ErrorArithmetic(s, pos) => write!(f, "{} ({})", s, pos),
|
||||||
Self::ErrorRuntime(s, pos) => write!(f, "{}: {} ({})", desc, s, pos),
|
Self::ErrorRuntime(s, pos) => {
|
||||||
|
write!(f, "{} ({})", if s.is_empty() { desc } else { s }, pos)
|
||||||
|
}
|
||||||
Self::LoopBreak => write!(f, "{}", desc),
|
Self::LoopBreak => write!(f, "{}", desc),
|
||||||
Self::Return(_, pos) => write!(f, "{} ({})", desc, pos),
|
Self::Return(_, pos) => write!(f, "{} ({})", desc, pos),
|
||||||
Self::ErrorReadingScriptFile(filename, err) => {
|
Self::ErrorReadingScriptFile(filename, err) => {
|
||||||
@ -128,6 +136,9 @@ impl std::fmt::Display for EvalAltResult {
|
|||||||
Self::ErrorBooleanArgMismatch(op, pos) => {
|
Self::ErrorBooleanArgMismatch(op, pos) => {
|
||||||
write!(f, "{} operator expects boolean operands ({})", 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 => {
|
Self::ErrorArrayBounds(_, index, pos) if *index < 0 => {
|
||||||
write!(f, "{}: {} < 0 ({})", desc, index, pos)
|
write!(f, "{}: {} < 0 ({})", desc, index, pos)
|
||||||
}
|
}
|
||||||
@ -161,3 +172,62 @@ impl From<ParseError> for EvalAltResult {
|
|||||||
Self::ErrorParsing(err)
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
47
src/scope.rs
47
src/scope.rs
@ -1,25 +1,32 @@
|
|||||||
|
//! Module that defines the `Scope` type representing a function call-stack scope.
|
||||||
|
|
||||||
use crate::any::{Any, Dynamic};
|
use crate::any::{Any, Dynamic};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
/// 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.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
|
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||||
/// use rhai::{Engine, Scope};
|
/// use rhai::{Engine, Scope};
|
||||||
///
|
///
|
||||||
/// let mut engine = Engine::new();
|
/// let mut engine = Engine::new();
|
||||||
/// let mut my_scope = Scope::new();
|
/// let mut my_scope = Scope::new();
|
||||||
///
|
///
|
||||||
/// assert!(engine.eval_with_scope::<()>(&mut my_scope, "let x = 5;").is_ok());
|
/// engine.eval_with_scope::<()>(&mut my_scope, false, "let x = 5;")?;
|
||||||
/// assert_eq!(engine.eval_with_scope::<i64>(&mut my_scope, "x + 1").unwrap(), 6);
|
///
|
||||||
|
/// 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,
|
/// 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(Vec<(String, Dynamic)>);
|
pub struct Scope<'a>(Vec<(Cow<'a, str>, Dynamic)>);
|
||||||
|
|
||||||
impl Scope {
|
impl<'a> Scope<'a> {
|
||||||
/// Create a new Scope.
|
/// Create a new Scope.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self(Vec::new())
|
Self(Vec::new())
|
||||||
@ -36,18 +43,18 @@ impl Scope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Add (push) a new variable to the Scope.
|
/// Add (push) a new variable to the Scope.
|
||||||
pub fn push<T: Any>(&mut self, key: String, value: T) {
|
pub fn push<K: Into<Cow<'a, str>>, T: Any>(&mut self, key: K, value: T) {
|
||||||
self.0.push((key, Box::new(value)));
|
self.0.push((key.into(), Box::new(value)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add (push) a new variable to the Scope.
|
/// Add (push) a new variable to the Scope.
|
||||||
pub(crate) fn push_dynamic(&mut self, key: String, value: Dynamic) {
|
pub(crate) fn push_dynamic<K: Into<Cow<'a, str>>>(&mut self, key: K, value: Dynamic) {
|
||||||
self.0.push((key, value));
|
self.0.push((key.into(), value));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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, Dynamic)> {
|
||||||
self.0.pop()
|
self.0.pop().map(|(key, value)| (key.to_string(), value))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Truncate (rewind) the Scope to a previous size.
|
/// Truncate (rewind) the Scope to a previous size.
|
||||||
@ -56,13 +63,13 @@ impl Scope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 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, String, Dynamic)> {
|
pub fn get(&self, key: &str) -> Option<(usize, &str, 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(|(_, (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.
|
/// Get the value of a variable in the Scope, starting from the last.
|
||||||
@ -97,20 +104,26 @@ impl Scope {
|
|||||||
self.0
|
self.0
|
||||||
.iter()
|
.iter()
|
||||||
.rev() // Always search a Scope in reverse order
|
.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.
|
/// Get a mutable iterator to variables in the Scope.
|
||||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &mut Dynamic)> {
|
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &mut Dynamic)> {
|
||||||
self.0
|
self.0
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.rev() // Always search a Scope in reverse order
|
.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 {
|
impl<'a, K> std::iter::Extend<(K, Dynamic)> for Scope<'a>
|
||||||
fn extend<T: IntoIterator<Item = (String, Dynamic)>>(&mut self, iter: T) {
|
where
|
||||||
self.0.extend(iter);
|
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)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,9 @@ use rhai::{Engine, EvalAltResult};
|
|||||||
fn test_engine_call_fn() -> Result<(), EvalAltResult> {
|
fn test_engine_call_fn() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
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);
|
assert_eq!(result, 126);
|
||||||
|
|
||||||
|
43
tests/math.rs
Normal file
43
tests/math.rs
Normal 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(())
|
||||||
|
}
|
@ -5,8 +5,8 @@ fn test_unary_minus() -> Result<(), EvalAltResult> {
|
|||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
assert_eq!(engine.eval::<i64>("let x = -5; x")?, -5);
|
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>("fn neg(x) { -x } neg(5)")?, -5);
|
||||||
assert_eq!(engine.eval::<i64>("5 - -(-5)")?, 0);
|
assert_eq!(engine.eval::<i64>("5 - -+++--+-5")?, 0);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -5,12 +5,15 @@ fn test_var_scope() -> Result<(), EvalAltResult> {
|
|||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
|
|
||||||
engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5")?;
|
engine.eval_with_scope::<()>(&mut scope, false, "let x = 4 + 5")?;
|
||||||
assert_eq!(engine.eval_with_scope::<i64>(&mut scope, "x")?, 9);
|
assert_eq!(engine.eval_with_scope::<i64>(&mut scope, false, "x")?, 9);
|
||||||
engine.eval_with_scope::<()>(&mut scope, "x = x + 1; x = x + 2;")?;
|
engine.eval_with_scope::<()>(&mut scope, false, "x = x + 1; x = x + 2;")?;
|
||||||
assert_eq!(engine.eval_with_scope::<i64>(&mut scope, "x")?, 12);
|
assert_eq!(engine.eval_with_scope::<i64>(&mut scope, false, "x")?, 12);
|
||||||
assert_eq!(engine.eval_with_scope::<()>(&mut scope, "{let x = 3}")?, ());
|
assert_eq!(
|
||||||
assert_eq!(engine.eval_with_scope::<i64>(&mut scope, "x")?, 12);
|
engine.eval_with_scope::<()>(&mut scope, false, "{let x = 3}")?,
|
||||||
|
()
|
||||||
|
);
|
||||||
|
assert_eq!(engine.eval_with_scope::<i64>(&mut scope, false, "x")?, 12);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -25,24 +28,18 @@ fn test_scope_eval() -> Result<(), EvalAltResult> {
|
|||||||
// Then push some initialized variables into the state
|
// Then push some initialized variables into the state
|
||||||
// NOTE: Remember the default numbers used by Rhai are i64 and f64.
|
// 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.
|
// Better stick to them or it gets hard to work with other variables in the script.
|
||||||
scope.push("y".into(), 42_i64);
|
scope.push("y", 42_i64);
|
||||||
scope.push("z".into(), 999_i64);
|
scope.push("z", 999_i64);
|
||||||
|
|
||||||
// First invocation
|
// First invocation
|
||||||
engine
|
engine
|
||||||
.eval_with_scope::<()>(
|
.eval_with_scope::<()>(&mut scope, false, " let x = 4 + 5 - y + z; y = 1;")
|
||||||
&mut scope,
|
|
||||||
r"
|
|
||||||
let x = 4 + 5 - y + z;
|
|
||||||
y = 1;
|
|
||||||
",
|
|
||||||
)
|
|
||||||
.expect("y and z not found?");
|
.expect("y and z not found?");
|
||||||
|
|
||||||
// Second invocation using the same state
|
// Second invocation using the same state
|
||||||
if let Ok(result) = engine.eval_with_scope::<i64>(&mut scope, "x") {
|
let result = engine.eval_with_scope::<i64>(&mut scope, false, "x")?;
|
||||||
println!("result: {}", result); // should print 966
|
|
||||||
}
|
println!("result: {}", result); // should print 966
|
||||||
|
|
||||||
// Variable y is changed in the script
|
// Variable y is changed in the script
|
||||||
assert_eq!(scope.get_value::<i64>("y").unwrap(), 1);
|
assert_eq!(scope.get_value::<i64>("y").unwrap(), 1);
|
||||||
|
Loading…
Reference in New Issue
Block a user