diff --git a/Cargo.toml b/Cargo.toml index 54678e04..f586d1f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rhai" -version = "0.10.1" +version = "0.10.2" edition = "2018" authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"] description = "Embedded scripting for Rust" @@ -16,3 +16,4 @@ include = [ [features] debug_msgs = [] +no_stdlib = [] \ No newline at end of file diff --git a/README.md b/README.md index 6f9c78ae..c78534a0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Rhai - embedded scripting for Rust +# Rhai - Embedded Scripting for Rust Rhai is an embedded scripting language for Rust that gives you a safe and easy way to add scripting to your applications. @@ -7,11 +7,11 @@ Rhai's current feature set: * Easy integration with Rust functions and data types * Fairly efficient (1 mil iterations in 0.75 sec on my 5 year old laptop) * Low compile-time overhead (~0.6 sec debug/~3 sec release for script runner app) -* Easy-to-use language based on JS+Rust +* Easy-to-use language similar to JS+Rust * Support for overloaded functions * No additional dependencies -**Note:** Currently, the version is 0.10.0-alpha1, so the language and APIs may change before they stabilize.* +**Note:** Currently, the version is 0.10.2, so the language and API's may change before they stabilize. ## Installation @@ -19,7 +19,7 @@ You can install Rhai using crates by adding this line to your dependencies: ```toml [dependencies] -rhai = "0.10.0" +rhai = "0.10.2" ``` or simply: @@ -33,6 +33,16 @@ 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`. +## Optional Features + +### `debug_msgs` + +Print debug messages to stdout (using `println!`) related to function registrations and function calls. + +### `no_stdlib` + +Exclude the standard library of utility functions in the build, and only include the minimum necessary functionalities. + ## Related Other cool projects to check out: @@ -90,7 +100,6 @@ cargo run --example rhai_runner scripts/any_script.rhai To get going with Rhai, you create an instance of the scripting engine and then run eval. ```rust -extern crate rhai; use rhai::Engine; fn main() { @@ -108,7 +117,7 @@ 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: +If you want to repeatedly evaluate a script, you can _compile_ it first into an AST (abstract syntax tree) form: ```rust // Compile to an AST and store it for later evaluations @@ -121,12 +130,25 @@ for _ in 0..42 { } ``` -Compiling a script file into AST is also supported: +Compiling a script file is also supported: ```rust let ast = Engine::compile_file("hello_world.rhai").unwrap(); ``` +Rhai also allows you to work _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust. +You do this via `call_fn`, which takes a compiled AST (output from `compile`) and the +function call arguments: + +```rust +// Define a function in a script and compile to AST +let ast = Engine::compile("fn hello(x, y) { x.len() + y }")?; + +// Evaluate the function in the AST, passing arguments into the script as a tuple +// (beware, arguments must be of the correct types because Rhai does not have built-in type conversions) +let result: i64 = engine.call_fn("hello", ast, (&mut String::from("abc"), &mut 123_i64))?; +``` + # Values and types The following primitive types are supported natively: @@ -144,6 +166,8 @@ All types are treated strictly separate by Rhai, meaning that `i32` and `i64` an 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. +There is also a `type_of` function to detect the type of a value. + ```rust let x = 42; let y = x * 100.0; // error: cannot multiply i64 with f64 @@ -151,7 +175,16 @@ 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()); +print("c is '" + c + "' and its code is " + c.to_int()); // prints "c is 'X' and its code is 88" + +// Use 'type_of' to get the type of variables +type_of(c) == "char"; +type_of(x) == "i64"; +y.type_of() == "f64"; + +if z.type_of() == "string" { + do_something_with_strong(z); +} ``` # Working with functions @@ -159,7 +192,6 @@ print("c is '" + c + "' and its code is " + c.to_int()); Rhai's scripting engine is very lightweight. It gets its ability from the functions in your program. To call these functions, you need to register them with the scripting engine. ```rust -extern crate rhai; use rhai::{Dynamic, Engine, RegisterFn}; // Normal function @@ -209,7 +241,6 @@ Generic functions can be used in Rhai, but you'll need to register separate inst ```rust use std::fmt::Display; -extern crate rhai; use rhai::{Engine, RegisterFn}; fn showit(x: &mut T) -> () { @@ -245,7 +276,6 @@ print(to_int(123)); // what will happen? Here's an more complete example of working with Rust. First the example, then we'll break it into parts: ```rust -extern crate rhai; use rhai::{Engine, RegisterFn}; #[derive(Clone)] @@ -335,6 +365,14 @@ if let Ok(result) = engine.eval::("let x = new_ts(); x.foo()") { } ``` +`type_of` works fine with custom types and returns the name of the type: + +```rust +let x = new_ts(); +print(x.type_of()); // prints "foo::bar::TestStruct" +``` + +If you use `register_type_with_name` to register the custom type with a special pretty-print name, `type_of` will return that instead. # Getters and setters @@ -391,25 +429,40 @@ a.x.change(); // Only a COPY of 'a.x' is changed. 'a.x' is NOT changed. a.x == 500; ``` -# Maintaining state +# Initializing and 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. -In this example, we thread the same state through multiple invocations: +In this example, we first create a state with a few initialized variables, then thread the same state through multiple invocations: ```rust -extern crate rhai; use rhai::{Engine, Scope}; fn main() { let mut engine = Engine::new(); + + // First create the state let mut scope = Scope::new(); - if let Ok(_) = engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5") { } else { assert!(false); } + // Then push some initialized variables into the state + // NOTE: Remember the default numbers used by Rhai are i64 and f64. + // Better stick to them or it gets hard to work with other variables in the script. + scope.push("y".into(), 42_i64); + scope.push("z".into(), 999_i64); + // First invocation + engine.eval_with_scope::<()>(&mut scope, r" + let x = 4 + 5 - y + z; + y = 1; + ").expect("y and z not found?"); + + // Second invocation using the same state if let Ok(result) = engine.eval_with_scope::(&mut scope, "x") { - println!("result: {}", result); + println!("result: {}", result); // should print 966 } + + // Variable y is changed in the script + assert_eq!(scope.get_value::("y").unwrap(), 1); } ``` @@ -498,7 +551,7 @@ fn add(x, y) { return x + y; } -print(add(2, 3)) +print(add(2, 3)); ``` Just like in Rust, you can also use an implicit return. @@ -508,14 +561,70 @@ fn add(x, y) { x + y } -print(add(2, 3)) +print(add(2, 3)); +``` + +Remember that functions defined in script always take `Dynamic` arguments (i.e. the arguments can be of any type). + +Arguments are passed by value, so all functions are _pure_ (i.e. they never modify their arguments). + +Furthermore, functions can only be defined at the top level, never inside a block or another function. + +```rust +// Top level is OK +fn add(x, y) { + x + y +} + +// The following will not compile +fn do_addition(x) { + fn add_y(n) { // functions cannot be defined inside another function + n + y + } + + add_y(x) +} +``` + +## Return + +```rust +return; + +return 123 + 456; +``` + +## Errors and Exceptions + +```rust +if error != "" { + throw error; // 'throw' takes a string to form the exception text +} + +throw; // no exception text +``` + +All of `Engine`'s evaluation/consuming methods return `Result` with `EvalAltResult` holding error information. + +Exceptions thrown via `throw` in the script can be captured by matching `Err(EvalAltResult::ErrorRuntime(reason, position))` with the exception text captured by the `reason` parameter. + +```rust +let result = engine.eval::(&mut scope, r#" + let x = 42; + + if x > 0 { + throw x + " is too large!"; + } +"#); + +println!(result); // prints "Runtime error: 42 is too large! (line 5, position 15)" ``` ## Arrays You can create arrays of values, and then access them with numeric indices. -The following standard functions operate on arrays: +The following functions (defined in the standard library but excluded if you use the `no_stdlib` feature) operate on arrays: * `push` - inserts an element at the end * `pop` - removes the last element and returns it (() if empty) @@ -531,9 +640,20 @@ 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 +ts.list = y; // arrays can be assigned completely (by value copy) +let foo = ts.list[1]; +foo == 42; + +let foo = [1, 2, 3][0]; +foo == 1; + +fn abc() { [42, 43, 44] } + +let foo = abc()[0]; +foo == 42; + +let foo = y[0]; +foo == 1; y.push(4); // 4 elements y.push(5); // 5 elements @@ -570,7 +690,7 @@ engine.register_fn("push", ); ``` -The type of a Rhai array is `rhai::Array`. +The type of a Rhai array is `rhai::Array`. `type_of()` returns `"array"`. ## For loops @@ -597,17 +717,34 @@ a.x = 500; a.update(); ``` +## Numbers + +```rust +let x = 123; // i64 +let x = 123.4; // f64 +let x = 123_456_789; // separators can be put anywhere inside the number + +let x = 0x12abcd; // i64 in hex +let x = 0o777; // i64 in oct +let x = 0b1010_1111; // i64 in binary +``` + +Conversion functions (defined in the standard library but excluded if you use the `no_stdlib` feature): + +* `to_int` - converts an `f32` or `f64` to `i64` +* `to_float` - converts an integer type to `f64` + ## Strings and Chars ```rust let name = "Bob"; let middle_initial = 'C'; -let last = 'Davis'; +let last = "Davis"; let full_name = name + " " + middle_initial + ". " + last; full_name == "Bob C. Davis"; -// String building with different types +// String building with different types (not available if 'no_stdlib' features is used) let age = 42; let record = full_name + ": age " + age; record == "Bob C. Davis: age 42"; @@ -616,16 +753,27 @@ record == "Bob C. Davis: age 42"; 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 +ts.s = record; + +let c = ts.s[4]; +c == 'C'; + +let c = "foo"[0]; +c == 'f'; + +let c = ("foo" + "bar")[5]; +c == 'r'; + +// Escape sequences in strings +record += " \u2764\n"; // escape sequence of '❤' in Unicode +record == "Bob C. Davis: age 42 ❤\n"; // '\n' = new-line // Unlike Rust, Rhai strings can be modified -record[4] = 'Z'; -record == "Bob Z. Davis: age 42"; +record[4] = '\x58'; // 0x58 = 'X' +record == "Bob X. Davis: age 42 ❤\n"; ``` -The following standard functions operate on strings: +The following standard functions (defined in the standard library but excluded if you use the `no_stdlib` feature) 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 @@ -641,6 +789,7 @@ full_name.len() == 14; full_name.trim(); full_name.len() == 12; +full_name == "Bob C. Davis"; full_name.pad(15, '$'); full_name.len() == 15; @@ -674,8 +823,17 @@ debug("world!"); // prints "world!" to stdout using debug formatting ```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)); +engine.on_print(|x| println!("hello: {}", x)); +engine.on_debug(|x| println!("DEBUG: {}", x)); + +// Redirect logging output to somewhere else +let mut log: Vec = Vec::new(); +engine.on_print(|x| log.push(format!("log: {}", x))); +engine.on_debug(|x| log.push(format!("DEBUG: {}", x))); + : + eval script + : +println!("{:?}", log); // 'log' captures all the 'print' and 'debug' results. ``` ## Comments diff --git a/TODO b/TODO deleted file mode 100644 index 8ae9f602..00000000 --- a/TODO +++ /dev/null @@ -1,7 +0,0 @@ -pre 1.0: - - basic threads - - stdlib -1.0: - - decide on postfix/prefix operators - - advanced threads + actors - - more literals diff --git a/src/any.rs b/src/any.rs index c3c969cb..c53c8c74 100644 --- a/src/any.rs +++ b/src/any.rs @@ -1,14 +1,21 @@ -use std::any::{type_name, Any as StdAny, TypeId}; +use std::any::{type_name, TypeId}; use std::fmt; +/// An raw value of any type. pub type Variant = dyn Any; + +/// A boxed dynamic type containing any value. pub type Dynamic = Box; -pub trait Any: StdAny { +/// A trait covering any type. +pub trait Any: std::any::Any { + /// Get the `TypeId` of this type. fn type_id(&self) -> TypeId; - fn type_name(&self) -> String; + /// Get the name of this type. + fn type_name(&self) -> &'static str; + /// Convert into `Dynamic`. fn into_dynamic(&self) -> Dynamic; /// This type may only be implemented by `rhai`. @@ -16,20 +23,15 @@ pub trait Any: StdAny { fn _closed(&self) -> _Private; } -impl Any for T -where - T: Clone + StdAny + ?Sized, -{ - #[inline] +impl Any for T { fn type_id(&self) -> TypeId { TypeId::of::() } - fn type_name(&self) -> String { - type_name::().to_string() + fn type_name(&self) -> &'static str { + type_name::() } - #[inline] fn into_dynamic(&self) -> Dynamic { Box::new(self.clone()) } @@ -40,20 +42,16 @@ where } impl Variant { - //#[inline] - // fn into_dynamic(&self) -> Box { - // Any::into_dynamic(self) - // } - #[inline] - pub fn is(&self) -> bool { + /// Is this `Variant` a specific type? + pub(crate) fn is(&self) -> bool { let t = TypeId::of::(); let boxed = ::type_id(self); t == boxed } - #[inline] - pub fn downcast_ref(&self) -> Option<&T> { + /// Get a reference of a specific type to the `Variant`. + pub(crate) fn downcast_ref(&self) -> Option<&T> { if self.is::() { unsafe { Some(&*(self as *const Variant as *const T)) } } else { @@ -61,8 +59,8 @@ impl Variant { } } - #[inline] - pub fn downcast_mut(&mut self) -> Option<&mut T> { + /// Get a mutable reference of a specific type to the `Variant`. + pub(crate) fn downcast_mut(&mut self) -> Option<&mut T> { if self.is::() { unsafe { Some(&mut *(self as *mut Variant as *mut T)) } } else { @@ -71,23 +69,40 @@ impl Variant { } } -impl Clone for Dynamic { - fn clone(&self) -> Self { - Any::into_dynamic(self.as_ref()) - } -} - impl fmt::Debug for Variant { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.pad("?") } } +impl Clone for Dynamic { + fn clone(&self) -> Self { + Any::into_dynamic(self.as_ref()) + } +} + +/// An extension trait that allows down-casting a `Dynamic` value to a specific type. pub trait AnyExt: Sized { + /// Get a copy of a `Dynamic` value as a specific type. fn downcast(self) -> Result, Self>; + + /// This type may only be implemented by `rhai`. + #[doc(hidden)] + fn _closed(&self) -> _Private; } impl AnyExt for Dynamic { + /// Get a copy of the `Dynamic` value as a specific type. + /// + /// # Example + /// + /// ```rust + /// use rhai::{Dynamic, Any, AnyExt}; + /// + /// let x: Dynamic = 42_u32.into_dynamic(); + /// + /// assert_eq!(*x.downcast::().unwrap(), 42); + /// ``` fn downcast(self) -> Result, Self> { if self.is::() { unsafe { @@ -98,9 +113,13 @@ impl AnyExt for Dynamic { Err(self) } } + + fn _closed(&self) -> _Private { + _Private + } } -/// Private type which ensures that `rhai::Any` can only +/// Private type which ensures that `rhai::Any` and `rhai::AnyExt` can only /// be implemented by this crate. #[doc(hidden)] pub struct _Private; diff --git a/src/api.rs b/src/api.rs index f1c32155..b27378f2 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,9 +1,96 @@ use crate::any::{Any, AnyExt, Dynamic}; -use crate::engine::{Engine, EvalAltResult, FnIntExt, FnSpec, Scope}; -use crate::parser::{lex, parse, ParseError, Position, AST}; +use crate::call::FuncArgs; +use crate::engine::{Engine, FnAny, FnIntExt, FnSpec}; +use crate::error::ParseError; +use crate::fn_register::RegisterFn; +use crate::parser::{lex, parse, Position, AST}; +use crate::result::EvalAltResult; +use crate::scope::Scope; +use std::any::TypeId; use std::sync::Arc; -impl Engine { +impl<'a> Engine<'a> { + pub(crate) fn register_fn_raw( + &mut self, + fn_name: &str, + args: Option>, + f: Box, + ) { + debug_println!( + "Register function: {} for {} parameter(s)", + fn_name, + if let Some(a) = &args { + format!("{}", a.len()) + } else { + "no".to_string() + } + ); + + let spec = FnSpec { + name: fn_name.to_string().into(), + args, + }; + + self.external_functions + .insert(spec, Arc::new(FnIntExt::Ext(f))); + } + + /// Register a custom type for use with the `Engine`. + /// The type must be `Clone`. + pub fn register_type(&mut self) { + self.register_type_with_name::(std::any::type_name::()); + } + + /// Register a custom type for use with the `Engine` with a name for the `type_of` function. + /// The type must be `Clone`. + pub fn register_type_with_name(&mut self, type_name: &str) { + // Add the pretty-print type name into the map + self.type_names.insert( + std::any::type_name::().to_string(), + type_name.to_string(), + ); + } + + /// Register an iterator adapter for a type with the `Engine`. + pub fn register_iterator(&mut self, f: F) + where + F: Fn(&Dynamic) -> Box> + 'static, + { + self.type_iterators.insert(TypeId::of::(), Arc::new(f)); + } + + /// Register a getter function for a member of a registered type with the `Engine`. + pub fn register_get( + &mut self, + name: &str, + callback: impl Fn(&mut T) -> U + 'static, + ) { + let get_name = "get$".to_string() + name; + self.register_fn(&get_name, callback); + } + + /// Register a setter function for a member of a registered type with the `Engine`. + pub fn register_set( + &mut self, + name: &str, + callback: impl Fn(&mut T, U) -> () + 'static, + ) { + let set_name = "set$".to_string() + name; + self.register_fn(&set_name, callback); + } + + /// Shorthand for registering both getter and setter functions + /// of a registered type with the `Engine`. + pub fn register_get_set( + &mut self, + name: &str, + 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); + } + /// Compile a string into an AST pub fn compile(input: &str) -> Result { let tokens = lex(input); @@ -16,12 +103,12 @@ impl Engine { use std::io::prelude::*; let mut f = File::open(filename) - .map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err))?; + .map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err))?; let mut contents = String::new(); f.read_to_string(&mut contents) - .map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err)) + .map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err)) .and_then(|_| Self::compile(&contents).map_err(EvalAltResult::ErrorParsing)) } @@ -31,12 +118,12 @@ impl Engine { use std::io::prelude::*; let mut f = File::open(filename) - .map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err))?; + .map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err))?; let mut contents = String::new(); f.read_to_string(&mut contents) - .map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err)) + .map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err)) .and_then(|_| self.eval::(&contents)) } @@ -68,40 +155,44 @@ impl Engine { scope: &mut Scope, ast: &AST, ) -> Result { - let AST(os, fns) = ast; + let AST(statements, functions) = ast; - fns.iter().for_each(|f| { - self.script_fns.insert( + functions.iter().for_each(|f| { + self.script_functions.insert( FnSpec { - ident: f.name.clone(), + name: f.name.clone().into(), args: None, }, Arc::new(FnIntExt::Int(f.clone())), ); }); - let result = os + let result = statements .iter() - .try_fold(Box::new(()) as Dynamic, |_, o| self.eval_stmt(scope, o)); + .try_fold(().into_dynamic(), |_, o| self.eval_stmt(scope, o)); - self.script_fns.clear(); // Clean up engine + self.script_functions.clear(); // Clean up engine match result { - Err(EvalAltResult::Return(out, pos)) => Ok(*out.downcast::().map_err(|a| { - let name = self.map_type_name((*a).type_name()); - EvalAltResult::ErrorMismatchOutputType(name, pos) - })?), + Err(EvalAltResult::Return(out, pos)) => out.downcast::().map(|v| *v).map_err(|a| { + EvalAltResult::ErrorMismatchOutputType( + self.map_type_name((*a).type_name()).to_string(), + pos, + ) + }), - Ok(out) => Ok(*out.downcast::().map_err(|a| { - let name = self.map_type_name((*a).type_name()); - EvalAltResult::ErrorMismatchOutputType(name, Position::eof()) - })?), + Ok(out) => out.downcast::().map(|v| *v).map_err(|a| { + EvalAltResult::ErrorMismatchOutputType( + self.map_type_name((*a).type_name()).to_string(), + Position::eof(), + ) + }), Err(err) => Err(err), } } - /// Evaluate a file, but only return errors, if there are any. + /// Evaluate a file, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need /// to keep track of possible errors pub fn consume_file(&mut self, filename: &str) -> Result<(), EvalAltResult> { @@ -109,23 +200,23 @@ impl Engine { use std::io::prelude::*; let mut f = File::open(filename) - .map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err))?; + .map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err))?; let mut contents = String::new(); f.read_to_string(&mut contents) - .map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err)) + .map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err)) .and_then(|_| self.consume(&contents)) } - /// Evaluate a string, but only return errors, if there are any. + /// Evaluate a string, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need /// 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. + /// Evaluate a string with own scope, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need /// to keep track of possible errors pub fn consume_with_scope( @@ -137,40 +228,116 @@ impl Engine { parse(&mut tokens.peekable()) .map_err(|err| EvalAltResult::ErrorParsing(err)) - .and_then(|AST(ref os, ref fns)| { - for f in fns { - // FIX - Why are functions limited to 6 parameters? - if f.params.len() > 6 { - return Ok(()); - } - - self.script_fns.insert( + .and_then(|AST(ref statements, ref functions)| { + for f in functions { + self.script_functions.insert( FnSpec { - ident: f.name.clone(), + name: f.name.clone().into(), args: None, }, Arc::new(FnIntExt::Int(f.clone())), ); } - let val = os + let val = statements .iter() - .try_fold(Box::new(()) as Dynamic, |_, o| self.eval_stmt(scope, o)) + .try_fold(().into_dynamic(), |_, o| self.eval_stmt(scope, o)) .map(|_| ()); - self.script_fns.clear(); // Clean up engine + self.script_functions.clear(); // Clean up engine val }) } - /// Overrides `on_print` - pub fn on_print(&mut self, callback: impl Fn(&str) + 'static) { + /// Call a script function defined in a compiled AST. + /// + /// # Example + /// + /// ```rust + /// # use rhai::{Engine, EvalAltResult}; + /// # fn main() -> Result<(), EvalAltResult> { + /// let mut engine = Engine::new(); + /// + /// let ast = Engine::compile("fn add(x, y) { x.len() + y }")?; + /// + /// let result: i64 = engine.call_fn("add", ast, (&mut String::from("abc"), &mut 123_i64))?; + /// + /// assert_eq!(result, 126); + /// # Ok(()) + /// # } + /// ``` + pub fn call_fn<'f, A: FuncArgs<'f>, T: Any + Clone>( + &mut self, + name: &str, + ast: AST, + args: A, + ) -> Result { + let pos = Default::default(); + + ast.1.iter().for_each(|f| { + self.script_functions.insert( + FnSpec { + name: f.name.clone().into(), + args: None, + }, + Arc::new(FnIntExt::Int(f.clone())), + ); + }); + + let result = self + .call_fn_raw(name, args.into_vec(), None, pos) + .and_then(|b| { + b.downcast().map(|b| *b).map_err(|a| { + EvalAltResult::ErrorMismatchOutputType( + self.map_type_name((*a).type_name()).into(), + pos, + ) + }) + }); + + self.script_functions.clear(); // Clean up engine + + result + } + + /// Override default action of `print` (print to stdout using `println!`) + /// + /// # Example + /// + /// ```rust + /// # use rhai::Engine; + /// let mut result = String::from(""); + /// { + /// let mut engine = Engine::new(); + /// + /// // Override action of 'print' function + /// engine.on_print(|s| result.push_str(s)); + /// engine.consume("print(40 + 2);").unwrap(); + /// } + /// assert_eq!(result, "42"); + /// ``` + pub fn on_print(&mut self, callback: impl FnMut(&str) + 'a) { self.on_print = Box::new(callback); } - /// Overrides `on_debug` - pub fn on_debug(&mut self, callback: impl Fn(&str) + 'static) { + /// Override default action of `debug` (print to stdout using `println!`) + /// + /// # Example + /// + /// ```rust + /// # use rhai::Engine; + /// let mut result = String::from(""); + /// { + /// let mut engine = Engine::new(); + /// + /// // Override action of 'debug' function + /// engine.on_debug(|s| result.push_str(s)); + /// engine.consume(r#"debug("hello");"#).unwrap(); + /// } + /// assert_eq!(result, "\"hello\""); + /// ``` + pub fn on_debug(&mut self, callback: impl FnMut(&str) + 'a) { self.on_debug = Box::new(callback); } } diff --git a/src/builtin.rs b/src/builtin.rs index da23abe2..ba175ea0 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -1,4 +1,6 @@ -use crate::{any::Any, Array, Dynamic, Engine, RegisterDynamicFn, RegisterFn}; +use crate::any::Any; +use crate::engine::{Array, Engine}; +use crate::fn_register::RegisterFn; use std::fmt::{Debug, Display}; use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub}; @@ -34,6 +36,7 @@ macro_rules! reg_func1 { ) } +#[cfg(not(feature = "no_stdlib"))] macro_rules! reg_func2x { ($self:expr, $x:expr, $op:expr, $v:ty, $r:ty, $( $y:ty ),*) => ( $( @@ -42,6 +45,7 @@ macro_rules! reg_func2x { ) } +#[cfg(not(feature = "no_stdlib"))] macro_rules! reg_func2y { ($self:expr, $x:expr, $op:expr, $v:ty, $r:ty, $( $y:ty ),*) => ( $( @@ -50,6 +54,7 @@ macro_rules! reg_func2y { ) } +#[cfg(not(feature = "no_stdlib"))] macro_rules! reg_func3 { ($self:expr, $x:expr, $op:expr, $v:ty, $w:ty, $r:ty, $( $y:ty ),*) => ( $( @@ -58,9 +63,9 @@ macro_rules! reg_func3 { ) } -impl Engine { - /// Register the built-in library. - pub(crate) fn register_builtins(&mut self) { +impl Engine<'_> { + /// Register the core built-in library. + pub(crate) fn register_core_lib(&mut self) { fn add(x: T, y: T) -> ::Output { x + y } @@ -103,9 +108,6 @@ impl Engine { 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 } @@ -133,9 +135,6 @@ impl Engine { fn pow_f64_i64(x: f64, y: i64) -> f64 { x.powi(y as i32) } - fn unit_eq(_a: (), _b: ()) -> bool { - true - } reg_op!(self, "+", add, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64); reg_op!(self, "-", sub, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64); @@ -171,13 +170,51 @@ impl Engine { reg_un!(self, "-", neg, i8, i16, i32, i64, f32, f64); reg_un!(self, "!", not, bool); - self.register_fn("+", concat); - self.register_fn("==", unit_eq); + self.register_fn("+", |x: String, y: String| x + &y); // String + String + self.register_fn("==", |_: (), _: ()| true); // () == () - // 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 print and debug + fn print_debug(x: T) -> String { + format!("{:?}", x) + } + fn print(x: T) -> String { + format!("{}", x) + } + + reg_func1!(self, "print", print, String, i8, u8, i16, u16); + reg_func1!(self, "print", print, String, i32, i64, u32, u64); + reg_func1!(self, "print", print, String, f32, f64, bool, char, String); + reg_func1!(self, "print", print_debug, String, Array); + self.register_fn("print", || "".to_string()); + self.register_fn("print", |_: ()| "".to_string()); + + reg_func1!(self, "debug", print_debug, String, i8, u8, i16, u16); + reg_func1!(self, "debug", print_debug, String, i32, i64, u32, u64); + reg_func1!(self, "debug", print_debug, String, f32, f64, bool, char); + reg_func1!(self, "debug", print_debug, String, String, Array, ()); + + // Register array iterator + self.register_iterator::(|a| { + Box::new(a.downcast_ref::().unwrap().clone().into_iter()) + }); + + // Register range function + self.register_iterator::, _>(|a| { + Box::new( + a.downcast_ref::>() + .unwrap() + .clone() + .map(|n| n.into_dynamic()), + ) + }); + + self.register_fn("range", |i1: i64, i2: i64| (i1..i2)); + } + + /// Register the built-in library. + #[cfg(not(feature = "no_stdlib"))] + pub(crate) fn register_stdlib(&mut self) { + use crate::fn_register::RegisterDynamicFn; // Register conversion functions self.register_fn("to_float", |x: i8| x as f64); @@ -202,26 +239,6 @@ impl Engine { self.register_fn("to_int", |ch: char| ch as i64); - // Register print and debug - fn print_debug(x: T) -> String { - format!("{:?}", x) - } - fn print(x: T) -> String { - format!("{}", x) - } - - reg_func1!(self, "print", print, String, i8, u8, i16, u16); - reg_func1!(self, "print", print, String, i32, i64, u32, u64); - reg_func1!(self, "print", print, String, f32, f64, bool, char, String); - reg_func1!(self, "print", print_debug, String, Array); - self.register_fn("print", || "".to_string()); - self.register_fn("print", |_: ()| "".to_string()); - - reg_func1!(self, "debug", print_debug, String, i8, u8, i16, u16); - reg_func1!(self, "debug", print_debug, String, i32, i64, u32, u64); - reg_func1!(self, "debug", print_debug, String, f32, f64, bool, char); - reg_func1!(self, "debug", print_debug, String, String, Array, ()); - // Register array utility functions fn push(list: &mut Array, item: T) { list.push(Box::new(item)); @@ -244,13 +261,12 @@ impl Engine { 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_dynamic_fn("pop", |list: &mut Array| { + list.pop().unwrap_or_else(|| ().into_dynamic()) + }); + self.register_dynamic_fn("shift", |list: &mut Array| match list.len() { + 0 => ().into_dynamic(), + _ => list.remove(0), }); self.register_fn("len", |list: &mut Array| list.len() as i64); self.register_fn("clear", |list: &mut Array| list.clear()); @@ -315,22 +331,5 @@ impl Engine { chars.iter().for_each(|&ch| s.push(ch)); } }); - - // Register array iterator - self.register_iterator::(|a| { - Box::new(a.downcast_ref::().unwrap().clone().into_iter()) - }); - - // Register range function - self.register_iterator::, _>(|a| { - Box::new( - a.downcast_ref::>() - .unwrap() - .clone() - .map(|n| Box::new(n) as Dynamic), - ) - }); - - self.register_fn("range", |i1: i64, i2: i64| (i1..i2)); } } diff --git a/src/call.rs b/src/call.rs index d7565cd9..b287f2bf 100644 --- a/src/call.rs +++ b/src/call.rs @@ -3,13 +3,27 @@ use crate::any::{Any, Variant}; -pub trait FunArgs<'a> { +/// Trait that represent arguments to a function call. +pub trait FuncArgs<'a> { + /// Convert to a `Vec` of `Variant` arguments. fn into_vec(self) -> Vec<&'a mut Variant>; } +impl<'a> FuncArgs<'a> for Vec<&'a mut Variant> { + fn into_vec(self) -> Self { + self + } +} + +impl<'a, T: Any> FuncArgs<'a> for &'a mut Vec { + fn into_vec(self) -> Vec<&'a mut Variant> { + self.iter_mut().map(|x| x as &mut Variant).collect() + } +} + macro_rules! impl_args { ($($p:ident),*) => { - impl<'a, $($p: Any + Clone),*> FunArgs<'a> for ($(&'a mut $p,)*) + impl<'a, $($p: Any + Clone),*> FuncArgs<'a> for ($(&'a mut $p,)*) { fn into_vec(self) -> Vec<&'a mut Variant> { let ($($p,)*) = self; diff --git a/src/engine.rs b/src/engine.rs index 89544c16..b00db1f9 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,137 +1,41 @@ use std::any::TypeId; +use std::borrow::Cow; use std::cmp::{PartialEq, PartialOrd}; use std::collections::HashMap; -use std::error::Error; 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, Position, Stmt}; +use crate::parser::{Expr, FnDef, Position, Stmt}; +use crate::result::EvalAltResult; +use crate::scope::Scope; +/// An dynamic array of `Dynamic` values. pub type Array = Vec; + pub type FnCallArgs<'a> = Vec<&'a mut Variant>; -#[derive(Debug)] -pub enum EvalAltResult { - ErrorParsing(ParseError), - ErrorFunctionNotFound(String, Position), - ErrorFunctionArgsMismatch(String, usize, Position), - ErrorBooleanArgMismatch(String, Position), - ErrorArrayBounds(usize, i64, Position), - ErrorStringBounds(usize, i64, Position), - ErrorIndexing(Position), - ErrorIndexExpr(Position), - ErrorIfGuard(Position), - ErrorFor(Position), - ErrorVariableNotFound(String, Position), - ErrorAssignmentToUnknownLHS(Position), - ErrorMismatchOutputType(String, Position), - ErrorCantOpenScriptFile(String, std::io::Error), - ErrorDotExpr(Position), - ErrorArithmetic(String, Position), - LoopBreak, - Return(Dynamic, Position), -} +const KEYWORD_PRINT: &'static str = "print"; +const KEYWORD_DEBUG: &'static str = "debug"; +const KEYWORD_TYPE_OF: &'static str = "type_of"; -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 => { - "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 => { - "Indexing a string expects a non-negative index" - } - Self::ErrorStringBounds(max, _, _) if *max == 0 => "Indexing of empty string", - Self::ErrorStringBounds(_, _, _) => "String index out of bounds", - Self::ErrorIfGuard(_) => "If guard expects boolean expression", - Self::ErrorFor(_) => "For loop expects array or range", - Self::ErrorVariableNotFound(_, _) => "Variable not found", - Self::ErrorAssignmentToUnknownLHS(_) => { - "Assignment to an unsupported left-hand side expression" - } - 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", - } - } - - fn cause(&self) -> Option<&dyn Error> { - None - } -} - -impl std::fmt::Display for EvalAltResult { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let desc = self.description(); - - match self { - Self::ErrorFunctionNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos), - Self::ErrorVariableNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos), - Self::ErrorIndexing(pos) => write!(f, "{} ({})", desc, pos), - Self::ErrorIndexExpr(pos) => write!(f, "{} ({})", desc, pos), - Self::ErrorIfGuard(pos) => write!(f, "{} ({})", desc, pos), - Self::ErrorFor(pos) => write!(f, "{} ({})", desc, pos), - Self::ErrorAssignmentToUnknownLHS(pos) => write!(f, "{} ({})", desc, pos), - Self::ErrorMismatchOutputType(s, pos) => write!(f, "{}: {} ({})", desc, s, pos), - Self::ErrorDotExpr(pos) => write!(f, "{} ({})", desc, pos), - Self::ErrorArithmetic(s, pos) => write!(f, "{}: {} ({})", desc, s, pos), - Self::LoopBreak => write!(f, "{}", desc), - Self::Return(_, pos) => write!(f, "{} ({})", desc, pos), - Self::ErrorCantOpenScriptFile(filename, err) => { - write!(f, "{} '{}': {}", desc, filename, err) - } - Self::ErrorParsing(p) => write!(f, "Syntax error: {}", p), - Self::ErrorFunctionArgsMismatch(fun, n, pos) => { - write!(f, "Function '{}' expects {} argument(s) ({})", fun, n, pos) - } - Self::ErrorBooleanArgMismatch(op, pos) => { - write!(f, "{} operator expects boolean operands ({})", op, pos) - } - Self::ErrorArrayBounds(_, index, pos) if *index < 0 => { - write!(f, "{}: {} < 0 ({})", desc, index, pos) - } - Self::ErrorArrayBounds(max, _, pos) if *max == 0 => write!(f, "{} ({})", desc, pos), - Self::ErrorArrayBounds(max, index, pos) => { - write!(f, "{} (max {}): {} ({})", desc, max - 1, index, pos) - } - Self::ErrorStringBounds(_, index, pos) if *index < 0 => { - write!(f, "{}: {} < 0 ({})", desc, index, pos) - } - Self::ErrorStringBounds(max, _, pos) if *max == 0 => write!(f, "{} ({})", desc, pos), - Self::ErrorStringBounds(max, index, pos) => { - write!(f, "{} (max {}): {} ({})", desc, max - 1, index, pos) - } - } - } +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +enum VariableType { + Array, + String, + Expression, } #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] -pub struct FnSpec { - pub ident: String, +pub struct FnSpec<'a> { + pub name: Cow<'a, str>, pub args: Option>, } type IteratorFn = dyn Fn(&Dynamic) -> Box>; -/// Rhai's engine type. This is what you use to run Rhai scripts +/// Rhai main scripting engine. /// /// ```rust -/// extern crate rhai; /// use rhai::Engine; /// /// fn main() { @@ -142,17 +46,17 @@ type IteratorFn = dyn Fn(&Dynamic) -> Box>; /// } /// } /// ``` -pub struct Engine { +pub struct Engine<'a> { /// A hashmap containing all compiled functions known to the engine - fns: HashMap>, + pub(crate) external_functions: HashMap, Arc>, /// A hashmap containing all script-defined functions - pub(crate) script_fns: HashMap>, + pub(crate) script_functions: HashMap, Arc>, /// A hashmap containing all iterators known to the engine - type_iterators: HashMap>, - type_names: HashMap, + pub(crate) type_iterators: HashMap>, + pub(crate) type_names: HashMap, - pub(crate) on_print: Box, - pub(crate) on_debug: Box, + pub(crate) on_print: Box, + pub(crate) on_debug: Box, } pub enum FnIntExt { @@ -162,100 +66,77 @@ pub enum FnIntExt { pub type FnAny = dyn Fn(FnCallArgs, Position) -> Result; -/// A type containing information about current scope. -/// Useful for keeping state between `Engine` runs -/// -/// ```rust -/// use rhai::{Engine, Scope}; -/// -/// let mut engine = Engine::new(); -/// let mut my_scope = Scope::new(); -/// -/// assert!(engine.eval_with_scope::<()>(&mut my_scope, "let x = 5;").is_ok()); -/// assert_eq!(engine.eval_with_scope::(&mut my_scope, "x + 1").unwrap(), 6); -/// ``` -/// -/// Between runs, `Engine` only remembers functions when not using own `Scope`. -pub type Scope = Vec<(String, Dynamic)>; - -impl Engine { - pub fn call_fn<'a, I, A, T>(&self, ident: I, args: A) -> Result - where - I: Into, - A: FunArgs<'a>, - T: Any + Clone, - { - 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| { - let name = self.map_type_name((*a).type_name()); - EvalAltResult::ErrorMismatchOutputType(name, pos) - }) - }) - } - +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, + pub(crate) fn call_fn_raw( + &mut self, + fn_name: &str, args: FnCallArgs, def_value: Option<&Dynamic>, pos: Position, ) -> Result { debug_println!( - "Trying to call function {:?} with args {:?}", - ident, + "Calling function: {} ({})", + fn_name, args.iter() - .map(|x| { self.map_type_name((**x).type_name()) }) + .map(|x| (*x).type_name()) + .map(|name| self.map_type_name(name)) .collect::>() + .join(", ") ); let mut spec = FnSpec { - ident: ident.clone(), + name: fn_name.into(), args: None, }; // First search in script-defined functions (can override built-in), // then in built-in's - let fn_def = self.script_fns.get(&spec).or_else(|| { - spec.args = Some(args.iter().map(|a| Any::type_id(&**a)).collect()); - self.fns.get(&spec) - }); + let fn_def = self + .script_functions + .get(&spec) + .or_else(|| { + spec.args = Some(args.iter().map(|a| Any::type_id(&**a)).collect()); + self.external_functions.get(&spec) + }) + .map(|f| f.clone()); if let Some(f) = fn_def { - match **f { + match *f { FnIntExt::Ext(ref f) => { - let r = f(args, pos); + let r = f(args, pos)?; - if r.is_err() { - return r; - } - - let callback = match ident.as_str() { - "print" => &self.on_print, - "debug" => &self.on_debug, - _ => return r, + let callback = match spec.name.as_ref() { + KEYWORD_PRINT => self.on_print.as_mut(), + KEYWORD_DEBUG => self.on_debug.as_mut(), + _ => return Ok(r), }; - Ok(Box::new(callback( - r.unwrap() - .downcast::() - .map(|x| *x) - .unwrap_or("error: not a string".into()) - .as_str(), - ))) + Ok(callback( + &r.downcast::() + .map(|s| *s) + .unwrap_or("error: not a string".into()), + ) + .into_dynamic()) } FnIntExt::Int(ref f) => { + if f.params.len() != args.len() { + return Err(EvalAltResult::ErrorFunctionArgsMismatch( + spec.name.into(), + f.params.len(), + args.len(), + pos, + )); + } + let mut scope = Scope::new(); scope.extend( f.params .iter() .cloned() - .zip(args.iter().map(|x| (&**x).into_dynamic())), + .zip(args.iter().map(|x| (*x).into_dynamic())), ); match self.eval_stmt(&mut scope, &*f.body) { @@ -264,83 +145,31 @@ impl Engine { } } } + } else if spec.name == KEYWORD_TYPE_OF && args.len() == 1 { + Ok(self + .map_type_name(args[0].type_name()) + .to_string() + .into_dynamic()) } else if let Some(val) = def_value { // Return default value Ok(val.clone()) } else { let types_list = args .iter() - .map(|x| (*(&**x).into_dynamic()).type_name()) + .map(|x| (*x).type_name()) .map(|name| self.map_type_name(name)) .collect::>(); Err(EvalAltResult::ErrorFunctionNotFound( - format!("{} ({})", ident, types_list.join(", ")), + format!("{} ({})", spec.name, types_list.join(", ")), pos, )) } } - 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 }; - - self.fns.insert(spec, Arc::new(FnIntExt::Ext(f))); - } - - /// Register a type for use with Engine. Keep in mind that - /// your type must implement Clone. - pub fn register_type(&mut self) { - // currently a no-op, exists for future extensibility - } - - /// Register an iterator adapter for a type. - pub fn register_iterator(&mut self, f: F) - where - 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: 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: 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( - &mut self, - name: &str, - 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); - } - + /// Chain-evaluate a dot setter fn get_dot_val_helper( - &self, + &mut self, scope: &mut Scope, this_ptr: &mut Variant, dot_rhs: &Expr, @@ -348,6 +177,7 @@ impl Engine { use std::iter::once; match dot_rhs { + // xxx.fn_name(args) Expr::FunctionCall(fn_name, args, def_value, pos) => { let mut args: Array = args .iter() @@ -358,158 +188,164 @@ impl Engine { .chain(args.iter_mut().map(|b| b.as_mut())) .collect(); - self.call_fn_raw(fn_name.into(), args, def_value.as_ref(), *pos) + self.call_fn_raw(fn_name, args, def_value.as_ref(), *pos) } + // xxx.id Expr::Identifier(id, pos) => { - let get_fn_name = "get$".to_string() + id; + let get_fn_name = format!("get${}", id); - self.call_fn_raw(get_fn_name, vec![this_ptr], None, *pos) + self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) } - Expr::Index(id, idx_raw, pos) => { - let idx = self - .eval_expr(scope, idx_raw)? - .downcast_ref::() - .map(|i| *i) - .ok_or(EvalAltResult::ErrorIndexExpr(idx_raw.position()))?; + // xxx.lhs[idx_expr] + Expr::Index(lhs, idx_expr) => { + let idx = self.eval_index_value(scope, idx_expr)?; - let get_fn_name = "get$".to_string() + id; - - 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, *pos)) - } else { - Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, *pos)) - } - } else if let Some(s) = (*val).downcast_mut() as Option<&mut String> { - if idx >= 0 { - s.chars() - .nth(idx as usize) - .map(|ch| Box::new(ch) as Dynamic) - .ok_or_else(|| { - EvalAltResult::ErrorStringBounds(s.chars().count(), idx, *pos) - }) - } else { - Err(EvalAltResult::ErrorStringBounds( - s.chars().count(), - idx, + let (lhs_value, _) = match lhs.as_ref() { + Expr::Identifier(id, pos) => { + let get_fn_name = format!("get${}", id); + ( + self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?, *pos, - )) + ) } - } else { - Err(EvalAltResult::ErrorIndexing(*pos)) - } + expr => return Err(EvalAltResult::ErrorDotExpr(expr.position())), + }; + + Self::get_indexed_value(lhs_value, idx, idx_expr.position()).map(|(v, _)| v) } - Expr::Dot(inner_lhs, inner_rhs) => match **inner_lhs { - Expr::Identifier(ref id, pos) => { - let get_fn_name = "get$".to_string() + id; - let value = self - .call_fn_raw(get_fn_name, vec![this_ptr], None, pos) - .and_then(|mut v| self.get_dot_val_helper(scope, v.as_mut(), inner_rhs))?; + // xxx.lhs.rhs + Expr::Dot(lhs, rhs) => match lhs.as_ref() { + // xxx.id.rhs + Expr::Identifier(id, pos) => { + let get_fn_name = format!("get${}", id); - // 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. + self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) + .and_then(|mut v| self.get_dot_val_helper(scope, v.as_mut(), rhs)) + } + // xxx.lhs[idx_expr].rhs + Expr::Index(lhs, idx_expr) => { + let idx = self.eval_index_value(scope, idx_expr)?; - Ok(value) + let (lhs_value, _) = match lhs.as_ref() { + Expr::Identifier(id, pos) => { + let get_fn_name = format!("get${}", id); + ( + self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?, + *pos, + ) + } + expr => return Err(EvalAltResult::ErrorDotExpr(expr.position())), + }; + + Self::get_indexed_value(lhs_value, idx, idx_expr.position()).and_then( + |(mut value, _)| self.get_dot_val_helper(scope, value.as_mut(), rhs), + ) } - Expr::Index(_, _, pos) => { - // TODO - Handle Expr::Index for these scenarios: - // - // let x = obj.prop[2].x; - // obj.prop[3] = 42; - // - Err(EvalAltResult::ErrorDotExpr(pos)) - } - _ => Err(EvalAltResult::ErrorDotExpr(inner_lhs.position())), + // Syntax error + _ => Err(EvalAltResult::ErrorDotExpr(lhs.position())), }, + // Syntax error _ => Err(EvalAltResult::ErrorDotExpr(dot_rhs.position())), } } - fn search_scope<'a, T>( - scope: &'a mut Scope, + /// Search for a variable within the scope, returning its value and index inside the Scope + fn search_scope( + scope: &Scope, id: &str, - map: impl FnOnce(&'a mut Variant) -> Result, + map: impl FnOnce(Dynamic) -> Result, begin: Position, ) -> Result<(usize, T), EvalAltResult> { scope - .iter_mut() - .enumerate() - .rev() - .find(|&(_, &mut (ref name, _))| id == name) + .get(id) .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.into(), begin)) - .and_then(move |(idx, &mut (_, ref mut val))| map(val.as_mut()).map(|val| (idx, val))) + .and_then(move |(idx, _, val)| map(val).map(|v| (idx, v))) } - fn indexed_value( - &self, + /// Evaluate the value of an index (must evaluate to i64) + fn eval_index_value( + &mut self, scope: &mut Scope, - id: &str, - idx: &Expr, - begin: Position, - ) -> Result<(bool, usize, usize, Dynamic), EvalAltResult> { - let idx = *self - .eval_expr(scope, idx)? + idx_expr: &Expr, + ) -> Result { + self.eval_expr(scope, idx_expr)? .downcast::() - .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; - - if idx >= 0 { - arr.get(idx as usize) - .cloned() - .ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr.len(), idx, begin)) - } else { - Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, begin)) - } - } else if let Some(s) = (*val).downcast_mut() as Option<&mut String> { - is_array = false; - - if idx >= 0 { - s.chars() - .nth(idx as usize) - .map(|ch| Box::new(ch) as Dynamic) - .ok_or_else(|| { - EvalAltResult::ErrorStringBounds(s.chars().count(), idx, begin) - }) - } else { - Err(EvalAltResult::ErrorStringBounds( - s.chars().count(), - idx, - begin, - )) - } - } else { - Err(EvalAltResult::ErrorIndexing(begin)) - } - }, - begin, - ) - .map(|(idx_sc, val)| (is_array, idx_sc, idx as usize, val)) + .map(|v| *v) + .map_err(|_| EvalAltResult::ErrorIndexExpr(idx_expr.position())) } + /// Get the value at the indexed position of a base type + fn get_indexed_value( + val: Dynamic, + idx: i64, + pos: Position, + ) -> Result<(Dynamic, VariableType), EvalAltResult> { + if val.is::() { + let arr = val.downcast::().expect("array expected"); + + if idx >= 0 { + arr.get(idx as usize) + .cloned() + .map(|v| (v, VariableType::Array)) + .ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr.len(), idx, pos)) + } else { + Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, pos)) + } + } else if val.is::() { + let s = val.downcast::().expect("string expected"); + + if idx >= 0 { + s.chars() + .nth(idx as usize) + .map(|ch| (ch.into_dynamic(), VariableType::String)) + .ok_or_else(|| EvalAltResult::ErrorStringBounds(s.chars().count(), idx, pos)) + } else { + Err(EvalAltResult::ErrorStringBounds( + s.chars().count(), + idx, + pos, + )) + } + } else { + Err(EvalAltResult::ErrorIndexingType(pos)) + } + } + + /// Evaluate an index expression + fn eval_index_expr( + &mut self, + scope: &mut Scope, + lhs: &Expr, + idx_expr: &Expr, + ) -> Result<(VariableType, Option<(String, usize)>, usize, Dynamic), EvalAltResult> { + let idx = self.eval_index_value(scope, idx_expr)?; + + match lhs { + // id[idx_expr] + Expr::Identifier(id, _) => Self::search_scope( + scope, + &id, + |val| Self::get_indexed_value(val, idx, idx_expr.position()), + lhs.position(), + ) + .map(|(src_idx, (val, source_type))| { + (source_type, Some((id.clone(), src_idx)), idx as usize, val) + }), + + // (expr)[idx_expr] + expr => Self::get_indexed_value(self.eval_expr(scope, expr)?, idx, idx_expr.position()) + .map(|(val, _)| (VariableType::Expression, None, idx as usize, val)), + } + } + + /// Replace a character at an index position in a mutable string fn str_replace_char(s: &mut String, idx: usize, new_ch: char) { // The new character - let ch = s.chars().nth(idx).unwrap(); + let ch = s.chars().nth(idx).expect("string index out of bounds"); // See if changed - if so, update the String if ch == new_ch { @@ -523,217 +359,239 @@ impl Engine { chars.iter().for_each(|&ch| s.push(ch)); } + /// Update the value at an index position in a variable inside the scope + fn update_indexed_variable_in_scope( + source_type: VariableType, + scope: &mut Scope, + id: &str, + src_idx: usize, + idx: usize, + val: Dynamic, + ) -> Option { + match source_type { + VariableType::Array => { + let arr = scope.get_mut_by_type::(id, src_idx); + Some((arr[idx as usize] = val).into_dynamic()) + } + + VariableType::String => { + let s = scope.get_mut_by_type::(id, src_idx); + // Value must be a character + let ch = *val + .downcast::() + .expect("char value expected to update an index position in a string"); + Some(Self::str_replace_char(s, idx as usize, ch).into_dynamic()) + } + + _ => None, + } + } + + /// Evaluate a dot chain getter fn get_dot_val( - &self, + &mut self, scope: &mut Scope, dot_lhs: &Expr, dot_rhs: &Expr, ) -> Result { match dot_lhs { + // xxx.??? Expr::Identifier(id, pos) => { - let (sc_idx, mut target) = - Self::search_scope(scope, id, |x| Ok(x.into_dynamic()), *pos)?; + let (sc_idx, mut target) = Self::search_scope(scope, id, Ok, *pos)?; let value = self.get_dot_val_helper(scope, target.as_mut(), dot_rhs); // In case the expression mutated `target`, we need to reassign it because // of the above `clone`. - scope[sc_idx].1 = target; + *scope.get_mut(id, sc_idx) = target; value } - Expr::Index(id, idx_raw, pos) => { - let (is_array, sc_idx, idx, mut target) = - self.indexed_value(scope, id, idx_raw, *pos)?; + // lhs[idx_expr].??? + Expr::Index(lhs, idx_expr) => { + let (source_type, src, idx, mut target) = + self.eval_index_expr(scope, lhs, idx_expr)?; 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`. - - if is_array { - scope[sc_idx].1.downcast_mut::().unwrap()[idx] = target; - } else { - Self::str_replace_char( - scope[sc_idx].1.downcast_mut::().unwrap(), // Root is a string + if let Some((id, src_idx)) = src { + Self::update_indexed_variable_in_scope( + source_type, + scope, + &id, + src_idx, idx, - *target.downcast::().unwrap(), // Target should be a char - ); + target, + ) + .expect("array or string source type expected for indexing"); } value } - _ => Err(EvalAltResult::ErrorDotExpr(dot_lhs.position())), + // {expr}.??? + expr => { + let mut target = self.eval_expr(scope, expr)?; + self.get_dot_val_helper(scope, target.as_mut(), dot_rhs) + } + // Syntax error + //_ => Err(EvalAltResult::ErrorDotExpr(dot_lhs.position())), } } + /// Chain-evaluate a dot setter fn set_dot_val_helper( - &self, + &mut self, this_ptr: &mut Variant, dot_rhs: &Expr, mut source_val: Dynamic, ) -> Result { match dot_rhs { + // xxx.id Expr::Identifier(id, pos) => { - let set_fn_name = "set$".to_string() + id; + let set_fn_name = format!("set${}", id); - self.call_fn_raw(set_fn_name, vec![this_ptr, source_val.as_mut()], None, *pos) + 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, pos) => { - let get_fn_name = "get$".to_string() + id; + // xxx.lhs.rhs + Expr::Dot(lhs, rhs) => match lhs.as_ref() { + Expr::Identifier(id, pos) => { + let get_fn_name = format!("get${}", id); - self.call_fn_raw(get_fn_name, vec![this_ptr], None, pos) + self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) .and_then(|mut v| { - self.set_dot_val_helper(v.as_mut(), inner_rhs, source_val) + self.set_dot_val_helper(v.as_mut(), rhs, source_val) .map(|_| v) // Discard Ok return value }) .and_then(|mut v| { - let set_fn_name = "set$".to_string() + id; + let set_fn_name = format!("set${}", id); - self.call_fn_raw(set_fn_name, vec![this_ptr, v.as_mut()], None, pos) + self.call_fn_raw(&set_fn_name, vec![this_ptr, v.as_mut()], None, *pos) }) } - _ => Err(EvalAltResult::ErrorDotExpr(inner_lhs.position())), + _ => Err(EvalAltResult::ErrorDotExpr(lhs.position())), }, + // Syntax error _ => Err(EvalAltResult::ErrorDotExpr(dot_rhs.position())), } } + // Evaluate a dot chain setter fn set_dot_val( - &self, + &mut self, scope: &mut Scope, dot_lhs: &Expr, dot_rhs: &Expr, source_val: Dynamic, ) -> Result { match dot_lhs { + // id.??? Expr::Identifier(id, pos) => { - let (sc_idx, mut target) = - Self::search_scope(scope, id, |x| Ok(x.into_dynamic()), *pos)?; + let (sc_idx, mut target) = Self::search_scope(scope, id, Ok, *pos)?; let value = self.set_dot_val_helper(target.as_mut(), dot_rhs, source_val); // In case the expression mutated `target`, we need to reassign it because // of the above `clone`. - scope[sc_idx].1 = target; + *scope.get_mut(id, sc_idx) = target; value } - Expr::Index(id, idx_raw, pos) => { - let (is_array, sc_idx, idx, mut target) = - self.indexed_value(scope, id, idx_raw, *pos)?; + // lhs[idx_expr].??? + Expr::Index(lhs, idx_expr) => { + let (source_type, src, idx, mut target) = + self.eval_index_expr(scope, lhs, idx_expr)?; 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`. - if is_array { - scope[sc_idx].1.downcast_mut::().unwrap()[idx] = target; - } else { - Self::str_replace_char( - scope[sc_idx].1.downcast_mut::().unwrap(), // Root is a string + + if let Some((id, src_idx)) = src { + Self::update_indexed_variable_in_scope( + source_type, + scope, + &id, + src_idx, idx, - *target.downcast::().unwrap(), // Target should be a char - ); + target, + ) + .expect("array or string source_type expected for indexing"); } value } + // Syntax error _ => Err(EvalAltResult::ErrorDotExpr(dot_lhs.position())), } } - fn eval_expr(&self, scope: &mut Scope, expr: &Expr) -> Result { + /// Evaluate an expression + fn eval_expr(&mut self, scope: &mut Scope, expr: &Expr) -> Result { match expr { - Expr::IntegerConstant(i, _) => Ok(Box::new(*i)), - Expr::FloatConstant(i, _) => Ok(Box::new(*i)), - Expr::StringConstant(s, _) => Ok(Box::new(s.clone())), - Expr::CharConstant(c, _) => Ok(Box::new(*c)), - - Expr::Identifier(id, pos) => scope - .iter() - .rev() - .filter(|(name, _)| id == name) - .next() - .map(|(_, val)| val.clone()) - .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.clone(), *pos)), - - Expr::Index(id, idx_raw, pos) => self - .indexed_value(scope, id, idx_raw, *pos) + Expr::IntegerConstant(i, _) => Ok((*i).into_dynamic()), + Expr::FloatConstant(i, _) => Ok((*i).into_dynamic()), + Expr::StringConstant(s, _) => Ok(s.into_dynamic()), + Expr::CharConstant(c, _) => Ok((*c).into_dynamic()), + Expr::Identifier(id, pos) => { + Self::search_scope(scope, id, Ok, *pos).map(|(_, val)| val) + } + Expr::Index(lhs, idx_expr) => self + .eval_index_expr(scope, lhs, idx_expr) .map(|(_, _, _, x)| x), - Expr::Assignment(ref id, rhs) => { + // lhs = rhs + Expr::Assignment(lhs, rhs) => { let rhs_val = self.eval_expr(scope, rhs)?; - match **id { - Expr::Identifier(ref n, pos) => scope - .iter_mut() - .rev() - .filter(|(name, _)| n == name) - .next() - .map(|(_, val)| { - *val = rhs_val; - Box::new(()) as Dynamic - }) - .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(n.clone(), pos)), - - Expr::Index(ref id, ref idx_raw, pos) => { - let idx_pos = idx_raw.position(); - - let idx = *match self.eval_expr(scope, &idx_raw)?.downcast_ref::() { - Some(x) => x, - _ => return Err(EvalAltResult::ErrorIndexExpr(idx_pos)), - }; - - let variable = &mut scope - .iter_mut() - .rev() - .filter(|(name, _)| id == name) - .map(|(_, val)| val) - .next(); - - let val = match variable { - Some(v) => v, - _ => return Err(EvalAltResult::ErrorVariableNotFound(id.clone(), pos)), - }; - - if let Some(arr) = val.downcast_mut() as Option<&mut Array> { - if idx < 0 { - Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, idx_pos)) - } else if idx as usize >= arr.len() { - Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, idx_pos)) - } else { - arr[idx as usize] = rhs_val; - Ok(Box::new(())) - } - } else if let Some(s) = val.downcast_mut() as Option<&mut String> { - let s_len = s.chars().count(); - - if idx < 0 { - Err(EvalAltResult::ErrorStringBounds(s_len, idx, idx_pos)) - } else if idx as usize >= s_len { - Err(EvalAltResult::ErrorStringBounds(s_len, idx, idx_pos)) - } else { - Self::str_replace_char( - s, - idx as usize, - *rhs_val.downcast::().unwrap(), - ); - Ok(Box::new(())) - } + match lhs.as_ref() { + // name = rhs + Expr::Identifier(name, pos) => { + if let Some((idx, _, _)) = scope.get(name) { + *scope.get_mut(name, idx) = rhs_val; + Ok(().into_dynamic()) } else { - Err(EvalAltResult::ErrorIndexExpr(idx_pos)) + Err(EvalAltResult::ErrorVariableNotFound(name.clone(), *pos)) } } - Expr::Dot(ref dot_lhs, ref dot_rhs) => { + // idx_lhs[idx_expr] = rhs + Expr::Index(idx_lhs, idx_expr) => { + let (source_type, src, idx, _) = + self.eval_index_expr(scope, idx_lhs, idx_expr)?; + + if let Some((id, src_idx)) = src { + Self::update_indexed_variable_in_scope( + source_type, + scope, + &id, + src_idx, + idx, + rhs_val, + ) + } else { + None + } + .ok_or_else(|| { + EvalAltResult::ErrorAssignmentToUnknownLHS(idx_lhs.position()) + }) + } + + // dot_lhs.dot_rhs = rhs + Expr::Dot(dot_lhs, dot_rhs) => { self.set_dot_val(scope, dot_lhs, dot_rhs, rhs_val) } - _ => Err(EvalAltResult::ErrorAssignmentToUnknownLHS(id.position())), + // Syntax error + _ => Err(EvalAltResult::ErrorAssignmentToUnknownLHS(lhs.position())), } } @@ -742,26 +600,30 @@ impl Engine { Expr::Array(contents, _) => { let mut arr = Vec::new(); - contents.iter().try_for_each(|item| { - let arg = self.eval_expr(scope, item)?; - arr.push(arg); - Ok(()) - })?; + contents + .iter() + .try_for_each::<_, Result<_, EvalAltResult>>(|item| { + let arg = self.eval_expr(scope, item)?; + arr.push(arg); + Ok(()) + })?; Ok(Box::new(arr)) } - Expr::FunctionCall(fn_name, args, def_value, pos) => self.call_fn_raw( - fn_name.into(), - args.iter() + Expr::FunctionCall(fn_name, args, def_value, pos) => { + let mut args = args + .iter() .map(|expr| self.eval_expr(scope, expr)) - .collect::>()? - .iter_mut() - .map(|b| b.as_mut()) - .collect(), - def_value.as_ref(), - *pos, - ), + .collect::>()?; + + self.call_fn_raw( + fn_name, + args.iter_mut().map(|b| b.as_mut()).collect(), + def_value.as_ref(), + *pos, + ) + } Expr::And(lhs, rhs) => Ok(Box::new( *self @@ -770,12 +632,13 @@ impl Engine { .map_err(|_| { EvalAltResult::ErrorBooleanArgMismatch("AND".into(), lhs.position()) })? - && *self - .eval_expr(scope, &*rhs)? - .downcast::() - .map_err(|_| { - EvalAltResult::ErrorBooleanArgMismatch("AND".into(), rhs.position()) - })?, + && // Short-circuit using && + *self + .eval_expr(scope, &*rhs)? + .downcast::() + .map_err(|_| { + EvalAltResult::ErrorBooleanArgMismatch("AND".into(), rhs.position()) + })?, )), Expr::Or(lhs, rhs) => Ok(Box::new( @@ -785,22 +648,24 @@ impl Engine { .map_err(|_| { EvalAltResult::ErrorBooleanArgMismatch("OR".into(), lhs.position()) })? - || *self - .eval_expr(scope, &*rhs)? - .downcast::() - .map_err(|_| { - EvalAltResult::ErrorBooleanArgMismatch("OR".into(), rhs.position()) - })?, + || // Short-circuit using || + *self + .eval_expr(scope, &*rhs)? + .downcast::() + .map_err(|_| { + EvalAltResult::ErrorBooleanArgMismatch("OR".into(), rhs.position()) + })?, )), - Expr::True(_) => Ok(Box::new(true)), - Expr::False(_) => Ok(Box::new(false)), - Expr::Unit(_) => Ok(Box::new(())), + Expr::True(_) => Ok(true.into_dynamic()), + Expr::False(_) => Ok(false.into_dynamic()), + Expr::Unit(_) => Ok(().into_dynamic()), } } + /// Evaluate a statement pub(crate) fn eval_stmt( - &self, + &mut self, scope: &mut Scope, stmt: &Stmt, ) -> Result { @@ -809,7 +674,7 @@ impl Engine { Stmt::Block(block) => { let prev_len = scope.len(); - let mut last_result: Result = Ok(Box::new(())); + let mut last_result: Result = Ok(().into_dynamic()); for block_stmt in block.iter() { last_result = self.eval_stmt(scope, block_stmt); @@ -834,10 +699,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 if let Some(stmt) = else_body { + self.eval_stmt(scope, stmt.as_ref()) } else { - Ok(Box::new(())) + Ok(().into_dynamic()) } }), @@ -846,12 +711,12 @@ impl Engine { Ok(guard_val) => { if *guard_val { match self.eval_stmt(scope, body) { - Err(EvalAltResult::LoopBreak) => return Ok(Box::new(())), + Err(EvalAltResult::LoopBreak) => return Ok(().into_dynamic()), Err(x) => return Err(x), _ => (), } } else { - return Ok(Box::new(())); + return Ok(().into_dynamic()); } } Err(_) => return Err(EvalAltResult::ErrorIfGuard(guard.position())), @@ -860,7 +725,7 @@ impl Engine { Stmt::Loop(body) => loop { match self.eval_stmt(scope, body) { - Err(EvalAltResult::LoopBreak) => return Ok(Box::new(())), + Err(EvalAltResult::LoopBreak) => return Ok(().into_dynamic()), Err(x) => return Err(x), _ => (), } @@ -871,11 +736,11 @@ impl Engine { let tid = Any::type_id(&*arr); if let Some(iter_fn) = self.type_iterators.get(&tid) { - scope.push((name.clone(), Box::new(()))); + scope.push(name.clone(), ()); let idx = scope.len() - 1; for a in iter_fn(&arr) { - scope[idx].1 = a; + *scope.get_mut(name, idx) = a; match self.eval_stmt(scope, body) { Err(EvalAltResult::LoopBreak) => break, @@ -883,8 +748,8 @@ impl Engine { _ => (), } } - scope.remove(idx); - Ok(Box::new(())) + scope.pop(); + Ok(().into_dynamic()) } else { return Err(EvalAltResult::ErrorFor(expr.position())); } @@ -892,58 +757,91 @@ impl Engine { Stmt::Break(_) => Err(EvalAltResult::LoopBreak), - Stmt::Return(pos) => Err(EvalAltResult::Return(Box::new(()), *pos)), + // Empty return + Stmt::ReturnWithVal(None, true, pos) => { + Err(EvalAltResult::Return(().into_dynamic(), *pos)) + } - Stmt::ReturnWithVal(a, pos) => { - let result = self.eval_expr(scope, a)?; - Err(EvalAltResult::Return(result, *pos)) + // Return value + Stmt::ReturnWithVal(Some(a), true, pos) => { + Err(EvalAltResult::Return(self.eval_expr(scope, a)?, *pos)) + } + + // Empty throw + Stmt::ReturnWithVal(None, false, pos) => { + Err(EvalAltResult::ErrorRuntime("".into(), *pos)) + } + + // Throw value + Stmt::ReturnWithVal(Some(a), false, pos) => { + let val = self.eval_expr(scope, a)?; + Err(EvalAltResult::ErrorRuntime( + val.downcast::() + .map(|s| *s) + .unwrap_or("".to_string()), + *pos, + )) } Stmt::Let(name, init, _) => { if let Some(v) = init { - let i = self.eval_expr(scope, v)?; - scope.push((name.clone(), i)); + let val = self.eval_expr(scope, v)?; + scope.push_dynamic(name.clone(), val); } else { - scope.push((name.clone(), Box::new(()))); + scope.push(name.clone(), ()); } - Ok(Box::new(())) + Ok(().into_dynamic()) } } } - pub(crate) fn map_type_name(&self, name: String) -> String { + /// Map a type_name into a pretty-print name + pub(crate) fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str { self.type_names - .get(&name) - .map(|x| x.clone()) - .unwrap_or(name.to_string()) + .get(name) + .map(|s| s.as_str()) + .unwrap_or(name) } /// Make a new engine - pub fn new() -> Engine { + pub fn new<'a>() -> Engine<'a> { + use std::any::type_name; + // User-friendly names for built-in types let type_names = [ - ("alloc::string::String", "string"), - ( - "alloc::vec::Vec>", - "array", - ), - ("alloc::boxed::Box", "dynamic"), + (type_name::(), "string"), + (type_name::(), "array"), + (type_name::(), "dynamic"), ] .iter() .map(|(k, v)| (k.to_string(), v.to_string())) .collect(); + // Create the new scripting Engine let mut engine = Engine { - fns: HashMap::new(), - script_fns: HashMap::new(), + external_functions: HashMap::new(), + script_functions: HashMap::new(), type_iterators: HashMap::new(), type_names, - on_print: Box::new(|x: &str| println!("{}", x)), - on_debug: Box::new(|x: &str| println!("{}", x)), + on_print: Box::new(default_print), // default print/debug implementations + on_debug: Box::new(default_print), }; - engine.register_builtins(); + engine.register_core_lib(); + + #[cfg(not(feature = "no_stdlib"))] + engine.register_stdlib(); // Register the standard library when no_stdlib is not set engine } } + +/// Print/debug to stdout +#[cfg(not(feature = "no_stdlib"))] +fn default_print(s: &str) { + println!("{}", s); +} + +/// No-op +#[cfg(feature = "no_stdlib")] +fn default_print(_: &str) {} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 00000000..3096040e --- /dev/null +++ b/src/error.rs @@ -0,0 +1,147 @@ +use crate::parser::Position; +use std::char; +use std::error::Error; +use std::fmt; + +/// Error when tokenizing the script text. +#[derive(Debug, Eq, PartialEq, Hash, Clone)] +pub enum LexError { + /// An unexpected character is encountered when tokenizing the script text. + UnexpectedChar(char), + /// A string literal is not terminated before a new-line or EOF. + UnterminatedString, + /// An string/character/numeric escape sequence is in an invalid format. + MalformedEscapeSequence(String), + /// An numeric literal is in an invalid format. + MalformedNumber(String), + /// An character literal is in an invalid format. + MalformedChar(String), + /// Error in the script text. + InputError(String), +} + +impl Error for LexError { + fn description(&self) -> &str { + match *self { + Self::UnexpectedChar(_) => "Unexpected character", + Self::UnterminatedString => "Open string is not terminated", + Self::MalformedEscapeSequence(_) => "Unexpected values in escape sequence", + Self::MalformedNumber(_) => "Unexpected characters in number", + Self::MalformedChar(_) => "Char constant not a single character", + Self::InputError(_) => "Input error", + } + } +} + +impl fmt::Display for LexError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::UnexpectedChar(c) => write!(f, "Unexpected '{}'", c), + Self::MalformedEscapeSequence(s) => write!(f, "Invalid escape sequence: '{}'", s), + Self::MalformedNumber(s) => write!(f, "Invalid number: '{}'", s), + Self::MalformedChar(s) => write!(f, "Invalid character: '{}'", s), + Self::InputError(s) => write!(f, "{}", s), + _ => write!(f, "{}", self.description()), + } + } +} + +/// Type of error encountered when parsing a script. +#[derive(Debug, PartialEq, Clone)] +pub enum ParseErrorType { + /// Error in the script text. Wrapped value is the error message. + BadInput(String), + /// The script ends prematurely. + InputPastEndOfFile, + /// An unknown operator is encountered. Wrapped value is the operator. + UnknownOperator(String), + /// An open `(` is missing the corresponding closing `)`. + MissingRightParen(String), + /// Expecting `(` but not finding one. + MissingLeftBrace, + /// An open `{` is missing the corresponding closing `}`. + MissingRightBrace(String), + /// An open `[` is missing the corresponding closing `]`. + MissingRightBracket(String), + /// An expression in function call arguments `()` has syntax error. + MalformedCallExpr, + /// An expression in indexing brackets `[]` has syntax error. + MalformedIndexExpr, + /// Missing a variable name after the `let` keyword. + VarExpectsIdentifier, + /// Defining a function `fn` in an appropriate place (e.g. inside another function). + WrongFnDefinition, + /// Missing a function name after the `fn` keyword. + FnMissingName, + /// A function definition is missing the parameters list. Wrapped value is the function name. + FnMissingParams(String), +} + +/// Error when parsing a script. +#[derive(Debug, PartialEq, Clone)] +pub struct ParseError(ParseErrorType, Position); + +impl ParseError { + /// Create a new `ParseError`. + pub(crate) fn new(err: ParseErrorType, pos: Position) -> Self { + Self(err, pos) + } + + /// Get the parse error. + pub fn error_type(&self) -> &ParseErrorType { + &self.0 + } + + /// Get the location in the script of the error. + pub fn position(&self) -> Position { + self.1 + } +} + +impl Error for ParseError { + fn description(&self) -> &str { + match self.0 { + ParseErrorType::BadInput(ref p) => p, + ParseErrorType::InputPastEndOfFile => "Script is incomplete", + ParseErrorType::UnknownOperator(_) => "Unknown operator", + ParseErrorType::MissingRightParen(_) => "Expecting ')'", + ParseErrorType::MissingLeftBrace => "Expecting '{'", + ParseErrorType::MissingRightBrace(_) => "Expecting '}'", + ParseErrorType::MissingRightBracket(_) => "Expecting ']'", + ParseErrorType::MalformedCallExpr => "Invalid expression in function call arguments", + ParseErrorType::MalformedIndexExpr => "Invalid index in indexing expression", + ParseErrorType::VarExpectsIdentifier => "Expecting name of a variable", + ParseErrorType::FnMissingName => "Expecting name in function declaration", + ParseErrorType::FnMissingParams(_) => "Expecting parameters in function declaration", + ParseErrorType::WrongFnDefinition => "Function definitions must be at top level and cannot be inside a block or another function", + } + } + + fn cause(&self) -> Option<&dyn Error> { + None + } +} + +impl fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.0 { + ParseErrorType::BadInput(ref s) => write!(f, "{}", s)?, + ParseErrorType::UnknownOperator(ref s) => write!(f, "{}: '{}'", self.description(), s)?, + ParseErrorType::FnMissingParams(ref s) => { + write!(f, "Expecting parameters for function '{}'", s)? + } + ParseErrorType::MissingRightParen(ref s) + | ParseErrorType::MissingRightBrace(ref s) + | ParseErrorType::MissingRightBracket(ref s) => { + write!(f, "{} for {}", self.description(), s)? + } + _ => write!(f, "{}", self.description())?, + } + + if !self.1.is_eof() { + write!(f, " ({})", self.1) + } else { + write!(f, " at the end of the script but there is no more input") + } + } +} diff --git a/src/fn_register.rs b/src/fn_register.rs index f340f5bc..9e84492f 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -1,13 +1,59 @@ use std::any::TypeId; use crate::any::{Any, Dynamic}; -use crate::engine::{Engine, EvalAltResult, FnCallArgs}; +use crate::engine::{Engine, FnCallArgs}; use crate::parser::Position; +use crate::result::EvalAltResult; +/// A trait to register custom functions with the `Engine`. +/// +/// # Example +/// +/// ```rust +/// use rhai::{Engine, RegisterFn}; +/// +/// // Normal function +/// fn add(x: i64, y: i64) -> i64 { +/// x + y +/// } +/// +/// let mut engine = Engine::new(); +/// +/// // You must use the trait rhai::RegisterFn to get this method. +/// engine.register_fn("add", add); +/// +/// if let Ok(result) = engine.eval::("add(40, 2)") { +/// println!("Answer: {}", result); // prints 42 +/// } +/// ``` pub trait RegisterFn { + /// Register a custom function with the `Engine`. fn register_fn(&mut self, name: &str, f: FN); } + +/// A trait to register custom functions that return `Dynamic` values with the `Engine`. +/// +/// # Example +/// +/// ```rust +/// use rhai::{Engine, RegisterDynamicFn, Dynamic}; +/// +/// // Function that returns a Dynamic value +/// fn get_an_any(x: i64) -> Dynamic { +/// Box::new(x) +/// } +/// +/// let mut engine = Engine::new(); +/// +/// // You must use the trait rhai::RegisterDynamicFn to get this method. +/// engine.register_dynamic_fn("get_an_any", get_an_any); +/// +/// if let Ok(result) = engine.eval::("get_an_any(42)") { +/// println!("Answer: {}", result); // prints 42 +/// } +/// ``` pub trait RegisterDynamicFn { + /// Register a custom function returning `Dynamic` values with the `Engine`. fn register_dynamic_fn(&mut self, name: &str, f: FN); } @@ -15,8 +61,8 @@ pub struct Ref(A); pub struct Mut(A); macro_rules! count_args { - () => {0usize}; - ($head:ident $($tail:ident)*) => {1usize + count_args!($($tail)*)}; + () => { 0_usize }; + ( $head:ident $($tail:ident)* ) => { 1_usize + count_args!($($tail)*) }; } macro_rules! def_register { @@ -28,65 +74,63 @@ macro_rules! def_register { $($par: Any + Clone,)* FN: Fn($($param),*) -> RET + 'static, RET: Any - > RegisterFn for Engine + > RegisterFn for Engine<'_> { fn register_fn(&mut self, name: &str, f: FN) { let fn_name = name.to_string(); let fun = move |mut args: FnCallArgs, pos: Position| { - // Check for length at the beginning to avoid - // per-element bound checks. + // 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, pos)); + Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos)) + } else { + #[allow(unused_variables, unused_mut)] + let mut drain = args.drain(..); + $( + // Downcast every element, return in case of a type mismatch + let $par = (drain.next().unwrap().downcast_mut() as Option<&mut $par>).unwrap(); + )* + + // Call the user-supplied function using ($clone) to + // potentially clone the value, otherwise pass the reference. + let r = f($(($clone)($par)),*); + Ok(Box::new(r) as Dynamic) } - - #[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>).unwrap(); - )* - - // Call the user-supplied function using ($clone) to - // potentially clone the value, otherwise pass the reference. - let r = f($(($clone)($par)),*); - Ok(Box::new(r) as Dynamic) }; - self.register_fn_raw(name.into(), Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun)); + self.register_fn_raw(name, Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun)); } } impl< $($par: Any + Clone,)* FN: Fn($($param),*) -> Dynamic + 'static, - > RegisterDynamicFn for Engine + > 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, pos: Position| { - // Check for length at the beginning to avoid - // per-element bound checks. + // 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, pos)); + Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos)) + } else { + #[allow(unused_variables, unused_mut)] + let mut drain = args.drain(..); + $( + // Downcast every element, return in case of a type mismatch + let $par = (drain.next().unwrap().downcast_mut() as Option<&mut $par>).unwrap(); + )* + + // Call the user-supplied function using ($clone) to + // potentially clone the value, otherwise pass the reference. + Ok(f($(($clone)($par)),*)) } - - #[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>).unwrap(); - )* - - // Call the user-supplied function using ($clone) to - // potentially clone the value, otherwise pass the reference. - Ok(f($(($clone)($par)),*)) }; - self.register_fn_raw(name.into(), Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun)); + self.register_fn_raw(name, Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun)); } } @@ -106,4 +150,4 @@ macro_rules! def_register { } #[cfg_attr(rustfmt, rustfmt_skip)] -def_register!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S); +def_register!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T); diff --git a/src/lib.rs b/src/lib.rs index a7ac4d8e..5c947e95 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,9 +34,24 @@ // needs to be here, because order matters for macros macro_rules! debug_println { - () => (#[cfg(feature = "debug_msgs")] {print!("\n")}); - ($fmt:expr) => (#[cfg(feature = "debug_msgs")] {print!(concat!($fmt, "\n"))}); - ($fmt:expr, $($arg:tt)*) => (#[cfg(feature = "debug_msgs")] {print!(concat!($fmt, "\n"), $($arg)*)}); + () => ( + #[cfg(feature = "debug_msgs")] + { + print!("\n"); + } + ); + ($fmt:expr) => ( + #[cfg(feature = "debug_msgs")] + { + print!(concat!($fmt, "\n")); + } + ); + ($fmt:expr, $($arg:tt)*) => ( + #[cfg(feature = "debug_msgs")] + { + print!(concat!($fmt, "\n"), $($arg)*); + } + ); } mod any; @@ -44,10 +59,17 @@ mod api; mod builtin; mod call; mod engine; +mod error; mod fn_register; mod parser; +mod result; +mod scope; -pub use any::Dynamic; -pub use engine::{Array, Engine, EvalAltResult, Scope}; +pub use any::{Any, AnyExt, Dynamic, Variant}; +pub use call::FuncArgs; +pub use engine::{Array, Engine}; +pub use error::{ParseError, ParseErrorType}; pub use fn_register::{RegisterDynamicFn, RegisterFn}; -pub use parser::{ParseError, ParseErrorType, AST}; +pub use parser::{Position, AST}; +pub use result::EvalAltResult; +pub use scope::Scope; diff --git a/src/parser.rs b/src/parser.rs index 2037679c..204998b0 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,153 +1,104 @@ -use crate::Dynamic; +use crate::any::Dynamic; +use crate::error::{LexError, ParseError, ParseErrorType}; use std::char; -use std::error::Error; -use std::fmt; use std::iter::Peekable; use std::str::Chars; -#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)] -pub enum LexError { - UnexpectedChar(char), - UnterminatedString, - MalformedEscapeSequence, - MalformedNumber, - MalformedChar, - Nothing, -} - type LERR = LexError; - -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", - LERR::Nothing => "This error is for internal use only", - } - } -} - -impl fmt::Display for LexError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - LERR::UnexpectedChar(c) => write!(f, "Unexpected '{}'", c), - _ => write!(f, "{}", self.description()), - } - } -} - -#[derive(Debug, PartialEq, Clone)] -pub enum ParseErrorType { - BadInput(String), - InputPastEndOfFile, - UnknownOperator, - MissingRightParen, - MissingLeftBrace, - MissingRightBrace, - MissingRightBracket, - MalformedCallExpr, - MalformedIndexExpr, - VarExpectsIdentifier, - FnMissingName, - FnMissingParams, -} - type PERR = ParseErrorType; -#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] +/// A location (line number + character position) in the input script. +#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] pub struct Position { - pub line: usize, - pub pos: usize, + line: usize, + pos: usize, } impl Position { - pub fn advance(&mut self) { + /// Create a new `Position`. + pub fn new(line: usize, position: usize) -> Self { + Self { + line, + pos: position, + } + } + + /// Get the line number (1-based), or `None` if EOF. + pub fn line(&self) -> Option { + match self.line { + 0 => None, + x => Some(x), + } + } + + /// Get the character position (1-based), or `None` if at beginning of a line. + pub fn position(&self) -> Option { + match self.pos { + 0 => None, + x => Some(x), + } + } + + /// Advance by one character position. + pub(crate) fn advance(&mut self) { self.pos += 1; } - pub fn rewind(&mut self) { - // Beware, should not rewind at zero position + + /// Go backwards by one character position. + /// + /// # Panics + /// + /// Panics if already at beginning of a line - cannot rewind to a previous line. + /// + pub(crate) fn rewind(&mut self) { + assert!(self.pos > 0, "cannot rewind at position 0"); self.pos -= 1; } - pub fn new_line(&mut self) { + + /// Advance to the next line. + pub(crate) fn new_line(&mut self) { self.line += 1; self.pos = 0; } - pub fn eof() -> Self { + + /// Create a `Position` at EOF. + pub(crate) fn eof() -> Self { Self { line: 0, pos: 0 } } + + /// Is the `Position` at EOF? pub fn is_eof(&self) -> bool { self.line == 0 } } +impl Default for Position { + fn default() -> Self { + Self::new(1, 0) + } +} + impl std::fmt::Display for Position { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { - match self.line { - 0 => write!(f, "EOF"), - _ => write!(f, "line {}, position {}", self.line, self.pos), - } - } -} - -#[derive(Debug, PartialEq, Clone)] -pub struct ParseError(PERR, Position); - -impl ParseError { - pub fn error_type(&self) -> &PERR { - &self.0 - } - pub fn line(&self) -> usize { - self.1.line - } - pub fn position(&self) -> usize { - self.1.pos - } - pub fn is_eof(&self) -> bool { - self.1.is_eof() - } -} - -impl Error for ParseError { - fn description(&self) -> &str { - match self.0 { - PERR::BadInput(ref p) => p, - PERR::InputPastEndOfFile => "Script is incomplete", - PERR::UnknownOperator => "Unknown operator", - PERR::MissingRightParen => "Expecting ')'", - PERR::MissingLeftBrace => "Expecting '{'", - PERR::MissingRightBrace => "Expecting '}'", - 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::FnMissingParams => "Expecting parameters in function declaration", - } - } - - fn cause(&self) -> Option<&dyn Error> { - None - } -} - -impl fmt::Display for ParseError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.0 { - PERR::BadInput(ref s) => write!(f, "{}", s)?, - _ => write!(f, "{}", self.description())?, - } - - if !self.is_eof() { - write!(f, " ({})", self.1) + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.is_eof() { + write!(f, "EOF") } else { - write!(f, " at the end of the script but there is no more input") + write!(f, "line {}, position {}", self.line, self.pos) } } } +impl std::fmt::Debug for Position { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.is_eof() { + write!(f, "(EOF)") + } else { + write!(f, "({}:{})", self.line, self.pos) + } + } +} + +/// Compiled AST (abstract syntax tree) of a Rhai script. pub struct AST(pub(crate) Vec, pub(crate) Vec); #[derive(Debug, Clone)] @@ -168,8 +119,7 @@ pub enum Stmt { Block(Vec), Expr(Box), Break(Position), - Return(Position), - ReturnWithVal(Box, Position), + ReturnWithVal(Option>, bool, Position), } #[derive(Debug, Clone)] @@ -182,7 +132,7 @@ pub enum Expr { FunctionCall(String, Vec, Option, Position), Assignment(Box, Box), Dot(Box, Box), - Index(String, Box, Position), + Index(Box, Box), Array(Vec, Position), And(Box, Box), Or(Box, Box), @@ -200,13 +150,16 @@ impl Expr { | 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!(), + Expr::Index(e, _) + | Expr::Assignment(e, _) + | Expr::Dot(e, _) + | Expr::And(e, _) + | Expr::Or(e, _) => e.position(), } } } @@ -256,6 +209,7 @@ pub enum Token { Fn, Break, Return, + Throw, PlusAssign, MinusAssign, MultiplyAssign, @@ -274,10 +228,85 @@ pub enum Token { PowerOfAssign, For, In, - LexErr(LexError), + LexError(LexError), } impl Token { + pub fn syntax(&self) -> std::borrow::Cow<'static, str> { + use self::Token::*; + + match *self { + IntegerConstant(ref s) => s.to_string().into(), + FloatConstant(ref s) => s.to_string().into(), + Identifier(ref s) => s.to_string().into(), + CharConstant(ref s) => s.to_string().into(), + LexError(ref err) => err.to_string().into(), + + ref token => (match token { + StringConst(_) => "string", + LeftBrace => "{", + RightBrace => "}", + LeftParen => "(", + RightParen => ")", + LeftBracket => "[", + RightBracket => "]", + Plus => "+", + UnaryPlus => "+", + Minus => "-", + UnaryMinus => "-", + Multiply => "*", + Divide => "/", + SemiColon => ";", + Colon => ":", + Comma => ",", + Period => ".", + Equals => "=", + True => "true", + False => "false", + Let => "let", + If => "if", + Else => "else", + While => "while", + Loop => "loop", + LessThan => "<", + GreaterThan => ">", + Bang => "!", + LessThanEqualsTo => "<=", + GreaterThanEqualsTo => ">=", + EqualsTo => "==", + NotEqualsTo => "!=", + Pipe => "|", + Or => "||", + Ampersand => "&", + And => "&&", + Fn => "fn", + Break => "break", + Return => "return", + Throw => "throw", + PlusAssign => "+=", + MinusAssign => "-=", + MultiplyAssign => "*=", + DivideAssign => "/=", + LeftShiftAssign => "<<=", + RightShiftAssign => ">>=", + AndAssign => "&=", + OrAssign => "|=", + XOrAssign => "^=", + LeftShift => "<<", + RightShift => ">>", + XOr => "^", + Modulo => "%", + ModuloAssign => "%=", + PowerOf => "~", + PowerOfAssign => "~=", + For => "for", + In => "in", + _ => panic!(), + }) + .into(), + } + } + // if another operator is after these, it's probably an unary operator // not sure about fn's name pub fn is_next_unary(&self) -> bool { @@ -328,6 +357,7 @@ impl Token { Modulo | ModuloAssign | Return | + Throw | PowerOf | In | PowerOfAssign => true, @@ -336,41 +366,23 @@ impl Token { } #[allow(dead_code)] - pub fn is_bin_op(&self) -> bool { + pub fn is_binary_op(&self) -> bool { use self::Token::*; match *self { - RightBrace | - RightParen | - RightBracket | - Plus | - Minus | - Multiply | - Divide | - Comma | - // Period | <- does period count? - Equals | - LessThan | - GreaterThan | - LessThanEqualsTo | - GreaterThanEqualsTo | - EqualsTo | - NotEqualsTo | - Pipe | - Or | - Ampersand | - And | - PowerOf => true, + RightBrace | RightParen | RightBracket | Plus | Minus | Multiply | Divide | Comma + | Equals | LessThan | GreaterThan | LessThanEqualsTo | GreaterThanEqualsTo + | EqualsTo | NotEqualsTo | Pipe | Or | Ampersand | And | PowerOf => true, _ => false, } } #[allow(dead_code)] - pub fn is_un_op(&self) -> bool { + pub fn is_unary_op(&self) -> bool { use self::Token::*; match *self { - UnaryPlus | UnaryMinus | Equals | Bang | Return => true, + UnaryPlus | UnaryMinus | Equals | Bang | Return | Throw => true, _ => false, } } @@ -390,7 +402,7 @@ impl<'a> TokenIterator<'a> { self.pos.rewind(); } fn new_line(&mut self) { - self.pos.new_line(); + self.pos.new_line() } pub fn parse_string_const( @@ -398,7 +410,7 @@ impl<'a> TokenIterator<'a> { enclosing_char: char, ) -> Result { let mut result = Vec::new(); - let mut escape = false; + let mut escape = String::with_capacity(12); loop { let next_char = self.char_stream.next(); @@ -410,107 +422,123 @@ impl<'a> TokenIterator<'a> { self.advance(); match next_char.unwrap() { - '\\' if !escape => escape = true, - '\\' if escape => { - escape = false; + '\\' if escape.is_empty() => { + escape.push('\\'); + } + '\\' if !escape.is_empty() => { + escape.clear(); result.push('\\'); } - 't' if escape => { - escape = false; + 't' if !escape.is_empty() => { + escape.clear(); result.push('\t'); } - 'n' if escape => { - escape = false; + 'n' if !escape.is_empty() => { + escape.clear(); result.push('\n'); } - 'r' if escape => { - escape = false; + 'r' if !escape.is_empty() => { + escape.clear(); result.push('\r'); } - 'x' if escape => { - escape = false; + 'x' if !escape.is_empty() => { + let mut seq = escape.clone(); + seq.push('x'); + escape.clear(); let mut out_val: u32 = 0; for _ in 0..2 { if let Some(c) = self.char_stream.next() { + seq.push(c); + self.advance(); + if let Some(d1) = c.to_digit(16) { out_val *= 16; out_val += d1; } else { - return Err((LERR::MalformedEscapeSequence, self.pos)); + return Err((LERR::MalformedEscapeSequence(seq), self.pos)); } } else { - return Err((LERR::MalformedEscapeSequence, self.pos)); + return Err((LERR::MalformedEscapeSequence(seq), self.pos)); } - self.advance(); } if let Some(r) = char::from_u32(out_val) { result.push(r); } else { - return Err((LERR::MalformedEscapeSequence, self.pos)); + return Err((LERR::MalformedEscapeSequence(seq), self.pos)); } } - 'u' if escape => { - escape = false; + 'u' if !escape.is_empty() => { + let mut seq = escape.clone(); + seq.push('u'); + escape.clear(); let mut out_val: u32 = 0; for _ in 0..4 { if let Some(c) = self.char_stream.next() { + seq.push(c); + self.advance(); + if let Some(d1) = c.to_digit(16) { out_val *= 16; out_val += d1; } else { - return Err((LERR::MalformedEscapeSequence, self.pos)); + return Err((LERR::MalformedEscapeSequence(seq), self.pos)); } } else { - return Err((LERR::MalformedEscapeSequence, self.pos)); + return Err((LERR::MalformedEscapeSequence(seq), self.pos)); } - self.advance(); } if let Some(r) = char::from_u32(out_val) { result.push(r); } else { - return Err((LERR::MalformedEscapeSequence, self.pos)); + return Err((LERR::MalformedEscapeSequence(seq), self.pos)); } } - 'U' if escape => { - escape = false; + 'U' if !escape.is_empty() => { + let mut seq = escape.clone(); + seq.push('U'); + escape.clear(); let mut out_val: u32 = 0; for _ in 0..8 { if let Some(c) = self.char_stream.next() { + seq.push(c); + self.advance(); + if let Some(d1) = c.to_digit(16) { out_val *= 16; out_val += d1; } else { - return Err((LERR::MalformedEscapeSequence, self.pos)); + return Err((LERR::MalformedEscapeSequence(seq), self.pos)); } } else { - return Err((LERR::MalformedEscapeSequence, self.pos)); + return Err((LERR::MalformedEscapeSequence(seq), self.pos)); } - self.advance(); } if let Some(r) = char::from_u32(out_val) { result.push(r); } else { - return Err((LERR::MalformedEscapeSequence, self.pos)); + return Err((LERR::MalformedEscapeSequence(seq), 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.pos)), + x if enclosing_char == x && !escape.is_empty() => result.push(x), + x if enclosing_char == x && escape.is_empty() => break, + _ if !escape.is_empty() => { + return Err((LERR::MalformedEscapeSequence(escape), self.pos)) + } '\n' => { self.rewind(); return Err((LERR::UnterminatedString, self.pos)); } x => { - escape = false; + escape.clear(); result.push(x); } } } - let out: String = result.iter().cloned().collect(); + let out: String = result.iter().collect(); Ok(out) } @@ -527,21 +555,21 @@ impl<'a> TokenIterator<'a> { let mut radix_base: Option = None; result.push(c); - while let Some(&nxt) = self.char_stream.peek() { - match nxt { - '0'..='9' => { - result.push(nxt); + while let Some(&next_char) = self.char_stream.peek() { + match next_char { + '0'..='9' | '_' => { + result.push(next_char); self.char_stream.next(); self.advance(); } '.' => { - result.push(nxt); + result.push(next_char); self.char_stream.next(); self.advance(); - while let Some(&nxt_float) = self.char_stream.peek() { - match nxt_float { - '0'..='9' => { - result.push(nxt_float); + while let Some(&next_char_in_float) = self.char_stream.peek() { + match next_char_in_float { + '0'..='9' | '_' => { + result.push(next_char_in_float); self.char_stream.next(); self.advance(); } @@ -549,14 +577,14 @@ impl<'a> TokenIterator<'a> { } } } - 'x' | 'X' => { - result.push(nxt); + 'x' | 'X' if c == '0' => { + result.push(next_char); self.char_stream.next(); self.advance(); - while let Some(&nxt_hex) = self.char_stream.peek() { - match nxt_hex { - '0'..='9' | 'a'..='f' | 'A'..='F' => { - result.push(nxt_hex); + while let Some(&next_char_in_hex) = self.char_stream.peek() { + match next_char_in_hex { + '0'..='9' | 'a'..='f' | 'A'..='F' | '_' => { + result.push(next_char_in_hex); self.char_stream.next(); self.advance(); } @@ -565,14 +593,14 @@ impl<'a> TokenIterator<'a> { } radix_base = Some(16); } - 'o' | 'O' => { - result.push(nxt); + 'o' | 'O' if c == '0' => { + result.push(next_char); self.char_stream.next(); self.advance(); - while let Some(&nxt_oct) = self.char_stream.peek() { - match nxt_oct { - '0'..='8' => { - result.push(nxt_oct); + while let Some(&next_char_in_oct) = self.char_stream.peek() { + match next_char_in_oct { + '0'..='8' | '_' => { + result.push(next_char_in_oct); self.char_stream.next(); self.advance(); } @@ -581,14 +609,14 @@ impl<'a> TokenIterator<'a> { } radix_base = Some(8); } - 'b' | 'B' => { - result.push(nxt); + 'b' | 'B' if c == '0' => { + result.push(next_char); self.char_stream.next(); self.advance(); - while let Some(&nxt_bin) = self.char_stream.peek() { - match nxt_bin { + while let Some(&next_char_in_binary) = self.char_stream.peek() { + match next_char_in_binary { '0' | '1' | '_' => { - result.push(nxt_bin); + result.push(next_char_in_binary); self.char_stream.next(); self.advance(); } @@ -602,18 +630,19 @@ impl<'a> TokenIterator<'a> { } if let Some(radix) = radix_base { - let out: String = result - .iter() - .cloned() - .skip(2) - .filter(|c| c != &'_') - .collect(); - if let Ok(val) = i64::from_str_radix(&out, radix) { - return Some((Token::IntegerConstant(val), pos)); - } + let out: String = result.iter().skip(2).filter(|&&c| c != '_').collect(); + + return Some(( + if let Ok(val) = i64::from_str_radix(&out, radix) { + Token::IntegerConstant(val) + } else { + Token::LexError(LERR::MalformedNumber(result.iter().collect())) + }, + pos, + )); } - let out: String = result.iter().cloned().collect(); + let out: String = result.iter().filter(|&&c| c != '_').collect(); return Some(( if let Ok(val) = out.parse::() { @@ -621,7 +650,7 @@ impl<'a> TokenIterator<'a> { } else if let Ok(val) = out.parse::() { Token::FloatConstant(val) } else { - Token::LexErr(LERR::MalformedNumber) + Token::LexError(LERR::MalformedNumber(result.iter().collect())) }, pos, )); @@ -630,9 +659,9 @@ impl<'a> TokenIterator<'a> { let mut result = Vec::new(); result.push(c); - while let Some(&nxt) = self.char_stream.peek() { - match nxt { - x if x.is_alphanumeric() || x == '_' => { + while let Some(&next_char) = self.char_stream.peek() { + match next_char { + x if x.is_ascii_alphanumeric() || x == '_' => { result.push(x); self.char_stream.next(); self.advance(); @@ -641,7 +670,7 @@ impl<'a> TokenIterator<'a> { } } - let out: String = result.iter().cloned().collect(); + let out: String = result.iter().collect(); return Some(( match out.as_str() { @@ -654,6 +683,7 @@ impl<'a> TokenIterator<'a> { "loop" => Token::Loop, "break" => Token::Break, "return" => Token::Return, + "throw" => Token::Throw, "fn" => Token::Fn, "for" => Token::For, "in" => Token::In, @@ -665,7 +695,7 @@ impl<'a> TokenIterator<'a> { '"' => { return match self.parse_string_const('"') { Ok(out) => Some((Token::StringConst(out), pos)), - Err(e) => Some((Token::LexErr(e.0), e.1)), + Err(e) => Some((Token::LexError(e.0), e.1)), } } '\'' => match self.parse_string_const('\'') { @@ -673,19 +703,19 @@ impl<'a> TokenIterator<'a> { let mut chars = result.chars(); return Some(( - if let Some(out) = chars.next() { + if let Some(first_char) = chars.next() { if chars.count() != 0 { - Token::LexErr(LERR::MalformedChar) + Token::LexError(LERR::MalformedChar(format!("'{}'", result))) } else { - Token::CharConstant(out) + Token::CharConstant(first_char) } } else { - Token::LexErr(LERR::MalformedChar) + Token::LexError(LERR::MalformedChar(format!("'{}'", result))) }, pos, )); } - Err(e) => return Some((Token::LexErr(e.0), e.1)), + Err(e) => return Some((Token::LexError(e.0), e.1)), }, '{' => return Some((Token::LeftBrace, pos)), '}' => return Some((Token::RightBrace, pos)), @@ -739,11 +769,12 @@ impl<'a> TokenIterator<'a> { self.char_stream.next(); self.advance(); while let Some(c) = self.char_stream.next() { - if c == '\n' { - self.new_line(); - break; - } else { - self.advance(); + match c { + '\n' => { + self.new_line(); + break; + } + _ => self.advance(), } } } @@ -937,7 +968,7 @@ impl<'a> TokenIterator<'a> { )) } x if x.is_whitespace() => (), - x => return Some((Token::LexErr(LERR::UnexpectedChar(x)), pos)), + x => return Some((Token::LexError(LERR::UnexpectedChar(x)), pos)), } } @@ -959,8 +990,8 @@ impl<'a> Iterator for TokenIterator<'a> { pub fn lex(input: &str) -> TokenIterator<'_> { TokenIterator { - last: Token::LexErr(LERR::Nothing), - pos: Position { line: 1, pos: 0 }, + last: Token::LexError(LERR::InputError("".into())), + pos: Position::new(1, 0), char_stream: input.chars().peekable(), } } @@ -1012,7 +1043,16 @@ fn parse_paren_expr<'a>( match input.next() { Some((Token::RightParen, _)) => Ok(expr), - _ => Err(ParseError(PERR::MissingRightParen, Position::eof())), + Some((_, pos)) => { + return Err(ParseError::new( + PERR::MissingRightParen("a matching ( in the expression".into()), + pos, + )) + } + None => Err(ParseError::new( + PERR::MissingRightParen("a matching ( in the expression".into()), + Position::eof(), + )), } } @@ -1037,8 +1077,24 @@ fn parse_call_expr<'a>( return Ok(Expr::FunctionCall(id, args, None, begin)); } Some(&(Token::Comma, _)) => (), - Some(&(_, pos)) => return Err(ParseError(PERR::MalformedCallExpr, pos)), - None => return Err(ParseError(PERR::MalformedCallExpr, Position::eof())), + Some(&(_, pos)) => { + return Err(ParseError::new( + PERR::MissingRightParen(format!( + "closing the arguments list to function call of '{}'", + id + )), + pos, + )) + } + None => { + return Err(ParseError::new( + PERR::MissingRightParen(format!( + "closing the arguments list to function call of '{}'", + id + )), + Position::eof(), + )) + } } input.next(); @@ -1046,24 +1102,27 @@ fn parse_call_expr<'a>( } fn parse_index_expr<'a>( - id: String, + lhs: Box, 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), begin)); - } - Some(&(_, pos)) => return Err(ParseError(PERR::MalformedIndexExpr, pos)), - None => return Err(ParseError(PERR::MalformedIndexExpr, Position::eof())), - }, - Err(mut err) => { - err.0 = PERR::MalformedIndexExpr; - return Err(err); + parse_expr(input).and_then(|idx_expr| match input.peek() { + Some(&(Token::RightBracket, _)) => { + input.next(); + return Ok(Expr::Index(lhs, Box::new(idx_expr))); } - } + Some(&(_, pos)) => { + return Err(ParseError::new( + PERR::MissingRightBracket("index expression".into()), + pos, + )) + } + None => { + return Err(ParseError::new( + PERR::MissingRightBracket("index expression".into()), + Position::eof(), + )) + } + }) } fn parse_ident_expr<'a>( @@ -1072,13 +1131,13 @@ fn parse_ident_expr<'a>( begin: Position, ) -> Result { match input.peek() { - Some(&(Token::LeftParen, pos)) => { + Some(&(Token::LeftParen, _)) => { input.next(); - parse_call_expr(id, input, pos) + parse_call_expr(id, input, begin) } - Some(&(Token::LeftBracket, pos)) => { + Some(&(Token::LeftBracket, _)) => { input.next(); - parse_index_expr(id, input, pos) + parse_index_expr(Box::new(Expr::Identifier(id, begin)), input) } Some(_) => Ok(Expr::Identifier(id, begin)), None => Ok(Expr::Identifier(id, Position::eof())), @@ -1091,21 +1150,20 @@ fn parse_array_expr<'a>( ) -> Result { let mut arr = Vec::new(); - let skip_contents = match input.peek() { - Some(&(Token::RightBracket, _)) => true, - _ => false, - }; + match input.peek() { + Some(&(Token::RightBracket, _)) => (), - if !skip_contents { - while let Some(_) = input.peek() { - arr.push(parse_expr(input)?); + _ => { + while input.peek().is_some() { + arr.push(parse_expr(input)?); - if let Some(&(Token::Comma, _)) = input.peek() { - input.next(); - } + if let Some(&(Token::Comma, _)) = input.peek() { + input.next(); + } - if let Some(&(Token::RightBracket, _)) = input.peek() { - break; + if let Some(&(Token::RightBracket, _)) = input.peek() { + break; + } } } } @@ -1115,41 +1173,70 @@ fn parse_array_expr<'a>( input.next(); Ok(Expr::Array(arr, begin)) } - Some(&(_, pos)) => Err(ParseError(PERR::MissingRightBracket, pos)), - None => Err(ParseError(PERR::MissingRightBracket, Position::eof())), + Some(&(_, pos)) => Err(ParseError::new( + PERR::MissingRightBracket("the end of array literal".into()), + pos, + )), + None => Err(ParseError::new( + PERR::MissingRightBracket("the end of array literal".into()), + Position::eof(), + )), } } fn parse_primary<'a>(input: &mut Peekable>) -> Result { - match input.next() { - Some((token, pos)) => match token { - Token::IntegerConstant(x) => Ok(Expr::IntegerConstant(x, pos)), - Token::FloatConstant(x) => Ok(Expr::FloatConstant(x, pos)), - Token::StringConst(s) => Ok(Expr::StringConstant(s, pos)), - Token::CharConstant(c) => Ok(Expr::CharConstant(c, pos)), - Token::Identifier(s) => parse_ident_expr(s, input, pos), - Token::LeftParen => parse_paren_expr(input, pos), - Token::LeftBracket => parse_array_expr(input, pos), - Token::True => Ok(Expr::True(pos)), - Token::False => Ok(Expr::False(pos)), - Token::LexErr(le) => Err(ParseError(PERR::BadInput(le.to_string()), pos)), - _ => Err(ParseError( - PERR::BadInput(format!("Unexpected {:?} token", token)), - pos, - )), - }, - None => Err(ParseError(PERR::InputPastEndOfFile, Position::eof())), + let token = input.next(); + + let mut follow_on = false; + + let mut root_expr = match token { + Some((Token::IntegerConstant(x), pos)) => Ok(Expr::IntegerConstant(x, pos)), + Some((Token::FloatConstant(x), pos)) => Ok(Expr::FloatConstant(x, pos)), + Some((Token::CharConstant(c), pos)) => Ok(Expr::CharConstant(c, pos)), + Some((Token::StringConst(s), pos)) => { + follow_on = true; + Ok(Expr::StringConstant(s, pos)) + } + Some((Token::Identifier(s), pos)) => { + follow_on = true; + parse_ident_expr(s, input, pos) + } + Some((Token::LeftParen, pos)) => { + follow_on = true; + parse_paren_expr(input, pos) + } + Some((Token::LeftBracket, pos)) => { + follow_on = true; + parse_array_expr(input, pos) + } + Some((Token::True, pos)) => Ok(Expr::True(pos)), + Some((Token::False, pos)) => Ok(Expr::False(pos)), + Some((Token::LexError(le), pos)) => { + Err(ParseError::new(PERR::BadInput(le.to_string()), pos)) + } + Some((token, pos)) => Err(ParseError::new( + PERR::BadInput(format!("Unexpected '{}'", token.syntax())), + pos, + )), + None => Err(ParseError::new(PERR::InputPastEndOfFile, Position::eof())), + }?; + + if !follow_on { + return Ok(root_expr); } + + // Tail processing all possible indexing + while let Some(&(Token::LeftBracket, _)) = input.peek() { + input.next(); + root_expr = parse_index_expr(Box::new(root_expr), input)?; + } + + Ok(root_expr) } fn parse_unary<'a>(input: &mut Peekable>) -> Result { - 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 => { + match input.peek() { + Some(&(Token::UnaryMinus, pos)) => { input.next(); Ok(Expr::FunctionCall( @@ -1159,11 +1246,11 @@ fn parse_unary<'a>(input: &mut Peekable>) -> Result { + Some(&(Token::UnaryPlus, _)) => { input.next(); parse_primary(input) } - Token::Bang => { + Some(&(Token::Bang, pos)) => { input.next(); Ok(Expr::FunctionCall( @@ -1177,22 +1264,22 @@ fn parse_unary<'a>(input: &mut Peekable>) -> Result( +fn parse_binary_op<'a>( input: &mut Peekable>, - prec: i8, + precedence: i8, lhs: Expr, ) -> Result { - let mut lhs_curr = lhs; + let mut current_lhs = lhs; loop { - let mut curr_prec = -1; + let mut current_precedence = -1; - if let Some(&(ref curr_op, _)) = input.peek() { - curr_prec = get_precedence(curr_op); + if let Some(&(ref current_op, _)) = input.peek() { + current_precedence = get_precedence(current_op); } - if curr_prec < prec { - return Ok(lhs_curr); + if current_precedence < precedence { + return Ok(current_lhs); } if let Some((op_token, pos)) = input.next() { @@ -1200,30 +1287,32 @@ fn parse_binop<'a>( let mut rhs = parse_unary(input)?; - let mut next_prec = -1; + let mut next_precedence = -1; if let Some(&(ref next_op, _)) = input.peek() { - next_prec = get_precedence(next_op); + next_precedence = get_precedence(next_op); } - if curr_prec < next_prec { - rhs = parse_binop(input, curr_prec + 1, rhs)?; - } else if curr_prec >= 100 { + if current_precedence < next_precedence { + rhs = parse_binary_op(input, current_precedence + 1, rhs)?; + } else if current_precedence >= 100 { // Always bind right to left for precedence over 100 - rhs = parse_binop(input, curr_prec, rhs)?; + rhs = parse_binary_op(input, current_precedence, rhs)?; } - lhs_curr = match op_token { - 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), + current_lhs = match op_token { + Token::Plus => Expr::FunctionCall("+".into(), vec![current_lhs, rhs], None, pos), + Token::Minus => Expr::FunctionCall("-".into(), vec![current_lhs, rhs], None, pos), + Token::Multiply => { + Expr::FunctionCall("*".into(), vec![current_lhs, rhs], None, pos) + } + Token::Divide => Expr::FunctionCall("/".into(), vec![current_lhs, rhs], None, pos), - Token::Equals => Expr::Assignment(Box::new(lhs_curr), Box::new(rhs)), + Token::Equals => Expr::Assignment(Box::new(current_lhs), Box::new(rhs)), Token::PlusAssign => { - let lhs_copy = lhs_curr.clone(); + let lhs_copy = current_lhs.clone(); Expr::Assignment( - Box::new(lhs_curr), + Box::new(current_lhs), Box::new(Expr::FunctionCall( "+".into(), vec![lhs_copy, rhs], @@ -1233,9 +1322,9 @@ fn parse_binop<'a>( ) } Token::MinusAssign => { - let lhs_copy = lhs_curr.clone(); + let lhs_copy = current_lhs.clone(); Expr::Assignment( - Box::new(lhs_curr), + Box::new(current_lhs), Box::new(Expr::FunctionCall( "-".into(), vec![lhs_copy, rhs], @@ -1244,35 +1333,53 @@ fn parse_binop<'a>( )), ) } - Token::Period => Expr::Dot(Box::new(lhs_curr), Box::new(rhs)), + Token::Period => Expr::Dot(Box::new(current_lhs), 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)), pos) - } - Token::NotEqualsTo => { - Expr::FunctionCall("!=".into(), vec![lhs_curr, rhs], Some(Box::new(false)), pos) - } - Token::LessThan => { - Expr::FunctionCall("<".into(), vec![lhs_curr, rhs], Some(Box::new(false)), pos) - } - Token::LessThanEqualsTo => { - Expr::FunctionCall("<=".into(), vec![lhs_curr, rhs], Some(Box::new(false)), pos) - } - Token::GreaterThan => { - Expr::FunctionCall(">".into(), vec![lhs_curr, rhs], Some(Box::new(false)), pos) - } - Token::GreaterThanEqualsTo => { - Expr::FunctionCall(">=".into(), vec![lhs_curr, rhs], Some(Box::new(false)), pos) - } + Token::EqualsTo => Expr::FunctionCall( + "==".into(), + vec![current_lhs, rhs], + Some(Box::new(false)), + pos, + ), + Token::NotEqualsTo => Expr::FunctionCall( + "!=".into(), + vec![current_lhs, rhs], + Some(Box::new(false)), + pos, + ), + Token::LessThan => Expr::FunctionCall( + "<".into(), + vec![current_lhs, rhs], + Some(Box::new(false)), + pos, + ), + Token::LessThanEqualsTo => Expr::FunctionCall( + "<=".into(), + vec![current_lhs, rhs], + Some(Box::new(false)), + pos, + ), + Token::GreaterThan => Expr::FunctionCall( + ">".into(), + vec![current_lhs, rhs], + Some(Box::new(false)), + pos, + ), + Token::GreaterThanEqualsTo => Expr::FunctionCall( + ">=".into(), + vec![current_lhs, 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, pos), + Token::Or => Expr::Or(Box::new(current_lhs), Box::new(rhs)), + Token::And => Expr::And(Box::new(current_lhs), Box::new(rhs)), + Token::XOr => Expr::FunctionCall("^".into(), vec![current_lhs, rhs], None, pos), Token::OrAssign => { - let lhs_copy = lhs_curr.clone(); + let lhs_copy = current_lhs.clone(); Expr::Assignment( - Box::new(lhs_curr), + Box::new(current_lhs), Box::new(Expr::FunctionCall( "|".into(), vec![lhs_copy, rhs], @@ -1282,9 +1389,9 @@ fn parse_binop<'a>( ) } Token::AndAssign => { - let lhs_copy = lhs_curr.clone(); + let lhs_copy = current_lhs.clone(); Expr::Assignment( - Box::new(lhs_curr), + Box::new(current_lhs), Box::new(Expr::FunctionCall( "&".into(), vec![lhs_copy, rhs], @@ -1294,9 +1401,9 @@ fn parse_binop<'a>( ) } Token::XOrAssign => { - let lhs_copy = lhs_curr.clone(); + let lhs_copy = current_lhs.clone(); Expr::Assignment( - Box::new(lhs_curr), + Box::new(current_lhs), Box::new(Expr::FunctionCall( "^".into(), vec![lhs_copy, rhs], @@ -1306,9 +1413,9 @@ fn parse_binop<'a>( ) } Token::MultiplyAssign => { - let lhs_copy = lhs_curr.clone(); + let lhs_copy = current_lhs.clone(); Expr::Assignment( - Box::new(lhs_curr), + Box::new(current_lhs), Box::new(Expr::FunctionCall( "*".into(), vec![lhs_copy, rhs], @@ -1318,9 +1425,9 @@ fn parse_binop<'a>( ) } Token::DivideAssign => { - let lhs_copy = lhs_curr.clone(); + let lhs_copy = current_lhs.clone(); Expr::Assignment( - Box::new(lhs_curr), + Box::new(current_lhs), Box::new(Expr::FunctionCall( "/".into(), vec![lhs_copy, rhs], @@ -1329,15 +1436,17 @@ fn parse_binop<'a>( )), ) } - Token::Pipe => Expr::FunctionCall("|".into(), vec![lhs_curr, rhs], None, pos), - Token::LeftShift => Expr::FunctionCall("<<".into(), vec![lhs_curr, rhs], None, pos), + Token::Pipe => Expr::FunctionCall("|".into(), vec![current_lhs, rhs], None, pos), + Token::LeftShift => { + Expr::FunctionCall("<<".into(), vec![current_lhs, rhs], None, pos) + } Token::RightShift => { - Expr::FunctionCall(">>".into(), vec![lhs_curr, rhs], None, pos) + Expr::FunctionCall(">>".into(), vec![current_lhs, rhs], None, pos) } Token::LeftShiftAssign => { - let lhs_copy = lhs_curr.clone(); + let lhs_copy = current_lhs.clone(); Expr::Assignment( - Box::new(lhs_curr), + Box::new(current_lhs), Box::new(Expr::FunctionCall( "<<".into(), vec![lhs_copy, rhs], @@ -1347,9 +1456,9 @@ fn parse_binop<'a>( ) } Token::RightShiftAssign => { - let lhs_copy = lhs_curr.clone(); + let lhs_copy = current_lhs.clone(); Expr::Assignment( - Box::new(lhs_curr), + Box::new(current_lhs), Box::new(Expr::FunctionCall( ">>".into(), vec![lhs_copy, rhs], @@ -1358,12 +1467,14 @@ fn parse_binop<'a>( )), ) } - Token::Ampersand => Expr::FunctionCall("&".into(), vec![lhs_curr, rhs], None, pos), - Token::Modulo => Expr::FunctionCall("%".into(), vec![lhs_curr, rhs], None, pos), + Token::Ampersand => { + Expr::FunctionCall("&".into(), vec![current_lhs, rhs], None, pos) + } + Token::Modulo => Expr::FunctionCall("%".into(), vec![current_lhs, rhs], None, pos), Token::ModuloAssign => { - let lhs_copy = lhs_curr.clone(); + let lhs_copy = current_lhs.clone(); Expr::Assignment( - Box::new(lhs_curr), + Box::new(current_lhs), Box::new(Expr::FunctionCall( "%".into(), vec![lhs_copy, rhs], @@ -1372,11 +1483,11 @@ fn parse_binop<'a>( )), ) } - Token::PowerOf => Expr::FunctionCall("~".into(), vec![lhs_curr, rhs], None, pos), + Token::PowerOf => Expr::FunctionCall("~".into(), vec![current_lhs, rhs], None, pos), Token::PowerOfAssign => { - let lhs_copy = lhs_curr.clone(); + let lhs_copy = current_lhs.clone(); Expr::Assignment( - Box::new(lhs_curr), + Box::new(current_lhs), Box::new(Expr::FunctionCall( "~".into(), vec![lhs_copy, rhs], @@ -1385,7 +1496,12 @@ fn parse_binop<'a>( )), ) } - _ => return Err(ParseError(PERR::UnknownOperator, pos)), + token => { + return Err(ParseError::new( + PERR::UnknownOperator(token.syntax().into()), + pos, + )) + } }; } } @@ -1393,7 +1509,7 @@ fn parse_binop<'a>( fn parse_expr<'a>(input: &mut Peekable>) -> Result { let lhs = parse_unary(input)?; - parse_binop(input, 0, lhs) + parse_binary_op(input, 0, lhs) } fn parse_if<'a>(input: &mut Peekable>) -> Result { @@ -1443,14 +1559,14 @@ fn parse_for<'a>(input: &mut Peekable>) -> Result s, - Some((_, pos)) => return Err(ParseError(PERR::VarExpectsIdentifier, pos)), - None => return Err(ParseError(PERR::VarExpectsIdentifier, Position::eof())), + Some((_, pos)) => return Err(ParseError::new(PERR::VarExpectsIdentifier, pos)), + None => return Err(ParseError::new(PERR::VarExpectsIdentifier, Position::eof())), }; match input.next() { Some((Token::In, _)) => {} - Some((_, pos)) => return Err(ParseError(PERR::VarExpectsIdentifier, pos)), - None => return Err(ParseError(PERR::VarExpectsIdentifier, Position::eof())), + Some((_, pos)) => return Err(ParseError::new(PERR::VarExpectsIdentifier, pos)), + None => return Err(ParseError::new(PERR::VarExpectsIdentifier, Position::eof())), } let expr = parse_expr(input)?; @@ -1463,13 +1579,13 @@ fn parse_for<'a>(input: &mut Peekable>) -> Result(input: &mut Peekable>) -> Result { let pos = match input.next() { Some((_, tok_pos)) => tok_pos, - _ => return Err(ParseError(PERR::InputPastEndOfFile, Position::eof())), + _ => return Err(ParseError::new(PERR::InputPastEndOfFile, Position::eof())), }; let name = match input.next() { Some((Token::Identifier(s), _)) => s, - Some((_, pos)) => return Err(ParseError(PERR::VarExpectsIdentifier, pos)), - None => return Err(ParseError(PERR::VarExpectsIdentifier, Position::eof())), + Some((_, pos)) => return Err(ParseError::new(PERR::VarExpectsIdentifier, pos)), + None => return Err(ParseError::new(PERR::VarExpectsIdentifier, Position::eof())), }; match input.peek() { @@ -1485,29 +1601,31 @@ fn parse_var<'a>(input: &mut Peekable>) -> Result(input: &mut Peekable>) -> Result { match input.peek() { Some(&(Token::LeftBrace, _)) => (), - Some(&(_, pos)) => return Err(ParseError(PERR::MissingLeftBrace, pos)), - None => return Err(ParseError(PERR::MissingLeftBrace, Position::eof())), + Some(&(_, pos)) => return Err(ParseError::new(PERR::MissingLeftBrace, pos)), + None => return Err(ParseError::new(PERR::MissingLeftBrace, Position::eof())), } input.next(); - let mut stmts = Vec::new(); + let mut statements = Vec::new(); - let skip_body = match input.peek() { - Some(&(Token::RightBrace, _)) => true, - _ => false, - }; + match input.peek() { + Some(&(Token::RightBrace, _)) => (), // empty block + Some(&(Token::Fn, pos)) => return Err(ParseError::new(PERR::WrongFnDefinition, pos)), - if !skip_body { - while let Some(_) = input.peek() { - stmts.push(parse_stmt(input)?); + _ => { + while input.peek().is_some() { + // Parse statements inside the block + statements.push(parse_stmt(input)?); - if let Some(&(Token::SemiColon, _)) = input.peek() { - input.next(); - } + // Notice semicolons are optional + if let Some(&(Token::SemiColon, _)) = input.peek() { + input.next(); + } - if let Some(&(Token::RightBrace, _)) = input.peek() { - break; + if let Some(&(Token::RightBrace, _)) = input.peek() { + break; + } } } } @@ -1515,10 +1633,16 @@ fn parse_block<'a>(input: &mut Peekable>) -> Result { input.next(); - Ok(Stmt::Block(stmts)) + Ok(Stmt::Block(statements)) } - Some(&(_, pos)) => Err(ParseError(PERR::MissingRightBrace, pos)), - None => Err(ParseError(PERR::MissingRightBrace, Position::eof())), + Some(&(_, pos)) => Err(ParseError::new( + PERR::MissingRightBrace("end of block".into()), + pos, + )), + None => Err(ParseError::new( + PERR::MissingRightBrace("end of block".into()), + Position::eof(), + )), } } @@ -1536,15 +1660,25 @@ fn parse_stmt<'a>(input: &mut Peekable>) -> Result { + Some(&(ref token @ Token::Return, _)) | Some(&(ref token @ Token::Throw, _)) => { + let is_return = match token { + Token::Return => true, + Token::Throw => false, + _ => panic!(), + }; + input.next(); + match input.peek() { - Some(&(Token::SemiColon, pos)) => Ok(Stmt::Return(pos)), + // return; or throw; + Some(&(Token::SemiColon, pos)) => Ok(Stmt::ReturnWithVal(None, is_return, pos)), + // Just a return/throw without anything at the end of script + None => Ok(Stmt::ReturnWithVal(None, is_return, Position::eof())), + // return or throw with expression Some(&(_, pos)) => { let ret = parse_expr(input)?; - Ok(Stmt::ReturnWithVal(Box::new(ret), pos)) + Ok(Stmt::ReturnWithVal(Some(Box::new(ret)), is_return, pos)) } - _ => parse_expr_stmt(input), } } Some(&(Token::LeftBrace, _)) => parse_block(input), @@ -1556,45 +1690,45 @@ fn parse_stmt<'a>(input: &mut Peekable>) -> Result(input: &mut Peekable>) -> Result { let pos = match input.next() { Some((_, tok_pos)) => tok_pos, - _ => return Err(ParseError(PERR::InputPastEndOfFile, Position::eof())), + _ => return Err(ParseError::new(PERR::InputPastEndOfFile, Position::eof())), }; let name = match input.next() { Some((Token::Identifier(s), _)) => s, - Some((_, pos)) => return Err(ParseError(PERR::FnMissingName, pos)), - None => return Err(ParseError(PERR::FnMissingName, Position::eof())), + Some((_, pos)) => return Err(ParseError::new(PERR::FnMissingName, pos)), + None => return Err(ParseError::new(PERR::FnMissingName, Position::eof())), }; match input.peek() { Some(&(Token::LeftParen, _)) => { input.next(); } - Some(&(_, pos)) => return Err(ParseError(PERR::FnMissingParams, pos)), - None => return Err(ParseError(PERR::FnMissingParams, Position::eof())), + Some(&(_, pos)) => return Err(ParseError::new(PERR::FnMissingParams(name), pos)), + None => { + return Err(ParseError::new( + PERR::FnMissingParams(name), + Position::eof(), + )) + } } let mut params = Vec::new(); - let skip_params = match input.peek() { + match input.peek() { Some(&(Token::RightParen, _)) => { input.next(); - true } - _ => false, - }; - - if !skip_params { - loop { + _ => loop { match input.next() { Some((Token::RightParen, _)) => break, Some((Token::Comma, _)) => (), Some((Token::Identifier(s), _)) => { params.push(s); } - Some((_, pos)) => return Err(ParseError(PERR::MalformedCallExpr, pos)), - None => return Err(ParseError(PERR::MalformedCallExpr, Position::eof())), + Some((_, pos)) => return Err(ParseError::new(PERR::MalformedCallExpr, pos)), + None => return Err(ParseError::new(PERR::MalformedCallExpr, Position::eof())), } - } + }, } let body = parse_block(input)?; @@ -1608,21 +1742,22 @@ fn parse_fn<'a>(input: &mut Peekable>) -> Result(input: &mut Peekable>) -> Result { - let mut stmts = Vec::new(); - let mut fndefs = Vec::new(); + let mut statements = Vec::new(); + let mut functions = Vec::new(); - while let Some(_) = input.peek() { + while input.peek().is_some() { match input.peek() { - Some(&(Token::Fn, _)) => fndefs.push(parse_fn(input)?), - _ => stmts.push(parse_stmt(input)?), + Some(&(Token::Fn, _)) => functions.push(parse_fn(input)?), + _ => statements.push(parse_stmt(input)?), } + // Notice semicolons are optional if let Some(&(Token::SemiColon, _)) = input.peek() { input.next(); } } - Ok(AST(stmts, fndefs)) + Ok(AST(statements, functions)) } pub fn parse<'a>(input: &mut Peekable>) -> Result { diff --git a/src/result.rs b/src/result.rs new file mode 100644 index 00000000..1d869c8b --- /dev/null +++ b/src/result.rs @@ -0,0 +1,163 @@ +use std::error::Error; + +use crate::any::Dynamic; +use crate::error::ParseError; +use crate::parser::Position; + +/// Evaluation result. +/// +/// All wrapped `Position` values represent the location in the script where the error occurs. +#[derive(Debug)] +pub enum EvalAltResult { + /// Syntax error. + ErrorParsing(ParseError), + /// Call to an unknown function. Wrapped value is the name of the function. + ErrorFunctionNotFound(String, Position), + /// Function call has incorrect number of arguments. + /// Wrapped values are the name of the function, the number of parameters required + /// and the actual number of arguments passed. + ErrorFunctionArgsMismatch(String, usize, usize, Position), + /// Non-boolean operand encountered for boolean operator. Wrapped value is the operator. + ErrorBooleanArgMismatch(String, Position), + /// Array access out-of-bounds. + /// Wrapped values are the current number of elements in the array and the index number. + ErrorArrayBounds(usize, i64, Position), + /// String indexing out-of-bounds. + /// Wrapped values are the current number of characters in the string and the index number. + ErrorStringBounds(usize, i64, Position), + /// Trying to index into a type that is not an array and not a string. + ErrorIndexingType(Position), + /// Trying to index into an array or string with an index that is not `i64`. + ErrorIndexExpr(Position), + /// The guard expression in an `if` statement does not return a boolean value. + ErrorIfGuard(Position), + /// The `for` statement encounters a type that is not an iterator. + ErrorFor(Position), + /// Usage of an unknown variable. Wrapped value is the name of the variable. + ErrorVariableNotFound(String, Position), + /// Assignment to an inappropriate LHS (left-hand-side) expression. + ErrorAssignmentToUnknownLHS(Position), + /// Returned type is not the same as the required output type. + /// Wrapped value is the type of the actual result. + ErrorMismatchOutputType(String, Position), + /// Error reading from a script file. Wrapped value is the path of the script file. + ErrorReadingScriptFile(String, std::io::Error), + /// Inappropriate member access. + ErrorDotExpr(Position), + /// Arithmetic error encountered. Wrapped value is the error message. + ErrorArithmetic(String, Position), + /// Run-time error encountered. Wrapped value is the error message. + ErrorRuntime(String, Position), + /// Internal use: Breaking out of loops. + LoopBreak, + /// Not an error: Value returned from a script via the `return` keyword. + /// Wrapped value is the result value. + 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::ErrorIndexingType(_) => "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 => { + "Indexing a string expects a non-negative index" + } + Self::ErrorStringBounds(max, _, _) if *max == 0 => "Indexing of empty string", + Self::ErrorStringBounds(_, _, _) => "String index out of bounds", + Self::ErrorIfGuard(_) => "If guard expects boolean expression", + Self::ErrorFor(_) => "For loop expects array or range", + Self::ErrorVariableNotFound(_, _) => "Variable not found", + Self::ErrorAssignmentToUnknownLHS(_) => { + "Assignment to an unsupported left-hand side expression" + } + Self::ErrorMismatchOutputType(_, _) => "Output type is incorrect", + Self::ErrorReadingScriptFile(_, _) => "Cannot read from script file", + Self::ErrorDotExpr(_) => "Malformed dot expression", + Self::ErrorArithmetic(_, _) => "Arithmetic error", + Self::ErrorRuntime(_, _) => "Runtime error", + Self::LoopBreak => "[Not Error] Breaks out of loop", + Self::Return(_, _) => "[Not Error] Function returns value", + } + } + + fn cause(&self) -> Option<&dyn Error> { + None + } +} + +impl std::fmt::Display for EvalAltResult { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let desc = self.description(); + + match self { + Self::ErrorFunctionNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos), + Self::ErrorVariableNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos), + Self::ErrorIndexingType(pos) => write!(f, "{} ({})", desc, pos), + Self::ErrorIndexExpr(pos) => write!(f, "{} ({})", desc, pos), + Self::ErrorIfGuard(pos) => write!(f, "{} ({})", desc, pos), + Self::ErrorFor(pos) => write!(f, "{} ({})", desc, pos), + Self::ErrorAssignmentToUnknownLHS(pos) => write!(f, "{} ({})", desc, pos), + Self::ErrorMismatchOutputType(s, pos) => write!(f, "{}: {} ({})", desc, s, pos), + Self::ErrorDotExpr(pos) => write!(f, "{} ({})", desc, pos), + Self::ErrorArithmetic(s, pos) => write!(f, "{}: {} ({})", desc, s, pos), + Self::ErrorRuntime(s, pos) if s.is_empty() => write!(f, "{} ({})", desc, pos), + Self::ErrorRuntime(s, pos) => write!(f, "{}: {} ({})", desc, s, pos), + Self::LoopBreak => write!(f, "{}", desc), + Self::Return(_, pos) => write!(f, "{} ({})", desc, pos), + Self::ErrorReadingScriptFile(filename, err) => { + write!(f, "{} '{}': {}", desc, filename, err) + } + Self::ErrorParsing(p) => write!(f, "Syntax error: {}", p), + Self::ErrorFunctionArgsMismatch(fun, need, n, pos) => write!( + f, + "Function '{}' expects {} argument(s) but {} found ({})", + fun, need, 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, + "Array index {} is out of bounds: only {} element{} in the array ({})", + index, + max, + if *max > 1 { "s" } else { "" }, + 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, + "String index {} is out of bounds: only {} character{} in the string ({})", + index, + max, + if *max > 1 { "s" } else { "" }, + pos + ), + } + } +} + +impl From for EvalAltResult { + fn from(err: ParseError) -> Self { + Self::ErrorParsing(err) + } +} diff --git a/src/scope.rs b/src/scope.rs new file mode 100644 index 00000000..9e7f63dc --- /dev/null +++ b/src/scope.rs @@ -0,0 +1,116 @@ +use crate::any::{Any, Dynamic}; + +/// A type containing information about current scope. +/// Useful for keeping state between `Engine` runs +/// +/// # Example +/// +/// ```rust +/// use rhai::{Engine, Scope}; +/// +/// let mut engine = Engine::new(); +/// let mut my_scope = Scope::new(); +/// +/// assert!(engine.eval_with_scope::<()>(&mut my_scope, "let x = 5;").is_ok()); +/// assert_eq!(engine.eval_with_scope::(&mut my_scope, "x + 1").unwrap(), 6); +/// ``` +/// +/// When searching for variables, newly-added variables are found before similarly-named but older variables, +/// allowing for automatic _shadowing_ of variables. +pub struct Scope(Vec<(String, Dynamic)>); + +impl Scope { + /// Create a new Scope. + pub fn new() -> Self { + Self(Vec::new()) + } + + /// Empty the Scope. + pub fn clear(&mut self) { + self.0.clear(); + } + + /// Get the number of variables inside the Scope. + pub fn len(&self) -> usize { + self.0.len() + } + + /// Add (push) a new variable to the Scope. + pub fn push(&mut self, key: String, value: T) { + self.0.push((key, Box::new(value))); + } + + /// Add (push) a new variable to the Scope. + pub(crate) fn push_dynamic(&mut self, key: String, value: Dynamic) { + self.0.push((key, value)); + } + + /// Remove (pop) the last variable from the Scope. + pub fn pop(&mut self) -> Option<(String, Dynamic)> { + self.0.pop() + } + + /// Truncate (rewind) the Scope to a previous size. + pub fn rewind(&mut self, size: usize) { + self.0.truncate(size); + } + + /// Find a variable in the Scope, starting from the last. + pub fn get(&self, key: &str) -> Option<(usize, String, Dynamic)> { + self.0 + .iter() + .enumerate() + .rev() // Always search a Scope in reverse order + .find(|(_, (name, _))| name == key) + .map(|(i, (name, value))| (i, name.clone(), value.clone())) + } + + /// Get the value of a variable in the Scope, starting from the last. + pub fn get_value(&self, key: &str) -> Option { + self.0 + .iter() + .enumerate() + .rev() // Always search a Scope in reverse order + .find(|(_, (name, _))| name == key) + .and_then(|(_, (_, value))| value.downcast_ref::()) + .map(|value| value.clone()) + } + + /// Get a mutable reference to a variable in the Scope. + pub(crate) fn get_mut(&mut self, key: &str, index: usize) -> &mut Dynamic { + let entry = self.0.get_mut(index).expect("invalid index in Scope"); + + assert_eq!(entry.0, key, "incorrect key at Scope entry"); + + &mut entry.1 + } + + /// Get a mutable reference to a variable in the Scope and downcast it to a specific type + pub(crate) fn get_mut_by_type(&mut self, key: &str, index: usize) -> &mut T { + self.get_mut(key, index) + .downcast_mut::() + .expect("wrong type cast") + } + + /// Get an iterator to variables in the Scope. + pub fn iter(&self) -> impl Iterator { + self.0 + .iter() + .rev() // Always search a Scope in reverse order + .map(|(key, value)| (key.as_str(), value)) + } + + /// Get a mutable iterator to variables in the Scope. + pub fn iter_mut(&mut self) -> impl Iterator { + self.0 + .iter_mut() + .rev() // Always search a Scope in reverse order + .map(|(key, value)| (key.as_str(), value)) + } +} + +impl std::iter::Extend<(String, Dynamic)> for Scope { + fn extend>(&mut self, iter: T) { + self.0.extend(iter); + } +} diff --git a/tests/engine.rs b/tests/engine.rs new file mode 100644 index 00000000..082daaa4 --- /dev/null +++ b/tests/engine.rs @@ -0,0 +1,14 @@ +use rhai::{Engine, EvalAltResult}; + +#[test] +fn test_engine_call_fn() -> Result<(), EvalAltResult> { + let mut engine = Engine::new(); + + let ast = Engine::compile("fn hello(x, y) { x.len() + y }")?; + + let result: i64 = engine.call_fn("hello", ast, (&mut String::from("abc"), &mut 123_i64))?; + + assert_eq!(result, 126); + + Ok(()) +} diff --git a/tests/get_set.rs b/tests/get_set.rs index d2718a50..0a9fdd16 100644 --- a/tests/get_set.rs +++ b/tests/get_set.rs @@ -78,7 +78,7 @@ fn test_big_get_set() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); engine.register_type::(); - engine.register_type::(); + engine.register_type_with_name::("TestParent"); engine.register_get_set("x", TestChild::get_x, TestChild::set_x); engine.register_get_set("child", TestParent::get_child, TestParent::set_child); @@ -90,5 +90,10 @@ fn test_big_get_set() -> Result<(), EvalAltResult> { 500 ); + assert_eq!( + engine.eval::("let a = new_tp(); a.type_of()")?, + "TestParent" + ); + Ok(()) } diff --git a/tests/string.rs b/tests/string.rs index e7df5d05..2032154b 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -5,13 +5,21 @@ fn test_string() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); assert_eq!( - engine.eval::("\"Test string: \\u2764\"")?, + engine.eval::(r#""Test string: \u2764""#)?, "Test string: ❤".to_string() ); assert_eq!( - engine.eval::("\"foo\" + \"bar\"")?, + engine.eval::(r#""Test string: \x58""#)?, + "Test string: X".to_string() + ); + assert_eq!( + engine.eval::(r#""foo" + "bar""#)?, "foobar".to_string() ); + assert_eq!( + engine.eval::(r#""foo" + 123.4556"#)?, + "foo123.4556".to_string() + ); Ok(()) } diff --git a/tests/throw.rs b/tests/throw.rs new file mode 100644 index 00000000..ca14cb3f --- /dev/null +++ b/tests/throw.rs @@ -0,0 +1,18 @@ +use rhai::{Engine, EvalAltResult}; + +#[test] +fn test_throw() { + let mut engine = Engine::new(); + + match engine.eval::(r#"if true { throw "hello" }"#) { + Ok(_) => panic!("not an error"), + Err(EvalAltResult::ErrorRuntime(s, _)) if s == "hello" => (), + Err(err) => panic!("wrong error: {}", err), + } + + match engine.eval::(r#"throw;"#) { + Ok(_) => panic!("not an error"), + Err(EvalAltResult::ErrorRuntime(s, _)) if s == "" => (), + Err(err) => panic!("wrong error: {}", err), + } +} diff --git a/tests/types.rs b/tests/types.rs new file mode 100644 index 00000000..1cf303ad --- /dev/null +++ b/tests/types.rs @@ -0,0 +1,17 @@ +use rhai::{Engine, EvalAltResult}; + +#[test] +fn test_type_of() -> Result<(), EvalAltResult> { + let mut engine = Engine::new(); + + assert_eq!(engine.eval::("type_of(60 + 5)")?, "i64"); + assert_eq!(engine.eval::("type_of(1.0 + 2.0)")?, "f64"); + assert_eq!( + engine.eval::(r#"type_of([1.0, 2, "hello"])"#)?, + "array" + ); + assert_eq!(engine.eval::(r#"type_of("hello")"#)?, "string"); + assert_eq!(engine.eval::("let x = 123; x.type_of()")?, "i64"); + + Ok(()) +} diff --git a/tests/var_scope.rs b/tests/var_scope.rs index 6b378299..5522ba67 100644 --- a/tests/var_scope.rs +++ b/tests/var_scope.rs @@ -14,3 +14,38 @@ fn test_var_scope() -> Result<(), EvalAltResult> { Ok(()) } + +#[test] +fn test_scope_eval() -> Result<(), EvalAltResult> { + let mut engine = Engine::new(); + + // First create the state + let mut scope = Scope::new(); + + // Then push some initialized variables into the state + // NOTE: Remember the default numbers used by Rhai are i64 and f64. + // Better stick to them or it gets hard to work with other variables in the script. + scope.push("y".into(), 42_i64); + scope.push("z".into(), 999_i64); + + // First invocation + engine + .eval_with_scope::<()>( + &mut scope, + r" + let x = 4 + 5 - y + z; + y = 1; + ", + ) + .expect("y and z not found?"); + + // Second invocation using the same state + if let Ok(result) = engine.eval_with_scope::(&mut scope, "x") { + println!("result: {}", result); // should print 966 + } + + // Variable y is changed in the script + assert_eq!(scope.get_value::("y").unwrap(), 1); + + Ok(()) +}