diff --git a/Cargo.toml b/Cargo.toml index 4ba4d03e..54678e04 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "rhai" -version = "0.9.1" +version = "0.10.1" edition = "2018" -authors = ["Jonathan Turner", "Lukáš Hozda"] +authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"] description = "Embedded scripting for Rust" homepage = "https://github.com/jonathandturner/rhai" repository = "https://github.com/jonathandturner/rhai" diff --git a/README.md b/README.md index 98f4d748..6f9c78ae 100644 --- a/README.md +++ b/README.md @@ -11,57 +11,76 @@ Rhai's current feature set: * Support for overloaded functions * No additional dependencies -**Note:** Currently, the version is 0.9.1, so the language and APIs may change before they stabilize.* +**Note:** Currently, the version is 0.10.0-alpha1, so the language and APIs may change before they stabilize.* ## Installation -You can install Rhai using crates by adding this line to your dependences: +You can install Rhai using crates by adding this line to your dependencies: ```toml [dependencies] -rhai = "0.8.1" +rhai = "0.10.0" ``` +or simply: + +```toml +[dependencies] +rhai = "*" +``` + +to use the latest version. + +Beware that in order to use pre-releases (alpha and beta) you need to specify the exact version in your `Cargo.toml`. + ## Related Other cool projects to check out: + * [ChaiScript](http://chaiscript.com/) - A strong inspiration for Rhai. An embedded scripting language for C++ that I helped created many moons ago, now being lead by my cousin. * You can also check out the list of [scripting languages for Rust](https://github.com/rust-unofficial/awesome-rust#scripting) on [awesome-rust](https://github.com/rust-unofficial/awesome-rust) ## Examples + The repository contains several examples in the `examples` folder: -- `arrays_and_structs` demonstrates registering a new type to Rhai and the usage of arrays on it -- `custom_types_and_methods` shows how to register a type and methods for it -- `hello` simple example that evaluates an expression and prints the result -- `reuse_scope` evaluates two pieces of code in separate runs, but using a common scope -- `rhai_runner` runs each filename passed to it as a Rhai script -- `simple_fn` shows how to register a Rust function to a Rhai engine -- `repl` a simple REPL, see source code for what it can do at the moment + +* `arrays_and_structs` demonstrates registering a new type to Rhai and the usage of arrays on it +* `custom_types_and_methods` shows how to register a type and methods for it +* `hello` simple example that evaluates an expression and prints the result +* `reuse_scope` evaluates two pieces of code in separate runs, but using a common scope +* `rhai_runner` runs each filename passed to it as a Rhai script +* `simple_fn` shows how to register a Rust function to a Rhai engine +* `repl` a simple REPL, see source code for what it can do at the moment Examples can be run with the following command: + ```bash cargo run --example name ``` ## Example Scripts + We also have a few examples scripts that showcase Rhai's features, all stored in the `scripts` folder: -- `array.rhai` - arrays in Rhai -- `assignment.rhai` - variable declarations -- `comments.rhai` - just comments -- `function_decl1.rhai` - a function without parameters -- `function_decl2.rhai` - a function with two parameters -- `function_decl3.rhai` - a function with many parameters -- `if1.rhai` - if example -- `loop.rhai` - endless loop in Rhai, this example emulates a do..while cycle -- `op1.rhai` - just a simple addition -- `op2.rhai` - simple addition and multiplication -- `op3.rhai` - change evaluation order with parenthesis -- `speed_test.rhai` - a simple program to measure the speed of Rhai's interpreter -- `string.rhai`- string operations -- `while.rhai` - while loop + +* `array.rhai` - arrays in Rhai +* `assignment.rhai` - variable declarations +* `comments.rhai` - just comments +* `for1.rhai` - for loops +* `function_decl1.rhai` - a function without parameters +* `function_decl2.rhai` - a function with two parameters +* `function_decl3.rhai` - a function with many parameters +* `if1.rhai` - if example +* `loop.rhai` - endless loop in Rhai, this example emulates a do..while cycle +* `op1.rhai` - just a simple addition +* `op2.rhai` - simple addition and multiplication +* `op3.rhai` - change evaluation order with parenthesis +* `speed_test.rhai` - a simple program to measure the speed of Rhai's interpreter +* `string.rhai`- string operations +* `while.rhai` - while loop To run the scripts, you can either make your own tiny program, or make use of the `rhai_runner` example program: + ```bash cargo run --example rhai_runner scripts/any_script.rhai ``` @@ -89,18 +108,70 @@ You can also evaluate a script file: if let Ok(result) = engine.eval_file::("hello_world.rhai") { ... } ``` +If you want to repeatedly evaluate a script, you can compile it first into an AST form: + +```rust +// Compile to an AST and store it for later evaluations +let ast = Engine::compile("40 + 2").unwrap(); + +for _ in 0..42 { + if let Ok(result) = engine.eval_ast::(&ast) { + println!("Answer: {}", result); // prints 42 + } +} +``` + +Compiling a script file into AST is also supported: + +```rust +let ast = Engine::compile_file("hello_world.rhai").unwrap(); +``` + +# Values and types + +The following primitive types are supported natively: + +* Integer: `i32`, `u32`, `i64` (default), `u64` +* Floating-point: `f32`, `f64` (default) +* Character: `char` +* Boolean: `bool` +* Array: `rhai::Array` +* Dynamic (i.e. can be anything): `rhai::Dynamic` + +# Value conversions + +All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different; you cannot even add them together. + +There is a `to_float` function to convert a supported number to an `f64`, and a `to_int` function to convert a supported number to `i64` and that's about it. For other conversions you can register your own conversion functions. + +```rust +let x = 42; +let y = x * 100.0; // error: cannot multiply i64 with f64 +let y = x.to_float() * 100.0; // works +let z = y.to_int() + x; // works + +let c = 'X'; // character +print("c is '" + c + "' and its code is " + c.to_int()); +``` + # Working with functions Rhai's scripting engine is very lightweight. It gets its ability from the functions in your program. To call these functions, you need to register them with the scripting engine. ```rust extern crate rhai; -use rhai::{Engine, RegisterFn}; +use rhai::{Dynamic, Engine, RegisterFn}; +// Normal function fn add(x: i64, y: i64) -> i64 { x + y } +// Function that returns a Dynamic value +fn get_an_any() -> Dynamic { + Box::new(42_i64) +} + fn main() { let mut engine = Engine::new(); @@ -109,6 +180,25 @@ fn main() { if let Ok(result) = engine.eval::("add(40, 2)") { println!("Answer: {}", result); // prints 42 } + + // Functions that return Dynamic values must use register_dynamic_fn() + engine.register_dynamic_fn("get_an_any", get_an_any); + + if let Ok(result) = engine.eval::("get_an_any()") { + println!("Answer: {}", result); // prints 42 + } +} +``` + +To return a `Dynamic` value, simply `Box` it and return it. + +```rust +fn decide(yes_no: bool) -> Dynamic { + if yes_no { + Box::new(42_i64) + } else { + Box::new("hello world!".to_string()) // remember &str is not supported + } } ``` @@ -137,6 +227,19 @@ fn main() { You can also see in this example how you can register multiple functions (or in this case multiple instances of the same function) to the same name in script. This gives you a way to overload functions and call the correct one, based on the types of the arguments, from your script. +# Override built-in functions + +Any similarly-named function defined in a script overrides any built-in function. + +```rust +// Override the built-in function 'to_int' +fn to_int(num) { + print("Ha! Gotcha!" + num); +} + +print(to_int(123)); // what will happen? +``` + # Custom types and methods Here's an more complete example of working with Rust. First the example, then we'll break it into parts: @@ -184,6 +287,7 @@ struct TestStruct { ``` Next, we create a few methods that we'll later use in our scripts. Notice that we register our custom type with the engine. + ```rust impl TestStruct { fn update(&mut self) { @@ -210,12 +314,28 @@ engine.register_fn("new_ts", TestStruct::new); ``` Finally, we call our script. The script can see the function and method we registered earlier. We need to get the result back out from script land just as before, this time casting to our custom struct type. + ```rust if let Ok(result) = engine.eval::("let x = new_ts(); x.update(); x") { println!("result: {}", result.x); // prints 1001 } ``` +In fact, any function with a first argument (either by copy or via a `&mut` reference) can be used as a method-call on that type because internally they are the same thing: methods on a type is implemented as a functions taking an first argument. + +```rust +fn foo(ts: &mut TestStruct) -> i64 { + ts.x +} + +engine.register_fn("foo", foo); + +if let Ok(result) = engine.eval::("let x = new_ts(); x.foo()") { + println!("result: {}", result); // prints 1 +} +``` + + # Getters and setters Similarly, you can work with members of your custom types. This works by registering a 'get' or a 'set' function for working with your struct. @@ -254,6 +374,23 @@ if let Ok(result) = engine.eval::("let a = new_ts(); a.x = 500; a.x") { } ``` +### WARNING: Gotcha's with Getters + +When you _get_ a property, the value is cloned. Any update to it downstream will **NOT** be reflected back to the custom type. + +This can introduce subtle bugs. For example: + +```rust +fn change(s) { + s = 42; +} + +let a = new_ts(); +a.x = 500; +a.x.change(); // Only a COPY of 'a.x' is changed. 'a.x' is NOT changed. +a.x == 500; +``` + # Maintaining state By default, Rhai treats each engine invocation as a fresh one, persisting only the functions that have been defined but no top-level state. This gives each one a fairly clean starting place. Sometimes, though, you want to continue using the same top-level state from one invocation to the next. @@ -284,35 +421,64 @@ fn main() { let x = 3; ``` -## Operators +## Numeric operators ```rust let x = (1 + 2) * (6 - 4) / 2; ``` +## Comparison operators + +You can compare most values of the same data type. If you compare two values of _different_ data types, the result is always `false`. + +```rust +42 == 42; // true +42 > 42; // false +"hello" > "foo"; // true +"42" == 42; // false +42 == 42.0; // false - i64 is different from f64 +``` + +## Boolean operators + +Double boolean operators `&&` and `||` _short-circuit_, meaning that the second operand will not be evaluated if the first one already proves the condition wrong. + +Single boolean operators `&` and `|` always evaluate both operands. + +```rust +this() || that(); // that() is not evaluated if this() is true +this() && that(); // that() is not evaluated if this() is false + +this() | that(); // both this() and that() are evaluated +this() & that(); // both this() and that() are evaluated +``` + ## If + ```rust if true { - print("it's true!"); -} -else { + print("It's true!"); +} else if true { + print("It's true again!"); +} else { print("It's false!"); } ``` ## While + ```rust let x = 10; + while x > 0 { print(x); - if x == 5 { - break; - } + if x == 5 { break; } x = x - 1; } ``` ## Loop + ```rust let x = 10; @@ -344,15 +510,83 @@ fn add(x, y) { print(add(2, 3)) ``` + ## Arrays You can create arrays of values, and then access them with numeric indices. -```rust -let y = [1, 2, 3]; -y[1] = 5; +The following standard functions operate on arrays: -print(y[1]); +* `push` - inserts an element at the end +* `pop` - removes the last element and returns it (() if empty) +* `shift` - removes the first element and returns it (() if empty) +* `len` - returns the number of elements +* `pad` - pads the array with an element until a specified length +* `clear` - empties the array +* `truncate` - cuts off the array at exactly a specified length (discarding all subsequent elements) + +```rust +let y = [1, 2, 3]; // 3 elements +y[1] = 42; + +print(y[1]); // prints 42 + +let foo = [1, 2, 3][0]; // a syntax error for now - cannot index into literals +let foo = ts.list[0]; // a syntax error for now - cannot index into properties +let foo = y[0]; // this works + +y.push(4); // 4 elements +y.push(5); // 5 elements + +print(y.len()); // prints 5 + +let first = y.shift(); // remove the first element, 4 elements remaining +first == 1; + +let last = y.pop(); // remove the last element, 3 elements remaining +last == 5; + +print(y.len()); // prints 3 + +y.pad(10, "hello"); // pad the array up to 10 elements + +print(y.len()); // prints 10 + +y.truncate(5); // truncate the array to 5 elements + +print(y.len()); // prints 5 + +y.clear(); // empty the array + +print(y.len()); // prints 0 +``` + +`push` and `pad` are only defined for standard built-in types. If you want to use them with +your own custom type, you need to define a specific override: + +```rust +engine.register_fn("push", + |list: &mut Array, item: MyType| list.push(Box::new(item)) +); +``` + +The type of a Rhai array is `rhai::Array`. + +## For loops + +```rust +let array = [1, 3, 5, 7, 9, 42]; + +for x in array { + print(x); + if x == 42 { break; } +} + +// The range function allows iterating from first..last-1 +for x in range(0,50) { + print(x); + if x == 42 { break; } +} ``` ## Members and methods @@ -368,6 +602,80 @@ a.update(); ```rust let name = "Bob"; let middle_initial = 'C'; +let last = 'Davis'; + +let full_name = name + " " + middle_initial + ". " + last; +full_name == "Bob C. Davis"; + +// String building with different types +let age = 42; +let record = full_name + ": age " + age; +record == "Bob C. Davis: age 42"; + +// Strings can be indexed to get a character +let c = record[4]; +c == 'C'; + +let c = "foo"[0]; // a syntax error for now - cannot index into literals +let c = ts.s[0]; // a syntax error for now - cannot index into properties +let c = record[0]; // this works + +// Unlike Rust, Rhai strings can be modified +record[4] = 'Z'; +record == "Bob Z. Davis: age 42"; +``` + +The following standard functions operate on strings: + +* `len` - returns the number of characters (not number of bytes) in the string +* `pad` - pads the string with an character until a specified number of characters +* `clear` - empties the string +* `truncate` - cuts off the string at exactly a specified number of characters +* `contains` - checks if a certain character or sub-string occurs in the string +* `replace` - replaces a substring with another +* `trim` - trims the string + +```rust +let full_name == " Bob C. Davis "; +full_name.len() == 14; + +full_name.trim(); +full_name.len() == 12; + +full_name.pad(15, '$'); +full_name.len() == 15; +full_name == "Bob C. Davis$$$"; + +full_name.truncate(6); +full_name.len() == 6; +full_name == "Bob C."; + +full_name.replace("Bob", "John"); +full_name.len() == 7; +full_name = "John C."; + +full_name.contains('C') == true; +full_name.contains("John") == true; + +full_name.clear(); +full_name.len() == 0; +``` + +## Print and Debug + +```rust +print("hello"); // prints hello to stdout +print(1 + 2 + 3); // prints 6 to stdout +print("hello" + 42); // prints hello42 to stdout +debug("world!"); // prints "world!" to stdout using debug formatting +``` + +### Overriding Print and Debug with Callback functions + +```rust +// Any function that takes a &str argument can be used to override print and debug +engine.on_print(|x: &str| println!("hello: {}", x)); +engine.on_debug(|x: &str| println!("DEBUG: {}", x)); ``` ## Comments @@ -412,6 +720,7 @@ The `+=` operator can also be used to build strings: ```rust let my_str = "abc"; my_str += "ABC"; +my_str += 12345; -my_str == "abcABC" +my_str == "abcABC12345" ``` diff --git a/TODO b/TODO index b5b4fdf5..8ae9f602 100644 --- a/TODO +++ b/TODO @@ -1,11 +1,7 @@ pre 1.0: - - binary ops - basic threads - stdlib - - floats - - REPL (consume functions) 1.0: - decide on postfix/prefix operators - - ranges, rustic for-loop - advanced threads + actors - more literals diff --git a/examples/repl.rs b/examples/repl.rs index 8a75c3d5..1990afed 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -1,34 +1,19 @@ use rhai::{Engine, RegisterFn, Scope}; -use std::fmt::Display; use std::io::{stdin, stdout, Write}; use std::process::exit; -fn showit(x: &mut T) -> () { - println!("{}", x) -} - -fn quit() { - exit(0); -} - pub fn main() { let mut engine = Engine::new(); let mut scope = Scope::new(); - engine.register_fn("print", showit as fn(x: &mut i32) -> ()); - engine.register_fn("print", showit as fn(x: &mut i64) -> ()); - engine.register_fn("print", showit as fn(x: &mut u32) -> ()); - engine.register_fn("print", showit as fn(x: &mut u64) -> ()); - engine.register_fn("print", showit as fn(x: &mut f32) -> ()); - engine.register_fn("print", showit as fn(x: &mut f64) -> ()); - engine.register_fn("print", showit as fn(x: &mut bool) -> ()); - engine.register_fn("print", showit as fn(x: &mut String) -> ()); - engine.register_fn("exit", quit); + engine.register_fn("exit", || exit(0)); loop { print!("> "); + let mut input = String::new(); stdout().flush().expect("couldn't flush stdout"); + if let Err(e) = stdin().read_line(&mut input) { println!("input error: {}", e); } diff --git a/examples/simple_fn.rs b/examples/simple_fn.rs index ccef105e..4be5e5b6 100644 --- a/examples/simple_fn.rs +++ b/examples/simple_fn.rs @@ -1,13 +1,14 @@ use rhai::{Engine, RegisterFn}; -fn add(x: i64, y: i64) -> i64 { - x + y -} - fn main() { let mut engine = Engine::new(); + fn add(x: i64, y: i64) -> i64 { + x + y + } + engine.register_fn("add", add); + if let Ok(result) = engine.eval::("add(40, 2)") { println!("Answer: {}", result); // prints 42 } diff --git a/src/any.rs b/src/any.rs index b6913517..c3c969cb 100644 --- a/src/any.rs +++ b/src/any.rs @@ -1,12 +1,15 @@ use std::any::{type_name, Any as StdAny, TypeId}; use std::fmt; +pub type Variant = dyn Any; +pub type Dynamic = Box; + pub trait Any: StdAny { fn type_id(&self) -> TypeId; fn type_name(&self) -> String; - fn box_clone(&self) -> Box; + fn into_dynamic(&self) -> Dynamic; /// This type may only be implemented by `rhai`. #[doc(hidden)] @@ -27,7 +30,7 @@ where } #[inline] - fn box_clone(&self) -> Box { + fn into_dynamic(&self) -> Dynamic { Box::new(self.clone()) } @@ -36,15 +39,15 @@ where } } -impl dyn Any { +impl Variant { //#[inline] - // fn box_clone(&self) -> Box { - // Any::box_clone(self) + // fn into_dynamic(&self) -> Box { + // Any::into_dynamic(self) // } #[inline] pub fn is(&self) -> bool { let t = TypeId::of::(); - let boxed = ::type_id(self); + let boxed = ::type_id(self); t == boxed } @@ -52,7 +55,7 @@ impl dyn Any { #[inline] pub fn downcast_ref(&self) -> Option<&T> { if self.is::() { - unsafe { Some(&*(self as *const dyn Any as *const T)) } + unsafe { Some(&*(self as *const Variant as *const T)) } } else { None } @@ -61,20 +64,20 @@ impl dyn Any { #[inline] pub fn downcast_mut(&mut self) -> Option<&mut T> { if self.is::() { - unsafe { Some(&mut *(self as *mut dyn Any as *mut T)) } + unsafe { Some(&mut *(self as *mut Variant as *mut T)) } } else { None } } } -impl Clone for Box { +impl Clone for Dynamic { fn clone(&self) -> Self { - Any::box_clone(self.as_ref() as &dyn Any) + Any::into_dynamic(self.as_ref()) } } -impl fmt::Debug for dyn Any { +impl fmt::Debug for Variant { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.pad("?") } @@ -84,11 +87,11 @@ pub trait AnyExt: Sized { fn downcast(self) -> Result, Self>; } -impl AnyExt for Box { +impl AnyExt for Dynamic { fn downcast(self) -> Result, Self> { if self.is::() { unsafe { - let raw: *mut dyn Any = Box::into_raw(self); + let raw: *mut Variant = Box::into_raw(self); Ok(Box::from_raw(raw as *mut T)) } } else { diff --git a/src/api.rs b/src/api.rs new file mode 100644 index 00000000..f1c32155 --- /dev/null +++ b/src/api.rs @@ -0,0 +1,176 @@ +use crate::any::{Any, AnyExt, Dynamic}; +use crate::engine::{Engine, EvalAltResult, FnIntExt, FnSpec, Scope}; +use crate::parser::{lex, parse, ParseError, Position, AST}; +use std::sync::Arc; + +impl Engine { + /// Compile a string into an AST + pub fn compile(input: &str) -> Result { + let tokens = lex(input); + parse(&mut tokens.peekable()) + } + + /// Compile a file into an AST + pub fn compile_file(filename: &str) -> Result { + use std::fs::File; + use std::io::prelude::*; + + let mut f = File::open(filename) + .map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err))?; + + let mut contents = String::new(); + + f.read_to_string(&mut contents) + .map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err)) + .and_then(|_| Self::compile(&contents).map_err(EvalAltResult::ErrorParsing)) + } + + /// Evaluate a file + pub fn eval_file(&mut self, filename: &str) -> Result { + use std::fs::File; + use std::io::prelude::*; + + let mut f = File::open(filename) + .map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err))?; + + let mut contents = String::new(); + + f.read_to_string(&mut contents) + .map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err)) + .and_then(|_| self.eval::(&contents)) + } + + /// Evaluate a string + pub fn eval(&mut self, input: &str) -> Result { + let mut scope = Scope::new(); + self.eval_with_scope(&mut scope, input) + } + + /// Evaluate a string with own scope + pub fn eval_with_scope( + &mut self, + scope: &mut Scope, + input: &str, + ) -> Result { + let ast = Self::compile(input).map_err(EvalAltResult::ErrorParsing)?; + self.eval_ast_with_scope(scope, &ast) + } + + /// Evaluate an AST + pub fn eval_ast(&mut self, ast: &AST) -> Result { + let mut scope = Scope::new(); + self.eval_ast_with_scope(&mut scope, ast) + } + + /// Evaluate an AST with own scope + pub fn eval_ast_with_scope( + &mut self, + scope: &mut Scope, + ast: &AST, + ) -> Result { + let AST(os, fns) = ast; + + fns.iter().for_each(|f| { + self.script_fns.insert( + FnSpec { + ident: f.name.clone(), + args: None, + }, + Arc::new(FnIntExt::Int(f.clone())), + ); + }); + + let result = os + .iter() + .try_fold(Box::new(()) as Dynamic, |_, o| self.eval_stmt(scope, o)); + + self.script_fns.clear(); // Clean up engine + + match result { + Err(EvalAltResult::Return(out, pos)) => Ok(*out.downcast::().map_err(|a| { + let name = self.map_type_name((*a).type_name()); + EvalAltResult::ErrorMismatchOutputType(name, pos) + })?), + + Ok(out) => Ok(*out.downcast::().map_err(|a| { + let name = self.map_type_name((*a).type_name()); + EvalAltResult::ErrorMismatchOutputType(name, Position::eof()) + })?), + + Err(err) => Err(err), + } + } + + /// Evaluate a file, but only return errors, if there are any. + /// Useful for when you don't need the result, but still need + /// to keep track of possible errors + pub fn consume_file(&mut self, filename: &str) -> Result<(), EvalAltResult> { + use std::fs::File; + use std::io::prelude::*; + + let mut f = File::open(filename) + .map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err))?; + + let mut contents = String::new(); + + f.read_to_string(&mut contents) + .map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err)) + .and_then(|_| self.consume(&contents)) + } + + /// Evaluate a string, but only return errors, if there are any. + /// Useful for when you don't need the result, but still need + /// to keep track of possible errors + pub fn consume(&mut self, input: &str) -> Result<(), EvalAltResult> { + self.consume_with_scope(&mut Scope::new(), input) + } + + /// Evaluate a string with own scope, but only return errors, if there are any. + /// Useful for when you don't need the result, but still need + /// to keep track of possible errors + pub fn consume_with_scope( + &mut self, + scope: &mut Scope, + input: &str, + ) -> Result<(), EvalAltResult> { + let tokens = lex(input); + + parse(&mut tokens.peekable()) + .map_err(|err| EvalAltResult::ErrorParsing(err)) + .and_then(|AST(ref os, ref fns)| { + for f in fns { + // FIX - Why are functions limited to 6 parameters? + if f.params.len() > 6 { + return Ok(()); + } + + self.script_fns.insert( + FnSpec { + ident: f.name.clone(), + args: None, + }, + Arc::new(FnIntExt::Int(f.clone())), + ); + } + + let val = os + .iter() + .try_fold(Box::new(()) as Dynamic, |_, o| self.eval_stmt(scope, o)) + .map(|_| ()); + + self.script_fns.clear(); // Clean up engine + + val + }) + } + + /// Overrides `on_print` + pub fn on_print(&mut self, callback: impl Fn(&str) + 'static) { + self.on_print = Box::new(callback); + } + + /// Overrides `on_debug` + pub fn on_debug(&mut self, callback: impl Fn(&str) + 'static) { + self.on_debug = Box::new(callback); + } +} diff --git a/src/builtin.rs b/src/builtin.rs new file mode 100644 index 00000000..da23abe2 --- /dev/null +++ b/src/builtin.rs @@ -0,0 +1,336 @@ +use crate::{any::Any, Array, Dynamic, Engine, RegisterDynamicFn, RegisterFn}; +use std::fmt::{Debug, Display}; +use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub}; + +macro_rules! reg_op { + ($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( + $( + $self.register_fn($x, $op as fn(x: $y, y: $y)->$y); + )* + ) +} + +macro_rules! reg_un { + ($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( + $( + $self.register_fn($x, $op as fn(x: $y)->$y); + )* + ) +} + +macro_rules! reg_cmp { + ($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( + $( + $self.register_fn($x, $op as fn(x: $y, y: $y)->bool); + )* + ) +} + +macro_rules! reg_func1 { + ($self:expr, $x:expr, $op:expr, $r:ty, $( $y:ty ),*) => ( + $( + $self.register_fn($x, $op as fn(x: $y)->$r); + )* + ) +} + +macro_rules! reg_func2x { + ($self:expr, $x:expr, $op:expr, $v:ty, $r:ty, $( $y:ty ),*) => ( + $( + $self.register_fn($x, $op as fn(x: $v, y: $y)->$r); + )* + ) +} + +macro_rules! reg_func2y { + ($self:expr, $x:expr, $op:expr, $v:ty, $r:ty, $( $y:ty ),*) => ( + $( + $self.register_fn($x, $op as fn(y: $y, x: $v)->$r); + )* + ) +} + +macro_rules! reg_func3 { + ($self:expr, $x:expr, $op:expr, $v:ty, $w:ty, $r:ty, $( $y:ty ),*) => ( + $( + $self.register_fn($x, $op as fn(x: $v, y: $w, z: $y)->$r); + )* + ) +} + +impl Engine { + /// Register the built-in library. + pub(crate) fn register_builtins(&mut self) { + fn add(x: T, y: T) -> ::Output { + x + y + } + fn sub(x: T, y: T) -> ::Output { + x - y + } + fn mul(x: T, y: T) -> ::Output { + x * y + } + fn div(x: T, y: T) -> ::Output { + x / y + } + fn neg(x: T) -> ::Output { + -x + } + fn lt(x: T, y: T) -> bool { + x < y + } + fn lte(x: T, y: T) -> bool { + x <= y + } + fn gt(x: T, y: T) -> bool { + x > y + } + fn gte(x: T, y: T) -> bool { + x >= y + } + fn eq(x: T, y: T) -> bool { + x == y + } + fn ne(x: T, y: T) -> bool { + x != y + } + fn and(x: bool, y: bool) -> bool { + x && y + } + fn or(x: bool, y: bool) -> bool { + x || y + } + fn not(x: bool) -> bool { + !x + } + fn concat(x: String, y: String) -> String { + x + &y + } + fn binary_and(x: T, y: T) -> ::Output { + x & y + } + fn binary_or(x: T, y: T) -> ::Output { + x | y + } + fn binary_xor(x: T, y: T) -> ::Output { + x ^ y + } + fn left_shift>(x: T, y: T) -> >::Output { + x.shl(y) + } + fn right_shift>(x: T, y: T) -> >::Output { + x.shr(y) + } + fn modulo>(x: T, y: T) -> >::Output { + x % y + } + fn pow_i64_i64(x: i64, y: i64) -> i64 { + x.pow(y as u32) + } + fn pow_f64_f64(x: f64, y: f64) -> f64 { + x.powf(y) + } + fn pow_f64_i64(x: f64, y: i64) -> f64 { + x.powi(y as i32) + } + fn unit_eq(_a: (), _b: ()) -> bool { + true + } + + reg_op!(self, "+", add, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64); + reg_op!(self, "-", sub, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64); + reg_op!(self, "*", mul, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64); + reg_op!(self, "/", div, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64); + + 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, ">", gt, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64, String, char); + reg_cmp!(self, ">=", gte, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64, String, char); + reg_cmp!( + self, "==", eq, i8, u8, i16, u16, i32, i64, u32, u64, bool, f32, f64, String, char + ); + reg_cmp!( + self, "!=", ne, i8, u8, i16, u16, i32, i64, u32, u64, bool, f32, f64, String, char + ); + + //reg_op!(self, "||", or, bool); + //reg_op!(self, "&&", and, bool); + reg_op!(self, "|", binary_or, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op!(self, "|", or, bool); + reg_op!(self, "&", binary_and, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op!(self, "&", and, bool); + reg_op!(self, "^", binary_xor, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op!(self, "<<", left_shift, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op!(self, ">>", right_shift, i8, u8, i16, u16); + reg_op!(self, ">>", right_shift, i32, i64, u32, u64); + reg_op!(self, "%", modulo, i8, u8, i16, u16, i32, i64, u32, u64); + self.register_fn("~", pow_i64_i64); + self.register_fn("~", pow_f64_f64); + self.register_fn("~", pow_f64_i64); + + reg_un!(self, "-", neg, i8, i16, i32, i64, f32, f64); + reg_un!(self, "!", not, bool); + + self.register_fn("+", concat); + self.register_fn("==", unit_eq); + + // self.register_fn("[]", idx); + // FIXME? Registering array lookups are a special case because we want to return boxes + // directly let ent = self.fns.entry("[]".to_string()).or_insert_with(Vec::new); + // (*ent).push(FnType::ExternalFn2(Box::new(idx))); + + // Register conversion functions + 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: i16| x as f64); + self.register_fn("to_float", |x: u16| x as f64); + self.register_fn("to_float", |x: i32| x as f64); + self.register_fn("to_float", |x: u32| x as f64); + self.register_fn("to_float", |x: i64| x as f64); + self.register_fn("to_float", |x: u64| x as f64); + self.register_fn("to_float", |x: f32| x as f64); + + self.register_fn("to_int", |x: i8| x as i64); + self.register_fn("to_int", |x: u8| x as i64); + self.register_fn("to_int", |x: i16| x as i64); + self.register_fn("to_int", |x: u16| 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: 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); + + // Register print and debug + fn print_debug(x: T) -> String { + format!("{:?}", x) + } + fn print(x: T) -> String { + format!("{}", x) + } + + reg_func1!(self, "print", print, String, i8, u8, i16, u16); + reg_func1!(self, "print", print, String, i32, i64, u32, u64); + reg_func1!(self, "print", print, String, f32, f64, bool, char, String); + reg_func1!(self, "print", print_debug, String, Array); + self.register_fn("print", || "".to_string()); + self.register_fn("print", |_: ()| "".to_string()); + + reg_func1!(self, "debug", print_debug, String, i8, u8, i16, u16); + reg_func1!(self, "debug", print_debug, String, i32, i64, u32, u64); + reg_func1!(self, "debug", print_debug, String, f32, f64, bool, char); + reg_func1!(self, "debug", print_debug, String, String, Array, ()); + + // Register array utility functions + fn push(list: &mut Array, item: T) { + list.push(Box::new(item)); + } + fn pad(list: &mut Array, len: i64, item: T) { + if len >= 0 { + while list.len() < len as usize { + push(list, item.clone()); + } + } + } + + reg_func2x!(self, "push", push, &mut Array, (), i8, u8, i16, u16); + reg_func2x!(self, "push", push, &mut Array, (), i32, i64, u32, u64); + reg_func2x!(self, "push", push, &mut Array, (), f32, f64, bool, char); + reg_func2x!(self, "push", push, &mut Array, (), String, Array, ()); + reg_func3!(self, "pad", pad, &mut Array, i64, (), i8, u8, i16, u16); + reg_func3!(self, "pad", pad, &mut Array, i64, (), i32, u32, f32); + reg_func3!(self, "pad", pad, &mut Array, i64, (), i64, u64, f64); + reg_func3!(self, "pad", pad, &mut Array, i64, (), bool, char); + reg_func3!(self, "pad", pad, &mut Array, i64, (), String, Array, ()); + + self.register_dynamic_fn("pop", |list: &mut Array| list.pop().unwrap_or(Box::new(()))); + self.register_dynamic_fn("shift", |list: &mut Array| { + if list.len() > 0 { + list.remove(0) + } else { + Box::new(()) + } + }); + self.register_fn("len", |list: &mut Array| list.len() as i64); + self.register_fn("clear", |list: &mut Array| list.clear()); + self.register_fn("truncate", |list: &mut Array, len: i64| { + if len >= 0 { + list.truncate(len as usize); + } + }); + + // Register string concatenate functions + fn prepend(x: T, y: String) -> String { + format!("{}{}", x, y) + } + fn append(x: String, y: T) -> String { + format!("{}{}", x, y) + } + + reg_func2x!( + self, "+", append, String, String, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64, + bool, char + ); + self.register_fn("+", |x: String, y: Array| format!("{}{:?}", x, y)); + self.register_fn("+", |x: String, _: ()| format!("{}", x)); + + reg_func2y!( + self, "+", prepend, String, String, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64, + bool, char + ); + self.register_fn("+", |x: Array, y: String| format!("{:?}{}", x, y)); + self.register_fn("+", |_: (), y: String| format!("{}", y)); + + // Register string utility functions + self.register_fn("len", |s: &mut String| s.chars().count() as i64); + self.register_fn("contains", |s: &mut String, ch: char| s.contains(ch)); + self.register_fn("contains", |s: &mut String, find: String| s.contains(&find)); + self.register_fn("clear", |s: &mut String| s.clear()); + self.register_fn("truncate", |s: &mut String, len: i64| { + if len >= 0 { + let chars: Vec<_> = s.chars().take(len as usize).collect(); + s.clear(); + chars.iter().for_each(|&ch| s.push(ch)); + } else { + s.clear(); + } + }); + self.register_fn("pad", |s: &mut String, len: i64, ch: char| { + for _ in 0..s.chars().count() - len as usize { + s.push(ch); + } + }); + self.register_fn("replace", |s: &mut String, find: String, sub: String| { + let new_str = s.replace(&find, &sub); + s.clear(); + s.push_str(&new_str); + }); + self.register_fn("trim", |s: &mut String| { + let trimmed = s.trim(); + + if trimmed.len() < s.len() { + let chars: Vec<_> = trimmed.chars().collect(); + s.clear(); + chars.iter().for_each(|&ch| s.push(ch)); + } + }); + + // Register array iterator + self.register_iterator::(|a| { + Box::new(a.downcast_ref::().unwrap().clone().into_iter()) + }); + + // Register range function + self.register_iterator::, _>(|a| { + Box::new( + a.downcast_ref::>() + .unwrap() + .clone() + .map(|n| Box::new(n) as Dynamic), + ) + }); + + self.register_fn("range", |i1: i64, i2: i64| (i1..i2)); + } +} diff --git a/src/call.rs b/src/call.rs index fa2439e9..d7565cd9 100644 --- a/src/call.rs +++ b/src/call.rs @@ -1,24 +1,22 @@ //! Helper module which defines `FnArgs` //! to make function calling easier. -use crate::any::Any; +use crate::any::{Any, Variant}; pub trait FunArgs<'a> { - fn into_vec(self) -> Vec<&'a mut dyn Any>; + fn into_vec(self) -> Vec<&'a mut Variant>; } macro_rules! impl_args { ($($p:ident),*) => { - impl<'a, $($p),*> FunArgs<'a> for ($(&'a mut $p,)*) - where - $($p: Any + Clone),* + impl<'a, $($p: Any + Clone),*> FunArgs<'a> for ($(&'a mut $p,)*) { - fn into_vec(self) -> Vec<&'a mut dyn Any> { + fn into_vec(self) -> Vec<&'a mut Variant> { let ($($p,)*) = self; #[allow(unused_variables, unused_mut)] let mut v = Vec::new(); - $(v.push($p as &mut dyn Any);)* + $(v.push($p as &mut Variant);)* v } diff --git a/src/engine.rs b/src/engine.rs index 9e942346..89544c16 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -2,99 +2,71 @@ use std::any::TypeId; use std::cmp::{PartialEq, PartialOrd}; use std::collections::HashMap; use std::error::Error; -use std::fmt; -use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Rem, Shl, Shr, Sub}; -use std::{convert::TryInto, sync::Arc}; +use std::sync::Arc; -use crate::any::{Any, AnyExt}; +use crate::any::{Any, AnyExt, Dynamic, Variant}; use crate::call::FunArgs; -use crate::fn_register::{RegisterBoxFn, RegisterFn}; -use crate::parser::{lex, parse, Expr, FnDef, ParseError, Stmt, AST}; -use fmt::{Debug, Display}; +use crate::fn_register::RegisterFn; +use crate::parser::{Expr, FnDef, ParseError, Position, Stmt}; -type Array = Vec>; +pub type Array = Vec; +pub type FnCallArgs<'a> = Vec<&'a mut Variant>; -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum EvalAltResult { - ErrorParseError(ParseError), - ErrorFunctionNotFound(String), - ErrorFunctionArgMismatch, - ErrorArrayOutOfBounds(usize, i64), - ErrorArrayMismatch, - ErrorIndexMismatch, - ErrorIfGuardMismatch, - ErrorForMismatch, - ErrorVariableNotFound(String), - ErrorAssignmentToUnknownLHS, - ErrorMismatchOutputType(String), - ErrorCantOpenScriptFile(String), - ErrorMalformedDotExpression, + ErrorParsing(ParseError), + ErrorFunctionNotFound(String, Position), + ErrorFunctionArgsMismatch(String, usize, Position), + ErrorBooleanArgMismatch(String, Position), + ErrorArrayBounds(usize, i64, Position), + ErrorStringBounds(usize, i64, Position), + ErrorIndexing(Position), + ErrorIndexExpr(Position), + ErrorIfGuard(Position), + ErrorFor(Position), + ErrorVariableNotFound(String, Position), + ErrorAssignmentToUnknownLHS(Position), + ErrorMismatchOutputType(String, Position), + ErrorCantOpenScriptFile(String, std::io::Error), + ErrorDotExpr(Position), + ErrorArithmetic(String, Position), LoopBreak, - Return(Box), -} - -impl EvalAltResult { - fn as_str(&self) -> Option<&str> { - Some(match *self { - EvalAltResult::ErrorCantOpenScriptFile(ref s) - | EvalAltResult::ErrorVariableNotFound(ref s) - | EvalAltResult::ErrorFunctionNotFound(ref s) - | EvalAltResult::ErrorMismatchOutputType(ref s) => s, - _ => return None, - }) - } -} - -impl PartialEq for EvalAltResult { - fn eq(&self, other: &Self) -> bool { - use EvalAltResult::*; - - match (self, other) { - (&ErrorParseError(ref a), &ErrorParseError(ref b)) => a == b, - (&ErrorFunctionNotFound(ref a), &ErrorFunctionNotFound(ref b)) => a == b, - (&ErrorFunctionArgMismatch, &ErrorFunctionArgMismatch) => true, - (&ErrorIndexMismatch, &ErrorIndexMismatch) => true, - (&ErrorArrayMismatch, &ErrorArrayMismatch) => true, - (&ErrorArrayOutOfBounds(max1, index1), &ErrorArrayOutOfBounds(max2, index2)) => { - max1 == max2 && index1 == index2 - } - (&ErrorIfGuardMismatch, &ErrorIfGuardMismatch) => true, - (&ErrorForMismatch, &ErrorForMismatch) => true, - (&ErrorVariableNotFound(ref a), &ErrorVariableNotFound(ref b)) => a == b, - (&ErrorAssignmentToUnknownLHS, &ErrorAssignmentToUnknownLHS) => true, - (&ErrorMismatchOutputType(ref a), &ErrorMismatchOutputType(ref b)) => a == b, - (&ErrorCantOpenScriptFile(ref a), &ErrorCantOpenScriptFile(ref b)) => a == b, - (&ErrorMalformedDotExpression, &ErrorMalformedDotExpression) => true, - (&LoopBreak, &LoopBreak) => true, - _ => false, - } - } + Return(Dynamic, Position), } impl Error for EvalAltResult { fn description(&self) -> &str { - match *self { - EvalAltResult::ErrorParseError(ref p) => p.description(), - EvalAltResult::ErrorFunctionNotFound(_) => "Function not found", - EvalAltResult::ErrorFunctionArgMismatch => "Function argument types do not match", - EvalAltResult::ErrorIndexMismatch => "Array access expects integer index", - EvalAltResult::ErrorArrayMismatch => "Indexing can only be performed on an array", - EvalAltResult::ErrorArrayOutOfBounds(_, index) if index < 0 => { + match self { + Self::ErrorParsing(p) => p.description(), + Self::ErrorFunctionNotFound(_, _) => "Function not found", + Self::ErrorFunctionArgsMismatch(_, _, _) => { + "Function call with wrong number of arguments" + } + Self::ErrorBooleanArgMismatch(_, _) => "Boolean operator expects boolean operands", + Self::ErrorIndexExpr(_) => "Indexing into an array or string expects an integer index", + Self::ErrorIndexing(_) => "Indexing can only be performed on an array or a string", + Self::ErrorArrayBounds(_, index, _) if *index < 0 => { "Array access expects non-negative index" } - EvalAltResult::ErrorArrayOutOfBounds(max, _) if max == 0 => "Access of empty array", - EvalAltResult::ErrorArrayOutOfBounds(_, _) => "Array index out of bounds", - EvalAltResult::ErrorIfGuardMismatch => "If guards expect boolean expression", - EvalAltResult::ErrorForMismatch => "For loops expect array", - EvalAltResult::ErrorVariableNotFound(_) => "Variable not found", - EvalAltResult::ErrorAssignmentToUnknownLHS => { + Self::ErrorArrayBounds(max, _, _) if *max == 0 => "Access of empty array", + Self::ErrorArrayBounds(_, _, _) => "Array index out of bounds", + Self::ErrorStringBounds(_, index, _) if *index < 0 => { + "Indexing a string expects a non-negative index" + } + Self::ErrorStringBounds(max, _, _) if *max == 0 => "Indexing of empty string", + Self::ErrorStringBounds(_, _, _) => "String index out of bounds", + Self::ErrorIfGuard(_) => "If guard expects boolean expression", + Self::ErrorFor(_) => "For loop expects array or range", + Self::ErrorVariableNotFound(_, _) => "Variable not found", + Self::ErrorAssignmentToUnknownLHS(_) => { "Assignment to an unsupported left-hand side expression" } - EvalAltResult::ErrorMismatchOutputType(_) => "Output type is incorrect", - EvalAltResult::ErrorCantOpenScriptFile(_) => "Cannot open script file", - EvalAltResult::ErrorMalformedDotExpression => "Malformed dot expression", - EvalAltResult::LoopBreak => "[Not Error] Breaks out of loop", - EvalAltResult::Return(_) => "[Not Error] Function returns value", + Self::ErrorMismatchOutputType(_, _) => "Output type is incorrect", + Self::ErrorCantOpenScriptFile(_, _) => "Cannot open script file", + Self::ErrorDotExpr(_) => "Malformed dot expression", + Self::ErrorArithmetic(_, _) => "Arithmetic error", + Self::LoopBreak => "[Not Error] Breaks out of loop", + Self::Return(_, _) => "[Not Error] Function returns value", } } @@ -103,23 +75,46 @@ impl Error for EvalAltResult { } } -impl fmt::Display for EvalAltResult { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(s) = self.as_str() { - write!(f, "{}: {}", self.description(), s) - } else { - match self { - EvalAltResult::ErrorParseError(ref p) => write!(f, "Syntax error: {}", p), - EvalAltResult::ErrorArrayOutOfBounds(_, index) if *index < 0 => { - write!(f, "{}: {} < 0", self.description(), index) - } - EvalAltResult::ErrorArrayOutOfBounds(max, _) if *max == 0 => { - write!(f, "{}", self.description()) - } - EvalAltResult::ErrorArrayOutOfBounds(max, index) => { - write!(f, "{} (max {}): {}", self.description(), max - 1, index) - } - err => write!(f, "{}", err.description()), +impl std::fmt::Display for EvalAltResult { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let desc = self.description(); + + match self { + Self::ErrorFunctionNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos), + Self::ErrorVariableNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos), + Self::ErrorIndexing(pos) => write!(f, "{} ({})", desc, pos), + Self::ErrorIndexExpr(pos) => write!(f, "{} ({})", desc, pos), + Self::ErrorIfGuard(pos) => write!(f, "{} ({})", desc, pos), + Self::ErrorFor(pos) => write!(f, "{} ({})", desc, pos), + Self::ErrorAssignmentToUnknownLHS(pos) => write!(f, "{} ({})", desc, pos), + Self::ErrorMismatchOutputType(s, pos) => write!(f, "{}: {} ({})", desc, s, pos), + Self::ErrorDotExpr(pos) => write!(f, "{} ({})", desc, pos), + Self::ErrorArithmetic(s, pos) => write!(f, "{}: {} ({})", desc, s, pos), + Self::LoopBreak => write!(f, "{}", desc), + Self::Return(_, pos) => write!(f, "{} ({})", desc, pos), + Self::ErrorCantOpenScriptFile(filename, err) => { + write!(f, "{} '{}': {}", desc, filename, err) + } + Self::ErrorParsing(p) => write!(f, "Syntax error: {}", p), + Self::ErrorFunctionArgsMismatch(fun, n, pos) => { + write!(f, "Function '{}' expects {} argument(s) ({})", fun, n, pos) + } + Self::ErrorBooleanArgMismatch(op, pos) => { + write!(f, "{} operator expects boolean operands ({})", op, pos) + } + Self::ErrorArrayBounds(_, index, pos) if *index < 0 => { + write!(f, "{}: {} < 0 ({})", desc, index, pos) + } + Self::ErrorArrayBounds(max, _, pos) if *max == 0 => write!(f, "{} ({})", desc, pos), + Self::ErrorArrayBounds(max, index, pos) => { + write!(f, "{} (max {}): {} ({})", desc, max - 1, index, pos) + } + Self::ErrorStringBounds(_, index, pos) if *index < 0 => { + write!(f, "{}: {} < 0 ({})", desc, index, pos) + } + Self::ErrorStringBounds(max, _, pos) if *max == 0 => write!(f, "{} ({})", desc, pos), + Self::ErrorStringBounds(max, index, pos) => { + write!(f, "{} (max {}): {} ({})", desc, max - 1, index, pos) } } } @@ -127,11 +122,11 @@ impl fmt::Display for EvalAltResult { #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] pub struct FnSpec { - ident: String, - args: Option>, + pub ident: String, + pub args: Option>, } -type IteratorFn = dyn Fn(&Box) -> Box>>; +type IteratorFn = dyn Fn(&Dynamic) -> Box>; /// Rhai's engine type. This is what you use to run Rhai scripts /// @@ -147,11 +142,17 @@ type IteratorFn = dyn Fn(&Box) -> Box> /// } /// } /// ``` -#[derive(Clone)] pub struct Engine { - /// A hashmap containing all functions known to the engine - pub fns: HashMap>, - pub type_iterators: HashMap>, + /// A hashmap containing all compiled functions known to the engine + fns: HashMap>, + /// A hashmap containing all script-defined functions + pub(crate) script_fns: HashMap>, + /// A hashmap containing all iterators known to the engine + type_iterators: HashMap>, + type_names: HashMap, + + pub(crate) on_print: Box, + pub(crate) on_debug: Box, } pub enum FnIntExt { @@ -159,7 +160,7 @@ pub enum FnIntExt { Int(FnDef), } -pub type FnAny = dyn Fn(Vec<&mut dyn Any>) -> Result, EvalAltResult>; +pub type FnAny = dyn Fn(FnCallArgs, Position) -> Result; /// A type containing information about current scope. /// Useful for keeping state between `Engine` runs @@ -175,7 +176,7 @@ pub type FnAny = dyn Fn(Vec<&mut dyn Any>) -> Result, EvalAltResult /// ``` /// /// Between runs, `Engine` only remembers functions when not using own `Scope`. -pub type Scope = Vec<(String, Box)>; +pub type Scope = Vec<(String, Dynamic)>; impl Engine { pub fn call_fn<'a, I, A, T>(&self, ident: I, args: A) -> Result @@ -184,74 +185,108 @@ impl Engine { A: FunArgs<'a>, T: Any + Clone, { - self.call_fn_raw(ident.into(), args.into_vec()) + let pos = Position { line: 0, pos: 0 }; + + self.call_fn_raw(ident.into(), args.into_vec(), None, pos) .and_then(|b| { - b.downcast() - .map(|b| *b) - .map_err(|a| EvalAltResult::ErrorMismatchOutputType((*a).type_name())) + b.downcast().map(|b| *b).map_err(|a| { + let name = self.map_type_name((*a).type_name()); + EvalAltResult::ErrorMismatchOutputType(name, pos) + }) }) } /// Universal method for calling functions, that are either /// registered with the `Engine` or written in Rhai - pub fn call_fn_raw( + fn call_fn_raw( &self, ident: String, - args: Vec<&mut dyn Any>, - ) -> Result, EvalAltResult> { + args: FnCallArgs, + def_value: Option<&Dynamic>, + pos: Position, + ) -> Result { debug_println!( "Trying to call function {:?} with args {:?}", ident, args.iter() - .map(|x| Any::type_name(&**x)) + .map(|x| { self.map_type_name((**x).type_name()) }) .collect::>() ); - let spec = FnSpec { + let mut spec = FnSpec { ident: ident.clone(), - args: Some(args.iter().map(|a| Any::type_id(&**a)).collect()), + args: None, }; - self.fns - .get(&spec) - .or_else(|| { - let spec1 = FnSpec { - ident: ident.clone(), - args: None, - }; - self.fns.get(&spec1) - }) - .ok_or_else(|| { - let typenames = args - .iter() - .map(|x| (*(&**x).box_clone()).type_name()) - .collect::>(); - EvalAltResult::ErrorFunctionNotFound(format!( - "{} ({})", - ident, - typenames.join(", ") - )) - }) - .and_then(move |f| match **f { - FnIntExt::Ext(ref f) => f(args), + // First search in script-defined functions (can override built-in), + // then in built-in's + let fn_def = self.script_fns.get(&spec).or_else(|| { + spec.args = Some(args.iter().map(|a| Any::type_id(&**a)).collect()); + self.fns.get(&spec) + }); + + if let Some(f) = fn_def { + match **f { + FnIntExt::Ext(ref f) => { + let r = f(args, pos); + + if r.is_err() { + return r; + } + + let callback = match ident.as_str() { + "print" => &self.on_print, + "debug" => &self.on_debug, + _ => return r, + }; + + Ok(Box::new(callback( + r.unwrap() + .downcast::() + .map(|x| *x) + .unwrap_or("error: not a string".into()) + .as_str(), + ))) + } FnIntExt::Int(ref f) => { let mut scope = Scope::new(); + scope.extend( f.params .iter() .cloned() - .zip(args.iter().map(|x| (&**x).box_clone())), + .zip(args.iter().map(|x| (&**x).into_dynamic())), ); match self.eval_stmt(&mut scope, &*f.body) { - Err(EvalAltResult::Return(x)) => Ok(x), + Err(EvalAltResult::Return(x, _)) => Ok(x), other => other, } } - }) + } + } else if let Some(val) = def_value { + // Return default value + Ok(val.clone()) + } else { + let types_list = args + .iter() + .map(|x| (*(&**x).into_dynamic()).type_name()) + .map(|name| self.map_type_name(name)) + .collect::>(); + + Err(EvalAltResult::ErrorFunctionNotFound( + format!("{} ({})", ident, types_list.join(", ")), + pos, + )) + } } - pub fn register_fn_raw(&mut self, ident: String, args: Option>, f: Box) { + pub(crate) fn register_fn_raw( + &mut self, + ident: String, + args: Option>, + f: Box, + ) { debug_println!("Register; {:?} with args {:?}", ident, args); let spec = FnSpec { ident, args }; @@ -268,39 +303,38 @@ impl Engine { /// Register an iterator adapter for a type. pub fn register_iterator(&mut self, f: F) where - F: 'static + Fn(&Box) -> Box>>, + F: Fn(&Dynamic) -> Box> + 'static, { self.type_iterators.insert(TypeId::of::(), Arc::new(f)); } /// Register a get function for a member of a registered type - pub fn register_get(&mut self, name: &str, get_fn: F) - where - F: 'static + Fn(&mut T) -> U, - { + pub fn register_get( + &mut self, + name: &str, + get_fn: impl Fn(&mut T) -> U + 'static, + ) { let get_name = "get$".to_string() + name; self.register_fn(&get_name, get_fn); } /// Register a set function for a member of a registered type - pub fn register_set(&mut self, name: &str, set_fn: F) - where - F: 'static + Fn(&mut T, U) -> (), - { + pub fn register_set( + &mut self, + name: &str, + set_fn: impl Fn(&mut T, U) -> () + 'static, + ) { let set_name = "set$".to_string() + name; self.register_fn(&set_name, set_fn); } /// Shorthand for registering both getters and setters - pub fn register_get_set( + pub fn register_get_set( &mut self, name: &str, - get_fn: F, - set_fn: G, - ) where - F: 'static + Fn(&mut T) -> U, - G: 'static + Fn(&mut T, U) -> (), - { + get_fn: impl Fn(&mut T) -> U + 'static, + set_fn: impl Fn(&mut T, U) -> () + 'static, + ) { self.register_get(name, get_fn); self.register_set(name, set_fn); } @@ -308,104 +342,185 @@ impl Engine { fn get_dot_val_helper( &self, scope: &mut Scope, - this_ptr: &mut dyn Any, + this_ptr: &mut Variant, dot_rhs: &Expr, - ) -> Result, EvalAltResult> { + ) -> Result { use std::iter::once; - match *dot_rhs { - Expr::FunctionCall(ref fn_name, ref args) => { + match dot_rhs { + Expr::FunctionCall(fn_name, args, def_value, pos) => { let mut args: Array = args .iter() .map(|arg| self.eval_expr(scope, arg)) .collect::, _>>()?; + let args = once(this_ptr) .chain(args.iter_mut().map(|b| b.as_mut())) .collect(); - self.call_fn_raw(fn_name.to_owned(), args) + self.call_fn_raw(fn_name.into(), args, def_value.as_ref(), *pos) } - Expr::Identifier(ref id) => { + + Expr::Identifier(id, pos) => { let get_fn_name = "get$".to_string() + id; - self.call_fn_raw(get_fn_name, vec![this_ptr]) + self.call_fn_raw(get_fn_name, vec![this_ptr], None, *pos) } - Expr::Index(ref id, ref idx_raw) => { - let idx = self.eval_expr(scope, idx_raw)?; + + Expr::Index(id, idx_raw, pos) => { + let idx = self + .eval_expr(scope, idx_raw)? + .downcast_ref::() + .map(|i| *i) + .ok_or(EvalAltResult::ErrorIndexExpr(idx_raw.position()))?; + let get_fn_name = "get$".to_string() + id; - let mut val = self.call_fn_raw(get_fn_name, vec![this_ptr])?; + let mut val = self.call_fn_raw(get_fn_name, vec![this_ptr], None, *pos)?; - ((*val).downcast_mut() as Option<&mut Array>) - .ok_or(EvalAltResult::ErrorArrayMismatch) - .and_then(|arr| { - idx.downcast_ref::() - .map(|idx| (arr, *idx)) - .ok_or(EvalAltResult::ErrorIndexMismatch) - }) - .and_then(|(arr, idx)| match idx { - x if x < 0 => Err(EvalAltResult::ErrorArrayOutOfBounds(0, x)), - x => arr - .get(x as usize) + if let Some(arr) = (*val).downcast_mut() as Option<&mut Array> { + if idx >= 0 { + arr.get(idx as usize) .cloned() - .ok_or(EvalAltResult::ErrorArrayOutOfBounds(arr.len(), x)), - }) - } - Expr::Dot(ref inner_lhs, ref inner_rhs) => match **inner_lhs { - Expr::Identifier(ref id) => { - let get_fn_name = "get$".to_string() + id; - self.call_fn_raw(get_fn_name, vec![this_ptr]) - .and_then(|mut v| self.get_dot_val_helper(scope, v.as_mut(), inner_rhs)) + .ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr.len(), idx, *pos)) + } else { + Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, *pos)) + } + } else if let Some(s) = (*val).downcast_mut() as Option<&mut String> { + if idx >= 0 { + s.chars() + .nth(idx as usize) + .map(|ch| Box::new(ch) as Dynamic) + .ok_or_else(|| { + EvalAltResult::ErrorStringBounds(s.chars().count(), idx, *pos) + }) + } else { + Err(EvalAltResult::ErrorStringBounds( + s.chars().count(), + idx, + *pos, + )) + } + } else { + Err(EvalAltResult::ErrorIndexing(*pos)) } - _ => Err(EvalAltResult::ErrorMalformedDotExpression), + } + + Expr::Dot(inner_lhs, inner_rhs) => match **inner_lhs { + Expr::Identifier(ref id, pos) => { + let get_fn_name = "get$".to_string() + id; + let value = self + .call_fn_raw(get_fn_name, vec![this_ptr], None, pos) + .and_then(|mut v| self.get_dot_val_helper(scope, v.as_mut(), inner_rhs))?; + + // TODO - Should propagate changes back in this scenario: + // + // fn update(p) { p = something_else; } + // obj.prop.update(); + // + // Right now, a copy of the object's property value is mutated, but not propagated + // back to the property via $set. + + Ok(value) + } + Expr::Index(_, _, pos) => { + // TODO - Handle Expr::Index for these scenarios: + // + // let x = obj.prop[2].x; + // obj.prop[3] = 42; + // + Err(EvalAltResult::ErrorDotExpr(pos)) + } + _ => Err(EvalAltResult::ErrorDotExpr(inner_lhs.position())), }, - _ => Err(EvalAltResult::ErrorMalformedDotExpression), + + _ => Err(EvalAltResult::ErrorDotExpr(dot_rhs.position())), } } - fn search_scope<'a, F, T>( + fn search_scope<'a, T>( scope: &'a mut Scope, id: &str, - map: F, - ) -> Result<(usize, T), EvalAltResult> - where - F: FnOnce(&'a mut dyn Any) -> Result, - { + map: impl FnOnce(&'a mut Variant) -> Result, + begin: Position, + ) -> Result<(usize, T), EvalAltResult> { scope .iter_mut() .enumerate() .rev() - .find(|&(_, &mut (ref name, _))| *id == *name) - .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.to_owned())) + .find(|&(_, &mut (ref name, _))| id == name) + .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.into(), begin)) .and_then(move |(idx, &mut (_, ref mut val))| map(val.as_mut()).map(|val| (idx, val))) } - fn array_value( + fn indexed_value( &self, scope: &mut Scope, id: &str, idx: &Expr, - ) -> Result<(usize, usize, Box), EvalAltResult> { - let idx_boxed = self + begin: Position, + ) -> Result<(bool, usize, usize, Dynamic), EvalAltResult> { + let idx = *self .eval_expr(scope, idx)? .downcast::() - .map_err(|_| EvalAltResult::ErrorIndexMismatch)?; - let idx_raw = *idx_boxed; - let idx = match idx_raw { - x if x < 0 => return Err(EvalAltResult::ErrorArrayOutOfBounds(0, x)), - x => x as usize, - }; - let (idx_sc, val) = Self::search_scope(scope, id, |val| { - ((*val).downcast_mut() as Option<&mut Array>) - .ok_or(EvalAltResult::ErrorArrayMismatch) - .and_then(|arr| { - arr.get(idx) - .cloned() - .ok_or(EvalAltResult::ErrorArrayOutOfBounds(arr.len(), idx_raw)) - }) - })?; + .map_err(|_| EvalAltResult::ErrorIndexExpr(idx.position()))?; - Ok((idx_sc, idx, val)) + let mut is_array = false; + + Self::search_scope( + scope, + id, + |val| { + if let Some(arr) = (*val).downcast_mut() as Option<&mut Array> { + is_array = true; + + if idx >= 0 { + arr.get(idx as usize) + .cloned() + .ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr.len(), idx, begin)) + } else { + Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, begin)) + } + } else if let Some(s) = (*val).downcast_mut() as Option<&mut String> { + is_array = false; + + if idx >= 0 { + s.chars() + .nth(idx as usize) + .map(|ch| Box::new(ch) as Dynamic) + .ok_or_else(|| { + EvalAltResult::ErrorStringBounds(s.chars().count(), idx, begin) + }) + } else { + Err(EvalAltResult::ErrorStringBounds( + s.chars().count(), + idx, + begin, + )) + } + } else { + Err(EvalAltResult::ErrorIndexing(begin)) + } + }, + begin, + ) + .map(|(idx_sc, val)| (is_array, idx_sc, idx as usize, val)) + } + + fn str_replace_char(s: &mut String, idx: usize, new_ch: char) { + // The new character + let ch = s.chars().nth(idx).unwrap(); + + // See if changed - if so, update the String + if ch == new_ch { + return; + } + + // Collect all the characters after the index + let mut chars: Vec = s.chars().collect(); + chars[idx] = new_ch; + s.clear(); + chars.iter().for_each(|&ch| s.push(ch)); } fn get_dot_val( @@ -413,10 +528,11 @@ impl Engine { scope: &mut Scope, dot_lhs: &Expr, dot_rhs: &Expr, - ) -> Result, EvalAltResult> { - match *dot_lhs { - Expr::Identifier(ref id) => { - let (sc_idx, mut target) = Self::search_scope(scope, id, |x| Ok(x.box_clone()))?; + ) -> Result { + match dot_lhs { + Expr::Identifier(id, pos) => { + let (sc_idx, mut target) = + Self::search_scope(scope, id, |x| Ok(x.into_dynamic()), *pos)?; let value = self.get_dot_val_helper(scope, target.as_mut(), dot_rhs); // In case the expression mutated `target`, we need to reassign it because @@ -425,35 +541,50 @@ impl Engine { value } - Expr::Index(ref id, ref idx_raw) => { - let (sc_idx, idx, mut target) = self.array_value(scope, id, idx_raw)?; + + Expr::Index(id, idx_raw, pos) => { + let (is_array, sc_idx, idx, mut target) = + self.indexed_value(scope, id, idx_raw, *pos)?; let value = self.get_dot_val_helper(scope, target.as_mut(), dot_rhs); // In case the expression mutated `target`, we need to reassign it because // of the above `clone`. - scope[sc_idx].1.downcast_mut::().unwrap()[idx] = target; + + if is_array { + scope[sc_idx].1.downcast_mut::().unwrap()[idx] = target; + } else { + Self::str_replace_char( + scope[sc_idx].1.downcast_mut::().unwrap(), // Root is a string + idx, + *target.downcast::().unwrap(), // Target should be a char + ); + } value } - _ => Err(EvalAltResult::ErrorMalformedDotExpression), + + _ => Err(EvalAltResult::ErrorDotExpr(dot_lhs.position())), } } fn set_dot_val_helper( &self, - this_ptr: &mut dyn Any, + this_ptr: &mut Variant, dot_rhs: &Expr, - mut source_val: Box, - ) -> Result, EvalAltResult> { - match *dot_rhs { - Expr::Identifier(ref id) => { + mut source_val: Dynamic, + ) -> Result { + match dot_rhs { + Expr::Identifier(id, pos) => { let set_fn_name = "set$".to_string() + id; - self.call_fn_raw(set_fn_name, vec![this_ptr, source_val.as_mut()]) + + self.call_fn_raw(set_fn_name, vec![this_ptr, source_val.as_mut()], None, *pos) } - Expr::Dot(ref inner_lhs, ref inner_rhs) => match **inner_lhs { - Expr::Identifier(ref id) => { + + Expr::Dot(inner_lhs, inner_rhs) => match **inner_lhs { + Expr::Identifier(ref id, pos) => { let get_fn_name = "get$".to_string() + id; - self.call_fn_raw(get_fn_name, vec![this_ptr]) + + self.call_fn_raw(get_fn_name, vec![this_ptr], None, pos) .and_then(|mut v| { self.set_dot_val_helper(v.as_mut(), inner_rhs, source_val) .map(|_| v) // Discard Ok return value @@ -461,12 +592,13 @@ impl Engine { .and_then(|mut v| { let set_fn_name = "set$".to_string() + id; - self.call_fn_raw(set_fn_name, vec![this_ptr, v.as_mut()]) + self.call_fn_raw(set_fn_name, vec![this_ptr, v.as_mut()], None, pos) }) } - _ => Err(EvalAltResult::ErrorMalformedDotExpression), + _ => Err(EvalAltResult::ErrorDotExpr(inner_lhs.position())), }, - _ => Err(EvalAltResult::ErrorMalformedDotExpression), + + _ => Err(EvalAltResult::ErrorDotExpr(dot_rhs.position())), } } @@ -475,11 +607,12 @@ impl Engine { scope: &mut Scope, dot_lhs: &Expr, dot_rhs: &Expr, - source_val: Box, - ) -> Result, EvalAltResult> { - match *dot_lhs { - Expr::Identifier(ref id) => { - let (sc_idx, mut target) = Self::search_scope(scope, id, |x| Ok(x.box_clone()))?; + source_val: Dynamic, + ) -> Result { + match dot_lhs { + Expr::Identifier(id, pos) => { + let (sc_idx, mut target) = + Self::search_scope(scope, id, |x| Ok(x.into_dynamic()), *pos)?; let value = self.set_dot_val_helper(target.as_mut(), dot_rhs, source_val); // In case the expression mutated `target`, we need to reassign it because @@ -488,123 +621,199 @@ impl Engine { value } - Expr::Index(ref id, ref idx_raw) => { - let (sc_idx, idx, mut target) = self.array_value(scope, id, idx_raw)?; + + Expr::Index(id, idx_raw, pos) => { + let (is_array, sc_idx, idx, mut target) = + self.indexed_value(scope, id, idx_raw, *pos)?; let value = self.set_dot_val_helper(target.as_mut(), dot_rhs, source_val); // In case the expression mutated `target`, we need to reassign it because // of the above `clone`. - scope[sc_idx].1.downcast_mut::().unwrap()[idx] = target; + if is_array { + scope[sc_idx].1.downcast_mut::().unwrap()[idx] = target; + } else { + Self::str_replace_char( + scope[sc_idx].1.downcast_mut::().unwrap(), // Root is a string + idx, + *target.downcast::().unwrap(), // Target should be a char + ); + } value } - _ => Err(EvalAltResult::ErrorMalformedDotExpression), + + _ => Err(EvalAltResult::ErrorDotExpr(dot_lhs.position())), } } - fn eval_expr(&self, scope: &mut Scope, expr: &Expr) -> Result, EvalAltResult> { - match *expr { - Expr::IntegerConstant(i) => Ok(Box::new(i)), - Expr::FloatConstant(i) => Ok(Box::new(i)), - Expr::StringConstant(ref s) => Ok(Box::new(s.clone())), - Expr::CharConstant(ref c) => Ok(Box::new(*c)), - Expr::Identifier(ref id) => { - for &mut (ref name, ref mut val) in &mut scope.iter_mut().rev() { - if *id == *name { - return Ok(val.clone()); - } - } - Err(EvalAltResult::ErrorVariableNotFound(id.clone())) - } - Expr::Index(ref id, ref idx_raw) => { - self.array_value(scope, id, idx_raw).map(|(_, _, x)| x) - } - Expr::Assignment(ref id, ref rhs) => { + fn eval_expr(&self, scope: &mut Scope, expr: &Expr) -> Result { + match expr { + Expr::IntegerConstant(i, _) => Ok(Box::new(*i)), + Expr::FloatConstant(i, _) => Ok(Box::new(*i)), + Expr::StringConstant(s, _) => Ok(Box::new(s.clone())), + Expr::CharConstant(c, _) => Ok(Box::new(*c)), + + Expr::Identifier(id, pos) => scope + .iter() + .rev() + .filter(|(name, _)| id == name) + .next() + .map(|(_, val)| val.clone()) + .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.clone(), *pos)), + + Expr::Index(id, idx_raw, pos) => self + .indexed_value(scope, id, idx_raw, *pos) + .map(|(_, _, _, x)| x), + + Expr::Assignment(ref id, rhs) => { let rhs_val = self.eval_expr(scope, rhs)?; match **id { - Expr::Identifier(ref n) => { - for &mut (ref name, ref mut val) in &mut scope.iter_mut().rev() { - if *n == *name { - *val = rhs_val; + Expr::Identifier(ref n, pos) => scope + .iter_mut() + .rev() + .filter(|(name, _)| n == name) + .next() + .map(|(_, val)| { + *val = rhs_val; + Box::new(()) as Dynamic + }) + .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(n.clone(), pos)), - return Ok(Box::new(())); + Expr::Index(ref id, ref idx_raw, pos) => { + let idx_pos = idx_raw.position(); + + let idx = *match self.eval_expr(scope, &idx_raw)?.downcast_ref::() { + Some(x) => x, + _ => return Err(EvalAltResult::ErrorIndexExpr(idx_pos)), + }; + + let variable = &mut scope + .iter_mut() + .rev() + .filter(|(name, _)| id == name) + .map(|(_, val)| val) + .next(); + + let val = match variable { + Some(v) => v, + _ => return Err(EvalAltResult::ErrorVariableNotFound(id.clone(), pos)), + }; + + if let Some(arr) = val.downcast_mut() as Option<&mut Array> { + if idx < 0 { + Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, idx_pos)) + } else if idx as usize >= arr.len() { + Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, idx_pos)) + } else { + arr[idx as usize] = rhs_val; + Ok(Box::new(())) } - } - Err(EvalAltResult::ErrorVariableNotFound(n.clone())) - } - Expr::Index(ref id, ref idx_raw) => { - let idx = self.eval_expr(scope, idx_raw)?; + } else if let Some(s) = val.downcast_mut() as Option<&mut String> { + let s_len = s.chars().count(); - for &mut (ref name, ref mut val) in &mut scope.iter_mut().rev() { - if *id == *name { - return if let Some(&i) = idx.downcast_ref::() { - if let Some(arr_typed) = - (*val).downcast_mut() as Option<&mut Array> - { - if i < 0 { - Err(EvalAltResult::ErrorArrayOutOfBounds(0, i)) - } else if i as usize >= arr_typed.len() { - Err(EvalAltResult::ErrorArrayOutOfBounds( - arr_typed.len(), - i, - )) - } else { - arr_typed[i as usize] = rhs_val; - Ok(Box::new(())) - } - } else { - Err(EvalAltResult::ErrorIndexMismatch) - } - } else { - Err(EvalAltResult::ErrorIndexMismatch) - }; + if idx < 0 { + Err(EvalAltResult::ErrorStringBounds(s_len, idx, idx_pos)) + } else if idx as usize >= s_len { + Err(EvalAltResult::ErrorStringBounds(s_len, idx, idx_pos)) + } else { + Self::str_replace_char( + s, + idx as usize, + *rhs_val.downcast::().unwrap(), + ); + Ok(Box::new(())) } + } else { + Err(EvalAltResult::ErrorIndexExpr(idx_pos)) } - - Err(EvalAltResult::ErrorVariableNotFound(id.clone())) } + Expr::Dot(ref dot_lhs, ref dot_rhs) => { self.set_dot_val(scope, dot_lhs, dot_rhs, rhs_val) } - _ => Err(EvalAltResult::ErrorAssignmentToUnknownLHS), + + _ => Err(EvalAltResult::ErrorAssignmentToUnknownLHS(id.position())), } } - Expr::Dot(ref lhs, ref rhs) => self.get_dot_val(scope, lhs, rhs), - Expr::Array(ref contents) => { + + Expr::Dot(lhs, rhs) => self.get_dot_val(scope, lhs, rhs), + + Expr::Array(contents, _) => { let mut arr = Vec::new(); - for item in &(*contents) { + contents.iter().try_for_each(|item| { let arg = self.eval_expr(scope, item)?; arr.push(arg); - } + Ok(()) + })?; Ok(Box::new(arr)) } - Expr::FunctionCall(ref fn_name, ref args) => self.call_fn_raw( - fn_name.to_owned(), + + Expr::FunctionCall(fn_name, args, def_value, pos) => self.call_fn_raw( + fn_name.into(), args.iter() - .map(|ex| self.eval_expr(scope, ex)) + .map(|expr| self.eval_expr(scope, expr)) .collect::>()? .iter_mut() .map(|b| b.as_mut()) .collect(), + def_value.as_ref(), + *pos, ), - Expr::True => Ok(Box::new(true)), - Expr::False => Ok(Box::new(false)), - Expr::Unit => Ok(Box::new(())), + + Expr::And(lhs, rhs) => Ok(Box::new( + *self + .eval_expr(scope, &*lhs)? + .downcast::() + .map_err(|_| { + EvalAltResult::ErrorBooleanArgMismatch("AND".into(), lhs.position()) + })? + && *self + .eval_expr(scope, &*rhs)? + .downcast::() + .map_err(|_| { + EvalAltResult::ErrorBooleanArgMismatch("AND".into(), rhs.position()) + })?, + )), + + Expr::Or(lhs, rhs) => Ok(Box::new( + *self + .eval_expr(scope, &*lhs)? + .downcast::() + .map_err(|_| { + EvalAltResult::ErrorBooleanArgMismatch("OR".into(), lhs.position()) + })? + || *self + .eval_expr(scope, &*rhs)? + .downcast::() + .map_err(|_| { + EvalAltResult::ErrorBooleanArgMismatch("OR".into(), rhs.position()) + })?, + )), + + Expr::True(_) => Ok(Box::new(true)), + Expr::False(_) => Ok(Box::new(false)), + Expr::Unit(_) => Ok(Box::new(())), } } - fn eval_stmt(&self, scope: &mut Scope, stmt: &Stmt) -> Result, EvalAltResult> { - match *stmt { - Stmt::Expr(ref e) => self.eval_expr(scope, e), - Stmt::Block(ref b) => { - let prev_len = scope.len(); - let mut last_result: Result, EvalAltResult> = Ok(Box::new(())); + pub(crate) fn eval_stmt( + &self, + scope: &mut Scope, + stmt: &Stmt, + ) -> Result { + match stmt { + Stmt::Expr(expr) => self.eval_expr(scope, expr), + + Stmt::Block(block) => { + let prev_len = scope.len(); + let mut last_result: Result = Ok(Box::new(())); + + for block_stmt in block.iter() { + last_result = self.eval_stmt(scope, block_stmt); - for s in b.iter() { - last_result = self.eval_stmt(scope, s); if let Err(x) = last_result { last_result = Err(x); break; @@ -617,37 +826,25 @@ impl Engine { last_result } - Stmt::If(ref guard, ref body) => { - let guard_result = self.eval_expr(scope, guard)?; - match guard_result.downcast::() { - Ok(g) => { - if *g { - self.eval_stmt(scope, body) - } else { - Ok(Box::new(())) - } + + Stmt::IfElse(guard, body, else_body) => self + .eval_expr(scope, guard)? + .downcast::() + .map_err(|_| EvalAltResult::ErrorIfGuard(guard.position())) + .and_then(|guard_val| { + if *guard_val { + self.eval_stmt(scope, body) + } else if else_body.is_some() { + self.eval_stmt(scope, else_body.as_ref().unwrap()) + } else { + Ok(Box::new(())) } - Err(_) => Err(EvalAltResult::ErrorIfGuardMismatch), - } - } - Stmt::IfElse(ref guard, ref body, ref else_body) => { - let guard_result = self.eval_expr(scope, guard)?; - match guard_result.downcast::() { - Ok(g) => { - if *g { - self.eval_stmt(scope, body) - } else { - self.eval_stmt(scope, else_body) - } - } - Err(_) => Err(EvalAltResult::ErrorIfGuardMismatch), - } - } - Stmt::While(ref guard, ref body) => loop { - let guard_result = self.eval_expr(scope, guard)?; - match guard_result.downcast::() { - Ok(g) => { - if *g { + }), + + Stmt::While(guard, body) => loop { + match self.eval_expr(scope, guard)?.downcast::() { + Ok(guard_val) => { + if *guard_val { match self.eval_stmt(scope, body) { Err(EvalAltResult::LoopBreak) => return Ok(Box::new(())), Err(x) => return Err(x), @@ -657,24 +854,29 @@ impl Engine { return Ok(Box::new(())); } } - Err(_) => return Err(EvalAltResult::ErrorIfGuardMismatch), + Err(_) => return Err(EvalAltResult::ErrorIfGuard(guard.position())), } }, - Stmt::Loop(ref body) => loop { + + Stmt::Loop(body) => loop { match self.eval_stmt(scope, body) { Err(EvalAltResult::LoopBreak) => return Ok(Box::new(())), Err(x) => return Err(x), _ => (), } }, - Stmt::For(ref name, ref expr, ref body) => { + + Stmt::For(name, expr, body) => { let arr = self.eval_expr(scope, expr)?; let tid = Any::type_id(&*arr); + if let Some(iter_fn) = self.type_iterators.get(&tid) { scope.push((name.clone(), Box::new(()))); let idx = scope.len() - 1; + for a in iter_fn(&arr) { scope[idx].1 = a; + match self.eval_stmt(scope, body) { Err(EvalAltResult::LoopBreak) => break, Err(x) => return Err(x), @@ -684,444 +886,63 @@ impl Engine { scope.remove(idx); Ok(Box::new(())) } else { - return Err(EvalAltResult::ErrorForMismatch); + return Err(EvalAltResult::ErrorFor(expr.position())); } } - Stmt::Break => Err(EvalAltResult::LoopBreak), - Stmt::Return => Err(EvalAltResult::Return(Box::new(()))), - Stmt::ReturnWithVal(ref a) => { + + Stmt::Break(_) => Err(EvalAltResult::LoopBreak), + + Stmt::Return(pos) => Err(EvalAltResult::Return(Box::new(()), *pos)), + + Stmt::ReturnWithVal(a, pos) => { let result = self.eval_expr(scope, a)?; - Err(EvalAltResult::Return(result)) + Err(EvalAltResult::Return(result, *pos)) } - Stmt::Let(ref name, ref init) => { - match *init { - Some(ref v) => { - let i = self.eval_expr(scope, v)?; - scope.push((name.clone(), i)); - } - None => scope.push((name.clone(), Box::new(()))), - }; + + Stmt::Let(name, init, _) => { + if let Some(v) = init { + let i = self.eval_expr(scope, v)?; + scope.push((name.clone(), i)); + } else { + scope.push((name.clone(), Box::new(()))); + } Ok(Box::new(())) } } } - /// Compile a string into an AST - pub fn compile(input: &str) -> Result { - let tokens = lex(input); - - let mut peekables = tokens.peekable(); - let tree = parse(&mut peekables); - - tree - } - - /// Compile a file into an AST - pub fn compile_file(fname: &str) -> Result { - use std::fs::File; - use std::io::prelude::*; - - if let Ok(mut f) = File::open(fname) { - let mut contents = String::new(); - - if f.read_to_string(&mut contents).is_ok() { - Self::compile(&contents).map_err(|err| EvalAltResult::ErrorParseError(err)) - } else { - Err(EvalAltResult::ErrorCantOpenScriptFile(fname.to_owned())) - } - } else { - Err(EvalAltResult::ErrorCantOpenScriptFile(fname.to_owned())) - } - } - - /// Evaluate a file - pub fn eval_file(&mut self, fname: &str) -> Result { - use std::fs::File; - use std::io::prelude::*; - - if let Ok(mut f) = File::open(fname) { - let mut contents = String::new(); - - if f.read_to_string(&mut contents).is_ok() { - self.eval::(&contents) - } else { - Err(EvalAltResult::ErrorCantOpenScriptFile(fname.to_owned())) - } - } else { - Err(EvalAltResult::ErrorCantOpenScriptFile(fname.to_owned())) - } - } - - /// Evaluate a string - pub fn eval(&mut self, input: &str) -> Result { - let mut scope = Scope::new(); - self.eval_with_scope(&mut scope, input) - } - - /// Evaluate a string with own scope - pub fn eval_with_scope( - &mut self, - scope: &mut Scope, - input: &str, - ) -> Result { - let ast = Self::compile(input).map_err(|err| EvalAltResult::ErrorParseError(err))?; - self.eval_ast_with_scope(scope, &ast) - } - - /// Evaluate an AST - pub fn eval_ast(&mut self, ast: &AST) -> Result { - let mut scope = Scope::new(); - self.eval_ast_with_scope(&mut scope, ast) - } - - /// Evaluate an AST with own scope - pub fn eval_ast_with_scope( - &mut self, - scope: &mut Scope, - ast: &AST, - ) -> Result { - let AST(os, fns) = ast; - let mut x: Result, EvalAltResult> = Ok(Box::new(())); - - for f in fns { - let name = f.name.clone(); - let local_f = f.clone(); - - let spec = FnSpec { - ident: name, - args: None, - }; - - self.fns.insert(spec, Arc::new(FnIntExt::Int(local_f))); - } - - for o in os { - x = match self.eval_stmt(scope, o) { - Ok(v) => Ok(v), - Err(e) => return Err(e), - } - } - - let x = x?; - - match x.downcast::() { - Ok(out) => Ok(*out), - Err(a) => Err(EvalAltResult::ErrorMismatchOutputType((*a).type_name())), - } - } - - /// Evaluate a file, but only return errors, if there are any. - /// Useful for when you don't need the result, but still need - /// to keep track of possible errors - pub fn consume_file(&mut self, fname: &str) -> Result<(), EvalAltResult> { - use std::fs::File; - use std::io::prelude::*; - - if let Ok(mut f) = File::open(fname) { - let mut contents = String::new(); - - if f.read_to_string(&mut contents).is_ok() { - if let e @ Err(_) = self.consume(&contents) { - e - } else { - Ok(()) - } - } else { - Err(EvalAltResult::ErrorCantOpenScriptFile(fname.to_owned())) - } - } else { - Err(EvalAltResult::ErrorCantOpenScriptFile(fname.to_owned())) - } - } - - /// Evaluate a string, but only return errors, if there are any. - /// Useful for when you don't need the result, but still need - /// to keep track of possible errors - pub fn consume(&mut self, input: &str) -> Result<(), EvalAltResult> { - self.consume_with_scope(&mut Scope::new(), input) - } - - /// Evaluate a string with own scope, but only return errors, if there are any. - /// Useful for when you don't need the result, but still need - /// to keep track of possible errors - pub fn consume_with_scope( - &mut self, - scope: &mut Scope, - input: &str, - ) -> Result<(), EvalAltResult> { - let tokens = lex(input); - - let mut peekables = tokens.peekable(); - let tree = parse(&mut peekables); - - match tree { - Ok(AST(ref os, ref fns)) => { - for f in fns { - if f.params.len() > 6 { - return Ok(()); - } - let name = f.name.clone(); - let local_f = f.clone(); - - let spec = FnSpec { - ident: name, - args: None, - }; - - self.fns.insert(spec, Arc::new(FnIntExt::Int(local_f))); - } - - for o in os { - if let Err(e) = self.eval_stmt(scope, o) { - return Err(e); - } - } - - Ok(()) - } - Err(_) => Err(EvalAltResult::ErrorFunctionArgMismatch), - } - } - - /// Register the default library. That means, numeric types, char, bool - /// String, arithmetics and string concatenations. - pub fn register_default_lib(engine: &mut Engine) { - macro_rules! reg_op { - ($engine:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( - $( - $engine.register_fn($x, $op as fn(x: $y, y: $y)->$y); - )* - ) - } - - macro_rules! reg_un { - ($engine:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( - $( - $engine.register_fn($x, $op as fn(x: $y)->$y); - )* - ) - } - - macro_rules! reg_cmp { - ($engine:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( - $( - $engine.register_fn($x, $op as fn(x: $y, y: $y)->bool); - )* - ) - } - - macro_rules! reg_func1 { - ($engine:expr, $x:expr, $op:expr, $r:ty, $( $y:ty ),*) => ( - $( - $engine.register_fn($x, $op as fn(x: $y)->$r); - )* - ) - } - - macro_rules! reg_func2x { - ($engine:expr, $x:expr, $op:expr, $v:ty, $r:ty, $( $y:ty ),*) => ( - $( - $engine.register_fn($x, $op as fn(x: $v, y: $y)->$r); - )* - ) - } - - macro_rules! reg_func2y { - ($engine:expr, $x:expr, $op:expr, $v:ty, $r:ty, $( $y:ty ),*) => ( - $( - $engine.register_fn($x, $op as fn(y: $y, x: $v)->$r); - )* - ) - } - - fn add(x: T, y: T) -> ::Output { - x + y - } - fn sub(x: T, y: T) -> ::Output { - x - y - } - fn mul(x: T, y: T) -> ::Output { - x * y - } - fn div(x: T, y: T) -> ::Output { - x / y - } - fn neg(x: T) -> ::Output { - -x - } - fn lt(x: T, y: T) -> bool { - x < y - } - fn lte(x: T, y: T) -> bool { - x <= y - } - fn gt(x: T, y: T) -> bool { - x > y - } - fn gte(x: T, y: T) -> bool { - x >= y - } - fn eq(x: T, y: T) -> bool { - x == y - } - fn ne(x: T, y: T) -> bool { - x != y - } - fn and(x: bool, y: bool) -> bool { - x && y - } - fn or(x: bool, y: bool) -> bool { - x || y - } - fn not(x: bool) -> bool { - !x - } - fn concat(x: String, y: String) -> String { - x + &y - } - fn binary_and(x: T, y: T) -> ::Output { - x & y - } - fn binary_or(x: T, y: T) -> ::Output { - x | y - } - fn binary_xor(x: T, y: T) -> ::Output { - x ^ y - } - fn left_shift>(x: T, y: T) -> >::Output { - x.shl(y) - } - fn right_shift>(x: T, y: T) -> >::Output { - x.shr(y) - } - fn modulo>(x: T, y: T) -> >::Output { - x % y - } - fn pow_i64_i64(x: i64, y: i64) -> i64 { - x.pow(y as u32) - } - fn pow_f64_f64(x: f64, y: f64) -> f64 { - x.powf(y) - } - fn pow_f64_i64(x: f64, y: i64) -> f64 { - x.powi(y as i32) - } - fn unit_eq(_a: (), _b: ()) -> bool { - true - } - - reg_op!(engine, "+", add, i32, i64, u32, u64, f32, f64); - reg_op!(engine, "-", sub, i32, i64, u32, u64, f32, f64); - reg_op!(engine, "*", mul, i32, i64, u32, u64, f32, f64); - reg_op!(engine, "/", div, i32, i64, u32, u64, f32, f64); - - reg_cmp!(engine, "<", lt, i32, i64, u32, u64, String, f64); - reg_cmp!(engine, "<=", lte, i32, i64, u32, u64, String, f64); - reg_cmp!(engine, ">", gt, i32, i64, u32, u64, String, f64); - reg_cmp!(engine, ">=", gte, i32, i64, u32, u64, String, f64); - reg_cmp!(engine, "==", eq, i32, i64, u32, u64, bool, String, f64); - reg_cmp!(engine, "!=", ne, i32, i64, u32, u64, bool, String, f64); - - reg_op!(engine, "||", or, bool); - reg_op!(engine, "&&", and, bool); - reg_op!(engine, "|", binary_or, i32, i64, u32, u64); - reg_op!(engine, "|", or, bool); - reg_op!(engine, "&", binary_and, i32, i64, u32, u64); - reg_op!(engine, "&", and, bool); - reg_op!(engine, "^", binary_xor, i32, i64, u32, u64); - reg_op!(engine, "<<", left_shift, i32, i64, u32, u64); - reg_op!(engine, ">>", right_shift, i32, i64, u32, u64); - reg_op!(engine, "%", modulo, i32, i64, u32, u64); - engine.register_fn("~", pow_i64_i64); - engine.register_fn("~", pow_f64_f64); - engine.register_fn("~", pow_f64_i64); - - reg_un!(engine, "-", neg, i32, i64, f32, f64); - reg_un!(engine, "!", not, bool); - - engine.register_fn("+", concat); - engine.register_fn("==", unit_eq); - - // engine.register_fn("[]", idx); - // FIXME? Registering array lookups are a special case because we want to return boxes - // directly let ent = engine.fns.entry("[]".to_string()).or_insert_with(Vec::new); - // (*ent).push(FnType::ExternalFn2(Box::new(idx))); - - // Register print and debug - fn print_debug(x: T) { - println!("{:?}", x); - } - fn print(x: T) { - println!("{}", x); - } - - reg_func1!(engine, "print", print, (), i32, i64, u32, u64); - reg_func1!(engine, "print", print, (), f32, f64, bool, String); - reg_func1!(engine, "print", print_debug, (), Array); - engine.register_fn("print", |_: ()| println!()); - - reg_func1!(engine, "debug", print_debug, (), i32, i64, u32, u64); - reg_func1!(engine, "debug", print_debug, (), f32, f64, bool, String); - reg_func1!(engine, "debug", print_debug, (), Array, ()); - - // Register array functions - fn push(list: &mut Array, item: T) { - list.push(Box::new(item)); - } - - reg_func2x!(engine, "push", push, &mut Array, (), i32, i64, u32, u64); - reg_func2x!(engine, "push", push, &mut Array, (), f32, f64, bool); - reg_func2x!(engine, "push", push, &mut Array, (), String, Array, ()); - - engine.register_box_fn("pop", |list: &mut Array| list.pop().unwrap()); - engine.register_box_fn("shift", |list: &mut Array| list.remove(0)); - engine.register_fn("len", |list: &mut Array| -> i64 { - list.len().try_into().unwrap() - }); - - // Register string concatenate functions - fn prepend(x: T, y: String) -> String { - format!("{}{}", x, y) - } - fn append(x: String, y: T) -> String { - format!("{}{}", x, y) - } - - reg_func2x!(engine, "+", append, String, String, i32, i64, u32, u64, f32, f64, bool); - engine.register_fn("+", |x: String, y: Array| format!("{}{:?}", x, y)); - engine.register_fn("+", |x: String, _: ()| format!("{}", x)); - - reg_func2y!(engine, "+", prepend, String, String, i32, i64, u32, u64, f32, f64, bool); - engine.register_fn("+", |x: Array, y: String| format!("{:?}{}", x, y)); - engine.register_fn("+", |_: (), y: String| format!("{}", y)); - - // Register array iterator - engine.register_iterator::(|a| { - Box::new(a.downcast_ref::().unwrap().clone().into_iter()) - }); - - // Register range function - use std::ops::Range; - engine.register_iterator::, _>(|a| { - Box::new( - a.downcast_ref::>() - .unwrap() - .clone() - .map(|n| Box::new(n) as Box), - ) - }); - - engine.register_fn("range", |i1: i64, i2: i64| (i1..i2)); + pub(crate) fn map_type_name(&self, name: String) -> String { + self.type_names + .get(&name) + .map(|x| x.clone()) + .unwrap_or(name.to_string()) } /// Make a new engine pub fn new() -> Engine { + // User-friendly names for built-in types + let type_names = [ + ("alloc::string::String", "string"), + ( + "alloc::vec::Vec>", + "array", + ), + ("alloc::boxed::Box", "dynamic"), + ] + .iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect(); + let mut engine = Engine { fns: HashMap::new(), + script_fns: HashMap::new(), type_iterators: HashMap::new(), + type_names, + on_print: Box::new(|x: &str| println!("{}", x)), + on_debug: Box::new(|x: &str| println!("{}", x)), }; - Engine::register_default_lib(&mut engine); + engine.register_builtins(); engine } diff --git a/src/fn_register.rs b/src/fn_register.rs index d1bb6b6e..f340f5bc 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -1,13 +1,14 @@ use std::any::TypeId; -use crate::any::Any; -use crate::engine::{Engine, EvalAltResult}; +use crate::any::{Any, Dynamic}; +use crate::engine::{Engine, EvalAltResult, FnCallArgs}; +use crate::parser::Position; pub trait RegisterFn { fn register_fn(&mut self, name: &str, f: FN); } -pub trait RegisterBoxFn { - fn register_box_fn(&mut self, name: &str, f: FN); +pub trait RegisterDynamicFn { + fn register_dynamic_fn(&mut self, name: &str, f: FN); } pub struct Ref(A); @@ -23,63 +24,69 @@ macro_rules! def_register { def_register!(imp); }; (imp $($par:ident => $mark:ty => $param:ty => $clone:expr),*) => { - impl<$($par,)* FN, RET> RegisterFn for Engine - where + impl< $($par: Any + Clone,)* FN: Fn($($param),*) -> RET + 'static, - RET: Any, + RET: Any + > RegisterFn for Engine { fn register_fn(&mut self, name: &str, f: FN) { - let fun = move |mut args: Vec<&mut dyn Any>| { + 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. - if args.len() != count_args!($($par)*) { - return Err(EvalAltResult::ErrorFunctionArgMismatch); + const NUM_ARGS: usize = count_args!($($par)*); + + if args.len() != NUM_ARGS { + return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, pos)); } #[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() as Option<&mut $par>) - .ok_or(EvalAltResult::ErrorFunctionArgMismatch)?; + let $par = ((*drain.next().unwrap()).downcast_mut() as Option<&mut $par>).unwrap(); )* // Call the user-supplied function using ($clone) to // potentially clone the value, otherwise pass the reference. let r = f($(($clone)($par)),*); - Ok(Box::new(r) as Box) + Ok(Box::new(r) as Dynamic) }; - self.register_fn_raw(name.to_owned(), Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun)); + self.register_fn_raw(name.into(), Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun)); } } - impl<$($par,)* FN> RegisterBoxFn for Engine - where + impl< $($par: Any + Clone,)* - FN: Fn($($param),*) -> Box + 'static + FN: Fn($($param),*) -> Dynamic + 'static, + > RegisterDynamicFn for Engine { - fn register_box_fn(&mut self, name: &str, f: FN) { - let fun = move |mut args: Vec<&mut dyn Any>| { + fn register_dynamic_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. - if args.len() != count_args!($($par)*) { - return Err(EvalAltResult::ErrorFunctionArgMismatch); + const NUM_ARGS: usize = count_args!($($par)*); + + if args.len() != NUM_ARGS { + return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, pos)); } #[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() as Option<&mut $par>) - .ok_or(EvalAltResult::ErrorFunctionArgMismatch)?; + let $par = ((*drain.next().unwrap()).downcast_mut() as Option<&mut $par>).unwrap(); )* // Call the user-supplied function using ($clone) to // potentially clone the value, otherwise pass the reference. Ok(f($(($clone)($par)),*)) }; - self.register_fn_raw(name.to_owned(), Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun)); + self.register_fn_raw(name.into(), Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun)); } } diff --git a/src/lib.rs b/src/lib.rs index c002e814..a7ac4d8e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,7 +25,7 @@ //! //! let mut engine = Engine::new(); //! engine.register_fn("compute_something", compute_something); -//! assert_eq!(engine.eval_file::("my_script.rhai"), Ok(true)); +//! assert_eq!(engine.eval_file::("my_script.rhai").unwrap(), true); //! ``` //! //! [Check out the README on GitHub for more information!](https://github.com/jonathandturner/rhai) @@ -40,12 +40,14 @@ macro_rules! debug_println { } mod any; +mod api; +mod builtin; mod call; mod engine; mod fn_register; mod parser; -pub use any::Any; -pub use engine::{Engine, EvalAltResult, Scope}; -pub use fn_register::{RegisterBoxFn, RegisterFn}; +pub use any::Dynamic; +pub use engine::{Array, Engine, EvalAltResult, Scope}; +pub use fn_register::{RegisterDynamicFn, RegisterFn}; pub use parser::{ParseError, ParseErrorType, AST}; diff --git a/src/parser.rs b/src/parser.rs index e8cfa364..2037679c 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,3 +1,4 @@ +use crate::Dynamic; use std::char; use std::error::Error; use std::fmt; @@ -7,6 +8,7 @@ use std::str::Chars; #[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)] pub enum LexError { UnexpectedChar(char), + UnterminatedString, MalformedEscapeSequence, MalformedNumber, MalformedChar, @@ -19,6 +21,7 @@ impl Error for LexError { fn description(&self) -> &str { match *self { LERR::UnexpectedChar(_) => "Unexpected character", + LERR::UnterminatedString => "Open string is not terminated", LERR::MalformedEscapeSequence => "Unexpected values in escape sequence", LERR::MalformedNumber => "Unexpected characters in number", LERR::MalformedChar => "Char constant not a single character", @@ -47,25 +50,63 @@ pub enum ParseErrorType { MissingRightBracket, MalformedCallExpr, MalformedIndexExpr, - VarExpectsIdentifier(Token), - FnMissingName(Token), + VarExpectsIdentifier, + FnMissingName, FnMissingParams, } type PERR = ParseErrorType; +#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] +pub struct Position { + pub line: usize, + pub pos: usize, +} + +impl Position { + pub fn advance(&mut self) { + self.pos += 1; + } + pub fn rewind(&mut self) { + // Beware, should not rewind at zero position + self.pos -= 1; + } + pub fn new_line(&mut self) { + self.line += 1; + self.pos = 0; + } + pub fn eof() -> Self { + Self { line: 0, pos: 0 } + } + pub fn is_eof(&self) -> bool { + self.line == 0 + } +} + +impl std::fmt::Display for Position { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { + match self.line { + 0 => write!(f, "EOF"), + _ => write!(f, "line {}, position {}", self.line, self.pos), + } + } +} + #[derive(Debug, PartialEq, Clone)] -pub struct ParseError(ParseErrorType, usize, usize); +pub struct ParseError(PERR, Position); impl ParseError { - pub fn error_type(&self) -> &ParseErrorType { + pub fn error_type(&self) -> &PERR { &self.0 } pub fn line(&self) -> usize { - self.1 + self.1.line } pub fn position(&self) -> usize { - self.2 + self.1.pos + } + pub fn is_eof(&self) -> bool { + self.1.is_eof() } } @@ -81,8 +122,8 @@ impl Error for ParseError { PERR::MissingRightBracket => "Expecting ']'", PERR::MalformedCallExpr => "Invalid expression in function call arguments", PERR::MalformedIndexExpr => "Invalid index in indexing expression", - PERR::VarExpectsIdentifier(_) => "Expecting name of a variable", - PERR::FnMissingName(_) => "Expecting name in function declaration", + PERR::VarExpectsIdentifier => "Expecting name of a variable", + PERR::FnMissingName => "Expecting name in function declaration", PERR::FnMissingParams => "Expecting parameters in function declaration", } } @@ -95,73 +136,83 @@ impl Error for ParseError { impl fmt::Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.0 { - PERR::BadInput(ref s) => { - write!(f, "{}", s)?; - } - PERR::VarExpectsIdentifier(ref token) | PERR::FnMissingName(ref token) => match token { - Token::None => write!(f, "{}", self.description())?, - _ => write!( - f, - "{} (but gets {:?} token instead)", - self.description(), - token - )?, - }, + PERR::BadInput(ref s) => write!(f, "{}", s)?, _ => write!(f, "{}", self.description())?, } - if self.line() > 0 { - write!(f, " at line {}, position {}", self.line(), self.position()) + if !self.is_eof() { + write!(f, " ({})", self.1) } else { - write!(f, " but script is incomplete") + write!(f, " at the end of the script but there is no more input") } } } pub struct AST(pub(crate) Vec, pub(crate) Vec); -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, Clone)] pub struct FnDef { pub name: String, pub params: Vec, pub body: Box, + pub pos: Position, } -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, Clone)] pub enum Stmt { - If(Box, Box), - IfElse(Box, Box, Box), + IfElse(Box, Box, Option>), While(Box, Box), Loop(Box), For(String, Box, Box), - Let(String, Option>), + Let(String, Option>, Position), Block(Vec), Expr(Box), - Break, - Return, - ReturnWithVal(Box), + Break(Position), + Return(Position), + ReturnWithVal(Box, Position), } -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, Clone)] pub enum Expr { - IntegerConstant(i64), - FloatConstant(f64), - Identifier(String), - CharConstant(char), - StringConstant(String), - FunctionCall(String, Vec), + IntegerConstant(i64, Position), + FloatConstant(f64, Position), + Identifier(String, Position), + CharConstant(char, Position), + StringConstant(String, Position), + FunctionCall(String, Vec, Option, Position), Assignment(Box, Box), Dot(Box, Box), - Index(String, Box), - Array(Vec), - True, - False, - Unit, + Index(String, Box, Position), + Array(Vec, Position), + And(Box, Box), + Or(Box, Box), + True(Position), + False(Position), + Unit(Position), +} + +impl Expr { + pub fn position(&self) -> Position { + match self { + Expr::IntegerConstant(_, pos) + | Expr::FloatConstant(_, pos) + | Expr::Identifier(_, pos) + | Expr::CharConstant(_, pos) + | Expr::StringConstant(_, pos) + | Expr::FunctionCall(_, _, _, pos) + | Expr::Index(_, _, pos) + | Expr::Array(_, pos) + | Expr::True(pos) + | Expr::False(pos) + | Expr::Unit(pos) => *pos, + + Expr::Assignment(_, _) | Expr::Dot(_, _) | Expr::And(_, _) | Expr::Or(_, _) => panic!(), + } + } } #[derive(Debug, PartialEq, Clone)] pub enum Token { - None, IntegerConstant(i64), FloatConstant(f64), Identifier(String), @@ -327,34 +378,38 @@ impl Token { pub struct TokenIterator<'a> { last: Token, - line: usize, - pos: usize, + pos: Position, char_stream: Peekable>, } impl<'a> TokenIterator<'a> { fn advance(&mut self) { - self.pos += 1; + self.pos.advance(); } - fn advance_line(&mut self) { - self.line += 1; - self.pos = 0; + fn rewind(&mut self) { + self.pos.rewind(); + } + fn new_line(&mut self) { + self.pos.new_line(); } pub fn parse_string_const( &mut self, enclosing_char: char, - ) -> Result { + ) -> Result { let mut result = Vec::new(); let mut escape = false; - while let Some(nxt) = self.char_stream.next() { - self.advance(); - if nxt == '\n' { - self.advance_line(); + loop { + let next_char = self.char_stream.next(); + + if next_char.is_none() { + return Err((LERR::UnterminatedString, Position::eof())); } - match nxt { + self.advance(); + + match next_char.unwrap() { '\\' if !escape => escape = true, '\\' if escape => { escape = false; @@ -381,10 +436,10 @@ impl<'a> TokenIterator<'a> { out_val *= 16; out_val += d1; } else { - return Err((LERR::MalformedEscapeSequence, self.line, self.pos)); + return Err((LERR::MalformedEscapeSequence, self.pos)); } } else { - return Err((LERR::MalformedEscapeSequence, self.line, self.pos)); + return Err((LERR::MalformedEscapeSequence, self.pos)); } self.advance(); } @@ -392,7 +447,7 @@ impl<'a> TokenIterator<'a> { if let Some(r) = char::from_u32(out_val) { result.push(r); } else { - return Err((LERR::MalformedEscapeSequence, self.line, self.pos)); + return Err((LERR::MalformedEscapeSequence, self.pos)); } } 'u' if escape => { @@ -404,10 +459,10 @@ impl<'a> TokenIterator<'a> { out_val *= 16; out_val += d1; } else { - return Err((LERR::MalformedEscapeSequence, self.line, self.pos)); + return Err((LERR::MalformedEscapeSequence, self.pos)); } } else { - return Err((LERR::MalformedEscapeSequence, self.line, self.pos)); + return Err((LERR::MalformedEscapeSequence, self.pos)); } self.advance(); } @@ -415,7 +470,7 @@ impl<'a> TokenIterator<'a> { if let Some(r) = char::from_u32(out_val) { result.push(r); } else { - return Err((LERR::MalformedEscapeSequence, self.line, self.pos)); + return Err((LERR::MalformedEscapeSequence, self.pos)); } } 'U' if escape => { @@ -427,10 +482,10 @@ impl<'a> TokenIterator<'a> { out_val *= 16; out_val += d1; } else { - return Err((LERR::MalformedEscapeSequence, self.line, self.pos)); + return Err((LERR::MalformedEscapeSequence, self.pos)); } } else { - return Err((LERR::MalformedEscapeSequence, self.line, self.pos)); + return Err((LERR::MalformedEscapeSequence, self.pos)); } self.advance(); } @@ -438,12 +493,16 @@ impl<'a> TokenIterator<'a> { if let Some(r) = char::from_u32(out_val) { result.push(r); } else { - return Err((LERR::MalformedEscapeSequence, self.line, self.pos)); + return Err((LERR::MalformedEscapeSequence, self.pos)); } } x if enclosing_char == x && escape => result.push(x), x if enclosing_char == x && !escape => break, - _ if escape => return Err((LERR::MalformedEscapeSequence, self.line, self.pos)), + _ if escape => return Err((LERR::MalformedEscapeSequence, self.pos)), + '\n' => { + self.rewind(); + return Err((LERR::UnterminatedString, self.pos)); + } x => { escape = false; result.push(x); @@ -455,15 +514,14 @@ impl<'a> TokenIterator<'a> { Ok(out) } - fn inner_next(&mut self) -> Option<(Token, usize, usize)> { + fn inner_next(&mut self) -> Option<(Token, Position)> { while let Some(c) = self.char_stream.next() { self.advance(); - let line = self.line; let pos = self.pos; match c { - '\n' => self.advance_line(), + '\n' => self.new_line(), '0'..='9' => { let mut result = Vec::new(); let mut radix_base: Option = None; @@ -551,7 +609,7 @@ impl<'a> TokenIterator<'a> { .filter(|c| c != &'_') .collect(); if let Ok(val) = i64::from_str_radix(&out, radix) { - return Some((Token::IntegerConstant(val), line, pos)); + return Some((Token::IntegerConstant(val), pos)); } } @@ -565,7 +623,6 @@ impl<'a> TokenIterator<'a> { } else { Token::LexErr(LERR::MalformedNumber) }, - line, pos, )); } @@ -587,7 +644,7 @@ impl<'a> TokenIterator<'a> { let out: String = result.iter().cloned().collect(); return Some(( - match out.as_ref() { + match out.as_str() { "true" => Token::True, "false" => Token::False, "let" => Token::Let, @@ -600,16 +657,15 @@ impl<'a> TokenIterator<'a> { "fn" => Token::Fn, "for" => Token::For, "in" => Token::In, - x => Token::Identifier(x.to_string()), + x => Token::Identifier(x.into()), }, - line, pos, )); } '"' => { return match self.parse_string_const('"') { - Ok(out) => Some((Token::StringConst(out), line, pos)), - Err(e) => Some((Token::LexErr(e.0), e.1, e.2)), + Ok(out) => Some((Token::StringConst(out), pos)), + Err(e) => Some((Token::LexErr(e.0), e.1)), } } '\'' => match self.parse_string_const('\'') { @@ -626,18 +682,17 @@ impl<'a> TokenIterator<'a> { } else { Token::LexErr(LERR::MalformedChar) }, - line, pos, )); } - Err(e) => return Some((Token::LexErr(e.0), e.1, e.2)), + Err(e) => return Some((Token::LexErr(e.0), e.1)), }, - '{' => return Some((Token::LeftBrace, line, pos)), - '}' => return Some((Token::RightBrace, line, pos)), - '(' => return Some((Token::LeftParen, line, pos)), - ')' => return Some((Token::RightParen, line, pos)), - '[' => return Some((Token::LeftBracket, line, pos)), - ']' => return Some((Token::RightBracket, line, pos)), + '{' => return Some((Token::LeftBrace, pos)), + '}' => return Some((Token::RightBrace, pos)), + '(' => return Some((Token::LeftParen, pos)), + ')' => return Some((Token::RightParen, pos)), + '[' => return Some((Token::LeftBracket, pos)), + ']' => return Some((Token::RightBracket, pos)), '+' => { return Some(( match self.char_stream.peek() { @@ -649,7 +704,6 @@ impl<'a> TokenIterator<'a> { _ if self.last.is_next_unary() => Token::UnaryPlus, _ => Token::Plus, }, - line, pos, )) } @@ -664,7 +718,6 @@ impl<'a> TokenIterator<'a> { _ if self.last.is_next_unary() => Token::UnaryMinus, _ => Token::Minus, }, - line, pos, )) } @@ -678,7 +731,6 @@ impl<'a> TokenIterator<'a> { } _ => Token::Multiply, }, - line, pos, )) } @@ -688,7 +740,7 @@ impl<'a> TokenIterator<'a> { self.advance(); while let Some(c) = self.char_stream.next() { if c == '\n' { - self.advance_line(); + self.new_line(); break; } else { self.advance(); @@ -715,7 +767,7 @@ impl<'a> TokenIterator<'a> { } self.advance(); } - '\n' => self.advance_line(), + '\n' => self.new_line(), _ => (), } @@ -727,27 +779,27 @@ impl<'a> TokenIterator<'a> { Some(&'=') => { self.char_stream.next(); self.advance(); - return Some((Token::DivideAssign, line, pos)); + return Some((Token::DivideAssign, pos)); } - _ => return Some((Token::Divide, line, pos)), + _ => return Some((Token::Divide, pos)), }, - ';' => return Some((Token::SemiColon, line, pos)), - ':' => return Some((Token::Colon, line, pos)), - ',' => return Some((Token::Comma, line, pos)), - '.' => return Some((Token::Period, line, pos)), + ';' => return Some((Token::SemiColon, pos)), + ':' => return Some((Token::Colon, pos)), + ',' => return Some((Token::Comma, pos)), + '.' => return Some((Token::Period, pos)), '=' => match self.char_stream.peek() { Some(&'=') => { self.char_stream.next(); self.advance(); - return Some((Token::EqualsTo, line, pos)); + return Some((Token::EqualsTo, pos)); } - _ => return Some((Token::Equals, line, pos)), + _ => return Some((Token::Equals, pos)), }, '<' => match self.char_stream.peek() { Some(&'=') => { self.char_stream.next(); self.advance(); - return Some((Token::LessThanEqualsTo, line, pos)); + return Some((Token::LessThanEqualsTo, pos)); } Some(&'<') => { self.char_stream.next(); @@ -756,16 +808,16 @@ impl<'a> TokenIterator<'a> { Some(&'=') => { self.char_stream.next(); self.advance(); - Some((Token::LeftShiftAssign, line, pos)) + Some((Token::LeftShiftAssign, pos)) } _ => { self.char_stream.next(); self.advance(); - Some((Token::LeftShift, line, pos)) + Some((Token::LeftShift, pos)) } }; } - _ => return Some((Token::LessThan, line, pos)), + _ => return Some((Token::LessThan, pos)), }, '>' => { return Some(( @@ -793,7 +845,6 @@ impl<'a> TokenIterator<'a> { } _ => Token::GreaterThan, }, - line, pos, )) } @@ -807,7 +858,6 @@ impl<'a> TokenIterator<'a> { } _ => Token::Bang, }, - line, pos, )) } @@ -826,7 +876,6 @@ impl<'a> TokenIterator<'a> { } _ => Token::Pipe, }, - line, pos, )) } @@ -845,7 +894,6 @@ impl<'a> TokenIterator<'a> { } _ => Token::Ampersand, }, - line, pos, )) } @@ -859,7 +907,6 @@ impl<'a> TokenIterator<'a> { } _ => Token::XOr, }, - line, pos, )) } @@ -873,7 +920,6 @@ impl<'a> TokenIterator<'a> { } _ => Token::Modulo, }, - line, pos, )) } @@ -887,12 +933,11 @@ impl<'a> TokenIterator<'a> { } _ => Token::PowerOf, }, - line, pos, )) } x if x.is_whitespace() => (), - x => return Some((Token::LexErr(LERR::UnexpectedChar(x)), line, pos)), + x => return Some((Token::LexErr(LERR::UnexpectedChar(x)), pos)), } } @@ -901,7 +946,7 @@ impl<'a> TokenIterator<'a> { } impl<'a> Iterator for TokenIterator<'a> { - type Item = (Token, usize, usize); + type Item = (Token, Position); // TODO - perhaps this could be optimized? fn next(&mut self) -> Option { @@ -915,13 +960,12 @@ impl<'a> Iterator for TokenIterator<'a> { pub fn lex(input: &str) -> TokenIterator<'_> { TokenIterator { last: Token::LexErr(LERR::Nothing), - line: 1, - pos: 0, + pos: Position { line: 1, pos: 0 }, char_stream: input.chars().peekable(), } } -fn get_precedence(token: &Token) -> i32 { +fn get_precedence(token: &Token) -> i8 { match *token { Token::Equals | Token::PlusAssign @@ -952,11 +996,14 @@ fn get_precedence(token: &Token) -> i32 { } } -fn parse_paren_expr<'a>(input: &mut Peekable>) -> Result { +fn parse_paren_expr<'a>( + input: &mut Peekable>, + begin: Position, +) -> Result { match input.peek() { - Some((Token::RightParen, _, _)) => { + Some((Token::RightParen, _)) => { input.next(); - return Ok(Expr::Unit); + return Ok(Expr::Unit(begin)); } _ => (), } @@ -964,39 +1011,34 @@ fn parse_paren_expr<'a>(input: &mut Peekable>) -> Result Ok(expr), - _ => Err(ParseError(PERR::MissingRightParen, 0, 0)), + Some((Token::RightParen, _)) => Ok(expr), + _ => Err(ParseError(PERR::MissingRightParen, Position::eof())), } } fn parse_call_expr<'a>( id: String, input: &mut Peekable>, + begin: Position, ) -> Result { let mut args = Vec::new(); - if let Some(&(Token::RightParen, _, _)) = input.peek() { + if let Some(&(Token::RightParen, _)) = input.peek() { input.next(); - return Ok(Expr::FunctionCall(id, args)); + return Ok(Expr::FunctionCall(id, args, None, begin)); } loop { - match parse_expr(input) { - Ok(arg) => args.push(arg), - Err(mut err) => { - err.0 = PERR::MalformedCallExpr; - return Err(err); - } - } + args.push(parse_expr(input)?); match input.peek() { - Some(&(Token::RightParen, _, _)) => { + Some(&(Token::RightParen, _)) => { input.next(); - return Ok(Expr::FunctionCall(id, args)); + return Ok(Expr::FunctionCall(id, args, None, begin)); } - Some(&(Token::Comma, _, _)) => (), - Some(&(_, line, pos)) => return Err(ParseError(PERR::MalformedCallExpr, line, pos)), - None => return Err(ParseError(PERR::MalformedCallExpr, 0, 0)), + Some(&(Token::Comma, _)) => (), + Some(&(_, pos)) => return Err(ParseError(PERR::MalformedCallExpr, pos)), + None => return Err(ParseError(PERR::MalformedCallExpr, Position::eof())), } input.next(); @@ -1006,15 +1048,16 @@ fn parse_call_expr<'a>( fn parse_index_expr<'a>( id: String, input: &mut Peekable>, + begin: Position, ) -> Result { match parse_expr(input) { Ok(idx) => match input.peek() { - Some(&(Token::RightBracket, _, _)) => { + Some(&(Token::RightBracket, _)) => { input.next(); - return Ok(Expr::Index(id, Box::new(idx))); + return Ok(Expr::Index(id, Box::new(idx), begin)); } - Some(&(_, line, pos)) => return Err(ParseError(PERR::MalformedIndexExpr, line, pos)), - None => return Err(ParseError(PERR::MalformedIndexExpr, 0, 0)), + Some(&(_, pos)) => return Err(ParseError(PERR::MalformedIndexExpr, pos)), + None => return Err(ParseError(PERR::MalformedIndexExpr, Position::eof())), }, Err(mut err) => { err.0 = PERR::MalformedIndexExpr; @@ -1026,86 +1069,94 @@ fn parse_index_expr<'a>( fn parse_ident_expr<'a>( id: String, input: &mut Peekable>, + begin: Position, ) -> Result { match input.peek() { - Some(&(Token::LeftParen, _, _)) => { + Some(&(Token::LeftParen, pos)) => { input.next(); - parse_call_expr(id, input) + parse_call_expr(id, input, pos) } - Some(&(Token::LeftBracket, _, _)) => { + Some(&(Token::LeftBracket, pos)) => { input.next(); - parse_index_expr(id, input) + parse_index_expr(id, input, pos) } - _ => Ok(Expr::Identifier(id)), + Some(_) => Ok(Expr::Identifier(id, begin)), + None => Ok(Expr::Identifier(id, Position::eof())), } } -fn parse_array_expr<'a>(input: &mut Peekable>) -> Result { +fn parse_array_expr<'a>( + input: &mut Peekable>, + begin: Position, +) -> Result { let mut arr = Vec::new(); let skip_contents = match input.peek() { - Some(&(Token::RightBracket, _, _)) => true, + Some(&(Token::RightBracket, _)) => true, _ => false, }; if !skip_contents { while let Some(_) = input.peek() { arr.push(parse_expr(input)?); - if let Some(&(Token::Comma, _, _)) = input.peek() { + + if let Some(&(Token::Comma, _)) = input.peek() { input.next(); } - if let Some(&(Token::RightBracket, _, _)) = input.peek() { + if let Some(&(Token::RightBracket, _)) = input.peek() { break; } } } match input.peek() { - Some(&(Token::RightBracket, _, _)) => { + Some(&(Token::RightBracket, _)) => { input.next(); - Ok(Expr::Array(arr)) + Ok(Expr::Array(arr, begin)) } - Some(&(_, line, pos)) => Err(ParseError(PERR::MissingRightBracket, line, pos)), - None => Err(ParseError(PERR::MissingRightBracket, 0, 0)), + Some(&(_, pos)) => Err(ParseError(PERR::MissingRightBracket, pos)), + None => Err(ParseError(PERR::MissingRightBracket, Position::eof())), } } fn parse_primary<'a>(input: &mut Peekable>) -> Result { match input.next() { - Some((token, line, pos)) => match token { - Token::IntegerConstant(ref x) => Ok(Expr::IntegerConstant(*x)), - Token::FloatConstant(ref x) => Ok(Expr::FloatConstant(*x)), - Token::StringConst(ref s) => Ok(Expr::StringConstant(s.clone())), - Token::CharConstant(ref c) => Ok(Expr::CharConstant(*c)), - Token::Identifier(ref s) => parse_ident_expr(s.clone(), input), - Token::LeftParen => parse_paren_expr(input), - Token::LeftBracket => parse_array_expr(input), - Token::True => Ok(Expr::True), - Token::False => Ok(Expr::False), - Token::LexErr(le) => Err(ParseError(PERR::BadInput(le.to_string()), line, pos)), + Some((token, pos)) => match token { + Token::IntegerConstant(x) => Ok(Expr::IntegerConstant(x, pos)), + Token::FloatConstant(x) => Ok(Expr::FloatConstant(x, pos)), + Token::StringConst(s) => Ok(Expr::StringConstant(s, pos)), + Token::CharConstant(c) => Ok(Expr::CharConstant(c, pos)), + Token::Identifier(s) => parse_ident_expr(s, input, pos), + Token::LeftParen => parse_paren_expr(input, pos), + Token::LeftBracket => parse_array_expr(input, pos), + Token::True => Ok(Expr::True(pos)), + Token::False => Ok(Expr::False(pos)), + Token::LexErr(le) => Err(ParseError(PERR::BadInput(le.to_string()), pos)), _ => Err(ParseError( PERR::BadInput(format!("Unexpected {:?} token", token)), - line, pos, )), }, - None => Err(ParseError(PERR::InputPastEndOfFile, 0, 0)), + None => Err(ParseError(PERR::InputPastEndOfFile, Position::eof())), } } fn parse_unary<'a>(input: &mut Peekable>) -> Result { - let token = match input.peek() { - Some((tok, _, _)) => tok.clone(), - None => return Err(ParseError(PERR::InputPastEndOfFile, 0, 0)), + let (token, pos) = match input.peek() { + Some((tok, tok_pos)) => (tok.clone(), *tok_pos), + None => return Err(ParseError(PERR::InputPastEndOfFile, Position::eof())), }; match token { Token::UnaryMinus => { input.next(); + Ok(Expr::FunctionCall( - "-".to_string(), + "-".into(), vec![parse_primary(input)?], + None, + pos, )) } Token::UnaryPlus => { @@ -1114,9 +1165,12 @@ fn parse_unary<'a>(input: &mut Peekable>) -> Result { input.next(); + Ok(Expr::FunctionCall( - "!".to_string(), + "!".into(), vec![parse_primary(input)?], + Some(Box::new(false)), // NOT operator, when operating on invalid operand, defaults to false + pos, )) } _ => parse_primary(input), @@ -1125,7 +1179,7 @@ fn parse_unary<'a>(input: &mut Peekable>) -> Result( input: &mut Peekable>, - prec: i32, + prec: i8, lhs: Expr, ) -> Result { let mut lhs_curr = lhs; @@ -1133,7 +1187,7 @@ fn parse_binop<'a>( loop { let mut curr_prec = -1; - if let Some(&(ref curr_op, _, _)) = input.peek() { + if let Some(&(ref curr_op, _)) = input.peek() { curr_prec = get_precedence(curr_op); } @@ -1141,12 +1195,14 @@ fn parse_binop<'a>( return Ok(lhs_curr); } - if let Some((op_token, line, pos)) = input.next() { + if let Some((op_token, pos)) = input.next() { + input.peek(); + let mut rhs = parse_unary(input)?; let mut next_prec = -1; - if let Some(&(ref next_op, _, _)) = input.peek() { + if let Some(&(ref next_op, _)) = input.peek() { next_prec = get_precedence(next_op); } @@ -1158,109 +1214,178 @@ fn parse_binop<'a>( } lhs_curr = match op_token { - Token::Plus => Expr::FunctionCall("+".to_string(), vec![lhs_curr, rhs]), - Token::Minus => Expr::FunctionCall("-".to_string(), vec![lhs_curr, rhs]), - Token::Multiply => Expr::FunctionCall("*".to_string(), vec![lhs_curr, rhs]), - Token::Divide => Expr::FunctionCall("/".to_string(), vec![lhs_curr, rhs]), + Token::Plus => Expr::FunctionCall("+".into(), vec![lhs_curr, rhs], None, pos), + Token::Minus => Expr::FunctionCall("-".into(), vec![lhs_curr, rhs], None, pos), + Token::Multiply => Expr::FunctionCall("*".into(), vec![lhs_curr, rhs], None, pos), + Token::Divide => Expr::FunctionCall("/".into(), vec![lhs_curr, rhs], None, pos), + Token::Equals => Expr::Assignment(Box::new(lhs_curr), Box::new(rhs)), Token::PlusAssign => { let lhs_copy = lhs_curr.clone(); Expr::Assignment( Box::new(lhs_curr), - Box::new(Expr::FunctionCall("+".to_string(), vec![lhs_copy, rhs])), + Box::new(Expr::FunctionCall( + "+".into(), + vec![lhs_copy, rhs], + None, + pos, + )), ) } Token::MinusAssign => { let lhs_copy = lhs_curr.clone(); Expr::Assignment( Box::new(lhs_curr), - Box::new(Expr::FunctionCall("-".to_string(), vec![lhs_copy, rhs])), + Box::new(Expr::FunctionCall( + "-".into(), + vec![lhs_copy, rhs], + None, + pos, + )), ) } Token::Period => Expr::Dot(Box::new(lhs_curr), Box::new(rhs)), - Token::EqualsTo => Expr::FunctionCall("==".to_string(), vec![lhs_curr, rhs]), - Token::NotEqualsTo => Expr::FunctionCall("!=".to_string(), vec![lhs_curr, rhs]), - Token::LessThan => Expr::FunctionCall("<".to_string(), vec![lhs_curr, rhs]), + + // Comparison operators default to false when passed invalid operands + Token::EqualsTo => { + Expr::FunctionCall("==".into(), vec![lhs_curr, rhs], Some(Box::new(false)), pos) + } + Token::NotEqualsTo => { + Expr::FunctionCall("!=".into(), vec![lhs_curr, rhs], Some(Box::new(false)), pos) + } + Token::LessThan => { + Expr::FunctionCall("<".into(), vec![lhs_curr, rhs], Some(Box::new(false)), pos) + } Token::LessThanEqualsTo => { - Expr::FunctionCall("<=".to_string(), vec![lhs_curr, rhs]) + Expr::FunctionCall("<=".into(), vec![lhs_curr, rhs], Some(Box::new(false)), pos) + } + Token::GreaterThan => { + Expr::FunctionCall(">".into(), vec![lhs_curr, rhs], Some(Box::new(false)), pos) } - Token::GreaterThan => Expr::FunctionCall(">".to_string(), vec![lhs_curr, rhs]), Token::GreaterThanEqualsTo => { - Expr::FunctionCall(">=".to_string(), vec![lhs_curr, rhs]) + Expr::FunctionCall(">=".into(), vec![lhs_curr, rhs], Some(Box::new(false)), pos) } - Token::Or => Expr::FunctionCall("||".to_string(), vec![lhs_curr, rhs]), - Token::And => Expr::FunctionCall("&&".to_string(), vec![lhs_curr, rhs]), - Token::XOr => Expr::FunctionCall("^".to_string(), vec![lhs_curr, rhs]), + + Token::Or => Expr::Or(Box::new(lhs_curr), Box::new(rhs)), + Token::And => Expr::And(Box::new(lhs_curr), Box::new(rhs)), + Token::XOr => Expr::FunctionCall("^".into(), vec![lhs_curr, rhs], None, pos), Token::OrAssign => { let lhs_copy = lhs_curr.clone(); Expr::Assignment( Box::new(lhs_curr), - Box::new(Expr::FunctionCall("|".to_string(), vec![lhs_copy, rhs])), + Box::new(Expr::FunctionCall( + "|".into(), + vec![lhs_copy, rhs], + None, + pos, + )), ) } Token::AndAssign => { let lhs_copy = lhs_curr.clone(); Expr::Assignment( Box::new(lhs_curr), - Box::new(Expr::FunctionCall("&".to_string(), vec![lhs_copy, rhs])), + Box::new(Expr::FunctionCall( + "&".into(), + vec![lhs_copy, rhs], + None, + pos, + )), ) } Token::XOrAssign => { let lhs_copy = lhs_curr.clone(); Expr::Assignment( Box::new(lhs_curr), - Box::new(Expr::FunctionCall("^".to_string(), vec![lhs_copy, rhs])), + Box::new(Expr::FunctionCall( + "^".into(), + vec![lhs_copy, rhs], + None, + pos, + )), ) } Token::MultiplyAssign => { let lhs_copy = lhs_curr.clone(); Expr::Assignment( Box::new(lhs_curr), - Box::new(Expr::FunctionCall("*".to_string(), vec![lhs_copy, rhs])), + Box::new(Expr::FunctionCall( + "*".into(), + vec![lhs_copy, rhs], + None, + pos, + )), ) } Token::DivideAssign => { let lhs_copy = lhs_curr.clone(); Expr::Assignment( Box::new(lhs_curr), - Box::new(Expr::FunctionCall("/".to_string(), vec![lhs_copy, rhs])), + Box::new(Expr::FunctionCall( + "/".into(), + vec![lhs_copy, rhs], + None, + pos, + )), ) } - Token::Pipe => Expr::FunctionCall("|".to_string(), vec![lhs_curr, rhs]), - Token::LeftShift => Expr::FunctionCall("<<".to_string(), vec![lhs_curr, rhs]), - Token::RightShift => Expr::FunctionCall(">>".to_string(), vec![lhs_curr, rhs]), + Token::Pipe => Expr::FunctionCall("|".into(), vec![lhs_curr, rhs], None, pos), + Token::LeftShift => Expr::FunctionCall("<<".into(), vec![lhs_curr, rhs], None, pos), + Token::RightShift => { + Expr::FunctionCall(">>".into(), vec![lhs_curr, rhs], None, pos) + } Token::LeftShiftAssign => { let lhs_copy = lhs_curr.clone(); Expr::Assignment( Box::new(lhs_curr), - Box::new(Expr::FunctionCall("<<".to_string(), vec![lhs_copy, rhs])), + Box::new(Expr::FunctionCall( + "<<".into(), + vec![lhs_copy, rhs], + None, + pos, + )), ) } Token::RightShiftAssign => { let lhs_copy = lhs_curr.clone(); Expr::Assignment( Box::new(lhs_curr), - Box::new(Expr::FunctionCall(">>".to_string(), vec![lhs_copy, rhs])), + Box::new(Expr::FunctionCall( + ">>".into(), + vec![lhs_copy, rhs], + None, + pos, + )), ) } - Token::Ampersand => Expr::FunctionCall("&".to_string(), vec![lhs_curr, rhs]), - Token::Modulo => Expr::FunctionCall("%".to_string(), vec![lhs_curr, rhs]), + Token::Ampersand => Expr::FunctionCall("&".into(), vec![lhs_curr, rhs], None, pos), + Token::Modulo => Expr::FunctionCall("%".into(), vec![lhs_curr, rhs], None, pos), Token::ModuloAssign => { let lhs_copy = lhs_curr.clone(); Expr::Assignment( Box::new(lhs_curr), - Box::new(Expr::FunctionCall("%".to_string(), vec![lhs_copy, rhs])), + Box::new(Expr::FunctionCall( + "%".into(), + vec![lhs_copy, rhs], + None, + pos, + )), ) } - Token::PowerOf => Expr::FunctionCall("~".to_string(), vec![lhs_curr, rhs]), + Token::PowerOf => Expr::FunctionCall("~".into(), vec![lhs_curr, rhs], None, pos), Token::PowerOfAssign => { let lhs_copy = lhs_curr.clone(); Expr::Assignment( Box::new(lhs_curr), - Box::new(Expr::FunctionCall("~".to_string(), vec![lhs_copy, rhs])), + Box::new(Expr::FunctionCall( + "~".into(), + vec![lhs_copy, rhs], + None, + pos, + )), ) } - _ => return Err(ParseError(PERR::UnknownOperator, line, pos)), + _ => return Err(ParseError(PERR::UnknownOperator, pos)), }; } } @@ -1278,16 +1403,21 @@ fn parse_if<'a>(input: &mut Peekable>) -> Result { + Some(&(Token::Else, _)) => { input.next(); - let else_body = parse_block(input)?; + + let else_body = match input.peek() { + Some(&(Token::If, _)) => parse_if(input)?, + _ => parse_block(input)?, + }; + Ok(Stmt::IfElse( Box::new(guard), Box::new(body), - Box::new(else_body), + Some(Box::new(else_body)), )) } - _ => Ok(Stmt::If(Box::new(guard), Box::new(body))), + _ => Ok(Stmt::IfElse(Box::new(guard), Box::new(body), None)), } } @@ -1312,19 +1442,15 @@ fn parse_for<'a>(input: &mut Peekable>) -> Result s.clone(), - Some((token, line, pos)) => { - return Err(ParseError(PERR::VarExpectsIdentifier(token), line, pos)) - } - None => return Err(ParseError(PERR::VarExpectsIdentifier(Token::None), 0, 0)), + Some((Token::Identifier(s), _)) => s, + Some((_, pos)) => return Err(ParseError(PERR::VarExpectsIdentifier, pos)), + None => return Err(ParseError(PERR::VarExpectsIdentifier, Position::eof())), }; match input.next() { - Some((Token::In, _, _)) => {} - Some((token, line, pos)) => { - return Err(ParseError(PERR::VarExpectsIdentifier(token), line, pos)) - } - None => return Err(ParseError(PERR::VarExpectsIdentifier(Token::None), 0, 0)), + Some((Token::In, _)) => {} + Some((_, pos)) => return Err(ParseError(PERR::VarExpectsIdentifier, pos)), + None => return Err(ParseError(PERR::VarExpectsIdentifier, Position::eof())), } let expr = parse_expr(input)?; @@ -1335,31 +1461,32 @@ fn parse_for<'a>(input: &mut Peekable>) -> Result(input: &mut Peekable>) -> Result { - input.next(); + let pos = match input.next() { + Some((_, tok_pos)) => tok_pos, + _ => return Err(ParseError(PERR::InputPastEndOfFile, Position::eof())), + }; let name = match input.next() { - Some((Token::Identifier(ref s), _, _)) => s.clone(), - Some((token, line, pos)) => { - return Err(ParseError(PERR::VarExpectsIdentifier(token), line, pos)) - } - None => return Err(ParseError(PERR::VarExpectsIdentifier(Token::None), 0, 0)), + Some((Token::Identifier(s), _)) => s, + Some((_, pos)) => return Err(ParseError(PERR::VarExpectsIdentifier, pos)), + None => return Err(ParseError(PERR::VarExpectsIdentifier, Position::eof())), }; match input.peek() { - Some(&(Token::Equals, _, _)) => { + Some(&(Token::Equals, _)) => { input.next(); let initializer = parse_expr(input)?; - Ok(Stmt::Let(name, Some(Box::new(initializer)))) + Ok(Stmt::Let(name, Some(Box::new(initializer)), pos)) } - _ => Ok(Stmt::Let(name, None)), + _ => Ok(Stmt::Let(name, None, pos)), } } fn parse_block<'a>(input: &mut Peekable>) -> Result { match input.peek() { - Some(&(Token::LeftBrace, _, _)) => (), - Some(&(_, line, pos)) => return Err(ParseError(PERR::MissingLeftBrace, line, pos)), - None => return Err(ParseError(PERR::MissingLeftBrace, 0, 0)), + Some(&(Token::LeftBrace, _)) => (), + Some(&(_, pos)) => return Err(ParseError(PERR::MissingLeftBrace, pos)), + None => return Err(ParseError(PERR::MissingLeftBrace, Position::eof())), } input.next(); @@ -1367,7 +1494,7 @@ fn parse_block<'a>(input: &mut Peekable>) -> Result true, + Some(&(Token::RightBrace, _)) => true, _ => false, }; @@ -1375,23 +1502,23 @@ fn parse_block<'a>(input: &mut Peekable>) -> Result { + Some(&(Token::RightBrace, _)) => { input.next(); Ok(Stmt::Block(stmts)) } - Some(&(_, line, pos)) => Err(ParseError(PERR::MissingRightBrace, line, pos)), - None => Err(ParseError(PERR::MissingRightBrace, 0, 0)), + Some(&(_, pos)) => Err(ParseError(PERR::MissingRightBrace, pos)), + None => Err(ParseError(PERR::MissingRightBrace, Position::eof())), } } @@ -1401,51 +1528,55 @@ fn parse_expr_stmt<'a>(input: &mut Peekable>) -> Result(input: &mut Peekable>) -> Result { match input.peek() { - Some(&(Token::If, _, _)) => parse_if(input), - Some(&(Token::While, _, _)) => parse_while(input), - Some(&(Token::Loop, _, _)) => parse_loop(input), - Some(&(Token::For, _, _)) => parse_for(input), - Some(&(Token::Break, _, _)) => { + Some(&(Token::If, _)) => parse_if(input), + Some(&(Token::While, _)) => parse_while(input), + Some(&(Token::Loop, _)) => parse_loop(input), + Some(&(Token::For, _)) => parse_for(input), + Some(&(Token::Break, pos)) => { input.next(); - Ok(Stmt::Break) + Ok(Stmt::Break(pos)) } - Some(&(Token::Return, _, _)) => { + Some(&(Token::Return, _)) => { input.next(); match input.peek() { - Some(&(Token::SemiColon, _, _)) => Ok(Stmt::Return), - _ => { + Some(&(Token::SemiColon, pos)) => Ok(Stmt::Return(pos)), + Some(&(_, pos)) => { let ret = parse_expr(input)?; - Ok(Stmt::ReturnWithVal(Box::new(ret))) + Ok(Stmt::ReturnWithVal(Box::new(ret), pos)) } + _ => parse_expr_stmt(input), } } - Some(&(Token::LeftBrace, _, _)) => parse_block(input), - Some(&(Token::Let, _, _)) => parse_var(input), + Some(&(Token::LeftBrace, _)) => parse_block(input), + Some(&(Token::Let, _)) => parse_var(input), _ => parse_expr_stmt(input), } } fn parse_fn<'a>(input: &mut Peekable>) -> Result { - input.next(); + let pos = match input.next() { + Some((_, tok_pos)) => tok_pos, + _ => return Err(ParseError(PERR::InputPastEndOfFile, Position::eof())), + }; let name = match input.next() { - Some((Token::Identifier(ref s), _, _)) => s.clone(), - Some((token, line, pos)) => return Err(ParseError(PERR::FnMissingName(token), line, pos)), - None => return Err(ParseError(PERR::FnMissingName(Token::None), 0, 0)), + Some((Token::Identifier(s), _)) => s, + Some((_, pos)) => return Err(ParseError(PERR::FnMissingName, pos)), + None => return Err(ParseError(PERR::FnMissingName, Position::eof())), }; match input.peek() { - Some(&(Token::LeftParen, _, _)) => { + Some(&(Token::LeftParen, _)) => { input.next(); } - Some(&(_, line, pos)) => return Err(ParseError(PERR::FnMissingParams, line, pos)), - None => return Err(ParseError(PERR::FnMissingParams, 0, 0)), + Some(&(_, pos)) => return Err(ParseError(PERR::FnMissingParams, pos)), + None => return Err(ParseError(PERR::FnMissingParams, Position::eof())), } let mut params = Vec::new(); let skip_params = match input.peek() { - Some(&(Token::RightParen, _, _)) => { + Some(&(Token::RightParen, _)) => { input.next(); true } @@ -1455,13 +1586,13 @@ fn parse_fn<'a>(input: &mut Peekable>) -> Result break, - Some((Token::Comma, _, _)) => (), - Some((Token::Identifier(ref s), _, _)) => { - params.push(s.clone()); + Some((Token::RightParen, _)) => break, + Some((Token::Comma, _)) => (), + Some((Token::Identifier(s), _)) => { + params.push(s); } - Some((_, line, pos)) => return Err(ParseError(PERR::MalformedCallExpr, line, pos)), - None => return Err(ParseError(PERR::MalformedCallExpr, 0, 0)), + Some((_, pos)) => return Err(ParseError(PERR::MalformedCallExpr, pos)), + None => return Err(ParseError(PERR::MalformedCallExpr, Position::eof())), } } } @@ -1472,6 +1603,7 @@ fn parse_fn<'a>(input: &mut Peekable>) -> Result(input: &mut Peekable>) -> Result fndefs.push(parse_fn(input)?), + Some(&(Token::Fn, _)) => fndefs.push(parse_fn(input)?), _ => stmts.push(parse_stmt(input)?), } - if let Some(&(Token::SemiColon, _, _)) = input.peek() { + if let Some(&(Token::SemiColon, _)) = input.peek() { input.next(); } } diff --git a/tests/arrays.rs b/tests/arrays.rs index 8b3d5884..cfd74e6e 100644 --- a/tests/arrays.rs +++ b/tests/arrays.rs @@ -1,19 +1,17 @@ -use rhai::Engine; -use rhai::RegisterFn; +use rhai::{Engine, EvalAltResult, RegisterFn}; #[test] -fn test_arrays() { +fn test_arrays() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("let x = [1, 2, 3]; x[1]"), Ok(2)); - assert_eq!( - engine.eval::("let y = [1, 2, 3]; y[1] = 5; y[1]"), - Ok(5) - ); + assert_eq!(engine.eval::("let x = [1, 2, 3]; x[1]")?, 2); + assert_eq!(engine.eval::("let y = [1, 2, 3]; y[1] = 5; y[1]")?, 5); + + Ok(()) } #[test] -fn test_array_with_structs() { +fn test_array_with_structs() -> Result<(), EvalAltResult> { #[derive(Clone)] struct TestStruct { x: i64, @@ -45,7 +43,7 @@ fn test_array_with_structs() { engine.register_fn("update", TestStruct::update); engine.register_fn("new_ts", TestStruct::new); - assert_eq!(engine.eval::("let a = [new_ts()]; a[0].x"), Ok(1)); + assert_eq!(engine.eval::("let a = [new_ts()]; a[0].x")?, 1); assert_eq!( engine.eval::( @@ -53,7 +51,9 @@ fn test_array_with_structs() { a[0].x = 100; \ a[0].update(); \ a[0].x", - ), - Ok(1100) + )?, + 1100 ); + + Ok(()) } diff --git a/tests/binary_ops.rs b/tests/binary_ops.rs index 4978b34d..edf560aa 100644 --- a/tests/binary_ops.rs +++ b/tests/binary_ops.rs @@ -1,13 +1,22 @@ -use rhai::Engine; +use rhai::{Engine, EvalAltResult}; #[test] -fn test_binary_ops() { +fn test_binary_ops() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("10 % 4"), Ok(2)); - assert_eq!(engine.eval::("10 << 4"), Ok(160)); - assert_eq!(engine.eval::("10 >> 4"), Ok(0)); - assert_eq!(engine.eval::("10 & 4"), Ok(0)); - assert_eq!(engine.eval::("10 | 4"), Ok(14)); - assert_eq!(engine.eval::("10 ^ 4"), Ok(14)); + assert_eq!(engine.eval::("10 % 4")?, 2); + assert_eq!(engine.eval::("10 << 4")?, 160); + assert_eq!(engine.eval::("10 >> 4")?, 0); + assert_eq!(engine.eval::("10 & 4")?, 0); + assert_eq!(engine.eval::("10 | 4")?, 14); + assert_eq!(engine.eval::("10 ^ 4")?, 14); + + assert_eq!(engine.eval::("42 == 42")?, true); + assert_eq!(engine.eval::("42 > 42")?, false); + + // Incompatible types compare to false + assert_eq!(engine.eval::("true == 42")?, false); + assert_eq!(engine.eval::(r#""42" == 42"#)?, false); + + Ok(()) } diff --git a/tests/bit_shift.rs b/tests/bit_shift.rs index 184f5262..7b0a4ab5 100644 --- a/tests/bit_shift.rs +++ b/tests/bit_shift.rs @@ -1,15 +1,15 @@ -use rhai::Engine; +use rhai::{Engine, EvalAltResult}; #[test] -fn test_left_shift() { +fn test_left_shift() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - - assert_eq!(engine.eval::("4 << 2"), Ok(16)); + assert_eq!(engine.eval::("4 << 2")?, 16); + Ok(()) } #[test] -fn test_right_shift() { +fn test_right_shift() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - - assert_eq!(engine.eval::("9 >> 1"), Ok(4)); + assert_eq!(engine.eval::("9 >> 1")?, 4); + Ok(()) } diff --git a/tests/bool_op.rs b/tests/bool_op.rs index 9565e8ee..7c1654bb 100644 --- a/tests/bool_op.rs +++ b/tests/bool_op.rs @@ -1,15 +1,104 @@ -use rhai::Engine; +use rhai::{Engine, EvalAltResult}; #[test] -fn test_bool_op1() { +fn test_bool_op1() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("true && (false || true)"), Ok(true)); + assert_eq!(engine.eval::("true && (false || true)")?, true); + assert_eq!(engine.eval::("true & (false | true)")?, true); + + Ok(()) } #[test] -fn test_bool_op2() { +fn test_bool_op2() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("false && (false || true)"), Ok(false)); + assert_eq!(engine.eval::("false && (false || true)")?, false); + assert_eq!(engine.eval::("false & (false | true)")?, false); + + Ok(()) +} + +#[test] +fn test_bool_op3() -> Result<(), EvalAltResult> { + let mut engine = Engine::new(); + + assert!(engine.eval::("true && (false || 123)").is_err()); + assert_eq!(engine.eval::("true && (true || 123)")?, true); + assert!(engine.eval::("123 && (false || true)").is_err()); + assert_eq!(engine.eval::("false && (true || 123)")?, false); + + Ok(()) +} + +#[test] +fn test_bool_op_short_circuit() -> Result<(), EvalAltResult> { + let mut engine = Engine::new(); + + assert_eq!( + engine.eval::( + r" + fn this() { true } + fn that() { 9/0 } + + this() || that(); + " + )?, + true + ); + + assert_eq!( + engine.eval::( + r" + fn this() { false } + fn that() { 9/0 } + + this() && that(); + " + )?, + false + ); + + Ok(()) +} + +#[test] +#[should_panic] +fn test_bool_op_no_short_circuit1() { + let mut engine = Engine::new(); + + assert_eq!( + engine + .eval::( + r" + fn this() { false } + fn that() { 9/0 } + + this() | that(); + " + ) + .unwrap(), + false + ); +} + +#[test] +#[should_panic] +fn test_bool_op_no_short_circuit2() { + let mut engine = Engine::new(); + + assert_eq!( + engine + .eval::( + r" + fn this() { false } + fn that() { 9/0 } + + this() & that(); + " + ) + .unwrap(), + false + ); } diff --git a/tests/chars.rs b/tests/chars.rs index 3c5a920b..4a5c2d98 100644 --- a/tests/chars.rs +++ b/tests/chars.rs @@ -1,14 +1,19 @@ -use rhai::Engine; +use rhai::{Engine, EvalAltResult}; #[test] -fn test_chars() { +fn test_chars() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("'y'"), Ok('y')); - assert_eq!(engine.eval::("'\\u2764'"), Ok('❤')); + assert_eq!(engine.eval::("'y'")?, 'y'); + assert_eq!(engine.eval::("'\\u2764'")?, '❤'); + assert_eq!(engine.eval::(r#"let x="hello"; x[2]"#)?, 'l'); + assert_eq!( + engine.eval::(r#"let x="hello"; x[2]='$'; x"#)?, + "he$lo".to_string() + ); - match engine.eval::("''") { - Err(_) => (), - _ => assert!(false), - } + assert!(engine.eval::("'\\uhello'").is_err()); + assert!(engine.eval::("''").is_err()); + + Ok(()) } diff --git a/tests/compound_equality.rs b/tests/compound_equality.rs index 7b51692a..65b3b3e4 100644 --- a/tests/compound_equality.rs +++ b/tests/compound_equality.rs @@ -1,68 +1,66 @@ -use rhai::Engine; +use rhai::{Engine, EvalAltResult}; #[test] -fn test_or_equals() { +fn test_or_equals() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("let x = 16; x |= 74; x"), Ok(90)); - assert_eq!(engine.eval::("let x = true; x |= false; x"), Ok(true)); - assert_eq!(engine.eval::("let x = false; x |= true; x"), Ok(true)); + assert_eq!(engine.eval::("let x = 16; x |= 74; x")?, 90); + assert_eq!(engine.eval::("let x = true; x |= false; x")?, true); + assert_eq!(engine.eval::("let x = false; x |= true; x")?, true); + + Ok(()) } #[test] -fn test_and_equals() { +fn test_and_equals() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("let x = 16; x &= 31; x"), Ok(16)); - assert_eq!( - engine.eval::("let x = true; x &= false; x"), - Ok(false) - ); - assert_eq!( - engine.eval::("let x = false; x &= true; x"), - Ok(false) - ); - assert_eq!(engine.eval::("let x = true; x &= true; x"), Ok(true)); + assert_eq!(engine.eval::("let x = 16; x &= 31; x")?, 16); + assert_eq!(engine.eval::("let x = true; x &= false; x")?, false); + assert_eq!(engine.eval::("let x = false; x &= true; x")?, false); + assert_eq!(engine.eval::("let x = true; x &= true; x")?, true); + + Ok(()) } #[test] -fn test_xor_equals() { +fn test_xor_equals() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - - assert_eq!(engine.eval::("let x = 90; x ^= 12; x"), Ok(86)); + assert_eq!(engine.eval::("let x = 90; x ^= 12; x")?, 86); + Ok(()) } #[test] -fn test_multiply_equals() { +fn test_multiply_equals() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - - assert_eq!(engine.eval::("let x = 2; x *= 3; x"), Ok(6)); + assert_eq!(engine.eval::("let x = 2; x *= 3; x")?, 6); + Ok(()) } #[test] -fn test_divide_equals() { +fn test_divide_equals() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - - assert_eq!(engine.eval::("let x = 6; x /= 2; x"), Ok(3)); + assert_eq!(engine.eval::("let x = 6; x /= 2; x")?, 3); + Ok(()) } #[test] -fn test_left_shift_equals() { +fn test_left_shift_equals() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - - assert_eq!(engine.eval::("let x = 9; x >>=1; x"), Ok(4)); + assert_eq!(engine.eval::("let x = 9; x >>=1; x")?, 4); + Ok(()) } #[test] -fn test_right_shift_equals() { +fn test_right_shift_equals() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - - assert_eq!(engine.eval::("let x = 4; x<<= 2; x"), Ok(16)); + assert_eq!(engine.eval::("let x = 4; x<<= 2; x")?, 16); + Ok(()) } #[test] -fn test_modulo_equals() { +fn test_modulo_equals() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - - assert_eq!(engine.eval::("let x = 10; x %= 4; x"), Ok(2)); + assert_eq!(engine.eval::("let x = 10; x %= 4; x")?, 2); + Ok(()) } diff --git a/tests/decrement.rs b/tests/decrement.rs index ab1dc6c8..75d85076 100644 --- a/tests/decrement.rs +++ b/tests/decrement.rs @@ -1,16 +1,17 @@ -use rhai::Engine; -use rhai::EvalAltResult; +use rhai::{Engine, EvalAltResult}; #[test] -fn test_decrement() { +fn test_decrement() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("let x = 10; x -= 7; x"), Ok(3)); + assert_eq!(engine.eval::("let x = 10; x -= 7; x")?, 3); - assert_eq!( - engine.eval::("let s = \"test\"; s -= \"ing\"; s"), - Err(EvalAltResult::ErrorFunctionNotFound( - "- (alloc::string::String, alloc::string::String)".to_string() - )) - ); + let r = engine.eval::("let s = \"test\"; s -= \"ing\"; s"); + + match r { + Err(EvalAltResult::ErrorFunctionNotFound(err, _)) if err == "- (string, string)" => (), + _ => panic!(), + } + + Ok(()) } diff --git a/tests/float.rs b/tests/float.rs index 516980d8..8e291e9e 100644 --- a/tests/float.rs +++ b/tests/float.rs @@ -1,23 +1,24 @@ -use rhai::Engine; -use rhai::RegisterFn; +use rhai::{Engine, EvalAltResult, RegisterFn}; #[test] -fn test_float() { +fn test_float() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); assert_eq!( - engine.eval::("let x = 0.0; let y = 1.0; x < y"), - Ok(true) + engine.eval::("let x = 0.0; let y = 1.0; x < y")?, + true ); assert_eq!( - engine.eval::("let x = 0.0; let y = 1.0; x > y"), - Ok(false) + engine.eval::("let x = 0.0; let y = 1.0; x > y")?, + false ); - assert_eq!(engine.eval::("let x = 9.9999; x"), Ok(9.9999)); + assert_eq!(engine.eval::("let x = 9.9999; x")?, 9.9999); + + Ok(()) } #[test] -fn struct_with_float() { +fn struct_with_float() -> Result<(), EvalAltResult> { #[derive(Clone)] struct TestStruct { x: f64, @@ -50,11 +51,13 @@ fn struct_with_float() { engine.register_fn("new_ts", TestStruct::new); assert_eq!( - engine.eval::("let ts = new_ts(); ts.update(); ts.x"), - Ok(6.789) + engine.eval::("let ts = new_ts(); ts.update(); ts.x")?, + 6.789 ); assert_eq!( - engine.eval::("let ts = new_ts(); ts.x = 10.1001; ts.x"), - Ok(10.1001) + engine.eval::("let ts = new_ts(); ts.x = 10.1001; ts.x")?, + 10.1001 ); + + Ok(()) } diff --git a/tests/for.rs b/tests/for.rs index 687c1b4b..63390b31 100644 --- a/tests/for.rs +++ b/tests/for.rs @@ -1,7 +1,7 @@ -use rhai::Engine; +use rhai::{Engine, EvalAltResult}; #[test] -fn test_for() { +fn test_for() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); let script = r" @@ -20,5 +20,7 @@ fn test_for() { sum1 + sum2 "; - assert_eq!(engine.eval::(script).unwrap(), 30); + assert_eq!(engine.eval::(script)?, 30); + + Ok(()) } diff --git a/tests/get_set.rs b/tests/get_set.rs index e3311278..d2718a50 100644 --- a/tests/get_set.rs +++ b/tests/get_set.rs @@ -1,8 +1,7 @@ -use rhai::Engine; -use rhai::RegisterFn; +use rhai::{Engine, EvalAltResult, RegisterFn}; #[test] -fn test_get_set() { +fn test_get_set() -> Result<(), EvalAltResult> { #[derive(Clone)] struct TestStruct { x: i64, @@ -29,14 +28,13 @@ fn test_get_set() { engine.register_get_set("x", TestStruct::get_x, TestStruct::set_x); engine.register_fn("new_ts", TestStruct::new); - assert_eq!( - engine.eval::("let a = new_ts(); a.x = 500; a.x"), - Ok(500) - ); + assert_eq!(engine.eval::("let a = new_ts(); a.x = 500; a.x")?, 500); + + Ok(()) } #[test] -fn test_big_get_set() { +fn test_big_get_set() -> Result<(), EvalAltResult> { #[derive(Clone)] struct TestChild { x: i64, @@ -88,7 +86,9 @@ fn test_big_get_set() { engine.register_fn("new_tp", TestParent::new); assert_eq!( - engine.eval::("let a = new_tp(); a.child.x = 500; a.child.x"), - Ok(500) + engine.eval::("let a = new_tp(); a.child.x = 500; a.child.x")?, + 500 ); + + Ok(()) } diff --git a/tests/if_block.rs b/tests/if_block.rs index 94775618..286aa7fd 100644 --- a/tests/if_block.rs +++ b/tests/if_block.rs @@ -1,10 +1,29 @@ -use rhai::Engine; +use rhai::{Engine, EvalAltResult}; #[test] -fn test_if() { +fn test_if() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("if true { 55 }"), Ok(55)); - assert_eq!(engine.eval::("if false { 55 } else { 44 }"), Ok(44)); - assert_eq!(engine.eval::("if true { 55 } else { 44 }"), Ok(55)); + assert_eq!(engine.eval::("if true { 55 }")?, 55); + assert_eq!(engine.eval::("if false { 55 } else { 44 }")?, 44); + assert_eq!(engine.eval::("if true { 55 } else { 44 }")?, 55); + assert_eq!( + engine.eval::("if false { 55 } else if true { 33 } else { 44 }")?, + 33 + ); + assert_eq!( + engine.eval::( + r" + if false { 55 } + else if false { 33 } + else if false { 66 } + else if false { 77 } + else if false { 88 } + else { 44 } + " + )?, + 44 + ); + + Ok(()) } diff --git a/tests/increment.rs b/tests/increment.rs index 661ae38e..62b1b36c 100644 --- a/tests/increment.rs +++ b/tests/increment.rs @@ -1,12 +1,14 @@ -use rhai::Engine; +use rhai::{Engine, EvalAltResult}; #[test] -fn test_increment() { +fn test_increment() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("let x = 1; x += 2; x"), Ok(3)); + assert_eq!(engine.eval::("let x = 1; x += 2; x")?, 3); assert_eq!( - engine.eval::("let s = \"test\"; s += \"ing\"; s"), - Ok("testing".to_string()) + engine.eval::("let s = \"test\"; s += \"ing\"; s")?, + "testing".to_string() ); + + Ok(()) } diff --git a/tests/internal_fn.rs b/tests/internal_fn.rs index f04a46ca..95f584a5 100644 --- a/tests/internal_fn.rs +++ b/tests/internal_fn.rs @@ -1,25 +1,30 @@ -use rhai::Engine; +use rhai::{Engine, EvalAltResult}; #[test] -fn test_internal_fn() { +fn test_internal_fn() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!( - engine.eval::("fn addme(a, b) { a+b } addme(3, 4)"), - Ok(7) - ); - assert_eq!(engine.eval::("fn bob() { return 4; 5 } bob()"), Ok(4)); + assert_eq!(engine.eval::("fn addme(a, b) { a+b } addme(3, 4)")?, 7); + assert_eq!(engine.eval::("fn bob() { return 4; 5 } bob()")?, 4); + + Ok(()) } #[test] -fn test_big_internal_fn() { +fn test_big_internal_fn() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); assert_eq!( engine.eval::( - "fn mathme(a, b, c, d, e, f) { a - b * c + d * e - f \ - } mathme(100, 5, 2, 9, 6, 32)", - ), - Ok(112) + r" + fn mathme(a, b, c, d, e, f) { + a - b * c + d * e - f + } + mathme(100, 5, 2, 9, 6, 32) + ", + )?, + 112 ); + + Ok(()) } diff --git a/tests/looping.rs b/tests/looping.rs index 4359df16..4baa0aab 100644 --- a/tests/looping.rs +++ b/tests/looping.rs @@ -1,12 +1,11 @@ -use rhai::Engine; +use rhai::{Engine, EvalAltResult}; #[test] -fn test_loop() { +fn test_loop() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert!(engine - .eval::( - " + assert!(engine.eval::( + r" let x = 0; let i = 0; @@ -22,6 +21,7 @@ fn test_loop() { x == 45 " - ) - .unwrap()) + )?); + + Ok(()) } diff --git a/tests/method_call.rs b/tests/method_call.rs index ee6b3dc0..f9f09652 100644 --- a/tests/method_call.rs +++ b/tests/method_call.rs @@ -1,8 +1,7 @@ -use rhai::Engine; -use rhai::RegisterFn; +use rhai::{Engine, EvalAltResult, RegisterFn}; #[test] -fn test_method_call() { +fn test_method_call() -> Result<(), EvalAltResult> { #[derive(Clone)] struct TestStruct { x: i64, @@ -25,9 +24,9 @@ fn test_method_call() { engine.register_fn("update", TestStruct::update); engine.register_fn("new_ts", TestStruct::new); - if let Ok(result) = engine.eval::("let x = new_ts(); x.update(); x") { - assert_eq!(result.x, 1001); - } else { - assert!(false); - } + let ts = engine.eval::("let x = new_ts(); x.update(); x")?; + + assert_eq!(ts.x, 1001); + + Ok(()) } diff --git a/tests/mismatched_op.rs b/tests/mismatched_op.rs index 944051d7..9964189b 100644 --- a/tests/mismatched_op.rs +++ b/tests/mismatched_op.rs @@ -4,12 +4,12 @@ use rhai::{Engine, EvalAltResult, RegisterFn}; fn test_mismatched_op() { let mut engine = Engine::new(); - assert_eq!( - engine.eval::("60 + \"hello\""), - Err(EvalAltResult::ErrorMismatchOutputType( - "alloc::string::String".into() - )) - ); + let r = engine.eval::("60 + \"hello\""); + + match r { + Err(EvalAltResult::ErrorMismatchOutputType(err, _)) if err == "string" => (), + _ => panic!(), + } } #[test] @@ -29,10 +29,14 @@ fn test_mismatched_op_custom_type() { engine.register_type::(); engine.register_fn("new_ts", TestStruct::new); - assert_eq!( - engine.eval::("60 + new_ts()"), - Err(EvalAltResult::ErrorFunctionNotFound( - "+ (i64, mismatched_op::test_mismatched_op_custom_type::TestStruct)".into() - )) - ); + let r = engine.eval::("60 + new_ts()"); + + match r { + Err(EvalAltResult::ErrorFunctionNotFound(err, _)) + if err == "+ (i64, mismatched_op::test_mismatched_op_custom_type::TestStruct)" => + { + () + } + _ => panic!(), + } } diff --git a/tests/not.rs b/tests/not.rs index 0e6252a3..8bade80a 100644 --- a/tests/not.rs +++ b/tests/not.rs @@ -1,16 +1,18 @@ -use rhai::Engine; +use rhai::{Engine, EvalAltResult}; #[test] -fn test_not() { +fn test_not() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); assert_eq!( - engine.eval::("let not_true = !true; not_true"), - Ok(false) + engine.eval::("let not_true = !true; not_true")?, + false ); - assert_eq!(engine.eval::("fn not(x) { !x } not(false)"), Ok(true)); + assert_eq!(engine.eval::("fn not(x) { !x } not(false)")?, true); // TODO - do we allow stacking unary operators directly? e.g '!!!!!!!true' - assert_eq!(engine.eval::("!(!(!(!(true))))"), Ok(true)); + assert_eq!(engine.eval::("!(!(!(!(true))))")?, true); + + Ok(()) } diff --git a/tests/number_literals.rs b/tests/number_literals.rs index 05185cb9..16e12719 100644 --- a/tests/number_literals.rs +++ b/tests/number_literals.rs @@ -1,35 +1,43 @@ -use rhai::Engine; +use rhai::{Engine, EvalAltResult}; #[test] -fn test_number_literal() { +fn test_number_literal() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("65"), Ok(65)); + assert_eq!(engine.eval::("65")?, 65); + + Ok(()) } #[test] -fn test_hex_literal() { +fn test_hex_literal() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("let x = 0xf; x"), Ok(15)); - assert_eq!(engine.eval::("let x = 0xff; x"), Ok(255)); + assert_eq!(engine.eval::("let x = 0xf; x")?, 15); + assert_eq!(engine.eval::("let x = 0xff; x")?, 255); + + Ok(()) } #[test] -fn test_octal_literal() { +fn test_octal_literal() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("let x = 0o77; x"), Ok(63)); - assert_eq!(engine.eval::("let x = 0o1234; x"), Ok(668)); + assert_eq!(engine.eval::("let x = 0o77; x")?, 63); + assert_eq!(engine.eval::("let x = 0o1234; x")?, 668); + + Ok(()) } #[test] -fn test_binary_literal() { +fn test_binary_literal() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("let x = 0b1111; x"), Ok(15)); + assert_eq!(engine.eval::("let x = 0b1111; x")?, 15); assert_eq!( - engine.eval::("let x = 0b0011_1100_1010_0101; x"), - Ok(15525) + engine.eval::("let x = 0b0011_1100_1010_0101; x")?, + 15525 ); + + Ok(()) } diff --git a/tests/ops.rs b/tests/ops.rs index c2f2d241..d1464dfb 100644 --- a/tests/ops.rs +++ b/tests/ops.rs @@ -1,19 +1,23 @@ -use rhai::Engine; +use rhai::{Engine, EvalAltResult}; #[test] -fn test_ops() { +fn test_ops() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("60 + 5"), Ok(65)); - assert_eq!(engine.eval::("(1 + 2) * (6 - 4) / 2"), Ok(3)); + assert_eq!(engine.eval::("60 + 5")?, 65); + assert_eq!(engine.eval::("(1 + 2) * (6 - 4) / 2")?, 3); + + Ok(()) } #[test] -fn test_op_prec() { +fn test_op_prec() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); assert_eq!( - engine.eval::("let x = 0; if x == 10 || true { x = 1} x"), - Ok(1) + engine.eval::("let x = 0; if x == 10 || true { x = 1} x")?, + 1 ); + + Ok(()) } diff --git a/tests/power_of.rs b/tests/power_of.rs index 45211fb6..ac41a611 100644 --- a/tests/power_of.rs +++ b/tests/power_of.rs @@ -1,36 +1,34 @@ -use rhai::Engine; +use rhai::{Engine, EvalAltResult}; #[test] -fn test_power_of() { +fn test_power_of() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("2 ~ 3"), Ok(8)); - assert_eq!(engine.eval::("(-2 ~ 3)"), Ok(-8)); - assert_eq!(engine.eval::("2.2 ~ 3.3"), Ok(13.489468760533386_f64)); - assert_eq!(engine.eval::("2.0~-2.0"), Ok(0.25_f64)); - assert_eq!(engine.eval::("(-2.0~-2.0)"), Ok(0.25_f64)); - assert_eq!(engine.eval::("(-2.0~-2)"), Ok(0.25_f64)); - assert_eq!(engine.eval::("4~3"), Ok(64)); + assert_eq!(engine.eval::("2 ~ 3")?, 8); + assert_eq!(engine.eval::("(-2 ~ 3)")?, -8); + assert_eq!(engine.eval::("2.2 ~ 3.3")?, 13.489468760533386_f64); + assert_eq!(engine.eval::("2.0~-2.0")?, 0.25_f64); + assert_eq!(engine.eval::("(-2.0~-2.0)")?, 0.25_f64); + assert_eq!(engine.eval::("(-2.0~-2)")?, 0.25_f64); + assert_eq!(engine.eval::("4~3")?, 64); + + Ok(()) } #[test] -fn test_power_of_equals() { +fn test_power_of_equals() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("let x = 2; x ~= 3; x"), Ok(8)); - assert_eq!(engine.eval::("let x = -2; x ~= 3; x"), Ok(-8)); + assert_eq!(engine.eval::("let x = 2; x ~= 3; x")?, 8); + assert_eq!(engine.eval::("let x = -2; x ~= 3; x")?, -8); assert_eq!( - engine.eval::("let x = 2.2; x ~= 3.3; x"), - Ok(13.489468760533386_f64) + engine.eval::("let x = 2.2; x ~= 3.3; x")?, + 13.489468760533386_f64 ); - assert_eq!( - engine.eval::("let x = 2.0; x ~= -2.0; x"), - Ok(0.25_f64) - ); - assert_eq!( - engine.eval::("let x = -2.0; x ~= -2.0; x"), - Ok(0.25_f64) - ); - assert_eq!(engine.eval::("let x = -2.0; x ~= -2; x"), Ok(0.25_f64)); - assert_eq!(engine.eval::("let x =4; x ~= 3; x"), Ok(64)); + assert_eq!(engine.eval::("let x = 2.0; x ~= -2.0; x")?, 0.25_f64); + assert_eq!(engine.eval::("let x = -2.0; x ~= -2.0; x")?, 0.25_f64); + assert_eq!(engine.eval::("let x = -2.0; x ~= -2; x")?, 0.25_f64); + assert_eq!(engine.eval::("let x =4; x ~= 3; x")?, 64); + + Ok(()) } diff --git a/tests/string.rs b/tests/string.rs index 5b819e94..e7df5d05 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -1,15 +1,17 @@ -use rhai::Engine; +use rhai::{Engine, EvalAltResult}; #[test] -fn test_string() { +fn test_string() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); assert_eq!( - engine.eval::("\"Test string: \\u2764\""), - Ok("Test string: ❤".to_string()) + engine.eval::("\"Test string: \\u2764\"")?, + "Test string: ❤".to_string() ); assert_eq!( - engine.eval::("\"foo\" + \"bar\""), - Ok("foobar".to_string()) + engine.eval::("\"foo\" + \"bar\"")?, + "foobar".to_string() ); + + Ok(()) } diff --git a/tests/unary_after_binary.rs b/tests/unary_after_binary.rs index 00778e7b..f44111d4 100644 --- a/tests/unary_after_binary.rs +++ b/tests/unary_after_binary.rs @@ -1,15 +1,17 @@ -use rhai::Engine; +use rhai::{Engine, EvalAltResult}; #[test] // TODO also add test case for unary after compound // Hah, turns out unary + has a good use after all! -fn test_unary_after_binary() { +fn test_unary_after_binary() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("10 % +4"), Ok(2)); - assert_eq!(engine.eval::("10 << +4"), Ok(160)); - assert_eq!(engine.eval::("10 >> +4"), Ok(0)); - assert_eq!(engine.eval::("10 & +4"), Ok(0)); - assert_eq!(engine.eval::("10 | +4"), Ok(14)); - assert_eq!(engine.eval::("10 ^ +4"), Ok(14)); + assert_eq!(engine.eval::("10 % +4")?, 2); + assert_eq!(engine.eval::("10 << +4")?, 160); + assert_eq!(engine.eval::("10 >> +4")?, 0); + assert_eq!(engine.eval::("10 & +4")?, 0); + assert_eq!(engine.eval::("10 | +4")?, 14); + assert_eq!(engine.eval::("10 ^ +4")?, 14); + + Ok(()) } diff --git a/tests/unary_minus.rs b/tests/unary_minus.rs index 49701bcb..b32e6860 100644 --- a/tests/unary_minus.rs +++ b/tests/unary_minus.rs @@ -1,10 +1,12 @@ -use rhai::Engine; +use rhai::{Engine, EvalAltResult}; #[test] -fn test_unary_minus() { +fn test_unary_minus() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("let x = -5; x"), Ok(-5)); - assert_eq!(engine.eval::("fn n(x) { -x } n(5)"), Ok(-5)); - assert_eq!(engine.eval::("5 - -(-5)"), Ok(0)); + assert_eq!(engine.eval::("let x = -5; x")?, -5); + assert_eq!(engine.eval::("fn n(x) { -x } n(5)")?, -5); + assert_eq!(engine.eval::("5 - -(-5)")?, 0); + + Ok(()) } diff --git a/tests/unit.rs b/tests/unit.rs index 7283b70b..5f54bb15 100644 --- a/tests/unit.rs +++ b/tests/unit.rs @@ -1,25 +1,22 @@ -use rhai::Engine; +use rhai::{Engine, EvalAltResult}; #[test] -fn test_unit() { +fn test_unit() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - - assert_eq!(engine.eval::<()>("let x = (); x"), Ok(())); + assert_eq!(engine.eval::<()>("let x = (); x")?, ()); + Ok(()) } #[test] -fn test_unit_eq() { +fn test_unit_eq() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - - assert_eq!( - engine.eval::("let x = (); let y = (); x == y"), - Ok(true) - ); + assert_eq!(engine.eval::("let x = (); let y = (); x == y")?, true); + Ok(()) } #[test] -fn test_unit_with_spaces() { +fn test_unit_with_spaces() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - - assert_eq!(engine.eval::<()>("let x = ( ); x"), Ok(())); + assert_eq!(engine.eval::<()>("let x = ( ); x")?, ()); + Ok(()) } diff --git a/tests/var_scope.rs b/tests/var_scope.rs index e802caa3..6b378299 100644 --- a/tests/var_scope.rs +++ b/tests/var_scope.rs @@ -1,28 +1,16 @@ -use rhai::{Engine, Scope}; +use rhai::{Engine, EvalAltResult, Scope}; #[test] -fn test_var_scope() { +fn test_var_scope() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); let mut scope = Scope::new(); - assert_eq!( - engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5"), - Ok(()) - ); + engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5")?; + assert_eq!(engine.eval_with_scope::(&mut scope, "x")?, 9); + engine.eval_with_scope::<()>(&mut scope, "x = x + 1; x = x + 2;")?; + assert_eq!(engine.eval_with_scope::(&mut scope, "x")?, 12); + assert_eq!(engine.eval_with_scope::<()>(&mut scope, "{let x = 3}")?, ()); + assert_eq!(engine.eval_with_scope::(&mut scope, "x")?, 12); - assert_eq!(engine.eval_with_scope::(&mut scope, "x"), Ok(9)); - - assert_eq!( - engine.eval_with_scope::<()>(&mut scope, "x = x + 1; x = x + 2;"), - Ok(()) - ); - - assert_eq!(engine.eval_with_scope::(&mut scope, "x"), Ok(12)); - - assert_eq!( - engine.eval_with_scope::<()>(&mut scope, "{let x = 3}"), - Ok(()) - ); - - assert_eq!(engine.eval_with_scope::(&mut scope, "x"), Ok(12)); + Ok(()) } diff --git a/tests/while_loop.rs b/tests/while_loop.rs index 3cbba708..8dc1ae85 100644 --- a/tests/while_loop.rs +++ b/tests/while_loop.rs @@ -1,14 +1,16 @@ -use rhai::Engine; +use rhai::{Engine, EvalAltResult}; #[test] -fn test_while() { +fn test_while() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); assert_eq!( engine.eval::( "let x = 0; while x < 10 { x = x + 1; if x > 5 { \ break } } x", - ), - Ok(6) + )?, + 6 ); + + Ok(()) }