From 60a7d51537b03a9e29d208a27a74e5f5519181a0 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 25 Feb 2020 10:40:48 +0800 Subject: [PATCH 01/30] Allow override of print and debug. --- src/engine.rs | 56 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 9e942346..fbdd9d6f 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -147,11 +147,12 @@ 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>, + on_print: Box, + on_debug: Box, } pub enum FnIntExt { @@ -233,7 +234,26 @@ impl Engine { )) }) .and_then(move |f| match **f { - FnIntExt::Ext(ref f) => f(args), + FnIntExt::Ext(ref f) => { + let r = f(args); + 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( @@ -1048,21 +1068,21 @@ impl Engine { // (*ent).push(FnType::ExternalFn2(Box::new(idx))); // Register print and debug - fn print_debug(x: T) { - println!("{:?}", x); + fn print_debug(x: T) -> String { + format!("{:?}", x) } - fn print(x: T) { - println!("{}", x); + fn print(x: T) -> String { + format!("{}", 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); + reg_func1!(engine, "print", print, String, i32, i64, u32, u64); + reg_func1!(engine, "print", print, String, f32, f64, bool, String); + reg_func1!(engine, "print", print_debug, String, 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, ()); + reg_func1!(engine, "debug", print_debug, String, i32, i64, u32, u64); + reg_func1!(engine, "debug", print_debug, String, f32, f64, bool, String); + reg_func1!(engine, "debug", print_debug, String, Array, ()); // Register array functions fn push(list: &mut Array, item: T) { @@ -1119,10 +1139,22 @@ impl Engine { let mut engine = Engine { fns: HashMap::new(), type_iterators: HashMap::new(), + on_print: Box::new(|x: &str| println!("{}", x)), + on_debug: Box::new(|x: &str| println!("{}", x)), }; Engine::register_default_lib(&mut engine); engine } + + /// 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); + } } From a0c56357121805c19c91f1f98c37e1b7e356083a Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 25 Feb 2020 10:43:13 +0800 Subject: [PATCH 02/30] Update README with new features. --- README.md | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 90 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 98f4d748..31eafcf6 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ We also have a few examples scripts that showcase Rhai's features, all stored in - `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 @@ -97,10 +98,16 @@ Rhai's scripting engine is very lightweight. It gets its ability from the funct extern crate rhai; use rhai::{Engine, RegisterFn}; +// Normal function fn add(x: i64, y: i64) -> i64 { x + y } +// Function that returns a dynamic value (i.e. Box) +fn get_an_any() -> Box { + Box::new(42_i64) +} + fn main() { let mut engine = Engine::new(); @@ -109,6 +116,13 @@ fn main() { if let Ok(result) = engine.eval::("add(40, 2)") { println!("Answer: {}", result); // prints 42 } + + // Functions that return dynamic values (i.e. Box) must use register_box_fn() + engine.register_box_fn("get_an_any", get_an_any); + + if let Ok(result) = engine.eval::("get_an_any()") { + println!("Answer: {}", result); // prints 42 + } } ``` @@ -344,15 +358,63 @@ fn add(x, y) { print(add(2, 3)) ``` + +To return a dynamic value, box it and return it as `Box`. + +```rust +fn decide(yes_no: bool) -> Box { + if yes_no { + Box::new(42_i64) + } else { + Box::new("hello world!".to_string()) // remember &str is not supported + } +} +``` + ## Arrays You can create arrays of values, and then access them with numeric indices. +The functions `push`, `pop` and `shift` can be used to insert and remove elements +to/from arrays. ```rust -let y = [1, 2, 3]; -y[1] = 5; +let y = [1, 2, 3]; // 3 elements +y[1] = 42; -print(y[1]); +print(y[1]); // prints 42 + +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; +``` + +`push` is only defined for standard built-in types. If you want to use `push` 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))); +``` + +## For +```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 +430,29 @@ 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"; + +let age = 42; +let name_and_age = full_name + ": age " + age; // String building with different types +name_and_age == "Bob C. Davis: age 42"; +``` + +## 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 +```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 +497,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" ``` From f8485932b5c01fdc051adc78689a0b0f65d02a99 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 25 Feb 2020 11:08:40 +0800 Subject: [PATCH 03/30] Add documentation on pre-compilation. --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 31eafcf6..94c4afc2 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,24 @@ 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 +let ast = Engine::compile("40 + 2").unwrap(); // AST generated can be stored for later evaluations + +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(); +``` + # 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. From 1820732865d8c8f9dc81ebb035764dedc36688ca Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 25 Feb 2020 11:12:42 +0800 Subject: [PATCH 04/30] Bump version to 0.10.0-alpha1. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4ba4d03e..66c13ab5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rhai" -version = "0.9.1" +version = "0.10.0-alpha1" edition = "2018" authors = ["Jonathan Turner", "Lukáš Hozda"] description = "Embedded scripting for Rust" From 7893a9734ba40b41fee2b9ee08550e9d585ee61f Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 25 Feb 2020 11:23:46 +0800 Subject: [PATCH 05/30] Minor fixes to README. --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 94c4afc2..f10e28fb 100644 --- a/README.md +++ b/README.md @@ -11,17 +11,19 @@ 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-alpha1" ``` +Beware that 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: From 2e296ff9d7a4662361c67cff86aa29e5a8c10056 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 25 Feb 2020 15:01:50 +0800 Subject: [PATCH 06/30] Fix MarkDown style. --- README.md | 100 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 67 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index f10e28fb..29ea9216 100644 --- a/README.md +++ b/README.md @@ -27,44 +27,51 @@ Beware that to use pre-releases (alpha and beta) you need to specify the exact v ## 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 -- `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 + +* `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 ``` @@ -95,7 +102,8 @@ 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 -let ast = Engine::compile("40 + 2").unwrap(); // AST generated can be stored for later evaluations +// 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) { @@ -116,15 +124,15 @@ Rhai's scripting engine is very lightweight. It gets its ability from the funct ```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 (i.e. Box) -fn get_an_any() -> Box { +// Function that returns a Dynamic value +fn get_an_any() -> Dynamic { Box::new(42_i64) } @@ -137,8 +145,8 @@ fn main() { println!("Answer: {}", result); // prints 42 } - // Functions that return dynamic values (i.e. Box) must use register_box_fn() - engine.register_box_fn("get_an_any", get_an_any); + // 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 @@ -218,6 +226,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) { @@ -244,12 +253,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. @@ -325,6 +350,7 @@ let x = (1 + 2) * (6 - 4) / 2; ``` ## If + ```rust if true { print("it's true!"); @@ -335,6 +361,7 @@ else { ``` ## While + ```rust let x = 10; while x > 0 { @@ -347,6 +374,7 @@ while x > 0 { ``` ## Loop + ```rust let x = 10; @@ -379,10 +407,10 @@ fn add(x, y) { print(add(2, 3)) ``` -To return a dynamic value, box it and return it as `Box`. +To return a `Dynamic` value, simply box it and return it. ```rust -fn decide(yes_no: bool) -> Box { +fn decide(yes_no: bool) -> Dynamic { if yes_no { Box::new(42_i64) } else { @@ -417,11 +445,15 @@ last == 5; `push` is only defined for standard built-in types. If you want to use `push` 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))); +engine.register_fn("push", |list: &mut rhai::Array, item: MyType| list.push(Box::new(item))); ``` -## For +The type of a Rhai array is `rhai::Array`. + +## For loops + ```rust let array = [1, 3, 5, 7, 9, 42]; @@ -461,6 +493,7 @@ name_and_age == "Bob C. Davis: age 42"; ``` ## Print and Debug + ```rust print("hello"); // prints hello to stdout print(1 + 2 + 3); // prints 6 to stdout @@ -468,7 +501,8 @@ print("hello" + 42); // prints hello42 to stdout debug("world!"); // prints "world!" to stdout using debug formatting ``` -## Overriding Print and Debug +### 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)); From 51abc4a2c19fcbf301ce6be3a8daa4ec12462d10 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 25 Feb 2020 15:02:27 +0800 Subject: [PATCH 07/30] Simplify API by introducing the Dynamic and Array type aliases. --- src/any.rs | 29 ++++++++++-------- src/call.rs | 12 ++++---- src/engine.rs | 75 ++++++++++++++++++++++++---------------------- src/fn_register.rs | 26 ++++++++-------- src/lib.rs | 4 +-- 5 files changed, 75 insertions(+), 71 deletions(-) diff --git a/src/any.rs b/src/any.rs index b6913517..3584a5e7 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() as &Variant) } } -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/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 fbdd9d6f..ae6220a7 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -6,13 +6,14 @@ use std::fmt; use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Rem, Shl, Shr, Sub}; use std::{convert::TryInto, 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}; -type Array = Vec>; +pub type Array = Vec; +pub type FnCallArgs<'a> = Vec<&'a mut Variant>; #[derive(Debug, Clone)] pub enum EvalAltResult { @@ -30,7 +31,7 @@ pub enum EvalAltResult { ErrorCantOpenScriptFile(String), ErrorMalformedDotExpression, LoopBreak, - Return(Box), + Return(Dynamic), } impl EvalAltResult { @@ -131,7 +132,7 @@ pub struct FnSpec { 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 /// @@ -160,7 +161,7 @@ pub enum FnIntExt { Int(FnDef), } -pub type FnAny = dyn Fn(Vec<&mut dyn Any>) -> Result, EvalAltResult>; +pub type FnAny = dyn Fn(FnCallArgs) -> Result; /// A type containing information about current scope. /// Useful for keeping state between `Engine` runs @@ -176,7 +177,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 @@ -195,11 +196,7 @@ impl Engine { /// Universal method for calling functions, that are either /// registered with the `Engine` or written in Rhai - pub fn call_fn_raw( - &self, - ident: String, - args: Vec<&mut dyn Any>, - ) -> Result, EvalAltResult> { + pub fn call_fn_raw(&self, ident: String, args: FnCallArgs) -> Result { debug_println!( "Trying to call function {:?} with args {:?}", ident, @@ -225,7 +222,7 @@ impl Engine { .ok_or_else(|| { let typenames = args .iter() - .map(|x| (*(&**x).box_clone()).type_name()) + .map(|x| (*(&**x).into_dynamic()).type_name()) .collect::>(); EvalAltResult::ErrorFunctionNotFound(format!( "{} ({})", @@ -260,7 +257,7 @@ impl Engine { 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) { @@ -288,13 +285,13 @@ impl Engine { /// Register an iterator adapter for a type. pub fn register_iterator(&mut self, f: F) where - F: 'static + Fn(&Box) -> Box>>, + F: 'static + Fn(&Dynamic) -> Box>, { 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) + pub fn register_get(&mut self, name: &str, get_fn: F) where F: 'static + Fn(&mut T) -> U, { @@ -303,7 +300,7 @@ impl Engine { } /// Register a set function for a member of a registered type - pub fn register_set(&mut self, name: &str, set_fn: F) + pub fn register_set(&mut self, name: &str, set_fn: F) where F: 'static + Fn(&mut T, U) -> (), { @@ -312,7 +309,7 @@ impl Engine { } /// Shorthand for registering both getters and setters - pub fn register_get_set( + pub fn register_get_set( &mut self, name: &str, get_fn: F, @@ -328,9 +325,9 @@ 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 { @@ -389,7 +386,7 @@ impl Engine { map: F, ) -> Result<(usize, T), EvalAltResult> where - F: FnOnce(&'a mut dyn Any) -> Result, + F: FnOnce(&'a mut Variant) -> Result, { scope .iter_mut() @@ -405,7 +402,7 @@ impl Engine { scope: &mut Scope, id: &str, idx: &Expr, - ) -> Result<(usize, usize, Box), EvalAltResult> { + ) -> Result<(usize, usize, Dynamic), EvalAltResult> { let idx_boxed = self .eval_expr(scope, idx)? .downcast::() @@ -433,10 +430,10 @@ impl Engine { scope: &mut Scope, dot_lhs: &Expr, dot_rhs: &Expr, - ) -> Result, EvalAltResult> { + ) -> Result { match *dot_lhs { Expr::Identifier(ref id) => { - let (sc_idx, mut target) = Self::search_scope(scope, id, |x| Ok(x.box_clone()))?; + let (sc_idx, mut target) = Self::search_scope(scope, id, |x| Ok(x.into_dynamic()))?; 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 @@ -461,10 +458,10 @@ impl Engine { fn set_dot_val_helper( &self, - this_ptr: &mut dyn Any, + this_ptr: &mut Variant, dot_rhs: &Expr, - mut source_val: Box, - ) -> Result, EvalAltResult> { + mut source_val: Dynamic, + ) -> Result { match *dot_rhs { Expr::Identifier(ref id) => { let set_fn_name = "set$".to_string() + id; @@ -495,11 +492,11 @@ impl Engine { scope: &mut Scope, dot_lhs: &Expr, dot_rhs: &Expr, - source_val: Box, - ) -> Result, EvalAltResult> { + source_val: Dynamic, + ) -> Result { match *dot_lhs { Expr::Identifier(ref id) => { - let (sc_idx, mut target) = Self::search_scope(scope, id, |x| Ok(x.box_clone()))?; + let (sc_idx, mut target) = Self::search_scope(scope, id, |x| Ok(x.into_dynamic()))?; 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 @@ -522,7 +519,7 @@ impl Engine { } } - fn eval_expr(&self, scope: &mut Scope, expr: &Expr) -> Result, EvalAltResult> { + 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)), @@ -616,12 +613,12 @@ impl Engine { } } - fn eval_stmt(&self, scope: &mut Scope, stmt: &Stmt) -> Result, EvalAltResult> { + fn eval_stmt(&self, scope: &mut Scope, stmt: &Stmt) -> Result { 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(())); + let mut last_result: Result = Ok(Box::new(())); for s in b.iter() { last_result = self.eval_stmt(scope, s); @@ -801,7 +798,7 @@ impl Engine { ast: &AST, ) -> Result { let AST(os, fns) = ast; - let mut x: Result, EvalAltResult> = Ok(Box::new(())); + let mut x: Result = Ok(Box::new(())); for f in fns { let name = f.name.clone(); @@ -1093,8 +1090,14 @@ impl Engine { 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_dynamic_fn("pop", |list: &mut Array| list.pop().unwrap_or(Box::new(()))); + engine.register_dynamic_fn("shift", |list: &mut Array| { + if list.len() > 0 { + list.remove(0) + } else { + Box::new(()) + } + }); engine.register_fn("len", |list: &mut Array| -> i64 { list.len().try_into().unwrap() }); @@ -1127,7 +1130,7 @@ impl Engine { a.downcast_ref::>() .unwrap() .clone() - .map(|n| Box::new(n) as Box), + .map(|n| Box::new(n) as Dynamic), ) }); diff --git a/src/fn_register.rs b/src/fn_register.rs index d1bb6b6e..72bf4a7e 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -1,13 +1,13 @@ use std::any::TypeId; -use crate::any::Any; -use crate::engine::{Engine, EvalAltResult}; +use crate::any::{Any, Dynamic}; +use crate::engine::{Engine, EvalAltResult, FnCallArgs}; 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); + fn register_dynamic_fn(&mut self, name: &str, f: FN); } pub struct Ref(A); @@ -23,14 +23,14 @@ 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 fun = move |mut args: FnCallArgs| { // Check for length at the beginning to avoid // per-element bound checks. if args.len() != count_args!($($par)*) { @@ -48,19 +48,19 @@ macro_rules! def_register { // 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)); } } - impl<$($par,)* FN> RegisterBoxFn for Engine - where + impl< $($par: Any + Clone,)* - FN: Fn($($param),*) -> Box + 'static + FN: Fn($($param),*) -> Dynamic + 'static, + > RegisterBoxFn 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 fun = move |mut args: FnCallArgs| { // Check for length at the beginning to avoid // per-element bound checks. if args.len() != count_args!($($par)*) { diff --git a/src/lib.rs b/src/lib.rs index c002e814..841c56b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,7 +45,7 @@ mod engine; mod fn_register; mod parser; -pub use any::Any; -pub use engine::{Engine, EvalAltResult, Scope}; +pub use any::Dynamic; +pub use engine::{Array, Engine, EvalAltResult, Scope}; pub use fn_register::{RegisterBoxFn, RegisterFn}; pub use parser::{ParseError, ParseErrorType, AST}; From 80a9abada60f5f1634e9b4466b2b6cb55043d3eb Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 25 Feb 2020 15:02:50 +0800 Subject: [PATCH 08/30] Introduce to_int and to_float conersion functions. --- README.md | 22 ++++++++++++++++++++++ src/engine.rs | 13 +++++++++++++ 2 files changed, 35 insertions(+) diff --git a/README.md b/README.md index 29ea9216..495bcf46 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,28 @@ Compiling a script file into AST is also supported: 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) +* 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 +``` + # 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. diff --git a/src/engine.rs b/src/engine.rs index ae6220a7..cc18c4e9 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1064,6 +1064,19 @@ impl Engine { // directly let ent = engine.fns.entry("[]".to_string()).or_insert_with(Vec::new); // (*ent).push(FnType::ExternalFn2(Box::new(idx))); + // Register conversion functions + engine.register_fn("to_float", |x: i32| x as f64); + engine.register_fn("to_float", |x: u32| x as f64); + engine.register_fn("to_float", |x: i64| x as f64); + engine.register_fn("to_float", |x: u64| x as f64); + engine.register_fn("to_float", |x: f32| x as f64); + + engine.register_fn("to_int", |x: i32| x as i64); + engine.register_fn("to_int", |x: u32| x as i64); + engine.register_fn("to_int", |x: u64| x as i64); + engine.register_fn("to_int", |x: f32| x as i64); + engine.register_fn("to_int", |x: f64| x as i64); + // Register print and debug fn print_debug(x: T) -> String { format!("{:?}", x) From 8128c0cf241d73f757a14987eed5ad19c535a7fd Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 25 Feb 2020 16:23:59 +0800 Subject: [PATCH 09/30] Add pad and truncate array functions. --- README.md | 23 ++++++++++++++++++++--- src/engine.rs | 26 +++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 495bcf46..75532d80 100644 --- a/README.md +++ b/README.md @@ -444,8 +444,15 @@ fn decide(yes_no: bool) -> Dynamic { ## Arrays You can create arrays of values, and then access them with numeric indices. -The functions `push`, `pop` and `shift` can be used to insert and remove elements -to/from arrays. + +The following standard functions operate on arrays: + +* `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 +* `truncate` - cuts off the array at exactly a specified length (discarding all subsequent elements) ```rust let y = [1, 2, 3]; // 3 elements @@ -463,9 +470,19 @@ 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 ``` -`push` is only defined for standard built-in types. If you want to use `push` with +`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 diff --git a/src/engine.rs b/src/engine.rs index cc18c4e9..f024d108 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -951,6 +951,14 @@ impl Engine { ) } + macro_rules! reg_func3 { + ($engine:expr, $x:expr, $op:expr, $v:ty, $w:ty, $r:ty, $( $y:ty ),*) => ( + $( + $engine.register_fn($x, $op as fn(x: $v, y: $w, z: $y)->$r); + )* + ) + } + fn add(x: T, y: T) -> ::Output { x + y } @@ -1095,13 +1103,24 @@ impl Engine { reg_func1!(engine, "debug", print_debug, String, Array, ()); // Register array functions - fn push(list: &mut Array, item: T) { + 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!(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, ()); + reg_func3!(engine, "pad", pad, &mut Array, i64, (), i32, i64); + reg_func3!(engine, "pad", pad, &mut Array, i64, (), u32, u64); + reg_func3!(engine, "pad", pad, &mut Array, i64, (), f32, f64, bool); + reg_func3!(engine, "pad", pad, &mut Array, i64, (), String, Array, ()); engine.register_dynamic_fn("pop", |list: &mut Array| list.pop().unwrap_or(Box::new(()))); engine.register_dynamic_fn("shift", |list: &mut Array| { @@ -1114,6 +1133,11 @@ impl Engine { engine.register_fn("len", |list: &mut Array| -> i64 { list.len().try_into().unwrap() }); + engine.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 { From 8723eedca96f6933b6d96568e76864e940e7e74e Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 28 Feb 2020 23:38:39 +0800 Subject: [PATCH 10/30] Rename RegisterBoxFn to RegisterDynamicFn. --- src/engine.rs | 2 +- src/fn_register.rs | 4 ++-- src/lib.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index f024d108..9d6bbd37 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -8,7 +8,7 @@ use std::{convert::TryInto, sync::Arc}; use crate::any::{Any, AnyExt, Dynamic, Variant}; use crate::call::FunArgs; -use crate::fn_register::{RegisterBoxFn, RegisterFn}; +use crate::fn_register::{RegisterDynamicFn, RegisterFn}; use crate::parser::{lex, parse, Expr, FnDef, ParseError, Stmt, AST}; use fmt::{Debug, Display}; diff --git a/src/fn_register.rs b/src/fn_register.rs index 72bf4a7e..2aab7623 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -6,7 +6,7 @@ use crate::engine::{Engine, EvalAltResult, FnCallArgs}; pub trait RegisterFn { fn register_fn(&mut self, name: &str, f: FN); } -pub trait RegisterBoxFn { +pub trait RegisterDynamicFn { fn register_dynamic_fn(&mut self, name: &str, f: FN); } @@ -57,7 +57,7 @@ macro_rules! def_register { impl< $($par: Any + Clone,)* FN: Fn($($param),*) -> Dynamic + 'static, - > RegisterBoxFn for Engine + > RegisterDynamicFn for Engine { fn register_dynamic_fn(&mut self, name: &str, f: FN) { let fun = move |mut args: FnCallArgs| { diff --git a/src/lib.rs b/src/lib.rs index 841c56b2..c9302bed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,5 +47,5 @@ mod parser; pub use any::Dynamic; pub use engine::{Array, Engine, EvalAltResult, Scope}; -pub use fn_register::{RegisterBoxFn, RegisterFn}; +pub use fn_register::{RegisterDynamicFn, RegisterFn}; pub use parser::{ParseError, ParseErrorType, AST}; From c9daab3754d25fdd020b536c65462f4fda10e0ea Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 29 Feb 2020 17:10:51 +0800 Subject: [PATCH 11/30] Simplify code by removing redirections. --- src/engine.rs | 124 +++++++++++++++++++++++++------------------------- src/parser.rs | 20 ++++---- 2 files changed, 72 insertions(+), 72 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 9d6bbd37..83498df0 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -36,7 +36,7 @@ pub enum EvalAltResult { impl EvalAltResult { fn as_str(&self) -> Option<&str> { - Some(match *self { + Some(match self { EvalAltResult::ErrorCantOpenScriptFile(ref s) | EvalAltResult::ErrorVariableNotFound(ref s) | EvalAltResult::ErrorFunctionNotFound(ref s) @@ -51,22 +51,22 @@ impl PartialEq for EvalAltResult { 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)) => { + (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, + (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, } } @@ -74,16 +74,18 @@ impl PartialEq for EvalAltResult { impl Error for EvalAltResult { fn description(&self) -> &str { - match *self { + 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 => { + EvalAltResult::ErrorArrayOutOfBounds(_, ref index) if *index < 0 => { "Array access expects non-negative index" } - EvalAltResult::ErrorArrayOutOfBounds(max, _) if max == 0 => "Access of empty array", + EvalAltResult::ErrorArrayOutOfBounds(ref 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", @@ -330,8 +332,8 @@ impl Engine { ) -> Result { use std::iter::once; - match *dot_rhs { - Expr::FunctionCall(ref fn_name, ref args) => { + match dot_rhs { + Expr::FunctionCall(fn_name, args) => { let mut args: Array = args .iter() .map(|arg| self.eval_expr(scope, arg)) @@ -342,12 +344,12 @@ impl Engine { self.call_fn_raw(fn_name.to_owned(), args) } - Expr::Identifier(ref id) => { + Expr::Identifier(id) => { let get_fn_name = "get$".to_string() + id; self.call_fn_raw(get_fn_name, vec![this_ptr]) } - Expr::Index(ref id, ref idx_raw) => { + Expr::Index(id, idx_raw) => { let idx = self.eval_expr(scope, idx_raw)?; let get_fn_name = "get$".to_string() + id; @@ -368,7 +370,7 @@ impl Engine { .ok_or(EvalAltResult::ErrorArrayOutOfBounds(arr.len(), x)), }) } - Expr::Dot(ref inner_lhs, ref inner_rhs) => match **inner_lhs { + Expr::Dot(inner_lhs, 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]) @@ -392,7 +394,7 @@ impl Engine { .iter_mut() .enumerate() .rev() - .find(|&(_, &mut (ref name, _))| *id == *name) + .find(|&(_, &mut (ref name, _))| id == name) .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.to_owned())) .and_then(move |(idx, &mut (_, ref mut val))| map(val.as_mut()).map(|val| (idx, val))) } @@ -431,8 +433,8 @@ impl Engine { dot_lhs: &Expr, dot_rhs: &Expr, ) -> Result { - match *dot_lhs { - Expr::Identifier(ref id) => { + match dot_lhs { + Expr::Identifier(id) => { let (sc_idx, mut target) = Self::search_scope(scope, id, |x| Ok(x.into_dynamic()))?; let value = self.get_dot_val_helper(scope, target.as_mut(), dot_rhs); @@ -442,7 +444,7 @@ impl Engine { value } - Expr::Index(ref id, ref idx_raw) => { + Expr::Index(id, idx_raw) => { let (sc_idx, idx, mut target) = self.array_value(scope, id, idx_raw)?; let value = self.get_dot_val_helper(scope, target.as_mut(), dot_rhs); @@ -462,12 +464,12 @@ impl Engine { dot_rhs: &Expr, mut source_val: Dynamic, ) -> Result { - match *dot_rhs { - Expr::Identifier(ref id) => { + match dot_rhs { + Expr::Identifier(id) => { let set_fn_name = "set$".to_string() + id; self.call_fn_raw(set_fn_name, vec![this_ptr, source_val.as_mut()]) } - Expr::Dot(ref inner_lhs, ref inner_rhs) => match **inner_lhs { + Expr::Dot(inner_lhs, 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]) @@ -494,8 +496,8 @@ impl Engine { dot_rhs: &Expr, source_val: Dynamic, ) -> Result { - match *dot_lhs { - Expr::Identifier(ref id) => { + match dot_lhs { + Expr::Identifier(id) => { let (sc_idx, mut target) = Self::search_scope(scope, id, |x| Ok(x.into_dynamic()))?; let value = self.set_dot_val_helper(target.as_mut(), dot_rhs, source_val); @@ -505,7 +507,7 @@ impl Engine { value } - Expr::Index(ref id, ref idx_raw) => { + Expr::Index(id, idx_raw) => { let (sc_idx, idx, mut target) = self.array_value(scope, id, idx_raw)?; let value = self.set_dot_val_helper(target.as_mut(), dot_rhs, source_val); @@ -520,29 +522,27 @@ impl Engine { } 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(ref s) => Ok(Box::new(s.clone())), - Expr::CharConstant(ref c) => Ok(Box::new(*c)), - Expr::Identifier(ref id) => { + 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) => { for &mut (ref name, ref mut val) in &mut scope.iter_mut().rev() { - if *id == *name { + 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) => { + Expr::Index(id, idx_raw) => self.array_value(scope, id, idx_raw).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 { + if n == name { *val = rhs_val; return Ok(Box::new(())); @@ -551,10 +551,10 @@ impl Engine { Err(EvalAltResult::ErrorVariableNotFound(n.clone())) } Expr::Index(ref id, ref idx_raw) => { - let idx = self.eval_expr(scope, idx_raw)?; + let idx = self.eval_expr(scope, &idx_raw)?; for &mut (ref name, ref mut val) in &mut scope.iter_mut().rev() { - if *id == *name { + if id == name { return if let Some(&i) = idx.downcast_ref::() { if let Some(arr_typed) = (*val).downcast_mut() as Option<&mut Array> @@ -587,8 +587,8 @@ impl Engine { _ => Err(EvalAltResult::ErrorAssignmentToUnknownLHS), } } - 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) { @@ -598,7 +598,7 @@ impl Engine { Ok(Box::new(arr)) } - Expr::FunctionCall(ref fn_name, ref args) => self.call_fn_raw( + Expr::FunctionCall(fn_name, args) => self.call_fn_raw( fn_name.to_owned(), args.iter() .map(|ex| self.eval_expr(scope, ex)) @@ -614,9 +614,9 @@ impl Engine { } fn eval_stmt(&self, scope: &mut Scope, stmt: &Stmt) -> Result { - match *stmt { - Stmt::Expr(ref e) => self.eval_expr(scope, e), - Stmt::Block(ref b) => { + match stmt { + Stmt::Expr(e) => self.eval_expr(scope, e), + Stmt::Block(b) => { let prev_len = scope.len(); let mut last_result: Result = Ok(Box::new(())); @@ -634,7 +634,7 @@ impl Engine { last_result } - Stmt::If(ref guard, ref body) => { + Stmt::If(guard, body) => { let guard_result = self.eval_expr(scope, guard)?; match guard_result.downcast::() { Ok(g) => { @@ -647,7 +647,7 @@ impl Engine { Err(_) => Err(EvalAltResult::ErrorIfGuardMismatch), } } - Stmt::IfElse(ref guard, ref body, ref else_body) => { + Stmt::IfElse(guard, body, else_body) => { let guard_result = self.eval_expr(scope, guard)?; match guard_result.downcast::() { Ok(g) => { @@ -660,7 +660,7 @@ impl Engine { Err(_) => Err(EvalAltResult::ErrorIfGuardMismatch), } } - Stmt::While(ref guard, ref body) => loop { + Stmt::While(guard, body) => loop { let guard_result = self.eval_expr(scope, guard)?; match guard_result.downcast::() { Ok(g) => { @@ -677,14 +677,14 @@ impl Engine { Err(_) => return Err(EvalAltResult::ErrorIfGuardMismatch), } }, - 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) { @@ -706,13 +706,13 @@ impl Engine { } Stmt::Break => Err(EvalAltResult::LoopBreak), Stmt::Return => Err(EvalAltResult::Return(Box::new(()))), - Stmt::ReturnWithVal(ref a) => { + Stmt::ReturnWithVal(a) => { let result = self.eval_expr(scope, a)?; Err(EvalAltResult::Return(result)) } - Stmt::Let(ref name, ref init) => { - match *init { - Some(ref v) => { + Stmt::Let(name, init) => { + match init { + Some(v) => { let i = self.eval_expr(scope, v)?; scope.push((name.clone(), i)); } diff --git a/src/parser.rs b/src/parser.rs index e8cfa364..af0a1c06 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1074,11 +1074,11 @@ fn parse_array_expr<'a>(input: &mut Peekable>) -> Result(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::IntegerConstant(x) => Ok(Expr::IntegerConstant(x)), + Token::FloatConstant(x) => Ok(Expr::FloatConstant(x)), + Token::StringConst(s) => Ok(Expr::StringConstant(s)), + Token::CharConstant(c) => Ok(Expr::CharConstant(c)), + Token::Identifier(s) => parse_ident_expr(s, input), Token::LeftParen => parse_paren_expr(input), Token::LeftBracket => parse_array_expr(input), Token::True => Ok(Expr::True), @@ -1312,7 +1312,7 @@ fn parse_for<'a>(input: &mut Peekable>) -> Result s.clone(), + Some((Token::Identifier(s), _, _)) => s, Some((token, line, pos)) => { return Err(ParseError(PERR::VarExpectsIdentifier(token), line, pos)) } @@ -1338,7 +1338,7 @@ fn parse_var<'a>(input: &mut Peekable>) -> Result s.clone(), + Some((Token::Identifier(s), _, _)) => s, Some((token, line, pos)) => { return Err(ParseError(PERR::VarExpectsIdentifier(token), line, pos)) } @@ -1429,7 +1429,7 @@ fn parse_fn<'a>(input: &mut Peekable>) -> Result s.clone(), + Some((Token::Identifier(s), _, _)) => s, Some((token, line, pos)) => return Err(ParseError(PERR::FnMissingName(token), line, pos)), None => return Err(ParseError(PERR::FnMissingName(Token::None), 0, 0)), }; @@ -1457,8 +1457,8 @@ fn parse_fn<'a>(input: &mut Peekable>) -> Result break, Some((Token::Comma, _, _)) => (), - Some((Token::Identifier(ref s), _, _)) => { - params.push(s.clone()); + 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)), From 4b3cf95871cf99c3f09da97497a612663a51fe8a Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 29 Feb 2020 20:12:10 +0800 Subject: [PATCH 12/30] Simplify position handling. --- src/parser.rs | 379 +++++++++++++++++++++++++------------------------- 1 file changed, 192 insertions(+), 187 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index af0a1c06..931418b4 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -52,20 +52,39 @@ pub enum ParseErrorType { FnMissingParams, } +#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] +pub struct Position { + line: usize, + pos: usize, +} + +impl Position { + fn advance(&mut self) { + self.pos += 1; + } + fn new_line(&mut self) { + self.line += 1; + self.pos = 0; + } + fn eof() -> Self { + Self { line: 0, pos: 0 } + } +} + type PERR = ParseErrorType; #[derive(Debug, PartialEq, Clone)] -pub struct ParseError(ParseErrorType, usize, usize); +pub struct ParseError(ParseErrorType, Position); impl ParseError { pub fn error_type(&self) -> &ParseErrorType { &self.0 } pub fn line(&self) -> usize { - self.1 + self.1.line } pub fn position(&self) -> usize { - self.2 + self.1.pos } } @@ -327,34 +346,29 @@ 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 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() { + while let Some(look_ahead) = self.char_stream.next() { self.advance(); - if nxt == '\n' { - self.advance_line(); - } - match nxt { + match look_ahead { '\\' if !escape => escape = true, '\\' if escape => { escape = false; @@ -381,10 +395,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 +406,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 +418,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 +429,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 +441,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,13 +452,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)), x => { + if look_ahead == '\n' { + self.new_line(); + } escape = false; result.push(x); } @@ -455,15 +472,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 +567,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 +581,6 @@ impl<'a> TokenIterator<'a> { } else { Token::LexErr(LERR::MalformedNumber) }, - line, pos, )); } @@ -600,16 +615,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 +640,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 +662,6 @@ impl<'a> TokenIterator<'a> { _ if self.last.is_next_unary() => Token::UnaryPlus, _ => Token::Plus, }, - line, pos, )) } @@ -664,7 +676,6 @@ impl<'a> TokenIterator<'a> { _ if self.last.is_next_unary() => Token::UnaryMinus, _ => Token::Minus, }, - line, pos, )) } @@ -678,7 +689,6 @@ impl<'a> TokenIterator<'a> { } _ => Token::Multiply, }, - line, pos, )) } @@ -688,7 +698,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 +725,7 @@ impl<'a> TokenIterator<'a> { } self.advance(); } - '\n' => self.advance_line(), + '\n' => self.new_line(), _ => (), } @@ -727,27 +737,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 +766,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 +803,6 @@ impl<'a> TokenIterator<'a> { } _ => Token::GreaterThan, }, - line, pos, )) } @@ -807,7 +816,6 @@ impl<'a> TokenIterator<'a> { } _ => Token::Bang, }, - line, pos, )) } @@ -826,7 +834,6 @@ impl<'a> TokenIterator<'a> { } _ => Token::Pipe, }, - line, pos, )) } @@ -845,7 +852,6 @@ impl<'a> TokenIterator<'a> { } _ => Token::Ampersand, }, - line, pos, )) } @@ -859,7 +865,6 @@ impl<'a> TokenIterator<'a> { } _ => Token::XOr, }, - line, pos, )) } @@ -873,7 +878,6 @@ impl<'a> TokenIterator<'a> { } _ => Token::Modulo, }, - line, pos, )) } @@ -887,12 +891,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 +904,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,8 +918,7 @@ 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(), } } @@ -954,7 +956,7 @@ fn get_precedence(token: &Token) -> i32 { fn parse_paren_expr<'a>(input: &mut Peekable>) -> Result { match input.peek() { - Some((Token::RightParen, _, _)) => { + Some((Token::RightParen, _)) => { input.next(); return Ok(Expr::Unit); } @@ -964,8 +966,8 @@ 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())), } } @@ -975,7 +977,7 @@ fn parse_call_expr<'a>( ) -> 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)); } @@ -990,13 +992,13 @@ fn parse_call_expr<'a>( } match input.peek() { - Some(&(Token::RightParen, _, _)) => { + Some(&(Token::RightParen, _)) => { input.next(); return Ok(Expr::FunctionCall(id, args)); } - 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(); @@ -1009,12 +1011,12 @@ fn parse_index_expr<'a>( ) -> 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))); } - 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; @@ -1028,11 +1030,11 @@ fn parse_ident_expr<'a>( input: &mut Peekable>, ) -> Result { match input.peek() { - Some(&(Token::LeftParen, _, _)) => { + Some(&(Token::LeftParen, _)) => { input.next(); parse_call_expr(id, input) } - Some(&(Token::LeftBracket, _, _)) => { + Some(&(Token::LeftBracket, _)) => { input.next(); parse_index_expr(id, input) } @@ -1044,36 +1046,36 @@ fn parse_array_expr<'a>(input: &mut Peekable>) -> Result 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)) } - 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 { + Some((token, pos)) => match token { Token::IntegerConstant(x) => Ok(Expr::IntegerConstant(x)), Token::FloatConstant(x) => Ok(Expr::FloatConstant(x)), Token::StringConst(s) => Ok(Expr::StringConstant(s)), @@ -1083,30 +1085,26 @@ fn parse_primary<'a>(input: &mut Peekable>) -> Result 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)), + 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)), + Some((tok, _)) => tok.clone(), + None => return Err(ParseError(PERR::InputPastEndOfFile, Position::eof())), }; match token { Token::UnaryMinus => { input.next(); - Ok(Expr::FunctionCall( - "-".to_string(), - vec![parse_primary(input)?], - )) + Ok(Expr::FunctionCall("-".into(), vec![parse_primary(input)?])) } Token::UnaryPlus => { input.next(); @@ -1114,10 +1112,7 @@ fn parse_unary<'a>(input: &mut Peekable>) -> Result { input.next(); - Ok(Expr::FunctionCall( - "!".to_string(), - vec![parse_primary(input)?], - )) + Ok(Expr::FunctionCall("!".into(), vec![parse_primary(input)?])) } _ => parse_primary(input), } @@ -1133,7 +1128,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 +1136,12 @@ 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() { 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 +1153,105 @@ 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]), + Token::Minus => Expr::FunctionCall("-".into(), vec![lhs_curr, rhs]), + Token::Multiply => Expr::FunctionCall("*".into(), vec![lhs_curr, rhs]), + Token::Divide => Expr::FunctionCall("/".into(), vec![lhs_curr, rhs]), 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])), ) } 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])), ) } 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]), - Token::LessThanEqualsTo => { - Expr::FunctionCall("<=".to_string(), vec![lhs_curr, rhs]) - } - Token::GreaterThan => Expr::FunctionCall(">".to_string(), vec![lhs_curr, rhs]), - Token::GreaterThanEqualsTo => { - Expr::FunctionCall(">=".to_string(), vec![lhs_curr, rhs]) - } - 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::EqualsTo => Expr::FunctionCall("==".into(), vec![lhs_curr, rhs]), + Token::NotEqualsTo => Expr::FunctionCall("!=".into(), vec![lhs_curr, rhs]), + Token::LessThan => Expr::FunctionCall("<".into(), vec![lhs_curr, rhs]), + Token::LessThanEqualsTo => Expr::FunctionCall("<=".into(), vec![lhs_curr, rhs]), + Token::GreaterThan => Expr::FunctionCall(">".into(), vec![lhs_curr, rhs]), + Token::GreaterThanEqualsTo => Expr::FunctionCall(">=".into(), vec![lhs_curr, rhs]), + Token::Or => Expr::FunctionCall("||".into(), vec![lhs_curr, rhs]), + Token::And => Expr::FunctionCall("&&".into(), vec![lhs_curr, rhs]), + Token::XOr => Expr::FunctionCall("^".into(), vec![lhs_curr, rhs]), 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])), ) } 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])), ) } 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])), ) } 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])), ) } 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])), ) } - 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]), + Token::LeftShift => Expr::FunctionCall("<<".into(), vec![lhs_curr, rhs]), + Token::RightShift => Expr::FunctionCall(">>".into(), vec![lhs_curr, rhs]), 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])), ) } 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])), ) } - 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]), + Token::Modulo => Expr::FunctionCall("%".into(), vec![lhs_curr, rhs]), 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])), ) } - Token::PowerOf => Expr::FunctionCall("~".to_string(), vec![lhs_curr, rhs]), + Token::PowerOf => Expr::FunctionCall("~".into(), vec![lhs_curr, rhs]), 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])), ) } - _ => return Err(ParseError(PERR::UnknownOperator, line, pos)), + _ => return Err(ParseError(PERR::UnknownOperator, pos)), }; } } @@ -1278,7 +1269,7 @@ fn parse_if<'a>(input: &mut Peekable>) -> Result { + Some(&(Token::Else, _)) => { input.next(); let else_body = parse_block(input)?; Ok(Stmt::IfElse( @@ -1312,19 +1303,25 @@ fn parse_for<'a>(input: &mut Peekable>) -> Result s, - Some((token, line, pos)) => { - return Err(ParseError(PERR::VarExpectsIdentifier(token), line, pos)) + Some((Token::Identifier(s), _)) => s, + Some((token, pos)) => return Err(ParseError(PERR::VarExpectsIdentifier(token), pos)), + None => { + return Err(ParseError( + PERR::VarExpectsIdentifier(Token::None), + Position::eof(), + )) } - None => return Err(ParseError(PERR::VarExpectsIdentifier(Token::None), 0, 0)), }; match input.next() { - Some((Token::In, _, _)) => {} - Some((token, line, pos)) => { - return Err(ParseError(PERR::VarExpectsIdentifier(token), line, pos)) + Some((Token::In, _)) => {} + Some((token, pos)) => return Err(ParseError(PERR::VarExpectsIdentifier(token), pos)), + None => { + return Err(ParseError( + PERR::VarExpectsIdentifier(Token::None), + Position::eof(), + )) } - None => return Err(ParseError(PERR::VarExpectsIdentifier(Token::None), 0, 0)), } let expr = parse_expr(input)?; @@ -1338,15 +1335,18 @@ fn parse_var<'a>(input: &mut Peekable>) -> Result s, - Some((token, line, pos)) => { - return Err(ParseError(PERR::VarExpectsIdentifier(token), line, pos)) + Some((Token::Identifier(s), _)) => s, + Some((token, pos)) => return Err(ParseError(PERR::VarExpectsIdentifier(token), pos)), + None => { + return Err(ParseError( + PERR::VarExpectsIdentifier(Token::None), + Position::eof(), + )) } - None => return Err(ParseError(PERR::VarExpectsIdentifier(Token::None), 0, 0)), }; match input.peek() { - Some(&(Token::Equals, _, _)) => { + Some(&(Token::Equals, _)) => { input.next(); let initializer = parse_expr(input)?; Ok(Stmt::Let(name, Some(Box::new(initializer)))) @@ -1357,9 +1357,9 @@ fn parse_var<'a>(input: &mut Peekable>) -> Result(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 +1367,7 @@ fn parse_block<'a>(input: &mut Peekable>) -> Result true, + Some(&(Token::RightBrace, _)) => true, _ => false, }; @@ -1375,23 +1375,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,26 +1401,26 @@ 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, _)) => { input.next(); Ok(Stmt::Break) } - Some(&(Token::Return, _, _)) => { + Some(&(Token::Return, _)) => { input.next(); match input.peek() { - Some(&(Token::SemiColon, _, _)) => Ok(Stmt::Return), + Some(&(Token::SemiColon, _)) => Ok(Stmt::Return), _ => { let ret = parse_expr(input)?; Ok(Stmt::ReturnWithVal(Box::new(ret))) } } } - 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), } } @@ -1429,23 +1429,28 @@ fn parse_fn<'a>(input: &mut Peekable>) -> Result s, - 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((token, pos)) => return Err(ParseError(PERR::FnMissingName(token), pos)), + None => { + return Err(ParseError( + PERR::FnMissingName(Token::None), + 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 +1460,13 @@ fn parse_fn<'a>(input: &mut Peekable>) -> Result break, - Some((Token::Comma, _, _)) => (), - Some((Token::Identifier(s), _, _)) => { + 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())), } } } @@ -1481,11 +1486,11 @@ fn parse_top_level<'a>(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(); } } From 17f0001b11609f1a7d0bea5ec8124fb154d63b17 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 29 Feb 2020 21:10:31 +0800 Subject: [PATCH 13/30] Fix string handling - error for unterminated strings. --- src/parser.rs | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 931418b4..eaab0271 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -7,6 +7,7 @@ use std::str::Chars; #[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)] pub enum LexError { UnexpectedChar(char), + UnterminatedString, MalformedEscapeSequence, MalformedNumber, MalformedChar, @@ -19,6 +20,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", @@ -62,6 +64,10 @@ impl Position { fn advance(&mut self) { self.pos += 1; } + fn rewind(&mut self) { + // Beware, should not rewind at zero position + self.pos -= 1; + } fn new_line(&mut self) { self.line += 1; self.pos = 0; @@ -132,7 +138,7 @@ impl fmt::Display for ParseError { if self.line() > 0 { write!(f, " at line {}, position {}", self.line(), self.position()) } else { - write!(f, " but script is incomplete") + write!(f, " at the end of the script but there is no more input") } } } @@ -354,6 +360,9 @@ impl<'a> TokenIterator<'a> { fn advance(&mut self) { self.pos.advance(); } + fn rewind(&mut self) { + self.pos.rewind(); + } fn new_line(&mut self) { self.pos.new_line(); } @@ -365,10 +374,16 @@ impl<'a> TokenIterator<'a> { let mut result = Vec::new(); let mut escape = false; - while let Some(look_ahead) = self.char_stream.next() { + loop { + let next_char = self.char_stream.next(); + + if next_char.is_none() { + return Err((LERR::UnterminatedString, Position::eof())); + } + self.advance(); - match look_ahead { + match next_char.unwrap() { '\\' if !escape => escape = true, '\\' if escape => { escape = false; @@ -458,10 +473,11 @@ impl<'a> TokenIterator<'a> { x if enclosing_char == x && escape => result.push(x), x if enclosing_char == x && !escape => break, _ if escape => return Err((LERR::MalformedEscapeSequence, self.pos)), + '\n' => { + self.rewind(); + return Err((LERR::UnterminatedString, self.pos)); + } x => { - if look_ahead == '\n' { - self.new_line(); - } escape = false; result.push(x); } @@ -983,13 +999,7 @@ fn parse_call_expr<'a>( } 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, _)) => { From 5f135353c07a79ae3c7795600e940e44dc2688bb Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 29 Feb 2020 21:18:48 +0800 Subject: [PATCH 14/30] Change precedence to i8. --- src/parser.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index eaab0271..539a06ed 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -939,7 +939,7 @@ pub fn lex(input: &str) -> TokenIterator<'_> { } } -fn get_precedence(token: &Token) -> i32 { +fn get_precedence(token: &Token) -> i8 { match *token { Token::Equals | Token::PlusAssign @@ -1130,7 +1130,7 @@ fn parse_unary<'a>(input: &mut Peekable>) -> Result( input: &mut Peekable>, - prec: i32, + prec: i8, lhs: Expr, ) -> Result { let mut lhs_curr = lhs; From e93fd7d3fecfd114ce4bf0648487c3d0f97c724d Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 1 Mar 2020 13:30:22 +0800 Subject: [PATCH 15/30] Properly handle char types. --- README.md | 67 ++++++- src/engine.rs | 514 +++++++++++++++++++++++++++++++------------------ src/parser.rs | 8 +- tests/chars.rs | 10 + 4 files changed, 401 insertions(+), 198 deletions(-) diff --git a/README.md b/README.md index 75532d80..096a34d9 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,7 @@ 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` @@ -138,6 +139,10 @@ There is a `to_float` function to convert a supported number to an `f64`, and a 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 @@ -335,6 +340,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. @@ -460,6 +482,10 @@ 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 @@ -526,9 +552,46 @@ let last = 'Davis'; let full_name = name + " " + middle_initial + ". " + last; full_name == "Bob C. Davis"; +// String building with different types let age = 42; -let name_and_age = full_name + ": age " + age; // String building with different types -name_and_age == "Bob C. Davis: 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 +* `truncate` - cuts off the string at exactly a specified number of characters +* `replace` - replaces a substring with another + +```rust +let full_name == "Bob C. Davis"; +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."; ``` ## Print and Debug diff --git a/src/engine.rs b/src/engine.rs index 83498df0..e4ab6e2e 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -4,7 +4,7 @@ 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, Dynamic, Variant}; use crate::call::FunArgs; @@ -20,16 +20,17 @@ pub enum EvalAltResult { ErrorParseError(ParseError), ErrorFunctionNotFound(String), ErrorFunctionArgMismatch, - ErrorArrayOutOfBounds(usize, i64), - ErrorArrayMismatch, - ErrorIndexMismatch, - ErrorIfGuardMismatch, - ErrorForMismatch, + ErrorArrayBounds(usize, i64), + ErrorStringBounds(usize, i64), + ErrorIndexing, + ErrorIndexExpr, + ErrorIfGuard, + ErrorFor, ErrorVariableNotFound(String), ErrorAssignmentToUnknownLHS, ErrorMismatchOutputType(String), ErrorCantOpenScriptFile(String), - ErrorMalformedDotExpression, + ErrorDotExpr, LoopBreak, Return(Dynamic), } @@ -54,18 +55,21 @@ impl PartialEq for EvalAltResult { (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)) => { + (ErrorIndexExpr, ErrorIndexExpr) => true, + (ErrorIndexing, ErrorIndexing) => true, + (ErrorArrayBounds(max1, index1), ErrorArrayBounds(max2, index2)) => { max1 == max2 && index1 == index2 } - (ErrorIfGuardMismatch, ErrorIfGuardMismatch) => true, - (ErrorForMismatch, ErrorForMismatch) => true, + (ErrorStringBounds(max1, index1), ErrorStringBounds(max2, index2)) => { + max1 == max2 && index1 == index2 + } + (ErrorIfGuard, ErrorIfGuard) => true, + (ErrorFor, ErrorFor) => 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, + (ErrorDotExpr, ErrorDotExpr) => true, (LoopBreak, LoopBreak) => true, _ => false, } @@ -75,29 +79,32 @@ impl PartialEq for EvalAltResult { 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(_, ref index) if *index < 0 => { + Self::ErrorParseError(ref p) => p.description(), + Self::ErrorFunctionNotFound(_) => "Function not found", + Self::ErrorFunctionArgMismatch => "Function argument types do not match", + 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(_, ref index) if *index < 0 => { "Array access expects non-negative index" } - EvalAltResult::ErrorArrayOutOfBounds(ref max, _) if *max == 0 => { - "Access of empty array" + Self::ErrorArrayBounds(ref max, _) if *max == 0 => "Access of empty array", + Self::ErrorArrayBounds(_, _) => "Array index out of bounds", + Self::ErrorStringBounds(_, ref index) if *index < 0 => { + "Indexing a string expects a non-negative index" } - 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::ErrorStringBounds(ref max, _) if *max == 0 => "Indexing of empty string", + Self::ErrorStringBounds(_, _) => "String index out of bounds", + Self::ErrorIfGuard => "If guards expect boolean expression", + Self::ErrorFor => "For loops expect array", + 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::LoopBreak => "[Not Error] Breaks out of loop", + Self::Return(_) => "[Not Error] Function returns value", } } @@ -113,13 +120,22 @@ impl fmt::Display for EvalAltResult { } else { match self { EvalAltResult::ErrorParseError(ref p) => write!(f, "Syntax error: {}", p), - EvalAltResult::ErrorArrayOutOfBounds(_, index) if *index < 0 => { + EvalAltResult::ErrorArrayBounds(_, index) if *index < 0 => { write!(f, "{}: {} < 0", self.description(), index) } - EvalAltResult::ErrorArrayOutOfBounds(max, _) if *max == 0 => { + EvalAltResult::ErrorArrayBounds(max, _) if *max == 0 => { write!(f, "{}", self.description()) } - EvalAltResult::ErrorArrayOutOfBounds(max, index) => { + EvalAltResult::ErrorArrayBounds(max, index) => { + write!(f, "{} (max {}): {}", self.description(), max - 1, index) + } + EvalAltResult::ErrorStringBounds(_, index) if *index < 0 => { + write!(f, "{}: {} < 0", self.description(), index) + } + EvalAltResult::ErrorStringBounds(max, _) if *max == 0 => { + write!(f, "{}", self.description()) + } + EvalAltResult::ErrorStringBounds(max, index) => { write!(f, "{} (max {}): {}", self.description(), max - 1, index) } err => write!(f, "{}", err.description()), @@ -222,14 +238,14 @@ impl Engine { self.fns.get(&spec1) }) .ok_or_else(|| { - let typenames = args + let type_names = args .iter() .map(|x| (*(&**x).into_dynamic()).type_name()) .collect::>(); EvalAltResult::ErrorFunctionNotFound(format!( "{} ({})", ident, - typenames.join(", ") + type_names.join(", ") )) }) .and_then(move |f| match **f { @@ -350,35 +366,65 @@ impl Engine { self.call_fn_raw(get_fn_name, vec![this_ptr]) } Expr::Index(id, idx_raw) => { - let idx = self.eval_expr(scope, idx_raw)?; + let idx = self + .eval_expr(scope, idx_raw)? + .downcast_ref::() + .map(|i| *i) + .ok_or(EvalAltResult::ErrorIndexExpr)?; + let get_fn_name = "get$".to_string() + id; let mut val = self.call_fn_raw(get_fn_name, vec![this_ptr])?; - ((*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 { + Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx)) + } else { + arr.get(idx as usize) .cloned() - .ok_or(EvalAltResult::ErrorArrayOutOfBounds(arr.len(), x)), - }) + .ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr.len(), idx)) + } + } else if let Some(s) = (*val).downcast_mut() as Option<&mut String> { + if idx < 0 { + Err(EvalAltResult::ErrorStringBounds(s.chars().count(), idx)) + } else { + s.chars() + .nth(idx as usize) + .map(|ch| Box::new(ch) as Dynamic) + .ok_or_else(|| EvalAltResult::ErrorStringBounds(s.chars().count(), idx)) + } + } else { + Err(EvalAltResult::ErrorIndexing) + } } Expr::Dot(inner_lhs, 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)) + let value = 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))?; + + // 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) } - _ => Err(EvalAltResult::ErrorMalformedDotExpression), + Expr::Index(_, _) => { + // TODO - Handle Expr::Index for these scenarios: + // + // let x = obj.prop[2].x; + // obj.prop[3] = 42; + // + Err(EvalAltResult::ErrorDotExpr) + } + _ => Err(EvalAltResult::ErrorDotExpr), }, - _ => Err(EvalAltResult::ErrorMalformedDotExpression), + _ => Err(EvalAltResult::ErrorDotExpr), } } @@ -399,32 +445,64 @@ impl Engine { .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, Dynamic), EvalAltResult> { - let idx_boxed = self + ) -> 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)?; - 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; + + return if idx < 0 { + Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx)) + } else { + arr.get(idx as usize) + .cloned() + .ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr.len(), idx)) + }; + } + + if let Some(s) = (*val).downcast_mut() as Option<&mut String> { + is_array = false; + + return if idx < 0 { + Err(EvalAltResult::ErrorStringBounds(s.chars().count(), idx)) + } else { + s.chars() + .nth(idx as usize) + .map(|ch| Box::new(ch) as Dynamic) + .ok_or_else(|| EvalAltResult::ErrorStringBounds(s.chars().count(), idx)) + }; + } + + Err(EvalAltResult::ErrorIndexing) + }) + .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.truncate(0); + chars.iter().for_each(|&ch| s.push(ch)); } fn get_dot_val( @@ -445,16 +523,27 @@ impl Engine { value } Expr::Index(id, idx_raw) => { - let (sc_idx, idx, mut target) = self.array_value(scope, id, idx_raw)?; + let (is_array, sc_idx, idx, mut target) = self.indexed_value(scope, id, idx_raw)?; 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 { + // Target should be a char + let new_ch = *target.downcast::().unwrap(); + + // Root should be a String + let s = scope[sc_idx].1.downcast_mut::().unwrap(); + + Self::str_replace_char(s, idx, new_ch); + } value } - _ => Err(EvalAltResult::ErrorMalformedDotExpression), + _ => Err(EvalAltResult::ErrorDotExpr), } } @@ -483,9 +572,9 @@ impl Engine { self.call_fn_raw(set_fn_name, vec![this_ptr, v.as_mut()]) }) } - _ => Err(EvalAltResult::ErrorMalformedDotExpression), + _ => Err(EvalAltResult::ErrorDotExpr), }, - _ => Err(EvalAltResult::ErrorMalformedDotExpression), + _ => Err(EvalAltResult::ErrorDotExpr), } } @@ -508,16 +597,26 @@ impl Engine { value } Expr::Index(id, idx_raw) => { - let (sc_idx, idx, mut target) = self.array_value(scope, id, idx_raw)?; + let (is_array, sc_idx, idx, mut target) = self.indexed_value(scope, id, idx_raw)?; 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 { + // Target should be a char + let new_ch = *target.downcast::().unwrap(); + + // Root should be a String + let s = scope[sc_idx].1.downcast_mut::().unwrap(); + + Self::str_replace_char(s, idx, new_ch); + } value } - _ => Err(EvalAltResult::ErrorMalformedDotExpression), + _ => Err(EvalAltResult::ErrorDotExpr), } } @@ -528,58 +627,72 @@ impl Engine { Expr::StringConstant(s) => Ok(Box::new(s.clone())), Expr::CharConstant(c) => Ok(Box::new(*c)), Expr::Identifier(id) => { - for &mut (ref name, ref mut val) in &mut scope.iter_mut().rev() { - if id == name { - return Ok(val.clone()); - } + match scope.iter().rev().filter(|(name, _)| id == name).next() { + Some((_, val)) => Ok(val.clone()), + _ => Err(EvalAltResult::ErrorVariableNotFound(id.clone())), } - Err(EvalAltResult::ErrorVariableNotFound(id.clone())) } - Expr::Index(id, idx_raw) => self.array_value(scope, id, idx_raw).map(|(_, _, x)| x), + Expr::Index(id, idx_raw) => { + self.indexed_value(scope, id, idx_raw).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 { + match scope.iter_mut().rev().filter(|(name, _)| n == name).next() { + Some((_, val)) => { *val = rhs_val; - - return Ok(Box::new(())); + Ok(Box::new(())) } + _ => Err(EvalAltResult::ErrorVariableNotFound(n.clone())), } - Err(EvalAltResult::ErrorVariableNotFound(n.clone())) } Expr::Index(ref id, ref idx_raw) => { - let idx = self.eval_expr(scope, &idx_raw)?; + let idx = *match self.eval_expr(scope, &idx_raw)?.downcast_ref::() { + Some(x) => x, + _ => return Err(EvalAltResult::ErrorIndexExpr), + }; - 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) - }; - } + 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())), + }; + + if let Some(arr) = val.downcast_mut() as Option<&mut Array> { + return if idx < 0 { + Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx)) + } else if idx as usize >= arr.len() { + Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx)) + } else { + arr[idx as usize] = rhs_val; + Ok(Box::new(())) + }; } - Err(EvalAltResult::ErrorVariableNotFound(id.clone())) + if let Some(s) = val.downcast_mut() as Option<&mut String> { + let s_len = s.chars().count(); + + return if idx < 0 { + Err(EvalAltResult::ErrorStringBounds(s_len, idx)) + } else if idx as usize >= s_len { + Err(EvalAltResult::ErrorStringBounds(s_len, idx)) + } else { + // Should be a char + let new_ch = *rhs_val.downcast::().unwrap(); + Self::str_replace_char(s, idx as usize, new_ch); + Ok(Box::new(())) + }; + } + + return Err(EvalAltResult::ErrorIndexExpr); } Expr::Dot(ref dot_lhs, ref dot_rhs) => { self.set_dot_val(scope, dot_lhs, dot_rhs, rhs_val) @@ -591,10 +704,11 @@ impl Engine { 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)) } @@ -615,13 +729,13 @@ impl Engine { fn eval_stmt(&self, scope: &mut Scope, stmt: &Stmt) -> Result { match stmt { - Stmt::Expr(e) => self.eval_expr(scope, e), - Stmt::Block(b) => { + 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 s in b.iter() { - last_result = self.eval_stmt(scope, s); + for block_stmt in block.iter() { + last_result = self.eval_stmt(scope, block_stmt); if let Err(x) = last_result { last_result = Err(x); break; @@ -634,37 +748,32 @@ impl Engine { last_result } - Stmt::If(guard, 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::If(guard, body) => self + .eval_expr(scope, guard)? + .downcast::() + .map_err(|_| EvalAltResult::ErrorIfGuard) + .and_then(|guard_val| { + if *guard_val { + self.eval_stmt(scope, body) + } else { + Ok(Box::new(())) } - Err(_) => Err(EvalAltResult::ErrorIfGuardMismatch), - } - } - Stmt::IfElse(guard, body, 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) - } + }), + Stmt::IfElse(guard, body, else_body) => self + .eval_expr(scope, guard)? + .downcast::() + .map_err(|_| EvalAltResult::ErrorIfGuard) + .and_then(|guard_val| { + if *guard_val { + self.eval_stmt(scope, body) + } else { + self.eval_stmt(scope, else_body) } - Err(_) => Err(EvalAltResult::ErrorIfGuardMismatch), - } - } + }), Stmt::While(guard, body) => loop { - let guard_result = self.eval_expr(scope, guard)?; - match guard_result.downcast::() { - Ok(g) => { - if *g { + 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), @@ -674,7 +783,7 @@ impl Engine { return Ok(Box::new(())); } } - Err(_) => return Err(EvalAltResult::ErrorIfGuardMismatch), + Err(_) => return Err(EvalAltResult::ErrorIfGuard), } }, Stmt::Loop(body) => loop { @@ -701,7 +810,7 @@ impl Engine { scope.remove(idx); Ok(Box::new(())) } else { - return Err(EvalAltResult::ErrorForMismatch); + return Err(EvalAltResult::ErrorFor); } } Stmt::Break => Err(EvalAltResult::LoopBreak), @@ -711,13 +820,12 @@ impl Engine { Err(EvalAltResult::Return(result)) } Stmt::Let(name, init) => { - match init { - Some(v) => { - let i = self.eval_expr(scope, v)?; - scope.push((name.clone(), i)); - } - None => scope.push((name.clone(), Box::new(()))), - }; + 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(())) } } @@ -734,38 +842,38 @@ impl Engine { } /// Compile a file into an AST - pub fn compile_file(fname: &str) -> Result { + pub fn compile_file(filename: &str) -> Result { use std::fs::File; use std::io::prelude::*; - if let Ok(mut f) = File::open(fname) { + if let Ok(mut f) = File::open(filename) { 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())) + Err(EvalAltResult::ErrorCantOpenScriptFile(filename.to_owned())) } } else { - Err(EvalAltResult::ErrorCantOpenScriptFile(fname.to_owned())) + Err(EvalAltResult::ErrorCantOpenScriptFile(filename.to_owned())) } } /// Evaluate a file - pub fn eval_file(&mut self, fname: &str) -> Result { + pub fn eval_file(&mut self, filename: &str) -> Result { use std::fs::File; use std::io::prelude::*; - if let Ok(mut f) = File::open(fname) { + if let Ok(mut f) = File::open(filename) { let mut contents = String::new(); if f.read_to_string(&mut contents).is_ok() { self.eval::(&contents) } else { - Err(EvalAltResult::ErrorCantOpenScriptFile(fname.to_owned())) + Err(EvalAltResult::ErrorCantOpenScriptFile(filename.to_owned())) } } else { - Err(EvalAltResult::ErrorCantOpenScriptFile(fname.to_owned())) + Err(EvalAltResult::ErrorCantOpenScriptFile(filename.to_owned())) } } @@ -830,11 +938,11 @@ impl Engine { /// 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> { + pub fn consume_file(&mut self, filename: &str) -> Result<(), EvalAltResult> { use std::fs::File; use std::io::prelude::*; - if let Ok(mut f) = File::open(fname) { + if let Ok(mut f) = File::open(filename) { let mut contents = String::new(); if f.read_to_string(&mut contents).is_ok() { @@ -844,10 +952,10 @@ impl Engine { Ok(()) } } else { - Err(EvalAltResult::ErrorCantOpenScriptFile(fname.to_owned())) + Err(EvalAltResult::ErrorCantOpenScriptFile(filename.to_owned())) } } else { - Err(EvalAltResult::ErrorCantOpenScriptFile(fname.to_owned())) + Err(EvalAltResult::ErrorCantOpenScriptFile(filename.to_owned())) } } @@ -1040,12 +1148,12 @@ impl Engine { 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_cmp!(engine, "<", lt, i32, i64, u32, u64, String, char, f32, f64); + reg_cmp!(engine, "<=", lte, i32, i64, u32, u64, String, char, f32, f64); + reg_cmp!(engine, ">", gt, i32, i64, u32, u64, String, char, f32, f64); + reg_cmp!(engine, ">=", gte, i32, i64, u32, u64, String, char, f32, f64); + reg_cmp!(engine, "==", eq, i32, i64, u32, u64, bool, String, char, f32, f64); + reg_cmp!(engine, "!=", ne, i32, i64, u32, u64, bool, String, char, f32, f64); reg_op!(engine, "||", or, bool); reg_op!(engine, "&&", and, bool); @@ -1084,6 +1192,7 @@ impl Engine { engine.register_fn("to_int", |x: u64| x as i64); engine.register_fn("to_int", |x: f32| x as i64); engine.register_fn("to_int", |x: f64| x as i64); + engine.register_fn("to_int", |ch: char| ch as i64); // Register print and debug fn print_debug(x: T) -> String { @@ -1094,15 +1203,15 @@ impl Engine { } reg_func1!(engine, "print", print, String, i32, i64, u32, u64); - reg_func1!(engine, "print", print, String, f32, f64, bool, String); + reg_func1!(engine, "print", print, String, f32, f64, bool, char, String); reg_func1!(engine, "print", print_debug, String, Array); engine.register_fn("print", |_: ()| println!()); reg_func1!(engine, "debug", print_debug, String, i32, i64, u32, u64); - reg_func1!(engine, "debug", print_debug, String, f32, f64, bool, String); - reg_func1!(engine, "debug", print_debug, String, Array, ()); + reg_func1!(engine, "debug", print_debug, String, f32, f64, bool, char); + reg_func1!(engine, "debug", print_debug, String, String, Array, ()); - // Register array functions + // Register array utility functions fn push(list: &mut Array, item: T) { list.push(Box::new(item)); } @@ -1115,11 +1224,11 @@ impl Engine { } 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, (), f32, f64, bool, char); reg_func2x!(engine, "push", push, &mut Array, (), String, Array, ()); - reg_func3!(engine, "pad", pad, &mut Array, i64, (), i32, i64); - reg_func3!(engine, "pad", pad, &mut Array, i64, (), u32, u64); - reg_func3!(engine, "pad", pad, &mut Array, i64, (), f32, f64, bool); + reg_func3!(engine, "pad", pad, &mut Array, i64, (), i32, u32, f32); + reg_func3!(engine, "pad", pad, &mut Array, i64, (), i64, u64, f64); + reg_func3!(engine, "pad", pad, &mut Array, i64, (), bool, char); reg_func3!(engine, "pad", pad, &mut Array, i64, (), String, Array, ()); engine.register_dynamic_fn("pop", |list: &mut Array| list.pop().unwrap_or(Box::new(()))); @@ -1130,9 +1239,7 @@ impl Engine { Box::new(()) } }); - engine.register_fn("len", |list: &mut Array| -> i64 { - list.len().try_into().unwrap() - }); + engine.register_fn("len", |list: &mut Array| -> i64 { list.len() as i64 }); engine.register_fn("truncate", |list: &mut Array, len: i64| { if len >= 0 { list.truncate(len as usize); @@ -1147,14 +1254,37 @@ impl Engine { format!("{}{}", x, y) } - reg_func2x!(engine, "+", append, String, String, i32, i64, u32, u64, f32, f64, bool); + reg_func2x!(engine, "+", append, String, String, i32, i64, u32, u64, f32, f64, bool, char); 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); + reg_func2y!(engine, "+", prepend, String, String, i32, i64, u32, u64, f32, f64, bool, char); engine.register_fn("+", |x: Array, y: String| format!("{:?}{}", x, y)); engine.register_fn("+", |_: (), y: String| format!("{}", y)); + // Register string utility functions + engine.register_fn("len", |s: &mut String| -> i64 { s.chars().count() as i64 }); + engine.register_fn("truncate", |s: &mut String, len: i64| { + if len >= 0 { + s.truncate(len as usize); + } + }); + engine.register_fn("pad", |s: &mut String, len: i64, ch: char| { + let gap = s.chars().count() - len as usize; + + for _ in 0..gap { + s.push(ch); + } + }); + engine.register_fn( + "replace", + |s: &mut String, pattern: String, replace: String| { + let new_str = s.replace(&pattern, &replace); + s.truncate(0); + s.push_str(&new_str); + }, + ); + // Register array iterator engine.register_iterator::(|a| { Box::new(a.downcast_ref::().unwrap().clone().into_iter()) diff --git a/src/parser.rs b/src/parser.rs index 539a06ed..d36ae1c7 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -54,6 +54,8 @@ pub enum ParseErrorType { FnMissingParams, } +type PERR = ParseErrorType; + #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] pub struct Position { line: usize, @@ -77,13 +79,11 @@ impl Position { } } -type PERR = ParseErrorType; - #[derive(Debug, PartialEq, Clone)] -pub struct ParseError(ParseErrorType, Position); +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 { diff --git a/tests/chars.rs b/tests/chars.rs index 3c5a920b..44657000 100644 --- a/tests/chars.rs +++ b/tests/chars.rs @@ -6,6 +6,16 @@ fn test_chars() { assert_eq!(engine.eval::("'y'"), Ok('y')); assert_eq!(engine.eval::("'\\u2764'"), Ok('❤')); + assert_eq!(engine.eval::(r#"let x="hello"; x[2]"#), Ok('l')); + assert_eq!( + engine.eval::(r#"let x="hello"; x[2]='$'; x"#), + Ok("he$lo".into()) + ); + + match engine.eval::("'\\uhello'") { + Err(_) => (), + _ => assert!(false), + } match engine.eval::("''") { Err(_) => (), From b152ed88f0e539685ff9be3b96996c8fefec44c3 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 1 Mar 2020 17:37:47 +0800 Subject: [PATCH 16/30] Move built-in functions into separate module. --- README.md | 25 +++- src/builtin.rs | 312 ++++++++++++++++++++++++++++++++++++++++++++++++ src/engine.rs | 314 ++----------------------------------------------- src/lib.rs | 1 + 4 files changed, 345 insertions(+), 307 deletions(-) create mode 100644 src/builtin.rs diff --git a/README.md b/README.md index 096a34d9..4096dc82 100644 --- a/README.md +++ b/README.md @@ -474,6 +474,7 @@ The following standard functions operate on arrays: * `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 @@ -506,6 +507,10 @@ 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 @@ -574,24 +579,36 @@ 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"; +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.len() == 15; full_name == "Bob C. Davis$$$"; full_name.truncate(6); -full_name.len() = 6; +full_name.len() == 6; full_name == "Bob C."; full_name.replace("Bob", "John"); -full_name.len() = 7; +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 diff --git a/src/builtin.rs b/src/builtin.rs new file mode 100644 index 00000000..5157eaca --- /dev/null +++ b/src/builtin.rs @@ -0,0 +1,312 @@ +use crate::{any::Any, Array, Dynamic, Engine, RegisterDynamicFn, RegisterFn}; +use std::fmt::{Debug, Display}; +use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, 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, i32, i64, u32, u64, f32, f64); + reg_op!(self, "-", sub, i32, i64, u32, u64, f32, f64); + reg_op!(self, "*", mul, i32, i64, u32, u64, f32, f64); + reg_op!(self, "/", div, i32, i64, u32, u64, f32, f64); + + reg_cmp!(self, "<", lt, i32, i64, u32, u64, String, char, f32, f64); + reg_cmp!(self, "<=", lte, i32, i64, u32, u64, String, char, f32, f64); + reg_cmp!(self, ">", gt, i32, i64, u32, u64, String, char, f32, f64); + reg_cmp!(self, ">=", gte, i32, i64, u32, u64, String, char, f32, f64); + reg_cmp!(self, "==", eq, i32, i64, u32, u64, bool, String, char, f32, f64); + reg_cmp!(self, "!=", ne, i32, i64, u32, u64, bool, String, char, f32, f64); + + reg_op!(self, "||", or, bool); + reg_op!(self, "&&", and, bool); + reg_op!(self, "|", binary_or, i32, i64, u32, u64); + reg_op!(self, "|", or, bool); + reg_op!(self, "&", binary_and, i32, i64, u32, u64); + reg_op!(self, "&", and, bool); + reg_op!(self, "^", binary_xor, i32, i64, u32, u64); + reg_op!(self, "<<", left_shift, i32, i64, u32, u64); + reg_op!(self, ">>", right_shift, i32, i64, u32, u64); + reg_op!(self, "%", modulo, 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, 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: 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: 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, 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", |_: ()| println!()); + + 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, (), 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, (), 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, 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, 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 + use std::ops::Range; + 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/engine.rs b/src/engine.rs index e4ab6e2e..38849e7e 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -3,14 +3,13 @@ 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::sync::Arc; use crate::any::{Any, AnyExt, Dynamic, Variant}; use crate::call::FunArgs; -use crate::fn_register::{RegisterDynamicFn, RegisterFn}; +use crate::fn_register::RegisterFn; use crate::parser::{lex, parse, Expr, FnDef, ParseError, Stmt, AST}; -use fmt::{Debug, Display}; +use fmt::Debug; pub type Array = Vec; pub type FnCallArgs<'a> = Vec<&'a mut Variant>; @@ -214,7 +213,7 @@ impl Engine { /// Universal method for calling functions, that are either /// registered with the `Engine` or written in Rhai - pub fn call_fn_raw(&self, ident: String, args: FnCallArgs) -> Result { + fn call_fn_raw(&self, ident: String, args: FnCallArgs) -> Result { debug_println!( "Trying to call function {:?} with args {:?}", ident, @@ -286,7 +285,12 @@ impl Engine { }) } - 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 }; @@ -501,7 +505,7 @@ impl Engine { // Collect all the characters after the index let mut chars: Vec = s.chars().collect(); chars[idx] = new_ch; - s.truncate(0); + s.clear(); chars.iter().for_each(|&ch| s.push(ch)); } @@ -1008,302 +1012,6 @@ impl Engine { } } - /// 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); - )* - ) - } - - macro_rules! reg_func3 { - ($engine:expr, $x:expr, $op:expr, $v:ty, $w:ty, $r:ty, $( $y:ty ),*) => ( - $( - $engine.register_fn($x, $op as fn(x: $v, y: $w, z: $y)->$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, char, f32, f64); - reg_cmp!(engine, "<=", lte, i32, i64, u32, u64, String, char, f32, f64); - reg_cmp!(engine, ">", gt, i32, i64, u32, u64, String, char, f32, f64); - reg_cmp!(engine, ">=", gte, i32, i64, u32, u64, String, char, f32, f64); - reg_cmp!(engine, "==", eq, i32, i64, u32, u64, bool, String, char, f32, f64); - reg_cmp!(engine, "!=", ne, i32, i64, u32, u64, bool, String, char, f32, 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 conversion functions - engine.register_fn("to_float", |x: i32| x as f64); - engine.register_fn("to_float", |x: u32| x as f64); - engine.register_fn("to_float", |x: i64| x as f64); - engine.register_fn("to_float", |x: u64| x as f64); - engine.register_fn("to_float", |x: f32| x as f64); - - engine.register_fn("to_int", |x: i32| x as i64); - engine.register_fn("to_int", |x: u32| x as i64); - engine.register_fn("to_int", |x: u64| x as i64); - engine.register_fn("to_int", |x: f32| x as i64); - engine.register_fn("to_int", |x: f64| x as i64); - engine.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!(engine, "print", print, String, i32, i64, u32, u64); - reg_func1!(engine, "print", print, String, f32, f64, bool, char, String); - reg_func1!(engine, "print", print_debug, String, Array); - engine.register_fn("print", |_: ()| println!()); - - reg_func1!(engine, "debug", print_debug, String, i32, i64, u32, u64); - reg_func1!(engine, "debug", print_debug, String, f32, f64, bool, char); - reg_func1!(engine, "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!(engine, "push", push, &mut Array, (), i32, i64, u32, u64); - reg_func2x!(engine, "push", push, &mut Array, (), f32, f64, bool, char); - reg_func2x!(engine, "push", push, &mut Array, (), String, Array, ()); - reg_func3!(engine, "pad", pad, &mut Array, i64, (), i32, u32, f32); - reg_func3!(engine, "pad", pad, &mut Array, i64, (), i64, u64, f64); - reg_func3!(engine, "pad", pad, &mut Array, i64, (), bool, char); - reg_func3!(engine, "pad", pad, &mut Array, i64, (), String, Array, ()); - - engine.register_dynamic_fn("pop", |list: &mut Array| list.pop().unwrap_or(Box::new(()))); - engine.register_dynamic_fn("shift", |list: &mut Array| { - if list.len() > 0 { - list.remove(0) - } else { - Box::new(()) - } - }); - engine.register_fn("len", |list: &mut Array| -> i64 { list.len() as i64 }); - engine.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!(engine, "+", append, String, String, i32, i64, u32, u64, f32, f64, bool, char); - 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, char); - engine.register_fn("+", |x: Array, y: String| format!("{:?}{}", x, y)); - engine.register_fn("+", |_: (), y: String| format!("{}", y)); - - // Register string utility functions - engine.register_fn("len", |s: &mut String| -> i64 { s.chars().count() as i64 }); - engine.register_fn("truncate", |s: &mut String, len: i64| { - if len >= 0 { - s.truncate(len as usize); - } - }); - engine.register_fn("pad", |s: &mut String, len: i64, ch: char| { - let gap = s.chars().count() - len as usize; - - for _ in 0..gap { - s.push(ch); - } - }); - engine.register_fn( - "replace", - |s: &mut String, pattern: String, replace: String| { - let new_str = s.replace(&pattern, &replace); - s.truncate(0); - s.push_str(&new_str); - }, - ); - - // 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 Dynamic), - ) - }); - - engine.register_fn("range", |i1: i64, i2: i64| (i1..i2)); - } - /// Make a new engine pub fn new() -> Engine { let mut engine = Engine { @@ -1313,7 +1021,7 @@ impl Engine { on_debug: Box::new(|x: &str| println!("{}", x)), }; - Engine::register_default_lib(&mut engine); + engine.register_builtins(); engine } diff --git a/src/lib.rs b/src/lib.rs index c9302bed..443b8a1d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,6 +40,7 @@ macro_rules! debug_println { } mod any; +mod builtin; mod call; mod engine; mod fn_register; From a64b01692bc802cfb6a3b6694767a63988fd354d Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 1 Mar 2020 19:26:57 +0800 Subject: [PATCH 17/30] Use impl Fn style. --- src/builtin.rs | 3 +-- src/engine.rs | 40 ++++++++++++++++++---------------------- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/src/builtin.rs b/src/builtin.rs index 5157eaca..0a6f0cfd 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -1,6 +1,6 @@ use crate::{any::Any, Array, Dynamic, Engine, RegisterDynamicFn, RegisterFn}; use std::fmt::{Debug, Display}; -use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Rem, Shl, Shr, Sub}; +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 ),*) => ( @@ -297,7 +297,6 @@ impl Engine { }); // Register range function - use std::ops::Range; self.register_iterator::, _>(|a| { Box::new( a.downcast_ref::>() diff --git a/src/engine.rs b/src/engine.rs index 38849e7e..108553ee 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -307,39 +307,38 @@ impl Engine { /// Register an iterator adapter for a type. pub fn register_iterator(&mut self, f: F) where - F: 'static + Fn(&Dynamic) -> 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); } @@ -432,14 +431,11 @@ impl Engine { } } - 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 Variant) -> Result, - { + map: impl FnOnce(&'a mut Variant) -> Result, + ) -> Result<(usize, T), EvalAltResult> { scope .iter_mut() .enumerate() From bedfe550058dd8f233062834161a1c2d7cc16287 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 2 Mar 2020 00:11:00 +0800 Subject: [PATCH 18/30] Minor code refactoring. --- src/engine.rs | 156 +++++++++++++++++++++++++++++--------------------- 1 file changed, 92 insertions(+), 64 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 108553ee..11229517 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -2,14 +2,12 @@ use std::any::TypeId; use std::cmp::{PartialEq, PartialOrd}; use std::collections::HashMap; use std::error::Error; -use std::fmt; use std::sync::Arc; use crate::any::{Any, AnyExt, Dynamic, Variant}; use crate::call::FunArgs; use crate::fn_register::RegisterFn; use crate::parser::{lex, parse, Expr, FnDef, ParseError, Stmt, AST}; -use fmt::Debug; pub type Array = Vec; pub type FnCallArgs<'a> = Vec<&'a mut Variant>; @@ -112,8 +110,8 @@ impl Error for EvalAltResult { } } -impl fmt::Display for EvalAltResult { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +impl std::fmt::Display for EvalAltResult { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if let Some(s) = self.as_str() { write!(f, "{}: {}", self.description(), s) } else { @@ -241,6 +239,7 @@ impl Engine { .iter() .map(|x| (*(&**x).into_dynamic()).type_name()) .collect::>(); + EvalAltResult::ErrorFunctionNotFound(format!( "{} ({})", ident, @@ -250,6 +249,7 @@ impl Engine { .and_then(move |f| match **f { FnIntExt::Ext(ref f) => { let r = f(args); + if r.is_err() { return r; } @@ -270,6 +270,7 @@ impl Engine { } FnIntExt::Int(ref f) => { let mut scope = Scope::new(); + scope.extend( f.params .iter() @@ -357,17 +358,20 @@ impl Engine { .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) } + Expr::Identifier(id) => { let get_fn_name = "get$".to_string() + id; self.call_fn_raw(get_fn_name, vec![this_ptr]) } + Expr::Index(id, idx_raw) => { let idx = self .eval_expr(scope, idx_raw)? @@ -380,26 +384,27 @@ impl Engine { let mut val = self.call_fn_raw(get_fn_name, vec![this_ptr])?; if let Some(arr) = (*val).downcast_mut() as Option<&mut Array> { - if idx < 0 { - Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx)) - } else { + if idx >= 0 { arr.get(idx as usize) .cloned() .ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr.len(), idx)) + } else { + Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx)) } } else if let Some(s) = (*val).downcast_mut() as Option<&mut String> { - if idx < 0 { - Err(EvalAltResult::ErrorStringBounds(s.chars().count(), idx)) - } else { + 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)) + } else { + Err(EvalAltResult::ErrorStringBounds(s.chars().count(), idx)) } } else { Err(EvalAltResult::ErrorIndexing) } } + Expr::Dot(inner_lhs, inner_rhs) => match **inner_lhs { Expr::Identifier(ref id) => { let get_fn_name = "get$".to_string() + id; @@ -462,29 +467,27 @@ impl Engine { if let Some(arr) = (*val).downcast_mut() as Option<&mut Array> { is_array = true; - return if idx < 0 { - Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx)) - } else { + if idx >= 0 { arr.get(idx as usize) .cloned() .ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr.len(), idx)) - }; - } - - if let Some(s) = (*val).downcast_mut() as Option<&mut String> { + } else { + Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx)) + } + } else if let Some(s) = (*val).downcast_mut() as Option<&mut String> { is_array = false; - return if idx < 0 { - Err(EvalAltResult::ErrorStringBounds(s.chars().count(), idx)) - } else { + 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)) - }; + } else { + Err(EvalAltResult::ErrorStringBounds(s.chars().count(), idx)) + } + } else { + Err(EvalAltResult::ErrorIndexing) } - - Err(EvalAltResult::ErrorIndexing) }) .map(|(idx_sc, val)| (is_array, idx_sc, idx as usize, val)) } @@ -522,6 +525,7 @@ impl Engine { value } + Expr::Index(id, idx_raw) => { let (is_array, sc_idx, idx, mut target) = self.indexed_value(scope, id, idx_raw)?; let value = self.get_dot_val_helper(scope, target.as_mut(), dot_rhs); @@ -532,13 +536,11 @@ impl Engine { if is_array { scope[sc_idx].1.downcast_mut::().unwrap()[idx] = target; } else { - // Target should be a char - let new_ch = *target.downcast::().unwrap(); - - // Root should be a String - let s = scope[sc_idx].1.downcast_mut::().unwrap(); - - Self::str_replace_char(s, idx, new_ch); + 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 @@ -556,11 +558,14 @@ impl Engine { match dot_rhs { Expr::Identifier(id) => { let set_fn_name = "set$".to_string() + id; + self.call_fn_raw(set_fn_name, vec![this_ptr, source_val.as_mut()]) } + Expr::Dot(inner_lhs, 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.set_dot_val_helper(v.as_mut(), inner_rhs, source_val) @@ -596,6 +601,7 @@ impl Engine { value } + Expr::Index(id, idx_raw) => { let (is_array, sc_idx, idx, mut target) = self.indexed_value(scope, id, idx_raw)?; let value = self.set_dot_val_helper(target.as_mut(), dot_rhs, source_val); @@ -605,13 +611,11 @@ impl Engine { if is_array { scope[sc_idx].1.downcast_mut::().unwrap()[idx] = target; } else { - // Target should be a char - let new_ch = *target.downcast::().unwrap(); - - // Root should be a String - let s = scope[sc_idx].1.downcast_mut::().unwrap(); - - Self::str_replace_char(s, idx, new_ch); + 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 @@ -626,28 +630,34 @@ impl Engine { 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) => { - match scope.iter().rev().filter(|(name, _)| id == name).next() { - Some((_, val)) => Ok(val.clone()), - _ => Err(EvalAltResult::ErrorVariableNotFound(id.clone())), - } - } + + Expr::Identifier(id) => scope + .iter() + .rev() + .filter(|(name, _)| id == name) + .next() + .map(|(_, val)| val.clone()) + .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.clone())), + Expr::Index(id, idx_raw) => { self.indexed_value(scope, id, idx_raw).map(|(_, _, _, x)| x) } + Expr::Assignment(ref id, rhs) => { let rhs_val = self.eval_expr(scope, rhs)?; match **id { - Expr::Identifier(ref n) => { - match scope.iter_mut().rev().filter(|(name, _)| n == name).next() { - Some((_, val)) => { - *val = rhs_val; - Ok(Box::new(())) - } - _ => Err(EvalAltResult::ErrorVariableNotFound(n.clone())), - } - } + Expr::Identifier(ref n) => 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())), + Expr::Index(ref id, ref idx_raw) => { let idx = *match self.eval_expr(scope, &idx_raw)?.downcast_ref::() { Some(x) => x, @@ -667,32 +677,32 @@ impl Engine { }; if let Some(arr) = val.downcast_mut() as Option<&mut Array> { - return if idx < 0 { + if idx < 0 { Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx)) } else if idx as usize >= arr.len() { Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx)) } else { arr[idx as usize] = rhs_val; Ok(Box::new(())) - }; - } - - if let Some(s) = val.downcast_mut() as Option<&mut String> { + } + } else if let Some(s) = val.downcast_mut() as Option<&mut String> { let s_len = s.chars().count(); - return if idx < 0 { + if idx < 0 { Err(EvalAltResult::ErrorStringBounds(s_len, idx)) } else if idx as usize >= s_len { Err(EvalAltResult::ErrorStringBounds(s_len, idx)) } else { - // Should be a char - let new_ch = *rhs_val.downcast::().unwrap(); - Self::str_replace_char(s, idx as usize, new_ch); + Self::str_replace_char( + s, + idx as usize, + *rhs_val.downcast::().unwrap(), + ); Ok(Box::new(())) - }; + } + } else { + Err(EvalAltResult::ErrorIndexExpr) } - - return Err(EvalAltResult::ErrorIndexExpr); } Expr::Dot(ref dot_lhs, ref dot_rhs) => { self.set_dot_val(scope, dot_lhs, dot_rhs, rhs_val) @@ -700,7 +710,9 @@ impl Engine { _ => Err(EvalAltResult::ErrorAssignmentToUnknownLHS), } } + Expr::Dot(lhs, rhs) => self.get_dot_val(scope, lhs, rhs), + Expr::Array(contents) => { let mut arr = Vec::new(); @@ -712,6 +724,7 @@ impl Engine { Ok(Box::new(arr)) } + Expr::FunctionCall(fn_name, args) => self.call_fn_raw( fn_name.to_owned(), args.iter() @@ -721,6 +734,7 @@ impl Engine { .map(|b| b.as_mut()) .collect(), ), + Expr::True => Ok(Box::new(true)), Expr::False => Ok(Box::new(false)), Expr::Unit => Ok(Box::new(())), @@ -730,12 +744,14 @@ impl Engine { 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); + if let Err(x) = last_result { last_result = Err(x); break; @@ -748,6 +764,7 @@ impl Engine { last_result } + Stmt::If(guard, body) => self .eval_expr(scope, guard)? .downcast::() @@ -759,6 +776,7 @@ impl Engine { Ok(Box::new(())) } }), + Stmt::IfElse(guard, body, else_body) => self .eval_expr(scope, guard)? .downcast::() @@ -770,6 +788,7 @@ impl Engine { self.eval_stmt(scope, else_body) } }), + Stmt::While(guard, body) => loop { match self.eval_expr(scope, guard)?.downcast::() { Ok(guard_val) => { @@ -786,6 +805,7 @@ impl Engine { Err(_) => return Err(EvalAltResult::ErrorIfGuard), } }, + Stmt::Loop(body) => loop { match self.eval_stmt(scope, body) { Err(EvalAltResult::LoopBreak) => return Ok(Box::new(())), @@ -793,14 +813,18 @@ impl Engine { _ => (), } }, + 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), @@ -813,12 +837,16 @@ impl Engine { return Err(EvalAltResult::ErrorFor); } } + Stmt::Break => Err(EvalAltResult::LoopBreak), + Stmt::Return => Err(EvalAltResult::Return(Box::new(()))), + Stmt::ReturnWithVal(a) => { let result = self.eval_expr(scope, a)?; Err(EvalAltResult::Return(result)) } + Stmt::Let(name, init) => { if let Some(v) = init { let i = self.eval_expr(scope, v)?; From 22a505b57b358c37e2d0234030d6b5b378600ce0 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 2 Mar 2020 12:08:03 +0800 Subject: [PATCH 19/30] Short-curcuit boolean operators. --- README.md | 16 ++++++++- src/builtin.rs | 4 +-- src/engine.rs | 89 +++++++++++++++++++++++++++++++++------------- src/fn_register.rs | 22 +++++++----- src/parser.rs | 8 +++-- tests/bool_op.rs | 88 ++++++++++++++++++++++++++++++++++++++++++++- 6 files changed, 187 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 4096dc82..cced83b6 100644 --- a/README.md +++ b/README.md @@ -387,12 +387,26 @@ fn main() { let x = 3; ``` -## Operators +## Numeric operators ```rust let x = (1 + 2) * (6 - 4) / 2; ``` +## 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 diff --git a/src/builtin.rs b/src/builtin.rs index 0a6f0cfd..7b928754 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -149,8 +149,8 @@ impl Engine { reg_cmp!(self, "==", eq, i32, i64, u32, u64, bool, String, char, f32, f64); reg_cmp!(self, "!=", ne, i32, i64, u32, u64, bool, String, char, f32, f64); - reg_op!(self, "||", or, bool); - reg_op!(self, "&&", and, bool); + //reg_op!(self, "||", or, bool); + //reg_op!(self, "&&", and, bool); reg_op!(self, "|", binary_or, i32, i64, u32, u64); reg_op!(self, "|", or, bool); reg_op!(self, "&", binary_and, i32, i64, u32, u64); diff --git a/src/engine.rs b/src/engine.rs index 11229517..87132e4f 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -14,9 +14,10 @@ pub type FnCallArgs<'a> = Vec<&'a mut Variant>; #[derive(Debug, Clone)] pub enum EvalAltResult { - ErrorParseError(ParseError), + ErrorParsing(ParseError), ErrorFunctionNotFound(String), - ErrorFunctionArgMismatch, + ErrorFunctionArgsMismatch(String, usize), + ErrorBooleanArgMismatch(String), ErrorArrayBounds(usize, i64), ErrorStringBounds(usize, i64), ErrorIndexing, @@ -28,6 +29,7 @@ pub enum EvalAltResult { ErrorMismatchOutputType(String), ErrorCantOpenScriptFile(String), ErrorDotExpr, + ErrorArithmetic(String), LoopBreak, Return(Dynamic), } @@ -35,10 +37,12 @@ pub enum EvalAltResult { 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, + Self::ErrorCantOpenScriptFile(s) + | Self::ErrorVariableNotFound(s) + | Self::ErrorFunctionNotFound(s) + | Self::ErrorMismatchOutputType(s) + | Self::ErrorCantOpenScriptFile(s) + | Self::ErrorArithmetic(s) => s, _ => return None, }) } @@ -49,9 +53,12 @@ impl PartialEq for EvalAltResult { use EvalAltResult::*; match (self, other) { - (ErrorParseError(ref a), ErrorParseError(ref b)) => a == b, - (ErrorFunctionNotFound(ref a), ErrorFunctionNotFound(ref b)) => a == b, - (ErrorFunctionArgMismatch, ErrorFunctionArgMismatch) => true, + (ErrorParsing(a), ErrorParsing(b)) => a == b, + (ErrorFunctionNotFound(a), ErrorFunctionNotFound(b)) => a == b, + (ErrorFunctionArgsMismatch(f1, n1), ErrorFunctionArgsMismatch(f2, n2)) => { + f1 == f2 && *n1 == *n2 + } + (ErrorBooleanArgMismatch(a), ErrorBooleanArgMismatch(b)) => a == b, (ErrorIndexExpr, ErrorIndexExpr) => true, (ErrorIndexing, ErrorIndexing) => true, (ErrorArrayBounds(max1, index1), ErrorArrayBounds(max2, index2)) => { @@ -62,11 +69,12 @@ impl PartialEq for EvalAltResult { } (ErrorIfGuard, ErrorIfGuard) => true, (ErrorFor, ErrorFor) => true, - (ErrorVariableNotFound(ref a), ErrorVariableNotFound(ref b)) => a == b, + (ErrorVariableNotFound(a), ErrorVariableNotFound(b)) => a == b, (ErrorAssignmentToUnknownLHS, ErrorAssignmentToUnknownLHS) => true, - (ErrorMismatchOutputType(ref a), ErrorMismatchOutputType(ref b)) => a == b, - (ErrorCantOpenScriptFile(ref a), ErrorCantOpenScriptFile(ref b)) => a == b, + (ErrorMismatchOutputType(a), ErrorMismatchOutputType(b)) => a == b, + (ErrorCantOpenScriptFile(a), ErrorCantOpenScriptFile(b)) => a == b, (ErrorDotExpr, ErrorDotExpr) => true, + (ErrorArithmetic(a), ErrorArithmetic(b)) => a == b, (LoopBreak, LoopBreak) => true, _ => false, } @@ -76,20 +84,21 @@ impl PartialEq for EvalAltResult { impl Error for EvalAltResult { fn description(&self) -> &str { match self { - Self::ErrorParseError(ref p) => p.description(), + Self::ErrorParsing(p) => p.description(), Self::ErrorFunctionNotFound(_) => "Function not found", - Self::ErrorFunctionArgMismatch => "Function argument types do not match", + 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(_, ref index) if *index < 0 => { + Self::ErrorArrayBounds(_, index) if *index < 0 => { "Array access expects non-negative index" } - Self::ErrorArrayBounds(ref max, _) if *max == 0 => "Access of empty array", + Self::ErrorArrayBounds(max, _) if *max == 0 => "Access of empty array", Self::ErrorArrayBounds(_, _) => "Array index out of bounds", - Self::ErrorStringBounds(_, ref index) if *index < 0 => { + Self::ErrorStringBounds(_, index) if *index < 0 => { "Indexing a string expects a non-negative index" } - Self::ErrorStringBounds(ref max, _) if *max == 0 => "Indexing of empty string", + Self::ErrorStringBounds(max, _) if *max == 0 => "Indexing of empty string", Self::ErrorStringBounds(_, _) => "String index out of bounds", Self::ErrorIfGuard => "If guards expect boolean expression", Self::ErrorFor => "For loops expect array", @@ -100,6 +109,7 @@ impl Error for EvalAltResult { 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", } @@ -116,7 +126,13 @@ impl std::fmt::Display for EvalAltResult { write!(f, "{}: {}", self.description(), s) } else { match self { - EvalAltResult::ErrorParseError(ref p) => write!(f, "Syntax error: {}", p), + EvalAltResult::ErrorParsing(p) => write!(f, "Syntax error: {}", p), + EvalAltResult::ErrorFunctionArgsMismatch(fun, n) => { + write!(f, "Function '{}' expects {} argument(s)", fun, n) + } + EvalAltResult::ErrorBooleanArgMismatch(op) => { + write!(f, "Boolean {} operator expects boolean operands", op) + } EvalAltResult::ErrorArrayBounds(_, index) if *index < 0 => { write!(f, "{}: {} < 0", self.description(), index) } @@ -704,9 +720,11 @@ impl Engine { Err(EvalAltResult::ErrorIndexExpr) } } + Expr::Dot(ref dot_lhs, ref dot_rhs) => { self.set_dot_val(scope, dot_lhs, dot_rhs, rhs_val) } + _ => Err(EvalAltResult::ErrorAssignmentToUnknownLHS), } } @@ -728,13 +746,35 @@ impl Engine { Expr::FunctionCall(fn_name, args) => self.call_fn_raw( fn_name.to_owned(), 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(), ), + Expr::And(lhs, rhs) => Ok(Box::new( + *self + .eval_expr(scope, &*lhs)? + .downcast::() + .map_err(|_| EvalAltResult::ErrorBooleanArgMismatch("AND".into()))? + && *self + .eval_expr(scope, &*rhs)? + .downcast::() + .map_err(|_| EvalAltResult::ErrorBooleanArgMismatch("AND".into()))?, + )), + + Expr::Or(lhs, rhs) => Ok(Box::new( + *self + .eval_expr(scope, &*lhs)? + .downcast::() + .map_err(|_| EvalAltResult::ErrorBooleanArgMismatch("OR".into()))? + || *self + .eval_expr(scope, &*rhs)? + .downcast::() + .map_err(|_| EvalAltResult::ErrorBooleanArgMismatch("OR".into()))?, + )), + Expr::True => Ok(Box::new(true)), Expr::False => Ok(Box::new(false)), Expr::Unit => Ok(Box::new(())), @@ -878,7 +918,7 @@ impl Engine { let mut contents = String::new(); if f.read_to_string(&mut contents).is_ok() { - Self::compile(&contents).map_err(|err| EvalAltResult::ErrorParseError(err)) + Self::compile(&contents).map_err(|err| EvalAltResult::ErrorParsing(err)) } else { Err(EvalAltResult::ErrorCantOpenScriptFile(filename.to_owned())) } @@ -917,7 +957,7 @@ impl Engine { scope: &mut Scope, input: &str, ) -> Result { - let ast = Self::compile(input).map_err(|err| EvalAltResult::ErrorParseError(err))?; + let ast = Self::compile(input).map_err(|err| EvalAltResult::ErrorParsing(err))?; self.eval_ast_with_scope(scope, &ast) } @@ -1005,9 +1045,8 @@ impl Engine { let tokens = lex(input); let mut peekables = tokens.peekable(); - let tree = parse(&mut peekables); - match tree { + match parse(&mut peekables) { Ok(AST(ref os, ref fns)) => { for f in fns { if f.params.len() > 6 { @@ -1032,7 +1071,7 @@ impl Engine { Ok(()) } - Err(_) => Err(EvalAltResult::ErrorFunctionArgMismatch), + Err(err) => Err(EvalAltResult::ErrorParsing(err)), } } diff --git a/src/fn_register.rs b/src/fn_register.rs index 2aab7623..c23794de 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -30,19 +30,22 @@ macro_rules! def_register { > RegisterFn for Engine { fn register_fn(&mut self, name: &str, f: FN) { + let fn_name = name.to_string(); + let fun = move |mut args: FnCallArgs| { // 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)); } #[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 @@ -60,19 +63,22 @@ macro_rules! def_register { > RegisterDynamicFn for Engine { fn register_dynamic_fn(&mut self, name: &str, f: FN) { + let fn_name = name.to_string(); + let fun = move |mut args: FnCallArgs| { // 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)); } #[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 diff --git a/src/parser.rs b/src/parser.rs index d36ae1c7..fccd1b30 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -179,6 +179,8 @@ pub enum Expr { Dot(Box, Box), Index(String, Box), Array(Vec), + And(Box, Box), + Or(Box, Box), True, False, Unit, @@ -618,7 +620,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, @@ -1189,8 +1191,8 @@ fn parse_binop<'a>( Token::LessThanEqualsTo => Expr::FunctionCall("<=".into(), vec![lhs_curr, rhs]), Token::GreaterThan => Expr::FunctionCall(">".into(), vec![lhs_curr, rhs]), Token::GreaterThanEqualsTo => Expr::FunctionCall(">=".into(), vec![lhs_curr, rhs]), - Token::Or => Expr::FunctionCall("||".into(), vec![lhs_curr, rhs]), - Token::And => Expr::FunctionCall("&&".into(), 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]), Token::OrAssign => { let lhs_copy = lhs_curr.clone(); diff --git a/tests/bool_op.rs b/tests/bool_op.rs index 9565e8ee..ffd90746 100644 --- a/tests/bool_op.rs +++ b/tests/bool_op.rs @@ -1,10 +1,11 @@ -use rhai::Engine; +use rhai::{Engine, EvalAltResult}; #[test] fn test_bool_op1() { let mut engine = Engine::new(); assert_eq!(engine.eval::("true && (false || true)"), Ok(true)); + assert_eq!(engine.eval::("true & (false | true)"), Ok(true)); } #[test] @@ -12,4 +13,89 @@ fn test_bool_op2() { let mut engine = Engine::new(); assert_eq!(engine.eval::("false && (false || true)"), Ok(false)); + assert_eq!(engine.eval::("false & (false | true)"), Ok(false)); +} + +#[test] +fn test_bool_op3() { + let mut engine = Engine::new(); + + assert_eq!( + engine.eval::("true && (false || 123)"), + Err(EvalAltResult::ErrorBooleanArgMismatch("OR".into())) + ); + + assert_eq!(engine.eval::("true && (true || 123)"), Ok(true)); + + assert_eq!( + engine.eval::("123 && (false || true)"), + Err(EvalAltResult::ErrorBooleanArgMismatch("AND".into())) + ); + + assert_eq!(engine.eval::("false && (true || 123)"), Ok(false)); +} + +#[test] +fn test_bool_op_short_circuit() { + let mut engine = Engine::new(); + + assert_eq!( + engine.eval::( + r" + fn this() { true } + fn that() { 9/0 } + + this() || that(); + " + ), + Ok(true) + ); + + assert_eq!( + engine.eval::( + r" + fn this() { false } + fn that() { 9/0 } + + this() && that(); + " + ), + Ok(false) + ); +} + +#[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(); + " + ), + Ok(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(); + " + ), + Ok(false) + ); } From a5e09295f87747bf4a43e670c8890164b065472b Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 2 Mar 2020 14:28:42 +0800 Subject: [PATCH 20/30] Allow comparisons between different types (returning false). --- README.md | 12 ++ src/any.rs | 2 +- src/api.rs | 173 ++++++++++++++++++++++++++ src/builtin.rs | 5 +- src/engine.rs | 294 ++++++++++---------------------------------- src/fn_register.rs | 4 +- src/lib.rs | 1 + src/parser.rs | 97 +++++++++------ tests/binary_ops.rs | 7 ++ 9 files changed, 329 insertions(+), 266 deletions(-) create mode 100644 src/api.rs diff --git a/README.md b/README.md index cced83b6..1f670281 100644 --- a/README.md +++ b/README.md @@ -393,6 +393,18 @@ let x = 3; 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. diff --git a/src/any.rs b/src/any.rs index 3584a5e7..c3c969cb 100644 --- a/src/any.rs +++ b/src/any.rs @@ -73,7 +73,7 @@ impl Variant { impl Clone for Dynamic { fn clone(&self) -> Self { - Any::into_dynamic(self.as_ref() as &Variant) + Any::into_dynamic(self.as_ref()) } } diff --git a/src/api.rs b/src/api.rs new file mode 100644 index 00000000..f8339944 --- /dev/null +++ b/src/api.rs @@ -0,0 +1,173 @@ +use crate::any::{Any, AnyExt}; +use crate::engine::{FnIntExt, FnSpec}; +use crate::parser::{lex, parse}; +use crate::{Dynamic, Engine, EvalAltResult, ParseError, Scope, AST}; +use std::sync::Arc; + +impl Engine { + /// 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(filename: &str) -> Result { + use std::fs::File; + use std::io::prelude::*; + + let mut f = File::open(filename) + .map_err(|_| EvalAltResult::ErrorCantOpenScriptFile(filename.into()))?; + + let mut contents = String::new(); + + f.read_to_string(&mut contents) + .map_err(|_| EvalAltResult::ErrorCantOpenScriptFile(filename.into())) + .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(|_| EvalAltResult::ErrorCantOpenScriptFile(filename.into()))?; + + let mut contents = String::new(); + + f.read_to_string(&mut contents) + .map_err(|_| EvalAltResult::ErrorCantOpenScriptFile(filename.into())) + .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; + + for f in fns { + let spec = FnSpec { + ident: f.name.clone(), + args: None, + }; + + self.script_fns + .insert(spec, 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)) | Ok(out) => Ok(*out + .downcast::() + .map_err(|err| EvalAltResult::ErrorMismatchOutputType((*err).type_name()))?), + 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(|_| EvalAltResult::ErrorCantOpenScriptFile(filename.into()))?; + + let mut contents = String::new(); + + f.read_to_string(&mut contents) + .map_err(|_| EvalAltResult::ErrorCantOpenScriptFile(filename.into())) + .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 { + if f.params.len() > 6 { + return Ok(()); + } + + let spec = FnSpec { + ident: f.name.clone(), + args: None, + }; + + self.script_fns + .insert(spec, 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 index 7b928754..9e6b033e 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -195,11 +195,14 @@ impl Engine { fn print(x: T) -> String { format!("{}", x) } + fn println() -> String { + "\n".to_string() + } 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", |_: ()| println!()); + self.register_fn("print", println); reg_func1!(self, "debug", print_debug, String, i32, i64, u32, u64); reg_func1!(self, "debug", print_debug, String, f32, f64, bool, char); diff --git a/src/engine.rs b/src/engine.rs index 87132e4f..ac8ef1bf 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use crate::any::{Any, AnyExt, Dynamic, Variant}; use crate::call::FunArgs; use crate::fn_register::RegisterFn; -use crate::parser::{lex, parse, Expr, FnDef, ParseError, Stmt, AST}; +use crate::parser::{Expr, FnDef, ParseError, Stmt}; pub type Array = Vec; pub type FnCallArgs<'a> = Vec<&'a mut Variant>; @@ -41,7 +41,6 @@ impl EvalAltResult { | Self::ErrorVariableNotFound(s) | Self::ErrorFunctionNotFound(s) | Self::ErrorMismatchOutputType(s) - | Self::ErrorCantOpenScriptFile(s) | Self::ErrorArithmetic(s) => s, _ => return None, }) @@ -159,8 +158,8 @@ impl std::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(&Dynamic) -> Box>; @@ -180,11 +179,15 @@ type IteratorFn = dyn Fn(&Dynamic) -> Box>; /// } /// ``` pub struct Engine { - /// A hashmap containing all functions known to the engine - pub fns: HashMap>, - pub type_iterators: HashMap>, - on_print: Box, - on_debug: Box, + /// 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>, + + pub(crate) on_print: Box, + pub(crate) on_debug: Box, } pub enum FnIntExt { @@ -217,7 +220,7 @@ impl Engine { A: FunArgs<'a>, T: Any + Clone, { - self.call_fn_raw(ident.into(), args.into_vec()) + self.call_fn_raw(ident.into(), args.into_vec(), None) .and_then(|b| { b.downcast() .map(|b| *b) @@ -227,7 +230,12 @@ impl Engine { /// Universal method for calling functions, that are either /// registered with the `Engine` or written in Rhai - fn call_fn_raw(&self, ident: String, args: FnCallArgs) -> Result { + fn call_fn_raw( + &self, + ident: String, + args: FnCallArgs, + def_value: Option<&Dynamic>, + ) -> Result { debug_println!( "Trying to call function {:?} with args {:?}", ident, @@ -241,28 +249,27 @@ impl Engine { args: Some(args.iter().map(|a| Any::type_id(&**a)).collect()), }; - self.fns + // First search in script-defined functions (can override built-in), + // then in built-in's, then retry again with no arguments + let fn_def = self + .script_fns .get(&spec) + .or_else(|| self.fns.get(&spec)) .or_else(|| { - let spec1 = FnSpec { + self.script_fns.get(&FnSpec { ident: ident.clone(), args: None, - }; - self.fns.get(&spec1) + }) }) - .ok_or_else(|| { - let type_names = args - .iter() - .map(|x| (*(&**x).into_dynamic()).type_name()) - .collect::>(); + .or_else(|| { + self.fns.get(&FnSpec { + ident: ident.clone(), + args: None, + }) + }); - EvalAltResult::ErrorFunctionNotFound(format!( - "{} ({})", - ident, - type_names.join(", ") - )) - }) - .and_then(move |f| match **f { + if let Some(f) = fn_def { + match **f { FnIntExt::Ext(ref f) => { let r = f(args); @@ -299,7 +306,22 @@ impl Engine { other => other, } } - }) + } + } else if let Some(val) = def_value { + // Return default value + Ok(val.clone()) + } else { + let type_names = args + .iter() + .map(|x| (*(&**x).into_dynamic()).type_name()) + .collect::>(); + + Err(EvalAltResult::ErrorFunctionNotFound(format!( + "{} ({})", + ident, + type_names.join(", ") + ))) + } } pub(crate) fn register_fn_raw( @@ -369,7 +391,7 @@ impl Engine { use std::iter::once; match dot_rhs { - Expr::FunctionCall(fn_name, args) => { + Expr::FunctionCall(fn_name, args, def_value) => { let mut args: Array = args .iter() .map(|arg| self.eval_expr(scope, arg)) @@ -379,13 +401,13 @@ impl Engine { .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()) } Expr::Identifier(id) => { 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) } Expr::Index(id, idx_raw) => { @@ -397,7 +419,7 @@ impl Engine { 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)?; if let Some(arr) = (*val).downcast_mut() as Option<&mut Array> { if idx >= 0 { @@ -425,7 +447,7 @@ impl Engine { Expr::Identifier(ref id) => { let get_fn_name = "get$".to_string() + id; let value = self - .call_fn_raw(get_fn_name, vec![this_ptr]) + .call_fn_raw(get_fn_name, vec![this_ptr], None) .and_then(|mut v| self.get_dot_val_helper(scope, v.as_mut(), inner_rhs))?; // TODO - Should propagate changes back in this scenario: @@ -462,7 +484,7 @@ impl Engine { .enumerate() .rev() .find(|&(_, &mut (ref name, _))| id == name) - .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.to_owned())) + .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.into())) .and_then(move |(idx, &mut (_, ref mut val))| map(val.as_mut()).map(|val| (idx, val))) } @@ -575,14 +597,14 @@ impl Engine { Expr::Identifier(id) => { 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) } Expr::Dot(inner_lhs, 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]) + self.call_fn_raw(get_fn_name, vec![this_ptr], None) .and_then(|mut v| { self.set_dot_val_helper(v.as_mut(), inner_rhs, source_val) .map(|_| v) // Discard Ok return value @@ -590,7 +612,7 @@ 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) }) } _ => Err(EvalAltResult::ErrorDotExpr), @@ -743,14 +765,15 @@ impl Engine { Ok(Box::new(arr)) } - Expr::FunctionCall(fn_name, args) => self.call_fn_raw( - fn_name.to_owned(), + Expr::FunctionCall(fn_name, args, def_value) => self.call_fn_raw( + fn_name.into(), args.iter() .map(|expr| self.eval_expr(scope, expr)) .collect::>()? .iter_mut() .map(|b| b.as_mut()) .collect(), + def_value.as_ref(), ), Expr::And(lhs, rhs) => Ok(Box::new( @@ -781,7 +804,11 @@ impl Engine { } } - fn eval_stmt(&self, scope: &mut Scope, stmt: &Stmt) -> Result { + pub(crate) fn eval_stmt( + &self, + scope: &mut Scope, + stmt: &Stmt, + ) -> Result { match stmt { Stmt::Expr(expr) => self.eval_expr(scope, expr), @@ -899,186 +926,11 @@ impl Engine { } } - /// 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(filename: &str) -> Result { - use std::fs::File; - use std::io::prelude::*; - - if let Ok(mut f) = File::open(filename) { - let mut contents = String::new(); - - if f.read_to_string(&mut contents).is_ok() { - Self::compile(&contents).map_err(|err| EvalAltResult::ErrorParsing(err)) - } else { - Err(EvalAltResult::ErrorCantOpenScriptFile(filename.to_owned())) - } - } else { - Err(EvalAltResult::ErrorCantOpenScriptFile(filename.to_owned())) - } - } - - /// Evaluate a file - pub fn eval_file(&mut self, filename: &str) -> Result { - use std::fs::File; - use std::io::prelude::*; - - if let Ok(mut f) = File::open(filename) { - let mut contents = String::new(); - - if f.read_to_string(&mut contents).is_ok() { - self.eval::(&contents) - } else { - Err(EvalAltResult::ErrorCantOpenScriptFile(filename.to_owned())) - } - } else { - Err(EvalAltResult::ErrorCantOpenScriptFile(filename.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::ErrorParsing(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 = 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, filename: &str) -> Result<(), EvalAltResult> { - use std::fs::File; - use std::io::prelude::*; - - if let Ok(mut f) = File::open(filename) { - 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(filename.to_owned())) - } - } else { - Err(EvalAltResult::ErrorCantOpenScriptFile(filename.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(); - - match parse(&mut peekables) { - 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) => Err(EvalAltResult::ErrorParsing(err)), - } - } - /// Make a new engine pub fn new() -> Engine { let mut engine = Engine { fns: HashMap::new(), + script_fns: HashMap::new(), type_iterators: HashMap::new(), on_print: Box::new(|x: &str| println!("{}", x)), on_debug: Box::new(|x: &str| println!("{}", x)), @@ -1088,14 +940,4 @@ impl Engine { engine } - - /// 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/fn_register.rs b/src/fn_register.rs index c23794de..8a65952f 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -53,7 +53,7 @@ macro_rules! def_register { let r = f($(($clone)($par)),*); 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)); } } @@ -85,7 +85,7 @@ macro_rules! def_register { // 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 443b8a1d..f7a3907e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,6 +40,7 @@ macro_rules! debug_println { } mod any; +mod api; mod builtin; mod call; mod engine; diff --git a/src/parser.rs b/src/parser.rs index fccd1b30..8f9db5f0 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; @@ -145,14 +146,14 @@ impl fmt::Display for ParseError { 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, } -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, Clone)] pub enum Stmt { If(Box, Box), IfElse(Box, Box, Box), @@ -167,14 +168,14 @@ pub enum Stmt { ReturnWithVal(Box), } -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, Clone)] pub enum Expr { IntegerConstant(i64), FloatConstant(f64), Identifier(String), CharConstant(char), StringConstant(String), - FunctionCall(String, Vec), + FunctionCall(String, Vec, Option), Assignment(Box, Box), Dot(Box, Box), Index(String, Box), @@ -997,7 +998,7 @@ fn parse_call_expr<'a>( if let Some(&(Token::RightParen, _)) = input.peek() { input.next(); - return Ok(Expr::FunctionCall(id, args)); + return Ok(Expr::FunctionCall(id, args, None)); } loop { @@ -1006,7 +1007,7 @@ fn parse_call_expr<'a>( match input.peek() { Some(&(Token::RightParen, _)) => { input.next(); - return Ok(Expr::FunctionCall(id, args)); + return Ok(Expr::FunctionCall(id, args, None)); } Some(&(Token::Comma, _)) => (), Some(&(_, pos)) => return Err(ParseError(PERR::MalformedCallExpr, pos)), @@ -1116,7 +1117,11 @@ fn parse_unary<'a>(input: &mut Peekable>) -> Result { input.next(); - Ok(Expr::FunctionCall("-".into(), vec![parse_primary(input)?])) + Ok(Expr::FunctionCall( + "-".into(), + vec![parse_primary(input)?], + None, + )) } Token::UnaryPlus => { input.next(); @@ -1124,7 +1129,11 @@ fn parse_unary<'a>(input: &mut Peekable>) -> Result { input.next(); - Ok(Expr::FunctionCall("!".into(), vec![parse_primary(input)?])) + Ok(Expr::FunctionCall( + "!".into(), + vec![parse_primary(input)?], + Some(Box::new(false)), // NOT operator, when operating on invalid operand, defaults to false + )) } _ => parse_primary(input), } @@ -1165,102 +1174,118 @@ fn parse_binop<'a>( } lhs_curr = match op_token { - Token::Plus => Expr::FunctionCall("+".into(), vec![lhs_curr, rhs]), - Token::Minus => Expr::FunctionCall("-".into(), vec![lhs_curr, rhs]), - Token::Multiply => Expr::FunctionCall("*".into(), vec![lhs_curr, rhs]), - Token::Divide => Expr::FunctionCall("/".into(), vec![lhs_curr, rhs]), + Token::Plus => Expr::FunctionCall("+".into(), vec![lhs_curr, rhs], None), + Token::Minus => Expr::FunctionCall("-".into(), vec![lhs_curr, rhs], None), + Token::Multiply => Expr::FunctionCall("*".into(), vec![lhs_curr, rhs], None), + Token::Divide => Expr::FunctionCall("/".into(), vec![lhs_curr, rhs], None), + 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("+".into(), vec![lhs_copy, rhs])), + Box::new(Expr::FunctionCall("+".into(), vec![lhs_copy, rhs], None)), ) } Token::MinusAssign => { let lhs_copy = lhs_curr.clone(); Expr::Assignment( Box::new(lhs_curr), - Box::new(Expr::FunctionCall("-".into(), vec![lhs_copy, rhs])), + Box::new(Expr::FunctionCall("-".into(), vec![lhs_copy, rhs], None)), ) } Token::Period => Expr::Dot(Box::new(lhs_curr), Box::new(rhs)), - Token::EqualsTo => Expr::FunctionCall("==".into(), vec![lhs_curr, rhs]), - Token::NotEqualsTo => Expr::FunctionCall("!=".into(), vec![lhs_curr, rhs]), - Token::LessThan => Expr::FunctionCall("<".into(), vec![lhs_curr, rhs]), - Token::LessThanEqualsTo => Expr::FunctionCall("<=".into(), vec![lhs_curr, rhs]), - Token::GreaterThan => Expr::FunctionCall(">".into(), vec![lhs_curr, rhs]), - Token::GreaterThanEqualsTo => Expr::FunctionCall(">=".into(), 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))) + } + Token::NotEqualsTo => { + Expr::FunctionCall("!=".into(), vec![lhs_curr, rhs], Some(Box::new(false))) + } + Token::LessThan => { + Expr::FunctionCall("<".into(), vec![lhs_curr, rhs], Some(Box::new(false))) + } + Token::LessThanEqualsTo => { + Expr::FunctionCall("<=".into(), vec![lhs_curr, rhs], Some(Box::new(false))) + } + Token::GreaterThan => { + Expr::FunctionCall(">".into(), vec![lhs_curr, rhs], Some(Box::new(false))) + } + Token::GreaterThanEqualsTo => { + Expr::FunctionCall(">=".into(), vec![lhs_curr, rhs], Some(Box::new(false))) + } + 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]), + Token::XOr => Expr::FunctionCall("^".into(), vec![lhs_curr, rhs], None), Token::OrAssign => { let lhs_copy = lhs_curr.clone(); Expr::Assignment( Box::new(lhs_curr), - Box::new(Expr::FunctionCall("|".into(), vec![lhs_copy, rhs])), + Box::new(Expr::FunctionCall("|".into(), vec![lhs_copy, rhs], None)), ) } Token::AndAssign => { let lhs_copy = lhs_curr.clone(); Expr::Assignment( Box::new(lhs_curr), - Box::new(Expr::FunctionCall("&".into(), vec![lhs_copy, rhs])), + Box::new(Expr::FunctionCall("&".into(), vec![lhs_copy, rhs], None)), ) } Token::XOrAssign => { let lhs_copy = lhs_curr.clone(); Expr::Assignment( Box::new(lhs_curr), - Box::new(Expr::FunctionCall("^".into(), vec![lhs_copy, rhs])), + Box::new(Expr::FunctionCall("^".into(), vec![lhs_copy, rhs], None)), ) } Token::MultiplyAssign => { let lhs_copy = lhs_curr.clone(); Expr::Assignment( Box::new(lhs_curr), - Box::new(Expr::FunctionCall("*".into(), vec![lhs_copy, rhs])), + Box::new(Expr::FunctionCall("*".into(), vec![lhs_copy, rhs], None)), ) } Token::DivideAssign => { let lhs_copy = lhs_curr.clone(); Expr::Assignment( Box::new(lhs_curr), - Box::new(Expr::FunctionCall("/".into(), vec![lhs_copy, rhs])), + Box::new(Expr::FunctionCall("/".into(), vec![lhs_copy, rhs], None)), ) } - Token::Pipe => Expr::FunctionCall("|".into(), vec![lhs_curr, rhs]), - Token::LeftShift => Expr::FunctionCall("<<".into(), vec![lhs_curr, rhs]), - Token::RightShift => Expr::FunctionCall(">>".into(), vec![lhs_curr, rhs]), + Token::Pipe => Expr::FunctionCall("|".into(), vec![lhs_curr, rhs], None), + Token::LeftShift => Expr::FunctionCall("<<".into(), vec![lhs_curr, rhs], None), + Token::RightShift => Expr::FunctionCall(">>".into(), vec![lhs_curr, rhs], None), Token::LeftShiftAssign => { let lhs_copy = lhs_curr.clone(); Expr::Assignment( Box::new(lhs_curr), - Box::new(Expr::FunctionCall("<<".into(), vec![lhs_copy, rhs])), + Box::new(Expr::FunctionCall("<<".into(), vec![lhs_copy, rhs], None)), ) } Token::RightShiftAssign => { let lhs_copy = lhs_curr.clone(); Expr::Assignment( Box::new(lhs_curr), - Box::new(Expr::FunctionCall(">>".into(), vec![lhs_copy, rhs])), + Box::new(Expr::FunctionCall(">>".into(), vec![lhs_copy, rhs], None)), ) } - Token::Ampersand => Expr::FunctionCall("&".into(), vec![lhs_curr, rhs]), - Token::Modulo => Expr::FunctionCall("%".into(), vec![lhs_curr, rhs]), + Token::Ampersand => Expr::FunctionCall("&".into(), vec![lhs_curr, rhs], None), + Token::Modulo => Expr::FunctionCall("%".into(), vec![lhs_curr, rhs], None), Token::ModuloAssign => { let lhs_copy = lhs_curr.clone(); Expr::Assignment( Box::new(lhs_curr), - Box::new(Expr::FunctionCall("%".into(), vec![lhs_copy, rhs])), + Box::new(Expr::FunctionCall("%".into(), vec![lhs_copy, rhs], None)), ) } - Token::PowerOf => Expr::FunctionCall("~".into(), vec![lhs_curr, rhs]), + Token::PowerOf => Expr::FunctionCall("~".into(), vec![lhs_curr, rhs], None), Token::PowerOfAssign => { let lhs_copy = lhs_curr.clone(); Expr::Assignment( Box::new(lhs_curr), - Box::new(Expr::FunctionCall("~".into(), vec![lhs_copy, rhs])), + Box::new(Expr::FunctionCall("~".into(), vec![lhs_copy, rhs], None)), ) } _ => return Err(ParseError(PERR::UnknownOperator, pos)), diff --git a/tests/binary_ops.rs b/tests/binary_ops.rs index 4978b34d..a0f7c451 100644 --- a/tests/binary_ops.rs +++ b/tests/binary_ops.rs @@ -10,4 +10,11 @@ fn test_binary_ops() { 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::("42 == 42"), Ok(true)); + assert_eq!(engine.eval::("42 > 42"), Ok(false)); + + // Incompatible types compare to false + assert_eq!(engine.eval::("true == 42"), Ok(false)); + assert_eq!(engine.eval::(r#""42" == 42"#), Ok(false)); } From adaa65f953dd6a19e27a0a05d86f9477d50884b1 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 2 Mar 2020 15:19:41 +0800 Subject: [PATCH 21/30] Allow script functions to override built-in functions. --- src/builtin.rs | 6 ++---- src/engine.rs | 26 +++++++------------------- 2 files changed, 9 insertions(+), 23 deletions(-) diff --git a/src/builtin.rs b/src/builtin.rs index 9e6b033e..d7fa632f 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -195,14 +195,12 @@ impl Engine { fn print(x: T) -> String { format!("{}", x) } - fn println() -> String { - "\n".to_string() - } 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", println); + self.register_fn("print", || "".to_string()); + self.register_fn("print", |_: ()| "".to_string()); reg_func1!(self, "debug", print_debug, String, i32, i64, u32, u64); reg_func1!(self, "debug", print_debug, String, f32, f64, bool, char); diff --git a/src/engine.rs b/src/engine.rs index ac8ef1bf..635b7ac6 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -244,29 +244,17 @@ impl Engine { .collect::>() ); - let spec = FnSpec { + let mut spec = FnSpec { ident: ident.clone(), - args: Some(args.iter().map(|a| Any::type_id(&**a)).collect()), + args: None, }; // First search in script-defined functions (can override built-in), - // then in built-in's, then retry again with no arguments - let fn_def = self - .script_fns - .get(&spec) - .or_else(|| self.fns.get(&spec)) - .or_else(|| { - self.script_fns.get(&FnSpec { - ident: ident.clone(), - args: None, - }) - }) - .or_else(|| { - self.fns.get(&FnSpec { - ident: ident.clone(), - args: None, - }) - }); + // 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 { From 710a07d8965befe7df592cffaa4da61be3595fc5 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 2 Mar 2020 15:19:59 +0800 Subject: [PATCH 22/30] Refactor some examples. --- examples/repl.rs | 21 +++------------------ examples/simple_fn.rs | 9 +++++---- 2 files changed, 8 insertions(+), 22 deletions(-) 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 } From 318bf979867a1385528fed8e27b311efe1093e37 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 2 Mar 2020 15:20:29 +0800 Subject: [PATCH 23/30] Bump version to 0.10.1. --- Cargo.toml | 4 ++-- README.md | 32 ++++++++++++++++++++++++++++---- TODO | 4 ---- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 66c13ab5..54678e04 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "rhai" -version = "0.10.0-alpha1" +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 1f670281..e0003a47 100644 --- a/README.md +++ b/README.md @@ -19,10 +19,19 @@ You can install Rhai using crates by adding this line to your dependencies: ```toml [dependencies] -rhai = "0.10.0-alpha1" +rhai = "0.10.0" ``` -Beware that to use pre-releases (alpha and beta) you need to specify the exact version in your `Cargo.toml`. +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 @@ -206,6 +215,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 with the correct argument types 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: @@ -477,7 +499,7 @@ fn add(x, y) { print(add(2, 3)) ``` -To return a `Dynamic` value, simply box it and return it. +To return a `Dynamic` value, simply `Box` it and return it. ```rust fn decide(yes_no: bool) -> Dynamic { @@ -543,7 +565,9 @@ print(y.len()); // prints 0 your own custom type, you need to define a specific override: ```rust -engine.register_fn("push", |list: &mut rhai::Array, item: MyType| list.push(Box::new(item))); +engine.register_fn("push", + |list: &mut Array, item: MyType| list.push(Box::new(item)) +); ``` The type of a Rhai array is `rhai::Array`. 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 From 103c62fb43eb2b90c223b8cb33ae8830fbb8ea42 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 2 Mar 2020 16:29:50 +0800 Subject: [PATCH 24/30] Include io::Error in file API's. --- src/api.rs | 18 +++++++----------- src/engine.rs | 36 +++++++++++++++++------------------- 2 files changed, 24 insertions(+), 30 deletions(-) diff --git a/src/api.rs b/src/api.rs index f8339944..6de0d2db 100644 --- a/src/api.rs +++ b/src/api.rs @@ -8,11 +8,7 @@ impl Engine { /// 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 + parse(&mut tokens.peekable()) } /// Compile a file into an AST @@ -21,12 +17,12 @@ impl Engine { use std::io::prelude::*; let mut f = File::open(filename) - .map_err(|_| EvalAltResult::ErrorCantOpenScriptFile(filename.into()))?; + .map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err))?; let mut contents = String::new(); f.read_to_string(&mut contents) - .map_err(|_| EvalAltResult::ErrorCantOpenScriptFile(filename.into())) + .map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err)) .and_then(|_| Self::compile(&contents).map_err(EvalAltResult::ErrorParsing)) } @@ -36,12 +32,12 @@ impl Engine { use std::io::prelude::*; let mut f = File::open(filename) - .map_err(|_| EvalAltResult::ErrorCantOpenScriptFile(filename.into()))?; + .map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err))?; let mut contents = String::new(); f.read_to_string(&mut contents) - .map_err(|_| EvalAltResult::ErrorCantOpenScriptFile(filename.into())) + .map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err)) .and_then(|_| self.eval::(&contents)) } @@ -107,12 +103,12 @@ impl Engine { use std::io::prelude::*; let mut f = File::open(filename) - .map_err(|_| EvalAltResult::ErrorCantOpenScriptFile(filename.into()))?; + .map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err))?; let mut contents = String::new(); f.read_to_string(&mut contents) - .map_err(|_| EvalAltResult::ErrorCantOpenScriptFile(filename.into())) + .map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err)) .and_then(|_| self.consume(&contents)) } diff --git a/src/engine.rs b/src/engine.rs index 635b7ac6..ef466481 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -12,7 +12,7 @@ use crate::parser::{Expr, FnDef, ParseError, Stmt}; pub type Array = Vec; pub type FnCallArgs<'a> = Vec<&'a mut Variant>; -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum EvalAltResult { ErrorParsing(ParseError), ErrorFunctionNotFound(String), @@ -27,7 +27,7 @@ pub enum EvalAltResult { ErrorVariableNotFound(String), ErrorAssignmentToUnknownLHS, ErrorMismatchOutputType(String), - ErrorCantOpenScriptFile(String), + ErrorCantOpenScriptFile(String, std::io::Error), ErrorDotExpr, ErrorArithmetic(String), LoopBreak, @@ -37,8 +37,7 @@ pub enum EvalAltResult { impl EvalAltResult { fn as_str(&self) -> Option<&str> { Some(match self { - Self::ErrorCantOpenScriptFile(s) - | Self::ErrorVariableNotFound(s) + Self::ErrorVariableNotFound(s) | Self::ErrorFunctionNotFound(s) | Self::ErrorMismatchOutputType(s) | Self::ErrorArithmetic(s) => s, @@ -71,7 +70,7 @@ impl PartialEq for EvalAltResult { (ErrorVariableNotFound(a), ErrorVariableNotFound(b)) => a == b, (ErrorAssignmentToUnknownLHS, ErrorAssignmentToUnknownLHS) => true, (ErrorMismatchOutputType(a), ErrorMismatchOutputType(b)) => a == b, - (ErrorCantOpenScriptFile(a), ErrorCantOpenScriptFile(b)) => a == b, + (ErrorCantOpenScriptFile(a, _), ErrorCantOpenScriptFile(b, _)) => a == b, (ErrorDotExpr, ErrorDotExpr) => true, (ErrorArithmetic(a), ErrorArithmetic(b)) => a == b, (LoopBreak, LoopBreak) => true, @@ -106,7 +105,7 @@ impl Error for EvalAltResult { "Assignment to an unsupported left-hand side expression" } Self::ErrorMismatchOutputType(_) => "Output type is incorrect", - Self::ErrorCantOpenScriptFile(_) => "Cannot open script file", + Self::ErrorCantOpenScriptFile(_, _) => "Cannot open script file", Self::ErrorDotExpr => "Malformed dot expression", Self::ErrorArithmetic(_) => "Arithmetic error", Self::LoopBreak => "[Not Error] Breaks out of loop", @@ -125,29 +124,28 @@ impl std::fmt::Display for EvalAltResult { write!(f, "{}: {}", self.description(), s) } else { match self { - EvalAltResult::ErrorParsing(p) => write!(f, "Syntax error: {}", p), - EvalAltResult::ErrorFunctionArgsMismatch(fun, n) => { + Self::ErrorCantOpenScriptFile(filename, err) => { + write!(f, "Cannot open script file '{}': {}", filename, err) + } + Self::ErrorParsing(p) => write!(f, "Syntax error: {}", p), + Self::ErrorFunctionArgsMismatch(fun, n) => { write!(f, "Function '{}' expects {} argument(s)", fun, n) } - EvalAltResult::ErrorBooleanArgMismatch(op) => { + Self::ErrorBooleanArgMismatch(op) => { write!(f, "Boolean {} operator expects boolean operands", op) } - EvalAltResult::ErrorArrayBounds(_, index) if *index < 0 => { + Self::ErrorArrayBounds(_, index) if *index < 0 => { write!(f, "{}: {} < 0", self.description(), index) } - EvalAltResult::ErrorArrayBounds(max, _) if *max == 0 => { - write!(f, "{}", self.description()) - } - EvalAltResult::ErrorArrayBounds(max, index) => { + Self::ErrorArrayBounds(max, _) if *max == 0 => write!(f, "{}", self.description()), + Self::ErrorArrayBounds(max, index) => { write!(f, "{} (max {}): {}", self.description(), max - 1, index) } - EvalAltResult::ErrorStringBounds(_, index) if *index < 0 => { + Self::ErrorStringBounds(_, index) if *index < 0 => { write!(f, "{}: {} < 0", self.description(), index) } - EvalAltResult::ErrorStringBounds(max, _) if *max == 0 => { - write!(f, "{}", self.description()) - } - EvalAltResult::ErrorStringBounds(max, index) => { + Self::ErrorStringBounds(max, _) if *max == 0 => write!(f, "{}", self.description()), + Self::ErrorStringBounds(max, index) => { write!(f, "{} (max {}): {}", self.description(), max - 1, index) } err => write!(f, "{}", err.description()), From cc87214750ec2e5660160e88cb905c3e951abefc Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 2 Mar 2020 17:04:45 +0800 Subject: [PATCH 25/30] Simplify code. --- src/api.rs | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/api.rs b/src/api.rs index 6de0d2db..4b66d55d 100644 --- a/src/api.rs +++ b/src/api.rs @@ -71,15 +71,15 @@ impl Engine { ) -> Result { let AST(os, fns) = ast; - for f in fns { - let spec = FnSpec { - ident: f.name.clone(), - args: None, - }; - - self.script_fns - .insert(spec, Arc::new(FnIntExt::Int(f.clone()))); - } + 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() @@ -133,17 +133,18 @@ impl Engine { .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(()); } - let spec = FnSpec { - ident: f.name.clone(), - args: None, - }; - - self.script_fns - .insert(spec, Arc::new(FnIntExt::Int(f.clone()))); + self.script_fns.insert( + FnSpec { + ident: f.name.clone(), + args: None, + }, + Arc::new(FnIntExt::Int(f.clone())), + ); } let val = os From ed8d2ac20fb16b25e0a242d9495e929dbb82453d Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 2 Mar 2020 17:04:56 +0800 Subject: [PATCH 26/30] Add else if control flow. --- README.md | 38 +++++++++++++++++++------------------- src/engine.rs | 16 +++------------- src/parser.rs | 14 +++++++++----- tests/if_block.rs | 17 +++++++++++++++++ 4 files changed, 48 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index e0003a47..6f9c78ae 100644 --- a/README.md +++ b/README.md @@ -190,6 +190,18 @@ fn main() { } ``` +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 + } +} +``` + # Working with generic functions Generic functions can be used in Rhai, but you'll need to register separate instances for each concrete type: @@ -217,7 +229,7 @@ You can also see in this example how you can register multiple functions (or in # Override built-in functions -Any similarly-named function defined in a script with the correct argument types overrides any built-in function. +Any similarly-named function defined in a script overrides any built-in function. ```rust // Override the built-in function 'to_int' @@ -445,9 +457,10 @@ this() & that(); // both this() and that() are evaluated ```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!"); } ``` @@ -456,11 +469,10 @@ else { ```rust let x = 10; + while x > 0 { print(x); - if x == 5 { - break; - } + if x == 5 { break; } x = x - 1; } ``` @@ -499,18 +511,6 @@ fn add(x, y) { print(add(2, 3)) ``` -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 - } -} -``` - ## Arrays You can create arrays of values, and then access them with numeric indices. diff --git a/src/engine.rs b/src/engine.rs index ef466481..156fa342 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -818,18 +818,6 @@ impl Engine { last_result } - Stmt::If(guard, body) => self - .eval_expr(scope, guard)? - .downcast::() - .map_err(|_| EvalAltResult::ErrorIfGuard) - .and_then(|guard_val| { - if *guard_val { - self.eval_stmt(scope, body) - } else { - Ok(Box::new(())) - } - }), - Stmt::IfElse(guard, body, else_body) => self .eval_expr(scope, guard)? .downcast::() @@ -837,8 +825,10 @@ impl Engine { .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 { - self.eval_stmt(scope, else_body) + Ok(Box::new(())) } }), diff --git a/src/parser.rs b/src/parser.rs index 8f9db5f0..001252d3 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -155,8 +155,7 @@ pub struct FnDef { #[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), @@ -1308,14 +1307,19 @@ fn parse_if<'a>(input: &mut Peekable>) -> Result { 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)), } } diff --git a/tests/if_block.rs b/tests/if_block.rs index 94775618..7440e448 100644 --- a/tests/if_block.rs +++ b/tests/if_block.rs @@ -7,4 +7,21 @@ fn test_if() { 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 false { 55 } else if true { 33 } else { 44 }"), + Ok(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 } + " + ), + Ok(44) + ); } From 0707fad1ca40331b676716c8475b413c27d3dfde Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 2 Mar 2020 22:11:56 +0800 Subject: [PATCH 27/30] Refactor tests. --- src/lib.rs | 2 +- tests/arrays.rs | 24 +++++------ tests/binary_ops.rs | 26 ++++++------ tests/bit_shift.rs | 14 +++---- tests/bool_op.rs | 83 +++++++++++++++++++------------------ tests/chars.rs | 25 +++++------ tests/compound_equality.rs | 66 ++++++++++++++--------------- tests/decrement.rs | 21 +++++----- tests/float.rs | 29 +++++++------ tests/for.rs | 8 ++-- tests/get_set.rs | 20 ++++----- tests/if_block.rs | 20 +++++---- tests/increment.rs | 12 +++--- tests/internal_fn.rs | 29 +++++++------ tests/looping.rs | 14 +++---- tests/method_call.rs | 15 ++++--- tests/mismatched_op.rs | 28 +++++++------ tests/not.rs | 14 ++++--- tests/number_literals.rs | 34 +++++++++------ tests/ops.rs | 18 ++++---- tests/power_of.rs | 46 ++++++++++---------- tests/string.rs | 14 ++++--- tests/unary_after_binary.rs | 18 ++++---- tests/unary_minus.rs | 12 +++--- tests/unit.rs | 23 +++++----- tests/var_scope.rs | 30 ++++---------- tests/while_loop.rs | 10 +++-- 27 files changed, 338 insertions(+), 317 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f7a3907e..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) 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 a0f7c451..edf560aa 100644 --- a/tests/binary_ops.rs +++ b/tests/binary_ops.rs @@ -1,20 +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"), Ok(true)); - assert_eq!(engine.eval::("42 > 42"), Ok(false)); + 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"), Ok(false)); - assert_eq!(engine.eval::(r#""42" == 42"#), Ok(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 ffd90746..7c1654bb 100644 --- a/tests/bool_op.rs +++ b/tests/bool_op.rs @@ -1,42 +1,39 @@ 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)"), 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)"), Ok(false)); + assert_eq!(engine.eval::("false && (false || true)")?, false); + assert_eq!(engine.eval::("false & (false | true)")?, false); + + Ok(()) } #[test] -fn test_bool_op3() { +fn test_bool_op3() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!( - engine.eval::("true && (false || 123)"), - Err(EvalAltResult::ErrorBooleanArgMismatch("OR".into())) - ); + 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); - assert_eq!(engine.eval::("true && (true || 123)"), Ok(true)); - - assert_eq!( - engine.eval::("123 && (false || true)"), - Err(EvalAltResult::ErrorBooleanArgMismatch("AND".into())) - ); - - assert_eq!(engine.eval::("false && (true || 123)"), Ok(false)); + Ok(()) } #[test] -fn test_bool_op_short_circuit() { +fn test_bool_op_short_circuit() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); assert_eq!( @@ -47,8 +44,8 @@ fn test_bool_op_short_circuit() { this() || that(); " - ), - Ok(true) + )?, + true ); assert_eq!( @@ -59,9 +56,11 @@ fn test_bool_op_short_circuit() { this() && that(); " - ), - Ok(false) + )?, + false ); + + Ok(()) } #[test] @@ -70,15 +69,17 @@ fn test_bool_op_no_short_circuit1() { let mut engine = Engine::new(); assert_eq!( - engine.eval::( - r" - fn this() { false } - fn that() { 9/0 } + engine + .eval::( + r" + fn this() { false } + fn that() { 9/0 } - this() | that(); - " - ), - Ok(false) + this() | that(); + " + ) + .unwrap(), + false ); } @@ -88,14 +89,16 @@ fn test_bool_op_no_short_circuit2() { let mut engine = Engine::new(); assert_eq!( - engine.eval::( - r" - fn this() { false } - fn that() { 9/0 } + engine + .eval::( + r" + fn this() { false } + fn that() { 9/0 } - this() & that(); - " - ), - Ok(false) + this() & that(); + " + ) + .unwrap(), + false ); } diff --git a/tests/chars.rs b/tests/chars.rs index 44657000..4a5c2d98 100644 --- a/tests/chars.rs +++ b/tests/chars.rs @@ -1,24 +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::(r#"let x="hello"; x[2]"#), Ok('l')); + 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"#), - Ok("he$lo".into()) + engine.eval::(r#"let x="hello"; x[2]='$'; x"#)?, + "he$lo".to_string() ); - match engine.eval::("'\\uhello'") { - Err(_) => (), - _ => assert!(false), - } + assert!(engine.eval::("'\\uhello'").is_err()); + assert!(engine.eval::("''").is_err()); - match engine.eval::("''") { - Err(_) => (), - _ => assert!(false), - } + 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..344c70ac 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.starts_with("- ") => (), + _ => 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 7440e448..286aa7fd 100644 --- a/tests/if_block.rs +++ b/tests/if_block.rs @@ -1,15 +1,15 @@ -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 }"), - Ok(33) + engine.eval::("if false { 55 } else if true { 33 } else { 44 }")?, + 33 ); assert_eq!( engine.eval::( @@ -21,7 +21,9 @@ fn test_if() { else if false { 88 } else { 44 } " - ), - Ok(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..314ea378 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 == "alloc::string::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(()) } From 0e96e1080ca5d133c4abea354fe5253825868c39 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 2 Mar 2020 22:13:14 +0800 Subject: [PATCH 28/30] Comprehensive error line number/character position during evaluation. --- src/api.rs | 16 +- src/engine.rs | 436 +++++++++++++++++++++++---------------------- src/fn_register.rs | 9 +- src/parser.rs | 342 ++++++++++++++++++++++------------- 4 files changed, 450 insertions(+), 353 deletions(-) diff --git a/src/api.rs b/src/api.rs index 4b66d55d..84cefab4 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,7 +1,6 @@ -use crate::any::{Any, AnyExt}; -use crate::engine::{FnIntExt, FnSpec}; -use crate::parser::{lex, parse}; -use crate::{Dynamic, Engine, EvalAltResult, ParseError, Scope, AST}; +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 { @@ -88,9 +87,14 @@ impl Engine { self.script_fns.clear(); // Clean up engine match result { - Err(EvalAltResult::Return(out)) | Ok(out) => Ok(*out + Err(EvalAltResult::Return(out, pos)) => Ok(*out .downcast::() - .map_err(|err| EvalAltResult::ErrorMismatchOutputType((*err).type_name()))?), + .map_err(|a| EvalAltResult::ErrorMismatchOutputType((*a).type_name(), pos))?), + + Ok(out) => Ok(*out.downcast::().map_err(|a| { + EvalAltResult::ErrorMismatchOutputType((*a).type_name(), Position::eof()) + })?), + Err(err) => Err(err), } } diff --git a/src/engine.rs b/src/engine.rs index 156fa342..7818d96a 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use crate::any::{Any, AnyExt, Dynamic, Variant}; use crate::call::FunArgs; use crate::fn_register::RegisterFn; -use crate::parser::{Expr, FnDef, ParseError, Stmt}; +use crate::parser::{Expr, FnDef, ParseError, Position, Stmt}; pub type Array = Vec; pub type FnCallArgs<'a> = Vec<&'a mut Variant>; @@ -15,101 +15,58 @@ pub type FnCallArgs<'a> = Vec<&'a mut Variant>; #[derive(Debug)] pub enum EvalAltResult { ErrorParsing(ParseError), - ErrorFunctionNotFound(String), - ErrorFunctionArgsMismatch(String, usize), - ErrorBooleanArgMismatch(String), - ErrorArrayBounds(usize, i64), - ErrorStringBounds(usize, i64), - ErrorIndexing, - ErrorIndexExpr, - ErrorIfGuard, - ErrorFor, - ErrorVariableNotFound(String), - ErrorAssignmentToUnknownLHS, - ErrorMismatchOutputType(String), + 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, - ErrorArithmetic(String), + ErrorDotExpr(Position), + ErrorArithmetic(String, Position), LoopBreak, - Return(Dynamic), -} - -impl EvalAltResult { - fn as_str(&self) -> Option<&str> { - Some(match self { - Self::ErrorVariableNotFound(s) - | Self::ErrorFunctionNotFound(s) - | Self::ErrorMismatchOutputType(s) - | Self::ErrorArithmetic(s) => s, - _ => return None, - }) - } -} - -impl PartialEq for EvalAltResult { - fn eq(&self, other: &Self) -> bool { - use EvalAltResult::*; - - match (self, other) { - (ErrorParsing(a), ErrorParsing(b)) => a == b, - (ErrorFunctionNotFound(a), ErrorFunctionNotFound(b)) => a == b, - (ErrorFunctionArgsMismatch(f1, n1), ErrorFunctionArgsMismatch(f2, n2)) => { - f1 == f2 && *n1 == *n2 - } - (ErrorBooleanArgMismatch(a), ErrorBooleanArgMismatch(b)) => a == b, - (ErrorIndexExpr, ErrorIndexExpr) => true, - (ErrorIndexing, ErrorIndexing) => true, - (ErrorArrayBounds(max1, index1), ErrorArrayBounds(max2, index2)) => { - max1 == max2 && index1 == index2 - } - (ErrorStringBounds(max1, index1), ErrorStringBounds(max2, index2)) => { - max1 == max2 && index1 == index2 - } - (ErrorIfGuard, ErrorIfGuard) => true, - (ErrorFor, ErrorFor) => true, - (ErrorVariableNotFound(a), ErrorVariableNotFound(b)) => a == b, - (ErrorAssignmentToUnknownLHS, ErrorAssignmentToUnknownLHS) => true, - (ErrorMismatchOutputType(a), ErrorMismatchOutputType(b)) => a == b, - (ErrorCantOpenScriptFile(a, _), ErrorCantOpenScriptFile(b, _)) => a == b, - (ErrorDotExpr, ErrorDotExpr) => true, - (ErrorArithmetic(a), ErrorArithmetic(b)) => a == b, - (LoopBreak, LoopBreak) => true, - _ => false, - } - } + Return(Dynamic, Position), } impl Error for EvalAltResult { fn description(&self) -> &str { 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 => { + 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" } - Self::ErrorArrayBounds(max, _) if *max == 0 => "Access of empty array", - Self::ErrorArrayBounds(_, _) => "Array index out of bounds", - Self::ErrorStringBounds(_, index) if *index < 0 => { + 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 guards expect boolean expression", - Self::ErrorFor => "For loops expect array", - Self::ErrorVariableNotFound(_) => "Variable not found", - Self::ErrorAssignmentToUnknownLHS => { + 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" } - Self::ErrorMismatchOutputType(_) => "Output type is incorrect", + Self::ErrorMismatchOutputType(_, _) => "Output type is incorrect", Self::ErrorCantOpenScriptFile(_, _) => "Cannot open script file", - Self::ErrorDotExpr => "Malformed dot expression", - Self::ErrorArithmetic(_) => "Arithmetic error", + Self::ErrorDotExpr(_) => "Malformed dot expression", + Self::ErrorArithmetic(_, _) => "Arithmetic error", Self::LoopBreak => "[Not Error] Breaks out of loop", - Self::Return(_) => "[Not Error] Function returns value", + Self::Return(_, _) => "[Not Error] Function returns value", } } @@ -120,35 +77,44 @@ impl Error for EvalAltResult { impl std::fmt::Display for EvalAltResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if let Some(s) = self.as_str() { - write!(f, "{}: {}", self.description(), s) - } else { - match self { - Self::ErrorCantOpenScriptFile(filename, err) => { - write!(f, "Cannot open script file '{}': {}", filename, err) - } - Self::ErrorParsing(p) => write!(f, "Syntax error: {}", p), - Self::ErrorFunctionArgsMismatch(fun, n) => { - write!(f, "Function '{}' expects {} argument(s)", fun, n) - } - Self::ErrorBooleanArgMismatch(op) => { - write!(f, "Boolean {} operator expects boolean operands", op) - } - Self::ErrorArrayBounds(_, index) if *index < 0 => { - write!(f, "{}: {} < 0", self.description(), index) - } - Self::ErrorArrayBounds(max, _) if *max == 0 => write!(f, "{}", self.description()), - Self::ErrorArrayBounds(max, index) => { - write!(f, "{} (max {}): {}", self.description(), max - 1, index) - } - Self::ErrorStringBounds(_, index) if *index < 0 => { - write!(f, "{}: {} < 0", self.description(), index) - } - Self::ErrorStringBounds(max, _) if *max == 0 => write!(f, "{}", self.description()), - Self::ErrorStringBounds(max, index) => { - write!(f, "{} (max {}): {}", self.description(), max - 1, index) - } - err => write!(f, "{}", err.description()), + 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) } } } @@ -193,7 +159,7 @@ pub enum FnIntExt { Int(FnDef), } -pub type FnAny = dyn Fn(FnCallArgs) -> Result; +pub type FnAny = dyn Fn(FnCallArgs, Position) -> Result; /// A type containing information about current scope. /// Useful for keeping state between `Engine` runs @@ -218,11 +184,13 @@ impl Engine { A: FunArgs<'a>, T: Any + Clone, { - self.call_fn_raw(ident.into(), args.into_vec(), None) + 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())) + .map_err(|a| EvalAltResult::ErrorMismatchOutputType((*a).type_name(), pos)) }) } @@ -233,6 +201,7 @@ impl Engine { ident: String, args: FnCallArgs, def_value: Option<&Dynamic>, + pos: Position, ) -> Result { debug_println!( "Trying to call function {:?} with args {:?}", @@ -257,7 +226,7 @@ impl Engine { if let Some(f) = fn_def { match **f { FnIntExt::Ext(ref f) => { - let r = f(args); + let r = f(args, pos); if r.is_err() { return r; @@ -288,7 +257,7 @@ impl Engine { ); match self.eval_stmt(&mut scope, &*f.body) { - Err(EvalAltResult::Return(x)) => Ok(x), + Err(EvalAltResult::Return(x, _)) => Ok(x), other => other, } } @@ -302,11 +271,10 @@ impl Engine { .map(|x| (*(&**x).into_dynamic()).type_name()) .collect::>(); - Err(EvalAltResult::ErrorFunctionNotFound(format!( - "{} ({})", - ident, - type_names.join(", ") - ))) + Err(EvalAltResult::ErrorFunctionNotFound( + format!("{} ({})", ident, type_names.join(", ")), + pos, + )) } } @@ -377,7 +345,7 @@ impl Engine { use std::iter::once; match dot_rhs { - Expr::FunctionCall(fn_name, args, def_value) => { + Expr::FunctionCall(fn_name, args, def_value, pos) => { let mut args: Array = args .iter() .map(|arg| self.eval_expr(scope, arg)) @@ -387,53 +355,59 @@ impl Engine { .chain(args.iter_mut().map(|b| b.as_mut())) .collect(); - self.call_fn_raw(fn_name.into(), args, def_value.as_ref()) + self.call_fn_raw(fn_name.into(), args, def_value.as_ref(), *pos) } - Expr::Identifier(id) => { + Expr::Identifier(id, pos) => { let get_fn_name = "get$".to_string() + id; - self.call_fn_raw(get_fn_name, vec![this_ptr], None) + self.call_fn_raw(get_fn_name, vec![this_ptr], None, *pos) } - Expr::Index(id, 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)?; + .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], None)?; + let mut val = self.call_fn_raw(get_fn_name, vec![this_ptr], None, *pos)?; if let Some(arr) = (*val).downcast_mut() as Option<&mut Array> { if idx >= 0 { arr.get(idx as usize) .cloned() - .ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr.len(), idx)) + .ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr.len(), idx, *pos)) } else { - Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx)) + 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)) + .ok_or_else(|| { + EvalAltResult::ErrorStringBounds(s.chars().count(), idx, *pos) + }) } else { - Err(EvalAltResult::ErrorStringBounds(s.chars().count(), idx)) + Err(EvalAltResult::ErrorStringBounds( + s.chars().count(), + idx, + *pos, + )) } } else { - Err(EvalAltResult::ErrorIndexing) + Err(EvalAltResult::ErrorIndexing(*pos)) } } Expr::Dot(inner_lhs, inner_rhs) => match **inner_lhs { - Expr::Identifier(ref id) => { + 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) + .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: @@ -446,17 +420,18 @@ impl Engine { Ok(value) } - Expr::Index(_, _) => { + Expr::Index(_, _, pos) => { // TODO - Handle Expr::Index for these scenarios: // // let x = obj.prop[2].x; // obj.prop[3] = 42; // - Err(EvalAltResult::ErrorDotExpr) + Err(EvalAltResult::ErrorDotExpr(pos)) } - _ => Err(EvalAltResult::ErrorDotExpr), + _ => Err(EvalAltResult::ErrorDotExpr(inner_lhs.position())), }, - _ => Err(EvalAltResult::ErrorDotExpr), + + _ => Err(EvalAltResult::ErrorDotExpr(dot_rhs.position())), } } @@ -464,13 +439,14 @@ impl Engine { scope: &'a mut Scope, id: &str, 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.into())) + .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.into(), begin)) .and_then(move |(idx, &mut (_, ref mut val))| map(val.as_mut()).map(|val| (idx, val))) } @@ -479,40 +455,52 @@ impl Engine { scope: &mut Scope, id: &str, idx: &Expr, + begin: Position, ) -> Result<(bool, usize, usize, Dynamic), EvalAltResult> { let idx = *self .eval_expr(scope, idx)? .downcast::() - .map_err(|_| EvalAltResult::ErrorIndexExpr)?; + .map_err(|_| EvalAltResult::ErrorIndexExpr(idx.position()))?; 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; + 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)) - } else { - Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx)) - } - } else if let Some(s) = (*val).downcast_mut() as Option<&mut String> { - is_array = false; + 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)) + 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::ErrorStringBounds(s.chars().count(), idx)) + Err(EvalAltResult::ErrorIndexing(begin)) } - } else { - Err(EvalAltResult::ErrorIndexing) - } - }) + }, + begin, + ) .map(|(idx_sc, val)| (is_array, idx_sc, idx as usize, val)) } @@ -539,8 +527,9 @@ impl Engine { dot_rhs: &Expr, ) -> Result { match dot_lhs { - Expr::Identifier(id) => { - let (sc_idx, mut target) = Self::search_scope(scope, id, |x| Ok(x.into_dynamic()))?; + 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 @@ -550,8 +539,9 @@ impl Engine { value } - Expr::Index(id, idx_raw) => { - let (is_array, sc_idx, idx, mut target) = self.indexed_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 @@ -569,7 +559,8 @@ impl Engine { value } - _ => Err(EvalAltResult::ErrorDotExpr), + + _ => Err(EvalAltResult::ErrorDotExpr(dot_lhs.position())), } } @@ -580,17 +571,17 @@ impl Engine { mut source_val: Dynamic, ) -> Result { match dot_rhs { - Expr::Identifier(id) => { + 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()], None) + self.call_fn_raw(set_fn_name, vec![this_ptr, source_val.as_mut()], None, *pos) } Expr::Dot(inner_lhs, inner_rhs) => match **inner_lhs { - Expr::Identifier(ref id) => { + Expr::Identifier(ref id, pos) => { let get_fn_name = "get$".to_string() + id; - self.call_fn_raw(get_fn_name, vec![this_ptr], None) + 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 @@ -598,12 +589,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()], None) + self.call_fn_raw(set_fn_name, vec![this_ptr, v.as_mut()], None, pos) }) } - _ => Err(EvalAltResult::ErrorDotExpr), + _ => Err(EvalAltResult::ErrorDotExpr(inner_lhs.position())), }, - _ => Err(EvalAltResult::ErrorDotExpr), + + _ => Err(EvalAltResult::ErrorDotExpr(dot_rhs.position())), } } @@ -615,8 +607,9 @@ impl Engine { source_val: Dynamic, ) -> Result { match dot_lhs { - Expr::Identifier(id) => { - let (sc_idx, mut target) = Self::search_scope(scope, id, |x| Ok(x.into_dynamic()))?; + 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 @@ -626,8 +619,9 @@ impl Engine { value } - Expr::Index(id, idx_raw) => { - let (is_array, sc_idx, idx, mut target) = self.indexed_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 @@ -644,34 +638,35 @@ impl Engine { value } - _ => Err(EvalAltResult::ErrorDotExpr), + + _ => Err(EvalAltResult::ErrorDotExpr(dot_lhs.position())), } } 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::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) => scope + Expr::Identifier(id, pos) => scope .iter() .rev() .filter(|(name, _)| id == name) .next() .map(|(_, val)| val.clone()) - .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.clone())), + .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.clone(), *pos)), - Expr::Index(id, idx_raw) => { - self.indexed_value(scope, id, idx_raw).map(|(_, _, _, x)| x) - } + 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) => scope + Expr::Identifier(ref n, pos) => scope .iter_mut() .rev() .filter(|(name, _)| n == name) @@ -680,12 +675,14 @@ impl Engine { *val = rhs_val; Box::new(()) as Dynamic }) - .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(n.clone())), + .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(n.clone(), pos)), + + Expr::Index(ref id, ref idx_raw, pos) => { + let idx_pos = idx_raw.position(); - Expr::Index(ref id, ref idx_raw) => { let idx = *match self.eval_expr(scope, &idx_raw)?.downcast_ref::() { Some(x) => x, - _ => return Err(EvalAltResult::ErrorIndexExpr), + _ => return Err(EvalAltResult::ErrorIndexExpr(idx_pos)), }; let variable = &mut scope @@ -697,14 +694,14 @@ impl Engine { let val = match variable { Some(v) => v, - _ => return Err(EvalAltResult::ErrorVariableNotFound(id.clone())), + _ => 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)) + Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, idx_pos)) } else if idx as usize >= arr.len() { - Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx)) + Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, idx_pos)) } else { arr[idx as usize] = rhs_val; Ok(Box::new(())) @@ -713,9 +710,9 @@ impl Engine { let s_len = s.chars().count(); if idx < 0 { - Err(EvalAltResult::ErrorStringBounds(s_len, idx)) + Err(EvalAltResult::ErrorStringBounds(s_len, idx, idx_pos)) } else if idx as usize >= s_len { - Err(EvalAltResult::ErrorStringBounds(s_len, idx)) + Err(EvalAltResult::ErrorStringBounds(s_len, idx, idx_pos)) } else { Self::str_replace_char( s, @@ -725,7 +722,7 @@ impl Engine { Ok(Box::new(())) } } else { - Err(EvalAltResult::ErrorIndexExpr) + Err(EvalAltResult::ErrorIndexExpr(idx_pos)) } } @@ -733,13 +730,13 @@ impl Engine { self.set_dot_val(scope, dot_lhs, dot_rhs, rhs_val) } - _ => Err(EvalAltResult::ErrorAssignmentToUnknownLHS), + _ => Err(EvalAltResult::ErrorAssignmentToUnknownLHS(id.position())), } } Expr::Dot(lhs, rhs) => self.get_dot_val(scope, lhs, rhs), - Expr::Array(contents) => { + Expr::Array(contents, _) => { let mut arr = Vec::new(); contents.iter().try_for_each(|item| { @@ -751,7 +748,7 @@ impl Engine { Ok(Box::new(arr)) } - Expr::FunctionCall(fn_name, args, def_value) => self.call_fn_raw( + Expr::FunctionCall(fn_name, args, def_value, pos) => self.call_fn_raw( fn_name.into(), args.iter() .map(|expr| self.eval_expr(scope, expr)) @@ -760,33 +757,42 @@ impl Engine { .map(|b| b.as_mut()) .collect(), def_value.as_ref(), + *pos, ), Expr::And(lhs, rhs) => Ok(Box::new( *self .eval_expr(scope, &*lhs)? .downcast::() - .map_err(|_| EvalAltResult::ErrorBooleanArgMismatch("AND".into()))? + .map_err(|_| { + EvalAltResult::ErrorBooleanArgMismatch("AND".into(), lhs.position()) + })? && *self .eval_expr(scope, &*rhs)? .downcast::() - .map_err(|_| EvalAltResult::ErrorBooleanArgMismatch("AND".into()))?, + .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()))? + .map_err(|_| { + EvalAltResult::ErrorBooleanArgMismatch("OR".into(), lhs.position()) + })? || *self .eval_expr(scope, &*rhs)? .downcast::() - .map_err(|_| EvalAltResult::ErrorBooleanArgMismatch("OR".into()))?, + .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(())), + Expr::True(_) => Ok(Box::new(true)), + Expr::False(_) => Ok(Box::new(false)), + Expr::Unit(_) => Ok(Box::new(())), } } @@ -821,7 +827,7 @@ impl Engine { Stmt::IfElse(guard, body, else_body) => self .eval_expr(scope, guard)? .downcast::() - .map_err(|_| EvalAltResult::ErrorIfGuard) + .map_err(|_| EvalAltResult::ErrorIfGuard(guard.position())) .and_then(|guard_val| { if *guard_val { self.eval_stmt(scope, body) @@ -845,7 +851,7 @@ impl Engine { return Ok(Box::new(())); } } - Err(_) => return Err(EvalAltResult::ErrorIfGuard), + Err(_) => return Err(EvalAltResult::ErrorIfGuard(guard.position())), } }, @@ -877,20 +883,20 @@ impl Engine { scope.remove(idx); Ok(Box::new(())) } else { - return Err(EvalAltResult::ErrorFor); + return Err(EvalAltResult::ErrorFor(expr.position())); } } - Stmt::Break => Err(EvalAltResult::LoopBreak), + Stmt::Break(_) => Err(EvalAltResult::LoopBreak), - Stmt::Return => Err(EvalAltResult::Return(Box::new(()))), + Stmt::Return(pos) => Err(EvalAltResult::Return(Box::new(()), *pos)), - Stmt::ReturnWithVal(a) => { + Stmt::ReturnWithVal(a, pos) => { let result = self.eval_expr(scope, a)?; - Err(EvalAltResult::Return(result)) + Err(EvalAltResult::Return(result, *pos)) } - Stmt::Let(name, init) => { + Stmt::Let(name, init, _) => { if let Some(v) = init { let i = self.eval_expr(scope, v)?; scope.push((name.clone(), i)); diff --git a/src/fn_register.rs b/src/fn_register.rs index 8a65952f..f340f5bc 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -2,6 +2,7 @@ use std::any::TypeId; 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); @@ -32,13 +33,13 @@ macro_rules! def_register { fn register_fn(&mut self, name: &str, f: FN) { let fn_name = name.to_string(); - let fun = move |mut args: FnCallArgs| { + let fun = move |mut args: FnCallArgs, pos: Position| { // Check for length at the beginning to avoid // per-element bound checks. const NUM_ARGS: usize = count_args!($($par)*); if args.len() != NUM_ARGS { - return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS)); + return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, pos)); } #[allow(unused_variables, unused_mut)] @@ -65,13 +66,13 @@ macro_rules! def_register { fn register_dynamic_fn(&mut self, name: &str, f: FN) { let fn_name = name.to_string(); - let fun = move |mut args: FnCallArgs| { + let fun = move |mut args: FnCallArgs, pos: Position| { // Check for length at the beginning to avoid // per-element bound checks. const NUM_ARGS: usize = count_args!($($par)*); if args.len() != NUM_ARGS { - return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS)); + return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, pos)); } #[allow(unused_variables, unused_mut)] diff --git a/src/parser.rs b/src/parser.rs index 001252d3..2037679c 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -50,8 +50,8 @@ pub enum ParseErrorType { MissingRightBracket, MalformedCallExpr, MalformedIndexExpr, - VarExpectsIdentifier(Token), - FnMissingName(Token), + VarExpectsIdentifier, + FnMissingName, FnMissingParams, } @@ -59,25 +59,37 @@ type PERR = ParseErrorType; #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] pub struct Position { - line: usize, - pos: usize, + pub line: usize, + pub pos: usize, } impl Position { - fn advance(&mut self) { + pub fn advance(&mut self) { self.pos += 1; } - fn rewind(&mut self) { + pub fn rewind(&mut self) { // Beware, should not rewind at zero position self.pos -= 1; } - fn new_line(&mut self) { + pub fn new_line(&mut self) { self.line += 1; self.pos = 0; } - fn eof() -> Self { + 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)] @@ -93,6 +105,9 @@ impl ParseError { pub fn position(&self) -> usize { self.1.pos } + pub fn is_eof(&self) -> bool { + self.1.is_eof() + } } impl Error for ParseError { @@ -107,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", } } @@ -121,23 +136,12 @@ 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, " at the end of the script but there is no more input") } @@ -151,6 +155,7 @@ pub struct FnDef { pub name: String, pub params: Vec, pub body: Box, + pub pos: Position, } #[derive(Debug, Clone)] @@ -159,36 +164,55 @@ pub enum Stmt { 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, Clone)] pub enum Expr { - IntegerConstant(i64), - FloatConstant(f64), - Identifier(String), - CharConstant(char), - StringConstant(String), - FunctionCall(String, Vec, Option), + 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), + Index(String, Box, Position), + Array(Vec, Position), And(Box, Box), Or(Box, Box), - True, - False, - Unit, + 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), @@ -972,11 +996,14 @@ fn get_precedence(token: &Token) -> i8 { } } -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, _)) => { input.next(); - return Ok(Expr::Unit); + return Ok(Expr::Unit(begin)); } _ => (), } @@ -992,12 +1019,13 @@ fn parse_paren_expr<'a>(input: &mut Peekable>) -> Result( id: String, input: &mut Peekable>, + begin: Position, ) -> Result { let mut args = Vec::new(); if let Some(&(Token::RightParen, _)) = input.peek() { input.next(); - return Ok(Expr::FunctionCall(id, args, None)); + return Ok(Expr::FunctionCall(id, args, None, begin)); } loop { @@ -1006,7 +1034,7 @@ fn parse_call_expr<'a>( match input.peek() { Some(&(Token::RightParen, _)) => { input.next(); - return Ok(Expr::FunctionCall(id, args, None)); + return Ok(Expr::FunctionCall(id, args, None, begin)); } Some(&(Token::Comma, _)) => (), Some(&(_, pos)) => return Err(ParseError(PERR::MalformedCallExpr, pos)), @@ -1020,12 +1048,13 @@ 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, _)) => { input.next(); - return Ok(Expr::Index(id, Box::new(idx))); + return Ok(Expr::Index(id, Box::new(idx), begin)); } Some(&(_, pos)) => return Err(ParseError(PERR::MalformedIndexExpr, pos)), None => return Err(ParseError(PERR::MalformedIndexExpr, Position::eof())), @@ -1040,21 +1069,26 @@ 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() { @@ -1065,6 +1099,7 @@ fn parse_array_expr<'a>(input: &mut Peekable>) -> Result(input: &mut Peekable>) -> Result { input.next(); - Ok(Expr::Array(arr)) + Ok(Expr::Array(arr, begin)) } Some(&(_, pos)) => Err(ParseError(PERR::MissingRightBracket, pos)), None => Err(ParseError(PERR::MissingRightBracket, Position::eof())), @@ -1088,15 +1123,15 @@ fn parse_array_expr<'a>(input: &mut Peekable>) -> Result(input: &mut Peekable>) -> Result { match input.next() { Some((token, pos)) => match token { - Token::IntegerConstant(x) => Ok(Expr::IntegerConstant(x)), - Token::FloatConstant(x) => Ok(Expr::FloatConstant(x)), - Token::StringConst(s) => Ok(Expr::StringConstant(s)), - Token::CharConstant(c) => Ok(Expr::CharConstant(c)), - Token::Identifier(s) => parse_ident_expr(s, input), - Token::LeftParen => parse_paren_expr(input), - Token::LeftBracket => parse_array_expr(input), - Token::True => Ok(Expr::True), - Token::False => Ok(Expr::False), + 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)), @@ -1108,18 +1143,20 @@ fn parse_primary<'a>(input: &mut Peekable>) -> Result(input: &mut Peekable>) -> Result { - let token = match input.peek() { - Some((tok, _)) => tok.clone(), + 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( "-".into(), vec![parse_primary(input)?], None, + pos, )) } Token::UnaryPlus => { @@ -1128,10 +1165,12 @@ fn parse_unary<'a>(input: &mut Peekable>) -> Result { input.next(); + Ok(Expr::FunctionCall( "!".into(), vec![parse_primary(input)?], Some(Box::new(false)), // NOT operator, when operating on invalid operand, defaults to false + pos, )) } _ => parse_primary(input), @@ -1157,6 +1196,8 @@ fn parse_binop<'a>( } if let Some((op_token, pos)) = input.next() { + input.peek(); + let mut rhs = parse_unary(input)?; let mut next_prec = -1; @@ -1173,118 +1214,175 @@ fn parse_binop<'a>( } lhs_curr = match op_token { - Token::Plus => Expr::FunctionCall("+".into(), vec![lhs_curr, rhs], None), - Token::Minus => Expr::FunctionCall("-".into(), vec![lhs_curr, rhs], None), - Token::Multiply => Expr::FunctionCall("*".into(), vec![lhs_curr, rhs], None), - Token::Divide => Expr::FunctionCall("/".into(), vec![lhs_curr, rhs], None), + 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("+".into(), vec![lhs_copy, rhs], None)), + 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("-".into(), vec![lhs_copy, rhs], None)), + Box::new(Expr::FunctionCall( + "-".into(), + vec![lhs_copy, rhs], + None, + pos, + )), ) } Token::Period => Expr::Dot(Box::new(lhs_curr), Box::new(rhs)), // Comparison operators default to false when passed invalid operands Token::EqualsTo => { - Expr::FunctionCall("==".into(), vec![lhs_curr, rhs], Some(Box::new(false))) + 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))) + 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))) + Expr::FunctionCall("<".into(), vec![lhs_curr, rhs], Some(Box::new(false)), pos) } Token::LessThanEqualsTo => { - Expr::FunctionCall("<=".into(), vec![lhs_curr, rhs], Some(Box::new(false))) + 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))) + Expr::FunctionCall(">".into(), vec![lhs_curr, rhs], Some(Box::new(false)), pos) } Token::GreaterThanEqualsTo => { - Expr::FunctionCall(">=".into(), vec![lhs_curr, rhs], Some(Box::new(false))) + Expr::FunctionCall(">=".into(), vec![lhs_curr, rhs], Some(Box::new(false)), pos) } 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), + 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("|".into(), vec![lhs_copy, rhs], None)), + 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("&".into(), vec![lhs_copy, rhs], None)), + 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("^".into(), vec![lhs_copy, rhs], None)), + 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("*".into(), vec![lhs_copy, rhs], None)), + 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("/".into(), vec![lhs_copy, rhs], None)), + Box::new(Expr::FunctionCall( + "/".into(), + vec![lhs_copy, rhs], + None, + pos, + )), ) } - Token::Pipe => Expr::FunctionCall("|".into(), vec![lhs_curr, rhs], None), - Token::LeftShift => Expr::FunctionCall("<<".into(), vec![lhs_curr, rhs], None), - Token::RightShift => Expr::FunctionCall(">>".into(), vec![lhs_curr, rhs], None), + 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("<<".into(), vec![lhs_copy, rhs], None)), + 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(">>".into(), vec![lhs_copy, rhs], None)), + Box::new(Expr::FunctionCall( + ">>".into(), + vec![lhs_copy, rhs], + None, + pos, + )), ) } - Token::Ampersand => Expr::FunctionCall("&".into(), vec![lhs_curr, rhs], None), - Token::Modulo => Expr::FunctionCall("%".into(), vec![lhs_curr, rhs], None), + 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("%".into(), vec![lhs_copy, rhs], None)), + Box::new(Expr::FunctionCall( + "%".into(), + vec![lhs_copy, rhs], + None, + pos, + )), ) } - Token::PowerOf => Expr::FunctionCall("~".into(), vec![lhs_curr, rhs], None), + 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("~".into(), vec![lhs_copy, rhs], None)), + Box::new(Expr::FunctionCall( + "~".into(), + vec![lhs_copy, rhs], + None, + pos, + )), ) } _ => return Err(ParseError(PERR::UnknownOperator, pos)), @@ -1345,24 +1443,14 @@ fn parse_for<'a>(input: &mut Peekable>) -> Result s, - Some((token, pos)) => return Err(ParseError(PERR::VarExpectsIdentifier(token), pos)), - None => { - return Err(ParseError( - PERR::VarExpectsIdentifier(Token::None), - Position::eof(), - )) - } + Some((_, pos)) => return Err(ParseError(PERR::VarExpectsIdentifier, pos)), + None => return Err(ParseError(PERR::VarExpectsIdentifier, Position::eof())), }; match input.next() { Some((Token::In, _)) => {} - Some((token, pos)) => return Err(ParseError(PERR::VarExpectsIdentifier(token), pos)), - None => { - return Err(ParseError( - PERR::VarExpectsIdentifier(Token::None), - Position::eof(), - )) - } + Some((_, pos)) => return Err(ParseError(PERR::VarExpectsIdentifier, pos)), + None => return Err(ParseError(PERR::VarExpectsIdentifier, Position::eof())), } let expr = parse_expr(input)?; @@ -1373,26 +1461,24 @@ 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(s), _)) => s, - Some((token, pos)) => return Err(ParseError(PERR::VarExpectsIdentifier(token), pos)), - None => { - return Err(ParseError( - PERR::VarExpectsIdentifier(Token::None), - Position::eof(), - )) - } + Some((_, pos)) => return Err(ParseError(PERR::VarExpectsIdentifier, pos)), + None => return Err(ParseError(PERR::VarExpectsIdentifier, Position::eof())), }; match input.peek() { 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)), } } @@ -1446,18 +1532,19 @@ fn parse_stmt<'a>(input: &mut Peekable>) -> Result parse_while(input), Some(&(Token::Loop, _)) => parse_loop(input), Some(&(Token::For, _)) => parse_for(input), - Some(&(Token::Break, _)) => { + Some(&(Token::Break, pos)) => { input.next(); - Ok(Stmt::Break) + Ok(Stmt::Break(pos)) } 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), @@ -1467,17 +1554,15 @@ fn parse_stmt<'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(s), _)) => s, - Some((token, pos)) => return Err(ParseError(PERR::FnMissingName(token), pos)), - None => { - return Err(ParseError( - PERR::FnMissingName(Token::None), - Position::eof(), - )) - } + Some((_, pos)) => return Err(ParseError(PERR::FnMissingName, pos)), + None => return Err(ParseError(PERR::FnMissingName, Position::eof())), }; match input.peek() { @@ -1518,6 +1603,7 @@ fn parse_fn<'a>(input: &mut Peekable>) -> Result Date: Mon, 2 Mar 2020 22:32:08 +0800 Subject: [PATCH 29/30] Add remaining integer types. --- src/builtin.rs | 60 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 18 deletions(-) diff --git a/src/builtin.rs b/src/builtin.rs index d7fa632f..da23abe2 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -137,33 +137,38 @@ impl Engine { true } - reg_op!(self, "+", add, i32, i64, u32, u64, f32, f64); - reg_op!(self, "-", sub, i32, i64, u32, u64, f32, f64); - reg_op!(self, "*", mul, i32, i64, u32, u64, f32, f64); - reg_op!(self, "/", div, i32, i64, u32, u64, f32, f64); + 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, i32, i64, u32, u64, String, char, f32, f64); - reg_cmp!(self, "<=", lte, i32, i64, u32, u64, String, char, f32, f64); - reg_cmp!(self, ">", gt, i32, i64, u32, u64, String, char, f32, f64); - reg_cmp!(self, ">=", gte, i32, i64, u32, u64, String, char, f32, f64); - reg_cmp!(self, "==", eq, i32, i64, u32, u64, bool, String, char, f32, f64); - reg_cmp!(self, "!=", ne, i32, i64, u32, u64, bool, String, char, 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, i32, i64, u32, u64); + reg_op!(self, "|", binary_or, i8, u8, i16, u16, i32, i64, u32, u64); reg_op!(self, "|", or, bool); - reg_op!(self, "&", binary_and, i32, i64, u32, u64); + reg_op!(self, "&", binary_and, i8, u8, i16, u16, i32, i64, u32, u64); reg_op!(self, "&", and, bool); - reg_op!(self, "^", binary_xor, i32, i64, u32, u64); - reg_op!(self, "<<", left_shift, i32, i64, u32, u64); + reg_op!(self, "^", binary_xor, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op!(self, "<<", left_shift, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op!(self, ">>", right_shift, i8, u8, i16, u16); reg_op!(self, ">>", right_shift, i32, i64, u32, u64); - reg_op!(self, "%", modulo, 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, i32, i64, f32, f64); + reg_un!(self, "-", neg, i8, i16, i32, i64, f32, f64); reg_un!(self, "!", not, bool); self.register_fn("+", concat); @@ -175,17 +180,26 @@ impl Engine { // (*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 @@ -196,12 +210,14 @@ impl Engine { 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, ()); @@ -218,9 +234,11 @@ impl Engine { } } + 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); @@ -250,11 +268,17 @@ impl Engine { format!("{}{}", x, y) } - reg_func2x!(self, "+", append, String, String, i32, i64, u32, u64, f32, f64, bool, char); + 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, i32, i64, u32, u64, f32, f64, bool, char); + 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)); From 366188234bc3c7ed4d6aa71cade7ac1c233daf61 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 2 Mar 2020 23:16:19 +0800 Subject: [PATCH 30/30] Pretty-print common type names. --- src/api.rs | 10 ++++++---- src/engine.rs | 36 ++++++++++++++++++++++++++++++------ tests/decrement.rs | 2 +- tests/mismatched_op.rs | 2 +- 4 files changed, 38 insertions(+), 12 deletions(-) diff --git a/src/api.rs b/src/api.rs index 84cefab4..f1c32155 100644 --- a/src/api.rs +++ b/src/api.rs @@ -87,12 +87,14 @@ impl Engine { self.script_fns.clear(); // Clean up engine match result { - Err(EvalAltResult::Return(out, pos)) => Ok(*out - .downcast::() - .map_err(|a| EvalAltResult::ErrorMismatchOutputType((*a).type_name(), pos))?), + 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| { - EvalAltResult::ErrorMismatchOutputType((*a).type_name(), Position::eof()) + let name = self.map_type_name((*a).type_name()); + EvalAltResult::ErrorMismatchOutputType(name, Position::eof()) })?), Err(err) => Err(err), diff --git a/src/engine.rs b/src/engine.rs index 7818d96a..89544c16 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -149,6 +149,7 @@ pub struct Engine { 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, @@ -188,9 +189,10 @@ impl Engine { 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(), pos)) + b.downcast().map(|b| *b).map_err(|a| { + let name = self.map_type_name((*a).type_name()); + EvalAltResult::ErrorMismatchOutputType(name, pos) + }) }) } @@ -207,7 +209,7 @@ impl Engine { "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::>() ); @@ -266,13 +268,14 @@ impl Engine { // Return default value Ok(val.clone()) } else { - let type_names = args + 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, type_names.join(", ")), + format!("{} ({})", ident, types_list.join(", ")), pos, )) } @@ -908,12 +911,33 @@ impl Engine { } } + 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)), }; diff --git a/tests/decrement.rs b/tests/decrement.rs index 344c70ac..75d85076 100644 --- a/tests/decrement.rs +++ b/tests/decrement.rs @@ -9,7 +9,7 @@ fn test_decrement() -> Result<(), EvalAltResult> { let r = engine.eval::("let s = \"test\"; s -= \"ing\"; s"); match r { - Err(EvalAltResult::ErrorFunctionNotFound(err, _)) if err.starts_with("- ") => (), + Err(EvalAltResult::ErrorFunctionNotFound(err, _)) if err == "- (string, string)" => (), _ => panic!(), } diff --git a/tests/mismatched_op.rs b/tests/mismatched_op.rs index 314ea378..9964189b 100644 --- a/tests/mismatched_op.rs +++ b/tests/mismatched_op.rs @@ -7,7 +7,7 @@ fn test_mismatched_op() { let r = engine.eval::("60 + \"hello\""); match r { - Err(EvalAltResult::ErrorMismatchOutputType(err, _)) if err == "alloc::string::String" => (), + Err(EvalAltResult::ErrorMismatchOutputType(err, _)) if err == "string" => (), _ => panic!(), } }